分段故障,在.data节中有变量

发布于 2025-02-05 14:55:49 字数 1234 浏览 3 评论 0原文

我正在尝试学习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

I am trying to learn nasm. I want to make a program that prints "Hello, world." n times (in this case 10). I am trying to save the loop register value in a constant so that it is not changed when the body of the loop is executed. When I try to do this I receive a segmentation fault error. I am not sure why this is happening.

My code:

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

The result:

Hello, world.
Segmentation fault (core dumped)

The cmd:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

多情癖 2025-02-12 14:55:49

章节.data是崩溃的直接原因。较低案例部分.data是特殊的,并链接为可执行文件的读写(私有)映射。部分名称对大小写。

上盘.data对于NASM或Linker不是特别的,并且最终以文本段无需写入许可的映射读取+exec

。代码>也很奇怪:默认情况下objdump -drwc -mintel仅拆卸.text procepter(避免拆卸数据,就好像是代码一样),因此显示为空输出对于您的可执行文件。

在较新的系统上权限,因此.text中的代码将segfault。与 indeblysbly。 >


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

$ 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]

As you can see, other than the special VDSO mappings and the stack, there's only the one file-backed mapping, and it has read+exec permission only.

Single-stepping inside GDB, the mov eax,DWORD PTR ds:0x400086 load succeeds, but the mov DWORD PTR ds:0x400086,eax store faults。 (请参阅 x86 tag wiki for GDB ASM提示。

) ,我们可以看到告诉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 在同一段中。这就是您想要的章节.rodata(一个标准的部分名称,您应该在其中添加读取的常数数据(例如字符串),但它对可变的全局变量不起作用。

修复您的ASM以使用pection .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 s segment 00是无w的r + e, .text部分在其中。段01是无exec的RW(read + Write),.Data部分在那里。

加载标签意味着它们将其映射到该过程的虚拟地址空间中。某些部分(例如调试信息)不是,只是其他工具的元数据。但是NASM标志未知的部分名称为Progbits,即加载,这就是为什么它能够链接并具有负载而不是segfault的原因。


将其修复到使用e节.DATA后,您的程序将在无segfaulting 的情况下运行。

循环运行一次迭代,因为以下2个字节步骤:DW 1不是零。 DWORD加载后,我的系统上的rax = 0x2c0001。 cmp 在0x002c0002和之间

”。 使用DD用于数据dword


顺便说一句,无需将循环计数器保持在内存中。您不使用RDI,RSI,RBP或R8..R15用于任何东西,因此您可以将其保存在寄存器中。像MOV EDI一样,限制在循环之前,dec edi/jnz在底部。

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

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

section .DATA is the direct cause of the crash. Lower-case section .data is special, and linked as a read-write (private) mapping of the executable. Section names are case-sensitive.

Upper-case .DATA is not special for nasm or the linker, and it ends up as part of the text segment mapped read+exec without write permission.

Upper-case .TEXT is also weird: by default objdump -drwC -Mintel only disassembles the .text section (to avoid disassembling data as if it were code), so it shows empty output for your executable.

On newer systems, the default for a section name NASM doesn't recognize doesn't include exec permission, so code in .TEXT will segfault. Same as Assembly section .code and .text behave differently


After starting the program under GDB (gdb ./foo, starti), I looked at the process's memory map from another 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]

As you can see, other than the special VDSO mappings and the stack, there's only the one file-backed mapping, and it has read+exec permission only.

Single-stepping inside GDB, the mov eax,DWORD PTR ds:0x400086 load succeeds, but the mov DWORD PTR ds:0x400086,eax store faults. (See the bottom of the x86 tag wiki for GDB asm tips.)

From readelf -a foo, we can see the ELF program headers that tell the OS's program loader how to map it into memory:

$ 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 

Notice how both .DATA and .TEXT are in the same segment. This is what you'd want for section .rodata (a standard section name where you should put read-only constant data like your string), but it won't work for mutable global variables.

After fixing your asm to use section .data and .text, readelf shows us:

$ 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 

Notice how segment 00 is R + E without W, and the .text section is in there. Segment 01 is RW (read + write) without exec, and the .data section is there.

The LOAD tag means they're mapped into the process's virtual address space. Some section (like debug info) aren't, and are just metadata for other tools. But NASM flags unknown section names as progbits, i.e. loaded, which is why it was able to link and have the load not segfault.


After fixing it to use section .data, your program runs without segfaulting.

The loop runs for one iteration, because the 2 bytes following step: dw 1 are not zero. After the dword load, RAX = 0x2c0001 on my system. (cmp between 0x002c0002 and 0xa makes the LE condition false because it's not less or equal.)

dw means "data word" or "define word". Use dd for a data dword.


BTW, there's no need to keep your loop counter in memory. You're not using RDI, RSI, RBP, or R8..R15 for anything so you could just keep it in a register. Like mov edi, limit before the loop, and dec edi / jnz at the bottom.

But actually you should use the 64-bit syscall ABI if you want to build 64-bit code, not the 32-bit int 0x80 ABI. What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?. Or build 32-bit executables if you're following a guide or tutorial written for that.

Anyway, in that case you'd be able to use ebx as your loop counter, because the syscall ABI uses different args for registers.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文