函数调用堆栈的过程

帅比萌擦擦* 提交于 2019-12-14 19:09:43

 

本篇来分析函数调用的过程:

通过下面一个简单的例子来进入话题:


 
  1. #include<stdio.h>

  2. int sum(int a,int b)

  3. {

  4. int tmp=0;

  5. tmp=a+b;

  6. return tmp;

  7. }

  8.  
  9. int main()

  10. {

  11. int a=10;

  12. int b=20;

  13. int ret=0;

  14.  
  15. ret=sum(a,b);

  16. printf("ret=%d\n",ret);

  17. return 0;

  18. }

首先,先从main函数开始,查看main()函数的反汇编代码,进去之后会发现在一开始就看到如下部分:

那这一部分是干什么的呢?先留个疑问,等会解决!

紧接着,继续看反汇编,如下图

画出在栈中的整个过程:

呃,画图的确有点麻烦,不过越画越清楚,^O^

整个函数的调用,结合上面两张图就基本明白了。那现在说一下刚开始的问题,在第一张图中也做了一点标注。对于每个函数的刚开始都会出现基本类似的指令。这一大堆指令总结起来就干了四件事情:

第一:将调用方的栈底地址入栈。====》push  ebp

第二:让原本指向调用方栈底的ebp指向当前函数的栈底。====》mov   ebp,esp

第三:给当前函数开辟栈帧。====>sub   esp,44h

第四:对开辟的栈帧进行初始化。初始化的大小不一定。====>rep   stos  

所以对于sum函数我们可以理解,但是在main函数刚开始也有这些指令,不由地,我们知道,main函数也是通过一个函数来进行调用的,所以也需要上面这四个步骤!!此时也就可以回答图二中我画??的地方咯,它一定存的是调用main函数的函数栈底地址。是不是很清楚呀^O^

趁热打铁,自己写一下整个过程吧!


 
  1. #include<stdio.h>

  2. int sum(int a,int b)

  3. {

  4. /*

  5. push ebp

  6. mov ebp,esp

  7. sub esp,44h

  8. push ebx

  9. push esi

  10. push edi

  11. lea edi,[ebp-44h]

  12. mov ecx,11h

  13. mov eax,0xccccccch

  14. rep stos ===>[esp,ebp]=0xcccccccc

  15. */

  16. int tmp=0;//mov dword ptr[ebp-4],0

  17. tmp=a+b;

  18. /*

  19. mov eax,dword ptr[ebp+8]

  20. add eax,dword ptr[ebp+0ch]

  21. mov dword ptr[ebp-4],eax

  22. */

  23. return tmp;//mov dword ptr[ebp-4],eax

  24. }

  25. /*

  26. mov eax,dword ptr[ebp-4]

  27. mov esp,ebp

  28. pop ebp

  29. ret

  30. */

  31.  
  32. int main()

  33. {

  34. /*

  35. push ebp

  36. mov ebp,esp

  37. sub esp,44h

  38. push ebx

  39. push esi

  40. push edi

  41. lea edi,[ebp-44h]

  42. mov ecx,11h

  43. mov eax,0xccccccch

  44. rep stos ===>[esp,ebp]=0xcccccccc

  45. */

  46. int a=10;//mov dword ptr[ebp-4],0Ah

  47. int b=20;//mov dword ptr[ebp-8],14h

  48. int ret=0;//mov dword ptr[ebp-0Ch],0

  49. ret=sum(a,b);

  50. /*

  51. mov eax,ptr[ebp-8]

  52. push eax

  53. mov ebx,ptr[ebp-4]

  54. push ebx

  55. push ecx

  56. call sum

  57. add esp,8

  58. mov dword ptr[ebp-0ch],eax

  59. */

  60. printf("ret=%d\n",ret);

  61. return 0;

  62. }

总结一下吧~

1、函数的运行都是在栈上开辟内存。

2、栈是通过esp(栈顶指针)、ebp(栈底指针)两个指针来标识的。

3、对于栈上的访问都是通过栈底指针的偏移来访问的。

4、在call一个函数时,有两件事情要做:先将调用函数的下一行指令的地址压入栈中;再进行跳转。

5、在函数调用时检查函数是否申明、函数名是否相同、函数的参数列表是否匹配、函数的返回值多大。

①如果  【函数的返回值<=4个字节】,则返回值通过寄存器eax带回。

②如果  【4<函数的返回值<=8个字节】,则返回值通过两个寄存器eax和edx带回。

③如果  【函数的返回值>8个字节】,则返回值通过产生的临时量带回。

6、函数结束ret指令干了两件事:先出栈;再将出栈的值放到CPU的PC寄存器中。因为PC寄存器中永远放的是下一次执行指令的地址,所以就顺理成章的在函数调用完之后依旧接着原来的代码继续执行。

END~

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!