以以下函數爲例,來了解函數的調用過程,即棧幀。
#include <stdio.h>
#include <stdlib.h>
int Add(int x, int y)
{
int ret = 0;
ret = x + y;
return ret;
}
int main()
{
int a = 10;
int b = 20;
int ret = 0;
ret = Add(a, b);
printf("%d\n", ret);
system("pause");
return 0;
}
在vs2013中調試該程序,並在調試窗口轉匯編,可以得到該程序的彙編語言。
1、在正式看彙編語言之前,我們需要先了解一下main()函數的調用。 main()函數被__tmainCRTStartup()函數調用,__tmainCRTStartup()函數被mainCRTStartup()函數調用。 所以,先爲mainCRTStartup()函數開闢空間,再爲__tmainCRTStartup()函數開闢空間,最後爲main函數開闢空間。
【注意】1)很顯然,本文中的圖像都是上爲高地址,下爲低地址
2)本文中省略了很多esp的變化過程,但讀者需知道,esp始終指向棧頂(出棧操作中esp會改變,但仍然只指向棧頂,被修改的部分已不在棧內)
2、進入main函數的彙編語言
保留__tmainCRTStartup()的棧底,因爲運行完main()後還要回到__tmainCRTStartup()
改變ebp的位置
爲main開闢空間,0E4h大小
壓棧ebx、esi、edi
加載有效地址 使edi指向ebp-0E4h處
39h = 0E4h/4 將39h存入ecx寄存器。四個字節爲一個單位,共39h個單位
將隨機數存入eax寄存器
從edi指向的位置向下重複賦值,一次2個字節,賦爲隨機值。即將棧幀開闢的空間全部初始化爲隨機值
將b的值放入eax寄存器中,然後將其壓棧;將a的值放入ecx寄存器中,然後將其壓棧。
用_b表示b的值,用_a表示a的值,該過程在內存中的表示如下:
調用Add函數,壓棧012D3F20(即 call指令下一條指令的地址)
暫時不用看這兩條指令,等Add函數調用完會返回此處
3、進入Add()函數的彙編語言
與上述過程類似,此過程在內存中的變化爲:
// 將main函數空間中存儲的_a的值(形參)存入eax寄存器,此時eax中的值爲10
// 將main函數空間中存儲的_b的值(形參)加入eax寄存器,此時eax中的值爲30
// 將eax寄存器中的值(30)存入Add函數中ret局部變量中
// 將ret局部變量的值(30)存入寄存器eax中,作爲返回值
// 這段代碼是爲了銷燬Add函數空間,首先edi、esi、ebx出棧
// 然後將ebp的值給esp,過程如下:
//然後將ebp指回main函數的ebp,銷燬存儲main函數ebp的空間
// 此時回到main函數中call指令的下一條指令處:
// 第一條指令銷燬形參,將_a、_b空間銷燬
// 第二條指令將eax寄存器中存儲的ret返回值(30)存入main函數空間的ret中
至此,Add函數已全部結束,main函數也基本結束,對於printf和system(“pause”),以及return 0就不再多做解釋。
全部過程整理到一張圖上爲: