函数调用过程中的压栈退栈
Updated:
常用的三个和栈有关的寄存器:
1.EIP
2.ESP
3.EBP
当调用fun函数开始时,三者的作用。
1.EIP寄存器里存储的是CPU下次要执行的指令的地址。
也就是调用完fun函数后,让CPU知道应该执行main函数中的printf(”函数调用结束”)语句了。
2.EBP寄存器里存储的是是栈的栈底指针,通常叫栈基址,这个是一开始进行fun()函数调用之前,由ESP传递给EBP的。(在函数调用前你可以这么理解:ESP存储的是栈顶地址,也是栈底地址。)
3.ESP寄存器里存储的是在调用函数fun()之后,栈的栈顶。并且始终指向栈顶。
栈和寄存器的关系:
栈用来临时存放变量的值或地址,只要涉及到CPU的运算,都要有寄存器(至少有一个)才能做
eax,ebx,ecx,edx就是这类常见的寄存器
压栈一般是先压参数,这个是调用函数做
然后才设置被调用函数的栈底地址和栈顶地址,这个是被调用函数做
原函数
int bar(int c, int d)
{
int e = c + d;
return e;
}
int foo(int a, int b)
{
return bar(a, b);
}
int main(void)
{
foo(2, 3);
return 0;
}
在栈中的位置图,和一般的栈表示不一样,位置反了过来
相应的汇编代码
$ gcc main.c -g
$ objdump -dS a.out
…
08048394
int bar(int c, int d)
{
8048394: 55 push %ebp//保存调用函数的栈底地址
8048395: 89 e5 mov %esp,%ebp//调用函数的栈顶就是被调用函数的栈底,更改ebp的值
8048397: 83 ec 10 sub $0x10,%esp//为本函数的局部变量分配空间,更改esp的值
int e = c + d;
804839a: 8b 55 0c mov 0xc(%ebp),%edx //把栈中地址是以栈底为基准,偏移量+12的变量存入寄存器
804839d: 8b 45 08 mov 0x8(%ebp),%eax//同上
80483a0: 01 d0 add %edx,%eax//相加操作
80483a2: 89 45 fc mov %eax,-0x4(%ebp)//把结果返回给本函数中的变量e
return e;
//函数有一个int型的返回值,这个返回值是通过eax寄存器传递的
80483a5: 8b 45 fc mov -0x4(%ebp),%eax
}
80483a8: c9 leave //函数开头的push %ebp和mov %esp,%ebp的逆操作
80483a9: c3 ret //call指令的逆操作:
1.现在esp所指向的栈顶保存着返回地址,把这个值恢复给eip,同时esp增加4,esp的值变成0xbff1c40c。
2.修改了程序计数器eip,因此跳转到返回地址0x80483c2继续执行。
080483aa
int foo(int a, int b)
{
80483aa: 55 push %ebp//同bar
80483ab: 89 e5 mov %esp,%ebp//同bar
80483ad: 83 ec 08 sub $0x8,%esp//同bar
return bar(a, b);
//压入参数c
80483b0: 8b 45 0c mov 0xc(%ebp),%eax
80483b3: 89 44 24 04 mov %eax,0x4(%esp)
//压入参数d
80483b7: 8b 45 08 mov 0x8(%ebp),%eax
80483ba: 89 04 24 mov %eax,(%esp)
80483bd: e8 d2 ff ff ff call 8048394
}
80483c2: c9 leave //同bar
80483c3: c3 ret //同bar
080483c4
int main(void)
{
80483c4: 8d 4c 24 04 lea 0x4(%esp),%ecx
80483c8: 83 e4 f0 and $0xfffffff0,%esp
80483cb: ff 71 fc pushl -0x4(%ecx)
80483ce: 55 push %ebp
80483cf: 89 e5 mov %esp,%ebp
80483d1: 51 push %ecx
80483d2: 83 ec 08 sub $0x8,%esp
foo(2, 3);
//压缩参数a,对栈中地址是以栈底为基准,偏移量+4的地址进行赋值
80483d5: c7 44 24 04 03 00 00 movl $0x3,0x4(%esp)
80483dc: 00
80483dd: c7 04 24 02 00 00 00 movl $0x2,(%esp)//
80483e4: e8 c1 ff ff ff call 80483aa
return 0;
80483e9: b8 00 00 00 00 mov $0x0,%eax
}
80483ee: 83 c4 08 add $0x8,%esp//释放栈空间
80483f1: 59 pop %ecx
80483f2: 5d pop %ebp//栈底地址出栈
80483f3: 8d 61 fc lea -0x4(%ecx),%esp
80483f6: c3 ret
注:
lea命令 和mov 命令操作方向跟编译器有关,有时候会是这样:
mov %esp,%ebp//%ebp = %esp
lea 0x4(%esp),%ecx//0x4(%esp) = 0x4(%esp)