stack和stack frame


  首先,我們先來了解下棧幀和棧的基本知識:
    • 棧幀也常被稱為“活動記錄”(activation record),是編譯器用來實現過程/函數調用的一種數據結構。
    • 從邏輯上講,棧幀就是一個函數執行的環境,包含所有與函數調用相關的數據:主要包括函數參數、函數中的局部變量、函數執行完后的返回地址,被函數修改的需要恢復的任何寄存器的副本。
  另外,需要注意的是: 棧是從高地址向低地址延伸的。每個函數的每次調用,都有它自己獨立的一個棧幀,這個棧幀中維持着所需要的各種信息。寄存器ebp用來指向當前的棧幀的底部(高地址),寄存器esp用來指向當前的棧幀的頂部(低地址),即 在函數調用執行過程中,尋找所需參數或變量信息時使用寄存器ebp來尋址,因為寄存器esp的值是經常變化的,而寄存器ebp的值對一個函數的棧幀來講是不變的。
  棧是一種LIFO形式的數據結構,所有的數據都是后進先出。這種形式的數據結構正好滿足我們調用函數的方式: 父函數調用子函數,父函數在前,子函數在后;返回時,子函數先返回,父函數后返回棧支持兩種基本操作,push和pop。push將數據壓入棧中,pop將棧中的數據彈出並存儲到指定寄存器或者內存中。
   pop操作后,棧中的數據並沒有被清空,只是該數據我們無法直接訪問。棧的形式不一定必須時向下生長的,也可以向上生長,只是我們的系統調用棧(call stack)用到的是向下生長形式而已。
  有了上面的基礎知識,讓我們以一個實際的例子來講解一下。
int Add(int a, int b)
{
    int c;
    c=a+b;
    return c;
}
Main()
{
    Add(10, 5);
    printf("hello world!");
}

程序從main()函數開始執行,首先將main()函數壓入系統調用棧(call stack)(下面如無特殊說明,用代指系統調用棧(call stack)),並給它分配一個棧幀用以保存所需信息。然后執行main函數中第一條語句,這是一條函數調用語句,在實際調用執行Add函數前需要做一些准備工作。

  首先將5和10兩個參數壓入棧,同時更新ESP指針的值,如下圖所示:

 
  注意,參數5和10壓入棧的順序,應該是從右向左依次壓入系統調用棧(call stack)。
  接下來我們需要搞清楚的是:當Add函數調用執行完畢之后,我們需通過某種方式返回到main函數中繼續執行下面的指令,在本例中也就是執行print函數。解決這個問題的方式就是將下一條指令的地址壓入棧中。
  以上准備工作就緒,下面開始調用執行Add函數。
  首先,我們需要將main函數用來尋址參數或變量信息的EBP寄存器值壓入棧中保存,以便於從Add函數返回之后,從棧中取出EBP的值賦給EBP寄存器讓main函數用來尋址參數或變量信息,同時更新ESP的值。為了Add函數能夠尋址到所需信息,將此時的ESP寄存器的值賦值給EBP寄存器(圖中的EBP-new)。此時將接着執行int c語句,為變量c開辟一段內存空間壓入棧中,同時更新ESP的值。接下來執行c=a+b,然后返回c,但main函數中並沒有聲明變量來存儲該返回值,故該返回值丟失。 函數返回時將ESP更新為EBP-new,接着將EBP-old彈出賦值給EBP寄存器,讓main函數拿來尋址所需信息,此時就從Add函數的棧幀恢復到了main函數的棧幀。接着彈出RET(Add函數的返回地址),對應的匯編代碼中會有一條ret指令,該指令會將RET返回地址保存到EIP寄存器中,然后處理器根據這個地址無條件跳轉到main函數的相應位置去取下一條指令即print函數繼續執行。print函數調用執行過程中壓棧出棧過程與Add函數類似,不再細說。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM