C函數調用過程原理及函數棧幀分析

在x86的計算機系統中,內存空間中的棧主要用于保存函數的參數,返回值,返回地址,本地變量等。一切的函數調用都要將不同的數據、地址壓入或者彈出棧。因此,為了更好地理解函數的調用,我們需要先來看看棧是怎么工作的。

棧是什么?

簡單來說,棧是一種LIFO形式的數據結構,所有的數據都是后進先出。這種形式的數據結構正好滿足我們調用函數的方式:父函數調用子函數,父函數在前,子函數在后;返回時,子函數先返回,父函數后返回。棧支持兩種基本操作,push和pop。push將數據壓入棧中,pop將棧中的數據彈出并存儲到指定寄存器或者內存中。

這里是一個push操作的例子。假設我們有一個棧,其中黃色部分是已經寫入數據的區域,綠色部分是還未寫入數據的區域。現在我們將0x50壓入棧中:

// 將0x50的壓入棧
push $0x50
圖一:壓棧操作

我們再來看看pop操作的例子:

// 將0x50彈出棧
pop

圖二:出棧操作

這里有兩點需要注意的,第一,上面例子中棧的生長方向是從高地址到低地址的,這是因為在下文講的棧幀中,棧就是向下生長的,因此這里也用這種形式的棧;第二,pop操作后,棧中的數據并沒有被清空,只是該數據我們無法直接訪問。有了這些棧的基本知識,我們現在可以來看看在x86-32bit系統下,C語言函數是如何調用的了。

棧幀是什么?

棧幀,也就是stack frame,其本質就是一種棧,只是這種棧專門用于保存函數調用過程中的各種信息(參數,返回地址,本地變量等)。棧幀有棧頂和棧底之分,其中棧頂的地址最低,棧底的地址最高,SP(棧指針)就是一直指向棧頂的。在x86-32bit中,我們用 %ebp 指向棧底,也就是基址指針;用 %esp 指向棧頂,也就是棧指針。下面是一個棧幀的示意圖:

圖三:棧幀示意圖

一般來說,我們將 %ebp%esp 之間區域當做棧幀(也有人認為該從函數參數開始,不過這不影響分析)。并不是整個棧空間只有一個棧幀,每調用一個函數,就會生成一個新的棧幀。在函數調用過程中,我們將調用函數的函數稱為“調用者(caller)”,將被調用的函數稱為“被調用者(callee)”。在這個過程中,1)“調用者”需要知道在哪里獲取“被調用者”返回的值;2)“被調用者”需要知道傳入的參數在哪里,3)返回的地址在哪里。同時,我們需要保證在“被調用者”返回后,%ebp, %esp 等寄存器的值應該和調用前一致。因此,我們需要使用棧來保存這些數據。

函數調用實例

函數的調用

我們直接通過實例來看函數是如何調用的。這是一個有參數但沒有調用任何函數的簡單函數,我們假設它被其他函數調用。

int MyFunction(int x, int y, int z)
{
    int a, b, c;
    a = 10;
    b = 5;
    c = 2;
    ...
}

int TestFunction()
{
    int x = 1, y = 2, z = 3;
    MyFunction1(1, 2, 3);
    ...
}

對于這個函數,當調用時,MyFunction() 的匯編代碼大致如下:

_MyFunction:
    push %ebp            ; //保存%ebp的值
    movl %esp, $ebp      ; //將%esp的值賦給%ebp,使新的%ebp指向棧頂
    movl -12(%esp), %esp ; //分配額外空間給本地變量
    movl $10, -4(%ebp)   ; 
    movl $5,  -8(%ebp)   ; 
    movl $2,  -12(%ebp)  ; 

光看代碼可能還是不太明白,我們先來看看此時的棧是什么樣的:

圖四:被調用者棧幀的生成

此時調用者做了兩件事情:第一,將被調用函數的參數按照從右到左的順序壓入棧中。第二,將返回地址壓入棧中。這兩件事都是調用者負責的,因此壓入的棧應該屬于調用者的棧幀。我們再來看看被調用者,它也做了兩件事情:第一,將老的(調用者的) %ebp 壓入棧,此時 %esp 指向它。第二,將 %esp 的值賦給 %ebp, %ebp 就有了新的值,它也指向存放老 %ebp 的棧空間。這時,它成了是函數 MyFunction() 棧幀的棧底。這樣,我們就保存了“調用者”函數的 %ebp,并且建立了一個新的棧幀。

只要這步弄明白了,下面的操作就好理解了。在 %ebp 更新后,我們先分配一塊0x12字節的空間用于存放本地變量,這步一般都是用 sub 或者 mov 指令實現。在這里使用的是 movl。通過使用 mov 配合 -4(%ebp), -8(%ebp)-12(%ebp) 我們便可以給 a, bc 賦值了。

圖五:本地變量賦值后的棧幀

函數的返回

上面講的都是函數的調用過程,我們現在來看看函數是如何返回的。從下面這個例子我們可以看出,和調用函數時正好相反。當函數完成自己的任務后,它會將 %esp 移到 %ebp 處,然后再彈出舊的 %ebp 的值到 %ebp。這樣,%ebp 就恢復到了函數調用前的狀態了。

int MyFunction( int x, int y, int z )
{
    int a, int b, int c;
    ...
    return;
}

其匯編大致如下:

_MyFunction:
    push %ebp
    movl %esp, %ebp
    movl -12(%esp), %esp
    ...
    mov %ebp, %esp
    pop %ebp
    ret

我們注意到最后有一個 ret 指令,這個指令相當于 pop + jum。它首先將數據(返回地址)彈出棧并保存到 %eip 中,然后處理器根據這個地址無條件地跳到相應位置獲取新的指令。

圖六:被調用者返回后的棧幀

總結

到這里,C函數的調用過程就基本講完了。函數的調用其實不難,只要搞懂了如何保存以及還原 %ebp%esp,就能明白函數是如何通過棧幀進行調用和返回的了。希望這篇文章對你有幫助!

引用

在我學習棧幀以及寫這篇文章的過程中,參考了下面這些文章,在這我感謝他們對我提供的大力的幫助。如果你對這些文章感興趣,請訪問以下鏈接:
1. x86 Instruction Set Reference
2. x86 Disassembly/Functions and Stack Frames
3. x86 Assembly Guide

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,468評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,431評論 2 378

推薦閱讀更多精彩內容

  • 原文地址:C語言函數調用棧(一)C語言函數調用棧(二) 0 引言 程序的執行過程可看作連續的函數調用。當一個函數執...
    小豬啊嗚閱讀 4,660評論 1 19
  • 首先寄存器使用慣例:eip :指令地址寄存器,保存程序計數器的值,當前執行的指令的下一條指令的地址值,16位中為i...
    扎Zn了老Fe閱讀 1,999評論 0 0
  • 棧: 在函數調用時,第一個進棧的是主函數中函數調用后的下一條指令(函數調用語句的下一條可執行語句)的地址,然后是函...
    zjfclimin閱讀 4,067評論 0 5
  • 站在巨人的肩膀上——IDA PRO權威指南閱讀筆記 一,窗口 view->open subviews 打開/關閉各...
    SueLyon閱讀 14,475評論 0 6
  • 最近很擔心,一直乖乖聽話的妹妹竟然早戀了。 這件事發生的突然,讓我難以接受。 妹妹現在正在上初中,戀愛了。 “我的...
    公子琴卿閱讀 1,779評論 0 0