以下面的程序为例:
#include<iostream>
using namespace std;
int sum(int a,int b)
{
int temp=0;
temp=a+b;
return temp;
}
int main()
{
int a=10;
int b=20;
int ret=sum(a,b);
cout<<ret<<endl;
return 0;
}
问题1:main函数调用sum,sum执行完后,怎么知道回到哪个函数中?
问题2:sum函数执行完,回到main后,怎么知道从哪行指令开始运行。
要描述一个栈,需要知道栈底和栈顶就可以了。其中esp来标识栈顶,ebp来标识栈底。(栈是从高地址向低地址增长的,所以ebp要比esp的地址大)
1)假设mian函数的栈帧是:
…esp
ret
b 10
a 20
…ebp0–0x0018ff40
2)然后就是对sum函数的参数进行压栈:
…esp
10=》int a
20=》int b
ret
b 10
a 20
…ebp0–0x0018ff40
3)需要注意的是sum的参数的压栈顺序是从有向左压栈。
上述压栈完毕后,进行call sum,call sum做的第一件事就是,将call的下一行指令的地址进行压栈(解决了上面的问题二)!然后第二件事情才是进入sum。这里假设call指令的下一行代码的地址是0x08124458。过程如下:
…esp
0x08124458
10=》int a
20=》int b
ret
b 10
a 20
…ebp0–0x0018ff40
4)进入sum函数后,在执行sum中的代码之前,要进行一些前处理:就是要将mian函数的栈帧即main的ebp压栈,然后开辟新的栈也就是sum的栈:
…esp
…ebp
0x0018ff40
0x08124458
10=》int a
20=》int b
ret
b 10
a 20
5)然后开始执行sum中的代码:
需要注意的是,temp=a+b的这一行代码,汇编指令中会有特殊的地址偏移,在栈上找到a和b的值。然后return temp这一行代码,是将temp的值放在了全局的eax寄存器上。
只有定义局部变量或者函数调用的时候才会进行压栈操作,其余的计算过程和return操作是不会在栈上体现的。
…esp
temp 0
…ebp
0x0018ff40
0x08124458
10=》int a
20=》int b
ret
b 10
a 20
6)执行完sum后,会进行栈帧的回退,首先,将esp的位置放到ebp的位置,然后执行栈的pop,pop出的地址值就是mian函数的ebp!将这个值赋给ebp,至此,就回到了mian的栈帧中:
…esp
0x08124458
10=》int a
20=》int b
ret
b 10
a 20
…ebp–0x0018ff40
7)然后开始继续运行,首先要找到mian中继续运行执行,方式就是执行一次栈的pop,将这个地址放入CPU的PC寄存器中。完毕后,栈的内容大概是:
…esp
10=》int a
20=》int b
ret
b 10
a 20
…ebp–0x0018ff40
8)然后程序开始从0x08124458开始执行,0x08124458中往往会有一个 add esp ,8 ,然后再是其他的 。这就会把esp加8个字节,也就是出栈两次,将栈中压入的sum的参数出栈,此时:
…esp
ret
b 10
a 20
…ebp–0x0018ff40
9)之后就交给指令了,这些指令会将eax寄存器的值赋给ret。完成sum函数的调用!!
…esp
ret 30
b 10
a 20
…ebp–0x0018ff40
下面回答问题1:问题1:main函数调用sum,sum执行完后,怎么知道回到哪个函数中?
就是通过压栈的顺序。
(未完待续,见视频)