当弹出x86堆栈以访问函数参数时,出现分段错误

37
我会尽力进行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

然而,当我运行生成的文件时,出现了分段错误。

但是,当我将

pop edx

替换为

mov edx, [esp+4]

程序正常工作。有人可以解释一下这是为什么吗?


2
pop edx 移动堆栈指针,而 mov edx, [esp+4] 则不会。通常在 C 语言中,清理堆栈的工作由调用者完成。 - Jabberwocky
问得好。+1 - fuz
@Jabberwocky 但是为什么会导致分段错误呢?栈对两个函数都是共用的,对吧? - Susmit Agrawal
2
因为你弹出了返回地址而不是参数,所以你不能像这样使用pop。 - R.. GitHub STOP HELPING ICE
11
因为返回地址在堆栈上,所以您的 pop edx 实际上是从堆栈中弹出返回地址。当执行 ret 时,处理器将跳转到堆栈上的任何地址。 - Jabberwocky
@Jabberwocky 哦,我现在明白了!谢谢! - Susmit Agrawal
1个回答

32

这是一个可能的汇编代码: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函数时,指令call会将地址retadd推入栈中。这实际上是一个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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接