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寄存器来取得。

 

完结撒花!👏👏

posted @ 2020-03-21 16:02  NeoZy  阅读(792)  评论(0编辑  收藏  举报