为什么从x86_64汇编函数调用C abort()函数会导致分段错误(SIGSEGV)而不是中止信号?
作者:互联网
考虑该程序:
main.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
为什么我仅在上一次运行时遇到分段错误,而没有按预期获得中止信号?
男子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中进行了测试.
我还在Ubuntu 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");
编译器很可能在这样的距离内生成了主函数,使得该堆栈在调用之前在16个字节的边界上对齐,因此它恰好可以工作.您不应该依赖这种行为.该代码还有其他潜在问题,但在这种情况下不会出现故障.调用之前,堆栈应正确对齐;您通常应该关注红色区域;并且您应该在调用约定中将所有易失性寄存器指定为Clobbers,包括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位代码中. David Wohlferd的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之前将其与调用中止的my_asm_func对齐堆栈.确保遵守ABI是确保代码按预期工作的关键.
关于重定位错误,这可能是因为使用的Ubuntu版本默认情况下使用位置独立代码(PIC)生成GCC代码.您可以通过在调用的函数名称后附加@plt来通过Procedure Linkage Table进行C库调用来解决此问题.彼得·科德斯(Peter Cordes)撰写了与此主题相关的Stackoverflow answer.
标签:x86-64,inline-assembly,c-3,linux,x86 来源: https://codeday.me/bug/20191024/1922278.html