浅谈函数调用的汇编实现细节(用栈来传递参数)
文章目錄
- 前言
- 程序分析
- 總結
前言
??????要想理解函數調用的匯編實現,需要清楚幾個基本概念。在此針對的是用棧來傳遞參數。
??????1,調用現場的保護:假設函數A調用函數B,一旦程序執行進入函數B中,當函數B執行結束后,我們肯定需要執行流繼續從函數A調用現場(callsite)的下一條語句繼續執行函數A。當然,各種應用程序在操作系統上也是這樣運行,否則的話,程序不就飛了?這不符合操作系統有始有終的性格。這一點特別像遞歸函數,雖然一層層嵌套,但是最終還是需要一層層返回的,即從哪個函數開始,就從哪個函數結束。
??????所以,當函數A準備調用函數B時,需要先把A中調用語句的下一條語句保存起來,通常都是保存到棧里面,這樣當函數B返回后,將之前壓入棧中的待執行語句從棧中彈出,然后執行流從這條語句接著執行,即函數A繼續執行。
??????2,關于棧的增長方向:棧是從高地址向低地址方向增長的。即棧底處于高地址,棧頂處于低地址,每次入棧需要將棧指針減小,每次入棧需要將棧指針增加。這點可以查看Ubuntu系統中的程序運行時空間布局得知。在Windows系統中也是一樣,通過相關的程序分析工具可以直觀的看到。
??????3,參數和返回值:函數調用在通常情況下都是需要傳遞參數和返回值的。系統API大都是采用_stdcall調用約定,函數入口參數按從右到左的順序入棧,由被調用者清理棧中的參數,返回值放在eax寄存器中。而C代碼中的子程序采用的是C調用約定,函數入口參數按從右到左的順序入棧,由調用者清理棧中的參數。
??????4,ebp和esp:在x86指令集中,ebp寄存器為棧幀寄存器,用來保存每一個函數的棧底位置(內存地址);esp寄存器為棧頂寄存器,用來保存每一個函數當前的棧頂位置。eip表示當前程序執行的指令地址。
程序分析
void f(int x,int y){int a,b;a=7,b=9;x=x+y;}int g=3;int main(){f(g,5);return 0;}??????上面是一個簡單的c程序函數調用。在main函數調用了函數 f。從程序語義上來分析,雖然這個 f 函數啥也沒干,不過不影響我們分析它的函數調用結構。
??????main函數對應的匯編代碼如下:
??????注意函數調用語句 f(g,5) 是從右往左將參數壓棧的。
??????其中第一條語句將參數 5 壓入棧中;
??????第二條語句取得 g 的值并存放到 eax 中,第三條語句將 eax 壓入棧中,整體實現功能將參數 g 壓入棧中;
??????然后是 call f 語句,可能有不明白的朋友會好奇為什么沒有將下一條語句 add esp,8 的指令地址保存呢?這個牽扯到 call 指令的功能了,它會先保存下一條指令的地址,然后再調用函數 f ;(具體的可以參考博客https://blog.csdn.net/Little_ant_/article/details/108115387講得比較細致一些)
?????? 最后一條語句 add esp,8 的作用是清除main函數棧中的參數,(注意:參數不等同于局部變量) 按照前面提到的,c程序通常由調用者進行清理。 這里清理掉參數 g 和 5,因為都是 int 類型,所以一共占據8字節空間。
??????f 函數對應的匯編如下:
f: push ebpmov ebp,espsub esp,8mov [ebp-4h],7mov [ebp-8h],9mov eax,[ebp+8h]add eax,[ebp+0ch]mov [ebp+8h],eaxmov esp,ebppop ebpret??????第一句:保存main函數的棧幀
??????第二句:設置當前的棧頂為 f 函數的棧幀地址(也就是棧底)
??????第三句:esp減8,可以理解為從棧中拿出8字節空間準備進行存儲局部變量。
??????第四、五句:存儲局部變量 a 和 b 的值,局部變量的存儲按照其賦值語句的先后順序依次將它們入棧。 而參數入棧的順序是從右到左的,從某種角度上來看,局部變量和參數入棧的順序是相反的。 一般情況下:ebp減去任何數值后得到的內存地址都是位于當前函數的??臻g里面的。
??????第六、七句:ebp+8h 表示的是參數 g 所在的棧地址。ebp+0ch 表示的是參數 5 所在的棧地址。這兩句的執行結果是將 g+5 的結果存儲到 eax寄存器中。一般情況下:ebp加上任何數值后得到的內存地址都是位于調用者函數的棧空間里面的。
??????第八句:將eax中 g+5 的最終結果存儲到參數 g 所在位置。 故實現了函數調用功能: g=g+5;
??????第九句:將當前 f 函數的棧底地址賦給棧頂指針esp,即清空 f 函數的函數棧(即清理棧中保存的兩個局部變量)。
??????第十句:將原來main函數的棧幀地址傳遞到ebp寄存器中,此時進入到了main函數原來的??臻g里了。
??????最后一句:用ret指令返回,這和call指令是相對應的。將原來call指令保存的返回地址傳遞給eip寄存器,程序執行流此時回到main函數了。
總結
??????本文簡單的梳理了關于函數調用的細節,所有相關的東西都有提到,只要好好理解,這其實并不困難。雖然這里我默認大家都有一些匯編的基礎,但是如果有哪一點不明白的話歡迎留言~
ps:后面有機會會把??臻g示意圖貼出來~
總結
以上是生活随笔為你收集整理的浅谈函数调用的汇编实现细节(用栈来传递参数)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 采用Huffman编码进行数据压缩
- 下一篇: java中的Native方法