C程序運行時內存結構分析
實驗知識
- 靜態變量存儲在靜態存儲區,局部變量存儲在動態存儲區(棧),代碼存放在代碼區
- 寄存器,EBP指向棧底,ESP指向棧頂,EIP指向正在執行指令的下一條指令,三個寄存器中保存的都是地址,32位系統,地址為4個字節即dword
- 所有寫在函數定義里面的語句都編譯成指令(驅動CPU) </ul>
實驗代碼
#include <stdio.h>
int fun(int a, int b);
int m = 10;
int main()
{
int i = 4;
int j = 5;
m = fun(i, j);
}
int fun(int a, int b)
{
int c = 0;
c = a + b;
return c;
} 這段代碼包含兩個函數,因此可以測試函數調用,此外還包含了靜態變量、局部變量、返回值等
實驗測試
測試工具:VC6.0
源代碼及對應的匯編如下
寄存器及內存狀態如下
EBP棧頂初始值為0018FF84h,ESP初始為0018FF48h
ESP和EBP在棧中的作用
在每個函數最開始的地方有兩條語句
push ebp mov ebp,esp
在函數返回前也有兩條語句
mov esp,ebp pop ebp
每運行一個函數就新開一段棧空間,所謂的開棧空間就是移動ebp棧底,在移動ebp之前,通過push ebp保存上一級函數的棧底,然后用ebp指向現在函數棧的棧頂,即為當前函數開辟了棧;接著給局部變量進行地址分配以及保存現場等,esp不斷向低地址 移動,當函數調用結束時,esp指回當前函數的棧頂(mov esp,ebp),然后上一級函數的棧頂地址出棧保存在ebp中(pop ebp)。因此,每一個函數的棧頂上面都保存著上一級函數的棧頂地址,用于當前函數結束時能夠返回上一級函數的棧,通過ebp和esp以及壓棧出棧操作對 棧進行維護。
逐條分析
main函數對應的匯編代碼如下
7: int main()
8: {
00401020 push ebp // ebp初始為0018FF84h壓棧,壓棧后esp = 0018FF48h - 4 = 0018FF44h
00401021 mov ebp,esp // ebp保存棧頂0,ebp=esp=0018FF44h
00401023 sub esp,48h // esp -= 48h開辟了一段棧空間,留待后面保存局部變量,此時esp=0018FF44h-48h=0018FEFCh
00401026 push ebx
00401027 push esi
00401028 push edi // ebx、esi和edi壓棧,esp = 0018FEFCh - 4*3 = 0018FEF0h
00401029 lea edi,[ebp-48h] // lea指令將ebp-48h作為偏移地址保存在edi中,edi=0018FEFCh,即棧中ebx的上面
0040102C mov ecx,12h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi] // 將eax重復保存在以edi開始的棧空間里,重復次數為ecx次,向高地址方向,共覆蓋12h*4=48h個地址,即棧中保存ebx的地址以上到ebp指向的地址這一段全部填充為cch
9: int i = 4;
00401038 mov dword ptr [ebp-4],4 // 保存變量i
10: int j = 5;
0040103F mov dword ptr [ebp-8],5 // 保存變量j
11: m = fun(i, j);
00401046 mov eax,dword ptr [ebp-8] // 將j保存在eax中
00401049 push eax // eax壓棧, esp=0018FEF0h-4=0018FEECh
0040104A mov ecx,dword ptr [ebp-4] // 將i保存在ecx中
0040104D push ecx // ecx壓棧,esp=0018FEECh-4=0018FEE8h
0040104E call @ILT+0(_fun) (00401005) // 以上實際上是為形參分配內存,順序從右到左,此步進行函數跳轉
00401053 add esp,8 // 形參的地址回收,esp=0018FEE8h+8=0018FEF0h
00401056 mov [_m (00424a30)],eax // 返回值存放在靜態變量m中
12: return 0;
0040105B xor eax,eax // 返回值置為0
13: }
0040105D pop edi
0040105E pop esi
0040105F pop ebx
00401060 add esp,48h
00401063 cmp ebp,esp
00401065 call __chkesp (004010d0)
0040106A mov esp,ebp
0040106C pop ebp
0040106D ret fun函數的匯編代碼理解
15: int fun(int a, int b)
16: {
00401090 push ebp
00401091 mov ebp,esp
00401093 sub esp,44h
00401096 push ebx
00401097 push esi
00401098 push edi
00401099 lea edi,[ebp-44h]
0040109C mov ecx,11h
004010A1 mov eax,0CCCCCCCCh
004010A6 rep stos dword ptr [edi] // 以上理解同main函數,ebp壓棧時保存的地址是0018FF44h,即main函數棧開始開始的地方,然后ebp指向當前函數棧開始的地方
17: int c = 0;
004010A8 mov dword ptr [ebp-4],0 // 為c分配地址,并賦值
18: c = a + b;
004010AF mov eax,dword ptr [ebp+8] // 獲得第一個參數
004010B2 add eax,dword ptr [ebp+0Ch] // 與第二個參數求和
004010B5 mov dword ptr [ebp-4],eax // 結果保存在c中
19: return c;
004010B8 mov eax,dword ptr [ebp-4] // 返回值存放在eax
20: }
004010BB pop edi // 現場恢復
004010BC pop esi
004010BD pop ebx
004010BE mov esp,ebp // 當前函數棧空間回收,以后可重新分配,esp=0018FEE8h
004010C0 pop ebp // ebp恢復為0018FF44h
004010C1 ret // 返回,等待執行函數調用的下一條指令 調用fun函數時的內存情況
局部變量i和j保存在48h空間的開始位置(高地址),即棧底附近,如下圖
在調用fun函數之前,將形參從右至左依次壓棧,如下圖
call fun函數時執行跳轉

來源:Mr-Lee
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!