gpt4 book ai didi

linux - 为什么从 x86_64 汇编函数调用 C abort() 函数会导致段错误 (SIGSEGV) 而不是中止信号?

转载 作者:可可西里 更新时间:2023-11-01 11:51:19 26 4
gpt4 key购买 nike

考虑程序:

主.c

#include <stdlib.h>

void my_asm_func(void);
__asm__(
".global my_asm_func;"
"my_asm_func:;"
"call abort;"
"ret;"
);

int main(int argc, char **argv) {
if (argv[1][0] == '0') {
abort();
} else if (argv[1][0] == '1') {
__asm__("call abort");
} else {
my_asm_func();
}
}

我编译为:

gcc -ggdb3 -O0 -o main.out main.c

然后我有:

$ ./main.out 0; echo $?
Aborted (core dumped)
134
$ ./main.out 1; echo $?
Aborted (core dumped)
134
$ ./main.out 2; echo $?
Segmentation fault (core dumped)
139

为什么我只在最后一次运行时收到段错误,而不是预期的中止信号?

man 7 信号:

   SIGABRT       6       Core    Abort signal from abort(3)
SIGSEGV 11 Core Invalid memory reference

根据 128 + SIGNUM 规则确认信号。

作为完整性检查,我还尝试从程序集调用其他函数,如下所示:

#include <stdlib.h>

void my_asm_func(void);
__asm__(
".global my_asm_func;"
"my_asm_func:;"
"lea puts_message(%rip), %rdi;"
"call puts;"
"ret;"
"puts_message: .asciz \"hello puts\""
);

int main(void) {
my_asm_func();
}

这确实有效并打印:

hello puts

在 Ubuntu 19.04 amd64、GCC 8.3.0、glibc 2.29 中测试。

我也在一个Ubunt Ubuntu 18.04 docker上试过了,结果是一样的,只是运行时程序输出:

./main.out: Symbol `abort' causes overflow in R_X86_64_PC32 relocation          
./main.out: Symbol `abort' causes overflow in R_X86_64_PC32 relocation

这感觉像是一个很好的线索。

最佳答案

在这段代码中,在全局范围内定义了一个函数(使用基本汇编):

void my_asm_func(void);

__asm__(
".global my_asm_func;"
"my_asm_func:;"
"call abort;"
"ret;"
);

您违反了 x86-64(AMD64) System V ABI 规则之一,该规则要求在执行 CALL 之前的某个点进行 16 字节堆栈对齐(可能更高,具体取决于参数)。

3.2.2 The Stack Frame

In addition to registers, each function has a frame on the run-time stack. This stack grows downwards from high addresses. Figure 3.3 shows the stack organization.

The end of the input argument area shall be aligned on a 16 (32, if __m256 is passed on stack) byte boundary. In other words, the value (%rsp + 8) is always a multiple of 16 (32) when control is transferred to the function entry point. The stack pointer, %rsp, always points to the end of the latest allocated stack frame.

进入函数后,堆栈将错位 8,因为 8 字节返回地址现在位于堆栈上。要将堆栈重新对齐到 16 字节的边界上,请在函数开头从 RSP 中减去 8,并在完成时将 8 加回 RSP。您也可以只在开头压入任何寄存器,如 RBP,然后弹出它以获得相同的效果。

这个版本的代码应该可以工作:

void my_asm_func(void);

__asm__(
".global my_asm_func;"
"my_asm_func:;"
"push %rbp;"
"call abort;"
"pop %rbp;"
"ret;"
);

关于这段碰巧有效的代码:

__asm__("call abort");

编译器生成 main 函数的方式可能使堆栈在调用之前在 16 字节边界上对齐,因此它恰好可以工作。你不应该依赖这种行为。此代码还有其他潜在问题,但在这种情况下不会出现故障。堆栈在调用之前应该正确对齐;您应该总体上关注红色区域;并且您应该将调用约定中的所有 volatile 寄存器指定为 clobber,包括 RAX/RCX/RDX/R8/R9/R10/R11、FPU 寄存器和 SIMD 寄存器。在这种情况下,abort 永远不会返回,因此这不是与您的代码相关的问题。

红色区域在 ABI 中是这样定义的:

The 128-byte area beyond the location pointed to by %rsp is considered to be reserved and shall not be modified by signal or interrupt handlers.8 Therefore, functions may use this area for temporary data that is not needed across function calls. In particular, leaf functions may use this area for their entire stack frame, rather than adjusting the stack pointer in the prologue and epilogue. This area is known as the red zone.

在内联汇编中调用函数通常不是一个好主意。可以在其他 Stackoverflow answer 中找到调用 printf 的示例这显示了执行 CALL 的复杂性,尤其是在带有红色区域的 64 位代码中。大卫沃尔弗德的 Dont Use Inline Asm总是一本好书。


这段代码碰巧起作用了:

void my_asm_func(void);
__asm__(
".global my_asm_func;"
"my_asm_func:;"
"lea puts_message(%rip), %rdi;"
"call puts;"
"ret;"
"puts_message: .asciz \"hello puts\""
);

但是您可能很幸运 puts 不需要正确对齐,并且您碰巧没有失败。您应该在调用 puts 之前对齐堆栈,如前所述,使用调用 abortmy_asm_func。确保符合 ABI 是确保代码按预期工作的关键。


关于重定位错误,这可能是因为所使用的 Ubuntu 版本默认使用位置无关代码 (PIC) 来生成 GCC 代码。您可以通过 Procedure Linkage Table 调用 C 库来解决此问题。通过将 @plt 附加到您 CALL 的函数名称。 Peter Cordes 写了一个相关的 Stackoverflow answer关于这个话题。

关于linux - 为什么从 x86_64 汇编函数调用 C abort() 函数会导致段错误 (SIGSEGV) 而不是中止信号?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56324948/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com