弹出 x86 堆栈时出现分段错误

Posted

技术标签:

【中文标题】弹出 x86 堆栈时出现分段错误【英文标题】:Segmentation fault when popping x86 stack 【发布时间】:2019-09-30 12:02:06 【问题描述】:

我正在尝试链接 x86 程序集和 C。

我的 C 程序:

extern int plus_10(int);

# include <stdio.h>

int main() 
    int x = plus_10(40);
    printf("%d\n", x);
    return 0;

我的汇编程序:

[bits 32]

section .text

global plus_10
plus_10:
    pop edx
    mov eax, 10
    add eax, edx
    ret

我将两者编译链接如下:

gcc -c prog.c -o prog_c.o -m32
nasm -f elf32 prog.asm -o prog_asm.o
gcc prog_c.o prog_asm.o -m32

但是,当我运行生成的文件时,我遇到了分段错误。

但是当我替换时

流行音乐

mov edx, [esp+4]

程序运行良好。有人能解释一下为什么会这样吗?

【问题讨论】:

pop edx 移动堆栈指针,mov edx, [esp+4] 不移动。通常在 C 中,由调用者来清理堆栈。 问得好问题。 +1 @Jabberwocky 但是为什么会导致分段错误呢?堆栈对于这两个函数是通用的,对吧? 因为你弹出了返回地址而不是参数。你不能像这样使用pop。 @SusmitAgrawal 因为返回地址在堆栈上。你的pop edx 实际上是从堆栈中弹出返回地址,当ret 被执行时,处理器会跳转到堆栈上的任何地址 【参考方案1】:

这是int x = plus_10(40);的可能汇编代码

        push    40                      ; push argument
        call    plus_10                 ; call function
retadd: add     esp, 4                  ; clean up stack (dummy pop)
        ; result of the function call is in EAX, per the calling convention

        ; if compiled without optimization, the caller might just store it:
        mov     DWORD PTR [ebp-x], eax  ; store return value
                                        ; (in eax) in x

现在当您调用plus_10 时,地址retaddcall 指令压入堆栈。它实际上是push+jmp,而ret 实际上是pop eip

所以您的堆栈在 plus_10 函数中如下所示:

|  ...   |
+--------+
|   40   |  <- ESP+4 points here (the function argument)
+--------+
| retadd |  <- ESP points here
+--------+

ESP 指向包含返回地址的内存位置。

现在,如果您使用pop edx,返回地址将进入edx,堆栈如下所示:

|  ...   |
+--------+
|   40   |  <- ESP points here
+--------+

现在,如果您此时执行 ret,程序实际上会跳转到地址 40,并且很可能会出现段错误或以其他不可预知的方式运行。

编译器生成的实际汇编代码可能不同,但这说明了问题。


顺便说一句,编写函数的一种更有效的方法是:对于这个小函数的非内联版本,这是大多数编译器在启用优化的情况下所做的。

global plus_10
plus_10:
    mov   eax,  [esp+4]    ; retval = first arg
    add   eax,  10         ; retval += 10
    ret

这比

更小,效率更高
    mov   eax,  10
    add   eax,  [esp+4]        ; decode to a load + add.
    ret

【讨论】:

cdecl 调用约定将期望值通过 eax 返回。所以你不能随便写 asm 函数,它必须与编译器生成的 C 兼容。 @Lundin 显然他的平台使用 cdecl 约定。我还写了可能的汇编代码,所以根据平台的不同,它可能会有所不同。编辑和澄清。谢谢。

以上是关于弹出 x86 堆栈时出现分段错误的主要内容,如果未能解决你的问题,请参考以下文章

从堆栈读取时出现分段错误

设置堆栈后在 C 中调用 printf 时出现分段错误

运行 C++ 代码时出现分段错误

将结构插入地图时出现分段错误

删除时出现分段错误

分配时出现分段错误[重复]