SECTION中存在变量的分段错误.数据



我正在努力学习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

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

大写.DATA对于nasm或链接器来说并不是特别的,它最终在没有写权限的情况下作为文本段映射read+exec的一部分

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

在较新的系统上,NASM无法识别的节名的默认值不包括exec权限,因此.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 asm提示,请参阅x86标记wiki的底部。)

readelf -a foo,我们可以看到ELF程序头,它告诉操作系统的程序加载程序如何将其映射到内存中:

$ 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在没有W的情况下是R+E,并且.text部分在其中。01段是RW(读+写),没有exec,.data段就在那里。

LOAD标记意味着它们被映射到进程的虚拟地址空间中。有些部分(如调试信息)不是,只是其他工具的元数据。但NASM将未知的节名标记为progbits,即已加载,这就是为什么它能够链接并使加载不发生segfault。


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

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

CCD_ 26表示";数据字";或";定义单词">对数据字使用dd


BTW,无需将循环计数器保存在内存中。你没有使用RDI、RSI、RBP或R8..R15做任何事情,所以你可以把它保存在寄存器中。就像循环之前的mov edi, limit和底部的dec edi / jnz一样。

但实际上,如果您想要构建64位代码,则应该使用64位syscallABI,而不是32位int 0x80ABI。如果在64位代码中使用32位int 0x80 Linux ABI,会发生什么?。或者构建32位可执行文件,如果您正在遵循为此编写的指南或教程。

无论如何,在这种情况下,您可以使用ebx作为循环计数器,因为系统调用ABI对寄存器使用不同的参数。

相关内容

  • 没有找到相关文章

最新更新