C程序運行時內存結構分析

jopen 9年前發布 | 43K 次閱讀 C程序 C/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

    源代碼及對應的匯編如下

    C程序運行時內存結構分析

    C程序運行時內存結構分析

    寄存器及內存狀態如下

    C程序運行時內存結構分析

    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函數時的內存情況

    C程序運行時內存結構分析

    局部變量i和j保存在48h空間的開始位置(高地址),即棧底附近,如下圖

    C程序運行時內存結構分析

    在調用fun函數之前,將形參從右至左依次壓棧,如下圖

    C程序運行時內存結構分析

    call fun函數時執行跳轉

    C程序運行時內存結構分析

    來源:Mr-Lee

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