文章目录
1 栈帧(Stack Frame)
每个函数的每次调用都会产生一个栈帧,用来记录函数的执行过程。eg:在C语言编译为汇编语言时,每个函数的第一句永远都是
pushl %ebp !保存上一个函数的栈基址
movl %esp, %ebp !初始化本次函数调用的栈
当函数funcA调用funcB时,栈的变化如下
void funcA(){
funcB(x,y);
}
void funcB(x,y){
do_sth(x,y);
}
- funcA把funcB所需的参数以从右向左←的方向压入栈中
- funcA执行汇编指令call funcB,并且把返回地址压入栈中,这里是把"{"压入了栈中。
- funcB开始执行,首选保存funcA的栈基址pushl ebp,然后用mov esp,ebp指令开启新的属于funcB的栈
- 在funcB中,依照定义的次序依次压入局部变量
- 在funcB中,通过ebp + 4,ebp + 8…ebp + 4n的方式来获得传递给funcB的第n个参数,通过ebp - 4,ebp - 8…ebp - 4n的方式来获得第n个局部变量
- 注意,栈的方向是从高地址到低地址的。
以下面的代码为例来描述栈帧的变化过程
void main(){
int res;
res = sum(3,4);
}
int sum(int x,int y){
int z;
z = x + y;
return z;
}
在32位系统上用gcc -S命令编译成汇编代码如下(会改变栈的命令用!标出)
.file "sum.c"
gcc_compiled.:
.tex?
.align 2
.globl _sum
_sum:
pushl %ebp (6)
movl %esp,%ebp !(7)
subl $4,%esp !(8)
movl 8(%ebp),%edx
addl 12(%ebp),%edx
movl %edx,-4(%ebp)
movl -4(%ebp),%eax
jmp L1
.align 2
L1:
leave !(9,10)
ret !(11)
.align 2
.globl _main
_main:
pushl %ebp !(1)
movl %esp,%ebp !(2)
subl $4,%esp !(3)
pushl $4 !(4)
pushl $3 !(4)
call _sum !(5)
movl %eax,-4(%ebp)
L2:
leave
ret
栈的变化情况如下
注:
- pushl eax的解释esp = esp - 4,*(esp) = ax,先改变栈顶,再存入数据
- popl eax的解释*(esp) = ax,esp = esp + 4先改变栈顶,再存入数据
- 下面的两种表示方法是等价的,第一个更规范,但是注意方向。
-
进入main,执行pushl %ebp
-
执行movl %esp,%ebp
-
执行subl $4,%esp
-
pushl $4 pushl $3
-
call _sum
-
push %ebp
-
mov %esp,%ebp
-
sub $4,%esp
-
leave
-
leave
-
ret