1. 程式人生 > >C語言彙編-函式呼叫堆疊的過程

C語言彙編-函式呼叫堆疊的過程

本篇來分析函式呼叫的過程:

通過下面一個簡單的例子來進入話題:

  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. int main()
  9. {
  10. int a=10;
  11. int b=20;
  12. int ret=0;
  13. ret=sum(a,b);
  14. printf("ret=%d\n",ret);
  15. return 0;
  16. }

首先,先從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. int main()
  32. {
  33. /*
  34. push ebp
  35. mov ebp,esp
  36. sub esp,44h
  37. push ebx
  38. push esi
  39. push edi
  40. lea edi,[ebp-44h]
  41. mov ecx,11h
  42. mov eax,0xccccccch
  43. rep stos ===>[esp,ebp]=0xcccccccc
  44. */
  45. int a=10;//mov dword ptr[ebp-4],0Ah
  46. int b=20;//mov dword ptr[ebp-8],14h
  47. int ret=0;//mov dword ptr[ebp-0Ch],0
  48. ret=sum(a,b);
  49. /*
  50. mov eax,ptr[ebp-8]
  51. push eax
  52. mov ebx,ptr[ebp-4]
  53. push ebx
  54. push ecx
  55. call sum
  56. add esp,8
  57. mov dword ptr[ebp-0ch],eax
  58. */
  59. printf("ret=%d\n",ret);
  60. return 0;
  61. }

總結一下吧~

1、函式的執行都是在棧上開闢記憶體。

2、棧是通過esp(棧頂指標)、ebp(棧底指標)兩個指標來標識的。

3、對於棧上的訪問都是通過棧底指標的偏移來訪問的。

4、在call一個函式時,有兩件事情要做:先將呼叫函式的下一行指令的地址壓入棧中;再進行跳轉。

5、在函式呼叫時檢查函式是否申明、函式名是否相同、函式的引數列表是否匹配、函式的返回值多大。

①如果  【函式的返回值<=4個位元組】,則返回值通過暫存器eax帶回。

②如果  【4<函式的返回值<=8個位元組】,則返回值通過兩個暫存器eax和edx帶回。

③如果  【函式的返回值>8個位元組】,則返回值通過產生的臨時量帶回。

6、函式結束ret指令幹了兩件事:先出棧;再將出棧的值放到CPU的PC暫存器中。因為PC暫存器中永遠放的是下一次執行指令的地址,所以就順理成章的在函式呼叫完之後依舊接著原來的程式碼繼續執行。

END~