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

Sus*_*wal 36 c x86 assembly gcc nasm

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

我的C程序:

extern int plus_10(int);

# include <stdio.h>

int main() {
    int x = plus_10(40);
    printf("%d\n", x);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我的汇编程序:

[bits 32]

section .text

global plus_10
plus_10:
    pop edx
    mov eax, 10
    add eax, edx
    ret
Run Code Online (Sandbox Code Playgroud)

我编译并链接两个如下:

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
Run Code Online (Sandbox Code Playgroud)

但是,当我运行结果文件时,出现了分段错误。

但是当我更换

流行版

mov edx,[esp + 4]

该程序工作正常。有人可以解释为什么会这样吗?

Jab*_*cky 31

这是可能的汇编代码 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
Run Code Online (Sandbox Code Playgroud)

现在,当您调用时plus_10,该地址retadd将被call指令压入堆栈。它实际上是一个push+ jmp,并且ret是有效pop eip

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

|  ...   |
+--------+
|   40   |  <- ESP+4 points here (the function argument)
+--------+
| retadd |  <- ESP points here
+--------+
Run Code Online (Sandbox Code Playgroud)

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

现在,如果使用pop edx返回地址edx,则堆栈看起来像这样:

|  ...   |
+--------+
|   40   |  <- ESP points here
+--------+
Run Code Online (Sandbox Code Playgroud)

现在,如果您ret在此时执行,该程序实际上将跳转到地址40,最有可能发生段错误或以其他某些不可预测的方式运行。

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


顺便说一句,一种更有效的编写函数的方法是:对于该微型函数的非内联版本,大多数编译器都会启用优化功能。

global plus_10
plus_10:
    mov   eax,  [esp+4]    ; retval = first arg
    add   eax,  10         ; retval += 10
    ret
Run Code Online (Sandbox Code Playgroud)

比它更小,效率更高

    mov   eax,  10
    add   eax,  [esp+4]        ; decode to a load + add.
    ret
Run Code Online (Sandbox Code Playgroud)

  • cdecl调用约定将期望该值通过eax返回。因此,您不能只是以自己喜欢的方式编写asm函数,它必须与编译器生成的C兼容。 (3认同)