SLP(Chapter 5):函數的調用返回

複習

  1. PUSH & POP
    SP 指向當前棧頂最後壓入的一個字節
    例子:

    1. PUSH EAX (32bits)
      假設壓棧前SP = 0x0012ff40,則PUSH執行後,
      0x0012ff3c~3f將用於存放EAX的值,
      SP=SP-4,即SP修改爲 0x0012ff3c
    2. POP AX (16bits)
      假設壓棧前SP = 0x0012ff40,則POP執行後,
      0x0012ff40~41的值拷貝入AX
      SP=SP+2,即SP修改爲 0x0012ff42
      Tips: SP的修改意味着以前數據理論上不能訪問了,但是該字節的內容並沒有被擦除。
  2. CALL & RET
    例子:

    1. CALL (導致ESP 修改-4/-8)
      PUSH IP( a near or short call) / PUSH CS PUSH IP (far call)
      JMP 跳轉至被調用函數
    2. RET (導致ESP 修改+4,近調用)
      POP IP
    3. RETF(導致ESP 修改+8,對應far call)
      POP IP POP CS
      注:近轉移 near/short call是同一代碼段內轉移,只需要變IP
      遠轉移 far call是不同代碼段間(調用另一個文件的代碼段)轉移,除了IP,還要改變CS
      page

1 參數和局部變量

作用域 Scope:

變量具有作用域,即同一變量名稱可能引用不同的值,具體激活哪個取決於其使用位置
example:局部變量是定義它的塊的私有變量;全局變量可以由其他塊訪問
mainlocal?

編譯器如何實現作用域

編譯器通過操作不同作用域中的不同內存地址來實現範圍,具有相同名稱的變量由編譯器映射到不同的地址。
硬件不知道作用域

參數

  1. 形參 formal parameter:編譯器按照被調用函數局部參數處理形參
  2. 實參 actual parameter:

編譯器實現

在這裏插入圖片描述
每一次調用,距離0X58

  • 問題 Problem:遞歸調用中,需要即使版本的變量,但無法在編譯時決定如何分配
  • 解決 Solution:
    • 全局變量靜態分配,即絕對地址
    • 局部變量動態分配,因爲不知道需要多少個,即相對地址
      • where:棧幀 stack frame(在談到內存分配,稱之爲棧幀)
      • how:激活記錄activation record(與棧幀實際上是一個東西,在談到如何運行時,習慣稱之爲激活記錄)
      • ebp & esp

2 激活記錄activation record / 棧幀 stack frame

2.1 基礎

局部變量和實際參數:

  • 動態分配
  • 減慢程序的執行速度

具體實現:爲了最大程度地降低成本,編譯器計算函數局部變量所需的總空間量,並將該空間分配到一個塊中(激活記錄active record)

硬件支持

  • 硬件編譯器和硬件在內部維護兩個重要值,這些值有助於以簡單而優雅的方式分隔和操作激活記錄
    堆棧指針 R --esp
    幀指針 R --ebp
  • 激活記錄以非常簡單的方式組織,以適應硬件

規則:只能訪問堆棧頂部的激活記錄

  • 只有當該函數調用的所有子函數返回,該函數纔可能返回,得到控制權。
  • 變量的scope:我們只能訪問當前正在執行的函數的局部變量(和全局變量—不在stack上)。

激活記錄 / 棧幀的定義:
爲每個函數調用分配的內存塊
生命週期:從函數調用到返回

2.2 CALL / RET 過程

在這裏插入圖片描述

Call(1. push IP; 2. Jmp)

調用函數時,編譯器和硬件:

  • 調用方 :保存上下文 context
    call
  • 被調用方:構造自己的棧幀 stack frame
    在這裏插入圖片描述

Return

callee
caller_2

彙編代碼例子:

在這裏插入圖片描述
在這裏插入圖片描述
main

3 函數的調用約定

why?

  • C 級函數調用
    參數
    局部變量
    返回值
  • 彙編/機器級別的功能調用
    call和 ret 指令

What?
調用約定 描述了 被調用代碼的接口:

  • 參數的分配順序
  • 放置參數的位置(推送在堆棧上或放置在寄存器中)
  • 函數可以使用哪些寄存器
    eax 的返回值
  • 調用方還是被調用方負責在返回時展開堆棧

類型:

  1. _cdecl(上面的例子)
    C Declaration 表示C語言默認的函數調用方法
    • 參數按"從右向左"的順序推送到堆棧
    • 調用方負責清除堆棧上的參數,手動清棧
    • 被調用函數不要求參數,所有參數數量不對,不會有編譯階段錯誤
  2. _stdcall / WINAPI
    • 參數按"從右向左"的順序推送到堆棧
    • 被調用方負責清除堆棧上的參數
  3. Pascal
    • 參數按"從左向右"的順序推送到堆棧
    • 被調用方負責清除堆棧上的參數

完整的函數調用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章