弹出 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
时,地址retadd
被call
指令压入堆栈。它实际上是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 堆栈时出现分段错误的主要内容,如果未能解决你的问题,请参考以下文章