以前接觸程序時,只知道程序寫的對,一般都能運行出來,但是卻不知道程序是怎麼一步一步將每一步編譯鏈接起來的,今天我們用匯編來看一下程序到底是怎麼在程序中運行的。
#include <stdio.h>
int Add(int x,int y)
{
int sum = 0;
sum = x+y;
return sum;
}
int main()
{
int a = 2;
int b = 3;
int ret = Add(a,b);
printf("%d\n",ret);
return 0;
}
我們在寫程序的時候都是先寫main函數,知道main函數是系統函數,直接調用就行。但是,實際上main函數是被三個其他的函數一次調用而來的,而程序在執行的時候會依次給main函數開闢一塊內存,然後在根據我們寫的程序依次以堆棧的形式在這塊內存上存取數據。如此一來,我們隊程序的調用就可以清楚明確的瞭解了。
在調試的過程中,我們轉到反彙編寫來看看。在調用main函數之後,系統先將ebp(棧底指針)和esp(棧頂指針)壓入到一塊堆棧中(運行時堆棧,又叫棧幀),然後是esp與4cH想減(即把esp指針向上挪4c空間大小),再將ebx,esi,edi分別壓到
堆棧中,經過le啊,mov,mov三條之令之後,main函數的初始空間就被填充成了13h(二進制位19)個cc
爲了更加形象的瞭解堆棧的創建和銷燬,我們以圖示的方式來演示一下
當吧ebx,esi,edi這三個寄存器壓入堆棧之後,將13h大小的空間初始化cc後,esp指針向上移動到edi處,此時main函數調用纔算結束。程序接着往下執行,這時候到了真正的主程序,執行int a=2, int b=3;看看內存中a=2和b=3是怎麼存儲的
有沒有注意到剛剛的ebp指針指向的位置是0018FF3C,現在02和03的位置正是在剛剛ebp指針的上面,一次我們可以想象到一條程序的存儲是先從棧底開始存放,逐層向上
接下來程序往下走進入函數部分
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,44h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-44h]
0040102C mov ecx,11h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
4: int sum = 0;
00401038 mov dword ptr [ebp-4],0
5: sum = x+y;
0040103F mov eax,dword ptr [ebp+8]
00401042 add eax,dword ptr [ebp+0Ch]
00401045 mov dword ptr [ebp-4],eax
6: return sum;
00401048 mov eax,dword ptr [ebp-4]
7: }
0040104B pop edi
0040104C pop esi
0040104D pop ebx
0040104E mov esp,ebp
00401050 pop ebp
00401051 ret
接下來到了好戲時間,先是執行int sum = 0;系統從ebp處往上分配給sum4個字節的內存賦值爲0,接着執行int x+y,仔細看這塊對應的彙編語言,它將ebp+8和ebp+0ch相加起來然後賦給了ebp-4,而我們知道,程序剛剛給sum = 0分配的內存正是ebp-4,而ebp+8h和ebp+0ch就是剛剛a和b形參的位置。這就說明,系統把形參a,b相加起來,把和放在了指向sum的內存處。
程序到這,堆棧的創建就算完成了,接下來就要到把在函數裏執行的結果返回到剛剛在main函數裏創建的指針處,繼續看彙編程序。
0040104B pop edi
0040104C pop esi
0040104D pop ebx
0040104E mov esp,ebp
00401050 pop ebp
00401051 ret
下來就是剛剛創建函數的堆棧的銷燬了,先是pop(彈出堆棧)edi,esi,ebx這三個寄存器,然後將ebp給esp,也就是說把剛剛上面開闢的空間收回,然後經過ret跳回至剛剛在main函數處調用函數那兒(現場保護)。
回到調用處是執行add esp+8,就是說把棧頂指針向下移動8位,也就是剛剛跳過存放形參的位置
經過了彙編程序這麼一走,我們看到了程序a+b的值在函數裏運算完,傳給了最初的ebp-0ch,就是剛剛實參b的上面(ret指向的內存),然後再經過printf函數打印出來,然後再執行一遍剛剛銷燬Add函數的步驟,這樣整個函數的堆棧創建和銷燬就算執行完了。
整理一下上面的步驟就是:
1、先給main函數開闢空間
2、將寄存器和實參分別壓到堆棧中
3、開闢一片形參空間,然後實參傳給形參
4、給Add函數開闢一片空間,將寄存器和函數裏的程序壓入到這片空間中
5、把剛剛的形參進行運算,將計算結果放在函數的空間中
6、把結果傳遞給主函數,並且打印出來
7、Add函數的銷燬
8、主函數的銷燬