SECTION .DATA中有变量的分段错误

时间:2019-01-10 17:54:47

标签: linux assembly x86 segmentation-fault nasm

我正在尝试学习鼻音。我想制作一个打印“你好,世界”的程序。 n次(在这种情况下为10次)。我试图将循环寄存器的值保存为一个常量,以便在执行循环主体时不更改它。当我尝试执行此操作时,我收到分段错误错误。我不确定为什么会这样。

我的代码:

SECTION .DATA
    print_str:          db 'Hello, world.', 10
    print_str_len:      equ $-print_str

    limit:              equ 10
    step:               dw 1

SECTION .TEXT
    GLOBAL _start 

_start:
    mov eax, 4              ; 'write' system call = 4
    mov ebx, 1              ; file descriptor 1 = STDOUT
    mov ecx, print_str      ; string to write
    mov edx, print_str_len  ; length of string to write
    int 80h                 ; call the kernel

    mov eax, [step]         ; moves the step value to eax
    inc eax                 ; Increment
    mov [step], eax         ; moves the eax value to step
    cmp eax, limit          ; Compare sil to the limit
    jle _start              ; Loop while less or equal

exit:
    mov eax, 1              ; 'exit' system call
    mov ebx, 0              ; exit with error code 0
    int 80h                 ; call the kernel

结果:

Hello, world.
Segmentation fault (core dumped)

cmd:

nasm -f elf64 file.asm -o file.o
ld file.o -o file
./file

1 个答案:

答案 0 :(得分:1)

section .DATA是崩溃的直接原因。小写的section .data是特殊的,并且链接为可执行文件的读写(私有)映射。

大写字母.DATA对于链接器而言并不特殊,它最终成为文本段的一部分,该文本段在没有写许可的情况下映射为read + exec。What's the difference of section and segment in ELF file format

大写的.TEXT也很奇怪:默认情况下,objdump -drwC -Mintel仅反汇编.text部分(以避免像编码一样反汇编数据),因此它为您显示空输出可执行文件。


在GDB(gdb ./foostarti)下启动程序后,我从另一个shell看了该进程的内存映射。

$ cat /proc/11343/maps
00400000-00401000 r-xp 00000000 00:31 110651257                          /tmp/foo
7ffff7ffa000-7ffff7ffd000 r--p 00000000 00:00 0                          [vvar]
7ffff7ffd000-7ffff7fff000 r-xp 00000000 00:00 0                          [vdso]
7ffffffde000-7ffffffff000 rwxp 00000000 00:00 0                          [stack]

如您所见,除了特殊的VDSO映射和堆栈之外,只有一个文件支持的映射,并且仅具有read + exec权限。

在GDB中单步执行,mov eax,DWORD PTR ds:0x400086加载成功,但是 mov DWORD PTR ds:0x400086,eax存储错误。 (有关GDB汇编技巧,请参见x86 tag wiki的底部。)

readelf -a foo 中,我们可以看到ELF程序标头,这些标头告诉OS的程序加载器如何将其映射到内存中:

$ readelf -a foo   # broken version
  ...
Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000000bf 0x00000000000000bf  R      0x200000

 Section to Segment mapping:
  Segment Sections...
   00     .DATA .TEXT 

请注意.DATA.TEXT处于同一段中。这就是您想要的section .rodata(标准节名称,您应该在其中放置只读常量数据(如字符串)),但不适用于可变的全局变量。

修复您的asm以使用section .data.text之后,readelf向我们显示:

$ readelf -a foo    # fixed version
  ...
Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000000e7 0x00000000000000e7  R E    0x200000
  LOAD           0x00000000000000e8 0x00000000006000e8 0x00000000006000e8
                 0x0000000000000010 0x0000000000000010  RW     0x200000

 Section to Segment mapping:
  Segment Sections...
   00     .text 
   01     .data 

请注意,段00为何是R + E而没有W,并且.text部分位于其中。段01是RW(读+写),没有exec,并且.data节在那里。

LOAD标记意味着它们已映射到进程的虚拟地址空间。某些部分(例如调试信息)不是,而仅仅是其他工具的元数据。但是NASM将未知的段名称标记为progbits,即已加载,这就是为什么它能够链接并且负载不会分段的原因。


将其修复为使用section .data后,您的程序将运行而不会出现段错误

循环运行一次迭代,因为step: dw 1之后的2个字节不为零。加载双字后,在我的系统上RAX = 0x2c0001。 (0x002c0002和cmp之间的0xa使LE条件为假,因为它不小于或等于。)

dw的意思是“数据字”或“定义字”。 dd用于数据双字


顺便说一句,无需将循环计数器保留在内存中。您没有将RDI,RSI,RBP或R8..R15用于任何东西,因此您可以将其保存在寄存器中。就像循环前的mov edi, limit,底部的dec edi / jnz

但是,如果您要构建64位代码,而不是32位syscall ABI,则实际上应该使用64位int 0x80 ABI。 What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?。如果您遵循为此编写的指南或教程,则可以构建32位可执行文件。

无论如何,在那种情况下,您可以使用ebx作为循环计数器,因为syscall ABI对寄存器使用了不同的args。