【问题标题】:Assembly "Hello world" program segmentation fault汇编“Hello world”程序分段错误
【发布时间】:2021-04-17 12:29:30
【问题描述】:

为什么会出现错误:

分段错误(核心转储)

这是汇编代码:

.intel_syntax noprefix
    
.data

    message: .asciz "Hello World!\n"

.text

.global main

main:
    lea rdi, message
    call printf

    ret

【问题讨论】:

  • 你得到一个段错误,因为 1) 你没有保持堆栈对齐到 16 个字节和 2) 你没有将 AL 寄存器归零以指示没有使用向量寄存器。顺便说一句,较早的版本没有问题#1,这也使#2 无关紧要,因此应该可以工作。

标签: assembly x86-64 calling-convention


【解决方案1】:

问题

System V ABI 要求您在调用函数之前将堆栈对齐为 16 字节。为了方便起见,ABI 保证“在函数入口时,如果您使用 8 * n(n 是奇数)子堆栈指针,您的堆栈将是 16 字节对齐的”。

如果您不遵循此调用约定,其他库可能会崩溃,因为如果它们需要使用需要特殊对齐的指令(例如 movdqa),则它们无法正确对齐堆栈帧。

解决方案

ammarfaizi2@integral:/tmp/test_asm$ cat test.S
.intel_syntax noprefix
    
.data

    message: .asciz "Hello World!\n"

.text

.global main

main:
    sub rsp, 8
    xor eax, eax
    lea rdi, [rip + message]
    call printf
    add rsp, 8
    ret
ammarfaizi2@integral:/tmp/test_asm$ gcc test.S -o test
ammarfaizi2@integral:/tmp/test_asm$ ./test
Hello World!
ammarfaizi2@integral:/tmp/test_asm$ 

推荐

如果你call 一个函数,接下来你要做的是ret,你可以用尾调用来简化代码。它使用jmp 来调用目标函数。如果之前设置过,请确保在跳转之前撤消当前函数堆栈帧。

要支持 PIE 和 PIC,请考虑使用 RIP 相对寻址来访问静态存储。它还提高了安全性。现在的编译器通常默认将目标编译为 PIE。

这部分是使用RIP相对寻址访问静态存储的例子:

lea rdi, [rip + message]

执行

ammarfaizi2@integral:/tmp/test_asm$ cat test.S
.intel_syntax noprefix
    
.data

    message: .asciz "Hello World!\n"

.text

.global main

main:
    xor eax, eax
    lea rdi, [rip + message]
    jmp printf

ammarfaizi2@integral:/tmp/test_asm$ gcc test.S -o test
ammarfaizi2@integral:/tmp/test_asm$ ./test
Hello World!
ammarfaizi2@integral:/tmp/test_asm$ 

编辑

为安全起见添加了xor eax, eax。见:glibc scanf Segmentation faults when called from a function that doesn't align RSP

【讨论】:

  • 关于尾调用的好点。值得补充的是,您将 lea 指令更改为 lea rdi, [rip + message] 以允许创建 PIE 可执行文件(否则会出现重定位错误)。另一种解决方案是指示 GCC 使用--static 创建一个 EX ELF(PIE 是具有不可覆盖符号的 SH ELF)。
  • @MargaretBloom 哦,对了,我添加了有关 RIP 相对寻址的信息。
  • 不设置 AL 仍然不安全。较旧的 GCC 版本编译可变参数函数以使用 AL 计算跳转到 movaps 存储序列。 (当前的 GCC,以及当前的 libc 版本,只需检查 0 / non-0 以有条件地运行所有 8 个或不运行所有 8 个,因此可能具有 AL > 8 的 ABI 违规实际上不会导致问题。)无论如何,您应该使用xor eax, eax。或者使用不是可变参数的puts
  • 对齐存储以转储可变参数函数的可能 XMM 参数是 printf 出错的常见原因(使用 AL!=0),但某些其他函数的代码生成有时确实包括 16 字节对齐加载或存储。例如glibc scanf Segmentation faults when called from a function that doesn't align RSP
  • @AmmarFaizi:对于正确性来说可以,但对于效率来说更糟的是,只修改现有 RAX 值的低字节,而不是使用归零惯用语将新值写入寄存器。 What is the best way to set a register to zero in x86 assembly: xor, mov or and?/Why doesn't GCC use partial registers?xor al,al 在大多数 CPU 上并不是特殊的归零习惯用法,所以 mov al,0 在某些 CPU 上实际上会更好。但还是比xor eax,eax差。
猜你喜欢
  • 2013-07-24
  • 1970-01-01
  • 2016-01-04
  • 2023-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多