弹出 x86 堆栈时出现分段错误
Segmentation fault when popping x86 stack
我正在尝试 link 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
我编译和link这两个如下:
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]
程序运行良好。有人可以解释为什么会这样吗?
这是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
指向包含 return 地址的内存位置。
现在,如果您使用 pop edx
,return 地址进入 edx
,堆栈如下所示:
| ... |
+--------+
| 40 | <- ESP points here
+--------+
现在,如果您此时执行 ret
,程序实际上将跳转到地址 40,并且很可能会出现段错误或以其他一些不可预测的方式运行。
编译器生成的实际汇编代码可能不同,但这说明了问题。
顺便说一句,编写函数的一种更有效的方法是:对于这个小函数的 non-inline 版本,大多数编译器在启用优化后都会这样做。
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
我正在尝试 link 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
我编译和link这两个如下:
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]
程序运行良好。有人可以解释为什么会这样吗?
这是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
指向包含 return 地址的内存位置。
现在,如果您使用 pop edx
,return 地址进入 edx
,堆栈如下所示:
| ... |
+--------+
| 40 | <- ESP points here
+--------+
现在,如果您此时执行 ret
,程序实际上将跳转到地址 40,并且很可能会出现段错误或以其他一些不可预测的方式运行。
编译器生成的实际汇编代码可能不同,但这说明了问题。
顺便说一句,编写函数的一种更有效的方法是:对于这个小函数的 non-inline 版本,大多数编译器在启用优化后都会这样做。
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