C++中一个函数被调用的完整过程分析
代码很简单,如下:
int add(int a,int b) { return a + b; } int main() { int x = add(3, 7); return 0; }
main函数第一行设置断点,调试并查看反汇编。(注意先push 7,再push3,这里可以很好地佐证函数参数是从右到左入栈!)
step运行,前两句完成后内存是这样:
到第三句call语句:
这个名字很长的调用内部实际就是一个jump跳转,调用的正是我们的add函数:
首先要知道一个事情:函数在内存中都有自己的栈帧,ebp:base pointer栈底指针;esp:stack pointer栈顶指针。
截止到push edi前面的语句执行情况:
首先把main函数的ebp入栈,并且把ebp值更新为当前esp(也就是新的栈帧的底部)
下面的sub语句和三条push语句执行的情况:
其中sub语句就是把esp(堆顶指针)下移,也就是新开了0x0c0h大小的空间。
接下来几句是将开的空间赋值为0ccccccc,略过不表:
下面这一句和程序无关,看名字推测是调试有关的,我们也跳过:
下面两句是实际执行加法运算,并把结果保存在eax寄存器里。(函数返回值一般通过eax返回)
之后这几句:
我们先看下此时的内存情况:
pop edi; 是把栈顶的值pop,并且赋给edi。注意这里栈顶的edi值是之前main函数里的edi值,所以经过这一步,edi寄存器恢复了main函数里它自己的值。
同理,esi、ebx也都恢复其在main函数里的值。这里大家应该看出来了:系统在恢复现场(调用add函数之前的状态)。
这样pop三次后,内存的情况:
下一句,add esp,0c0h; 还记得之前开栈空间的时候是sub esp 0c0h;吗?这是因为内存中栈从高地址向低地址扩展,不懂得可以看这里:https://www.cnblogs.com/FdWzy/p/12424308.html
言归正传,之前申请了0c0h长度的空间是做减法的,那么现在释放就要做加法。完成后的内存状况:
接下来两句:
实际上经过我们上面的分析,此时ebp就应该等于esp。但系统加这两句应该是为了防止意外出现,提高鲁棒性来的,我们先略过。
最后:
把ebp的值赋给esp(当然此时正常情况下ebp和esp值相同)。
最后pop栈顶,值赋给ebp:
此时我们的ebp寄存器也恢复到了之前main函数里的ebp值。但esp还没有恢复到原油的值。因为栈顶还有两个形参7和3,难道系统不管这两个参数了吗?
这时候我们继续运行,发现了:add esp 8; 果然,系统把esp向上移动8,也就是pop掉了add函数调用剩余的最后资源。
这样,我们的系统就好像没调用过add函数一样,可以继续从main函数里暂停的地方继续运行了!当然对于add函数的返回值我们可以从eax寄存器来取得。
完结撒花!👏👏