为什么从x86_64汇编函数调用C abort()函数会导致分段错误(SIGSEGV)而不是中止信号? [英] Why does calling the C abort() function from an x86_64 assembly function lead to segmentation fault (SIGSEGV) instead of an abort signal?

查看:107
本文介绍了为什么从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

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

Why do I get the segmentation fault only for the last run, and not an abort signal as expected?

man 7信号:

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

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

confirms the signals due to the 128 + SIGNUM rule.

作为健全性检查,我还尝试从程序集中进行其他函数调用,如下所示:

As a sanity check I also tried to make other function calls from assembly as in:

#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中进行了测试.

Tested in Ubuntu 19.04 amd64, GCC 8.3.0, glibc 2.29.

我也曾在Ubuntu Ubuntu 18.04 Docker中尝试过,结果是一样的,除了程序在运行时输出:

I also tried it in an Ubunt Ubuntu 18.04 docker, and the results were the same, except that the program outputs when running:

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

感觉很好.

推荐答案

在此代码中,该代码在全局范围内定义了一个函数(使用基本程序集):

In this code that defines a function at global scope (with basic assembly):

void my_asm_func(void);

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

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

You violate one of the x86-64(AMD64) System V ABI rules that requires 16 byte stack alignment (may be higher depending on the parameters) at a point just before a CALL is made.

3.2.2堆栈框架

除寄存器外,每个函数在运行时堆栈上都有一个框架.该堆栈从高处向下生长 地址.图3.3显示了堆栈组织.

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.

T 输入自变量区域的末尾应对齐16(32,如果已传递__m256 在堆栈上)字节边界.换句话说,值(%rsp + 8)为 当控制权转移到计算机时,总是16(32)的倍数. 功能入口点.堆栈指针%rsp始终指向 最新分配的堆栈帧的末尾.

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 ,然后弹出它以获得相同的效果.

Upon entry to a function the stack will be misaligned by 8 because the 8 byte return address is now on the stack. To align the stack back on a 16 byte boundary subtract 8 from RSP at the beginning of the function and add 8 back to RSP when finished. You can also just push any register like RBP at the beginning and pop it after to get the same effect.

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

This version of the code should work:

void my_asm_func(void);

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


关于碰巧起作用的这段代码:


Regarding this code that happened to work:

__asm__("call abort");

编译器很可能在很远的距离内生成了main函数,以使堆栈在此调用之前在16个字节的边界上对齐,因此它可以正常工作.您不应该依赖这种行为.此代码还有其他潜在问题,但在这种情况下不会出现故障.调用之前,堆栈应正确对齐;您通常应该关注红色区域;并且您应该在调用约定中将所有易失性寄存器指定为Clobbers,包括 RAX/RCX/RDX/R8/R9/R10/R11 ,FPU寄存器和SIMD寄存器.在这种情况下,abort永远不会返回,因此这与您的代码无关.

The compiler likely generated the main function in such away that the stack was aligned on a 16 byte boundary prior to this call so it happened to work. You shouldn't rely on this behavior. There are other potential issues with this code, but don't present as a failure in this case. The stack should be properly aligned before the call; you should be concerned in general about the red zone; and you should specify all the volatile registers in the calling conventions as clobbers including RAX/RCX/RDX/R8/R9/R10/R11, the FPU registers, and the SIMD registers. In this case abort never returns so this isn't an issue related to your code.

在ABI中以这种方式定义红色区域:

The red-zone is defined in the ABI this way:

超出%rsp所指位置的128字节区域被认为是 保留,并且不得被信号或中断处理程序修改.8因此, 功能可以使用该区域存储功能中不需要的临时数据 电话.特别是,叶子函数可以在整个堆栈框架中使用此区域, 而不是调整序言和结尾中的堆栈指针. 该区域是 被称为红色区域.

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答案中找到调用printf的示例,该示例特别显示了执行CALL的复杂性在带有红色区域的64位代码中. David Wohlferd的不要使用内联Asm 总是很好的阅读.

It is generally a bad idea to call a function in inline assembly. An example of calling printf can be found in this other Stackoverflow answer which shows the complexities of doing a CALL especially in 64-bit code with red-zone. David Wohlferd's Dont Use Inline Asm is always a good read.

此代码恰好起作用:

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是确保代码按预期工作的关键.

but you were probably lucky that puts didn't need proper alignment and you happened to get no failure. You should be aligning the stack before calling puts as described earlier with the my_asm_func that called abort. Ensuring compliance with the ABI is the key to ensuring code will work as expected.

关于重定位错误,这可能是因为默认情况下,所使用的Ubuntu版本使用的位置独立代码(PIC)用于GCC代码生成.您可以通过@plt附加到您CALL的函数名称上,nofollow noreferrer>过程链接表.彼得·科德斯(Peter Cordes)为此主题写了一个相关的 Stackoverflow答案.

Regarding the relocation errors, that is probably because the version of Ubuntu being used is using Position Independent Code (PIC) by default for GCC code generation. You could fix the issue by making the C library calls though the Procedure Linkage Table by appending @plt to the function names you CALL. Peter Cordes wrote a related Stackoverflow answer on this topic.

这篇关于为什么从x86_64汇编函数调用C abort()函数会导致分段错误(SIGSEGV)而不是中止信号?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆