機器代碼層面的函數(shù)調(diào)用

2018.04.18

CPU 發(fā)出指令把硬盤程序指令搬到內(nèi)存,操作系統(tǒng)給程序指令分配內(nèi)存。然后操作系統(tǒng)會告訴 CPU 「程序入口點」,也就是第一條指令地址。

CPU 從內(nèi)存出取得的指令一般是這三類:

  1. 把數(shù)據(jù)從內(nèi)存運到 CPU 寄存器。
  2. 對寄存器數(shù)據(jù)進行運算。
  3. 把寄存器數(shù)據(jù)寫入到內(nèi)存。

x86 體系機器:每次函數(shù)調(diào)用都會創(chuàng)建一個幀。

幀,內(nèi)存中一段連續(xù)空間。

多個函數(shù)幀在內(nèi)存里排起來, 就像一個先進后出的棧一樣。

但這個棧與普通的棧不一樣,這個棧的棧底在上面,從上往下生長(從高地址往低地址生長)。
4 個字節(jié)為單位,4 個字節(jié)內(nèi)從低地址往高地址生長。

ebp,CPU 特殊寄存器,指向棧中當前函數(shù)幀的起始地址。
esp,CPU 特殊寄存器,指向棧中當前函數(shù)幀的尾地址。

ebp 與 esp

下面是 CPU 向內(nèi)存取的函數(shù)調(diào)用指令。

  1. 把 ebp 的值壓入棧中。(CPU -> 內(nèi)存)
    這時,地址 800~804 的內(nèi)容是 1000,esp 指向地址 800 。

  2. 把 esp 的值賦給 ebp 。(CPU)
    這時,esp、ebp 同時指向地址 800,一個新的函數(shù)幀誕生!
    函數(shù)幀的起始地址是 800, 里邊的內(nèi)容是1000 。

  3. 把 esp 的值減去 24 。(CPU)
    esp 指向地址 776,這是為準備函數(shù)實參騰出空間。
    減去 24 是為了數(shù)據(jù)對齊。24 + 4(入棧的 ebp) + 4(返回地址) = 32 。
    x86 編程規(guī)定函數(shù)幀是 16 的整數(shù)倍。

  4. 把 10 放到 ebp 減去 4 的地址,把 20 放到 ebp 減去8的地址。(CPU -> 內(nèi)存)
    其實就是:int x=10; int y=20 。

  5. 把 x 地址放到 esp 指向的地址,把 y 地址放到 esp+4 指向的地址。
    其實就是準備實參 &x, &y 。

準備函數(shù)實參
  1. 把地址 100 壓入棧中。
    這是調(diào)用函數(shù)之前,調(diào)用方下一條指令地址(函數(shù)返回地址)壓入棧。
int sum = add(&x, &y); 
printf("the sum is %d\n",sum);  // 假設這條指令的地址是 100
準備返回地址
  1. 函數(shù)調(diào)用前的保護現(xiàn)場,以便返回時恢復現(xiàn)場。 (CPU -> 內(nèi)存)
    把寄存器 ebp 的值壓到棧里去
    把 esp 的值賦給 ebp
    把寄存器 ebx 的值壓入棧
保護現(xiàn)場
  1. CPU 取出數(shù)據(jù)計算。(CPU)
int add(int *xp , int *yp){
    int x = *xp;
    int y = *yp;
    return x+y
}

把 ebp 加 8 的數(shù)據(jù)取出來放到 edx 寄存器。取得 xp 。
把 ebp 加 12 的數(shù)據(jù)取出來放到 ecx 寄存器。取得 yp 。
把 edx 指向的內(nèi)存地址的數(shù)據(jù)取出來,放到 ebx 寄存器。取得 *xp 放入 ebx 。
把 ecx 指向的內(nèi)存地址的數(shù)據(jù)取出來,放到 eax 寄存器。取得 *yp 放入 eax 。
把 ebx 和 eax 的值加起來,放到 eax 寄存器中。取得 *xp+*yp,放入 eax 。

  1. 恢復現(xiàn)場。(內(nèi)存 -> CPU)
    把 esp 指向的數(shù)據(jù)彈出的 ebx 寄存器。
    把 esp 指向的數(shù)據(jù)彈出到 ebp 寄存器。
恢復現(xiàn)場
  1. 返回。
    CPU 取出返回地址 100,去那里找到指令繼續(xù)執(zhí)行。

函數(shù)調(diào)用總結(jié)

  1. 把參數(shù)和返回地址準備好。

  2. 新建函數(shù)幀:
    把寄存器 ebp 的值壓入棧。
    把 esp 的值賦給 ebp。

  3. 函數(shù)調(diào)用完后, 重置 ebp 和 esp,讓他們重新指向調(diào)用方的函數(shù)幀。

內(nèi)存緩沖區(qū)溢出

用戶輸入的數(shù)據(jù)是從低地址向高地址存放的。

黑客輸入精心設計的數(shù)據(jù)過多以至于覆蓋了返回地址,讓返回地址指向惡意代碼入口地址。

輸入函數(shù)有邊界檢查就可避免攻擊。

參考文檔:
http://mp.weixin.qq.com/s/hX8tHnoq4gdkhcLHUGzEbg
http://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=2665513039&idx=1&sn=381c1b8c7f86906c4838050b8c1db2bb&scene=21#wechat_redirect

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容