C函數(shù)調(diào)用過(guò)程原理及函數(shù)棧幀分析

在x86的計(jì)算機(jī)系統(tǒng)中憋他,內(nèi)存空間中的棧主要用于保存函數(shù)的參數(shù)于置,返回值,返回地址吧恃,本地變量等。一切的函數(shù)調(diào)用都要將不同的數(shù)據(jù)冒晰、地址壓入或者彈出棧同衣。因此,為了更好地理解函數(shù)的調(diào)用壶运,我們需要先來(lái)看看棧是怎么工作的。

棧是什么浪秘?

簡(jiǎn)單來(lái)說(shuō)蒋情,棧是一種LIFO形式的數(shù)據(jù)結(jié)構(gòu),所有的數(shù)據(jù)都是后進(jìn)先出耸携。這種形式的數(shù)據(jù)結(jié)構(gòu)正好滿足我們調(diào)用函數(shù)的方式:父函數(shù)調(diào)用子函數(shù)棵癣,父函數(shù)在前,子函數(shù)在后夺衍;返回時(shí)狈谊,子函數(shù)先返回,父函數(shù)后返回沟沙。棧支持兩種基本操作河劝,push和pop。push將數(shù)據(jù)壓入棧中矛紫,pop將棧中的數(shù)據(jù)彈出并存儲(chǔ)到指定寄存器或者內(nèi)存中赎瞎。

這里是一個(gè)push操作的例子。假設(shè)我們有一個(gè)棧颊咬,其中黃色部分是已經(jīng)寫(xiě)入數(shù)據(jù)的區(qū)域务甥,綠色部分是還未寫(xiě)入數(shù)據(jù)的區(qū)域。現(xiàn)在我們將0x50壓入棧中:

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

我們?cè)賮?lái)看看pop操作的例子:

// 將0x50彈出棧
pop

圖二:出棧操作

這里有兩點(diǎn)需要注意的喳篇,第一敞临,上面例子中棧的生長(zhǎng)方向是從高地址到低地址的,這是因?yàn)樵谙挛闹v的棧幀中麸澜,棧就是向下生長(zhǎng)的挺尿,因此這里也用這種形式的棧;第二痰憎,pop操作后票髓,棧中的數(shù)據(jù)并沒(méi)有被清空,只是該數(shù)據(jù)我們無(wú)法直接訪問(wèn)铣耘。有了這些棧的基本知識(shí)洽沟,我們現(xiàn)在可以來(lái)看看在x86-32bit系統(tǒng)下,C語(yǔ)言函數(shù)是如何調(diào)用的了蜗细。

棧幀是什么裆操?

棧幀怒详,也就是stack frame,其本質(zhì)就是一種棧踪区,只是這種棧專門用于保存函數(shù)調(diào)用過(guò)程中的各種信息(參數(shù)昆烁,返回地址,本地變量等)缎岗。棧幀有棧頂和棧底之分静尼,其中棧頂?shù)牡刂纷畹停瑮5椎牡刂纷罡叽矗琒P(棧指針)就是一直指向棧頂?shù)氖竺臁T趚86-32bit中,我們用 %ebp 指向棧底眷细,也就是基址指針拦盹;用 %esp 指向棧頂,也就是棧指針溪椎。下面是一個(gè)棧幀的示意圖:

圖三:棧幀示意圖

一般來(lái)說(shuō)普舆,我們將 %ebp%esp 之間區(qū)域當(dāng)做棧幀(也有人認(rèn)為該從函數(shù)參數(shù)開(kāi)始,不過(guò)這不影響分析)校读。并不是整個(gè)椪勇拢空間只有一個(gè)棧幀,每調(diào)用一個(gè)函數(shù)地熄,就會(huì)生成一個(gè)新的棧幀华临。在函數(shù)調(diào)用過(guò)程中,我們將調(diào)用函數(shù)的函數(shù)稱為“調(diào)用者(caller)”端考,將被調(diào)用的函數(shù)稱為“被調(diào)用者(callee)”雅潭。在這個(gè)過(guò)程中,1)“調(diào)用者”需要知道在哪里獲取“被調(diào)用者”返回的值却特;2)“被調(diào)用者”需要知道傳入的參數(shù)在哪里扶供,3)返回的地址在哪里。同時(shí)裂明,我們需要保證在“被調(diào)用者”返回后椿浓,%ebp, %esp 等寄存器的值應(yīng)該和調(diào)用前一致。因此闽晦,我們需要使用棧來(lái)保存這些數(shù)據(jù)扳碍。

函數(shù)調(diào)用實(shí)例

函數(shù)的調(diào)用

我們直接通過(guò)實(shí)例來(lái)看函數(shù)是如何調(diào)用的。這是一個(gè)有參數(shù)但沒(méi)有調(diào)用任何函數(shù)的簡(jiǎn)單函數(shù)仙蛉,我們假設(shè)它被其他函數(shù)調(diào)用笋敞。

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);
    ...
}

對(duì)于這個(gè)函數(shù),當(dāng)調(diào)用時(shí)荠瘪,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)  ; 

光看代碼可能還是不太明白赛惩,我們先來(lái)看看此時(shí)的棧是什么樣的:

圖四:被調(diào)用者棧幀的生成

此時(shí)調(diào)用者做了兩件事情:第一,將被調(diào)用函數(shù)的參數(shù)按照從右到左的順序壓入棧中趁餐。第二喷兼,將返回地址壓入棧中。這兩件事都是調(diào)用者負(fù)責(zé)的后雷,因此壓入的棧應(yīng)該屬于調(diào)用者的棧幀季惯。我們?cè)賮?lái)看看被調(diào)用者,它也做了兩件事情:第一喷面,將老的(調(diào)用者的) %ebp 壓入棧星瘾,此時(shí) %esp 指向它。第二惧辈,將 %esp 的值賦給 %ebp, %ebp 就有了新的值,它也指向存放老 %ebp 的椏拇桑空間盒齿。這時(shí),它成了是函數(shù) MyFunction() 棧幀的棧底困食。這樣边翁,我們就保存了“調(diào)用者”函數(shù)的 %ebp,并且建立了一個(gè)新的棧幀硕盹。

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

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

函數(shù)的返回

上面講的都是函數(shù)的調(diào)用過(guò)程,我們現(xiàn)在來(lái)看看函數(shù)是如何返回的聘惦。從下面這個(gè)例子我們可以看出某饰,和調(diào)用函數(shù)時(shí)正好相反。當(dāng)函數(shù)完成自己的任務(wù)后善绎,它會(huì)將 %esp 移到 %ebp 處黔漂,然后再?gòu)棾雠f的 %ebp 的值到 %ebp。這樣禀酱,%ebp 就恢復(fù)到了函數(shù)調(diào)用前的狀態(tài)了炬守。

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

我們注意到最后有一個(gè) ret 指令,這個(gè)指令相當(dāng)于 pop + jum比勉。它首先將數(shù)據(jù)(返回地址)彈出棧并保存到 %eip 中劳较,然后處理器根據(jù)這個(gè)地址無(wú)條件地跳到相應(yīng)位置獲取新的指令驹止。

圖六:被調(diào)用者返回后的棧幀

總結(jié)

到這里,C函數(shù)的調(diào)用過(guò)程就基本講完了观蜗。函數(shù)的調(diào)用其實(shí)不難臊恋,只要搞懂了如何保存以及還原 %ebp%esp,就能明白函數(shù)是如何通過(guò)棧幀進(jìn)行調(diào)用和返回的了墓捻。希望這篇文章對(duì)你有幫助抖仅!

引用

在我學(xué)習(xí)棧幀以及寫(xiě)這篇文章的過(guò)程中,參考了下面這些文章砖第,在這我感謝他們對(duì)我提供的大力的幫助撤卢。如果你對(duì)這些文章感興趣,請(qǐng)?jiān)L問(wèn)以下鏈接:
1. x86 Instruction Set Reference
2. x86 Disassembly/Functions and Stack Frames
3. x86 Assembly Guide

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末梧兼,一起剝皮案震驚了整個(gè)濱河市放吩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌羽杰,老刑警劉巖渡紫,帶你破解...
    沈念sama閱讀 212,080評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異考赛,居然都是意外死亡惕澎,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門颜骤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)唧喉,“玉大人,你說(shuō)我怎么就攤上這事忍抽“诵ⅲ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,630評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵梯找,是天一觀的道長(zhǎng)唆阿。 經(jīng)常有香客問(wèn)我,道長(zhǎng)锈锤,這世上最難降的妖魔是什么驯鳖? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,554評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮久免,結(jié)果婚禮上浅辙,老公的妹妹穿的比我還像新娘。我一直安慰自己阎姥,他們只是感情好记舆,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,662評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著呼巴,像睡著了一般泽腮。 火紅的嫁衣襯著肌膚如雪御蒲。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,856評(píng)論 1 290
  • 那天诊赊,我揣著相機(jī)與錄音厚满,去河邊找鬼。 笑死碧磅,一個(gè)胖子當(dāng)著我的面吹牛碘箍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鲸郊,決...
    沈念sama閱讀 39,014評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼丰榴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了秆撮?” 一聲冷哼從身側(cè)響起四濒,我...
    開(kāi)封第一講書(shū)人閱讀 37,752評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎职辨,沒(méi)想到半個(gè)月后峻黍,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,212評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拨匆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,541評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挽拂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惭每。...
    茶點(diǎn)故事閱讀 38,687評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖亏栈,靈堂內(nèi)的尸體忽然破棺而出台腥,到底是詐尸還是另有隱情,我是刑警寧澤绒北,帶...
    沈念sama閱讀 34,347評(píng)論 4 331
  • 正文 年R本政府宣布黎侈,位于F島的核電站,受9級(jí)特大地震影響闷游,放射性物質(zhì)發(fā)生泄漏峻汉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,973評(píng)論 3 315
  • 文/蒙蒙 一脐往、第九天 我趴在偏房一處隱蔽的房頂上張望休吠。 院中可真熱鬧,春花似錦业簿、人聲如沸瘤礁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,777評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)柜思。三九已至岩调,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赡盘,已是汗流浹背号枕。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,006評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留亡脑,地道東北人堕澄。 一個(gè)月前我還...
    沈念sama閱讀 46,406評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像霉咨,于是被迫代替她去往敵國(guó)和親蛙紫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,576評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • 原文地址:C語(yǔ)言函數(shù)調(diào)用棧(一)C語(yǔ)言函數(shù)調(diào)用棧(二) 0 引言 程序的執(zhí)行過(guò)程可看作連續(xù)的函數(shù)調(diào)用途戒。當(dāng)一個(gè)函數(shù)執(zhí)...
    小豬啊嗚閱讀 4,598評(píng)論 1 19
  • 首先寄存器使用慣例:eip :指令地址寄存器坑傅,保存程序計(jì)數(shù)器的值,當(dāng)前執(zhí)行的指令的下一條指令的地址值喷斋,16位中為i...
    扎Zn了老Fe閱讀 1,967評(píng)論 0 0
  • 棧: 在函數(shù)調(diào)用時(shí)唁毒,第一個(gè)進(jìn)棧的是主函數(shù)中函數(shù)調(diào)用后的下一條指令(函數(shù)調(diào)用語(yǔ)句的下一條可執(zhí)行語(yǔ)句)的地址,然后是函...
    zjfclimin閱讀 3,958評(píng)論 0 5
  • 站在巨人的肩膀上——IDA PRO權(quán)威指南閱讀筆記 一星爪,窗口 view->open subviews 打開(kāi)/關(guān)閉各...
    SueLyon閱讀 14,367評(píng)論 0 6
  • 最近很擔(dān)心顽腾,一直乖乖聽(tīng)話的妹妹竟然早戀了近零。 這件事發(fā)生的突然,讓我難以接受抄肖。 妹妹現(xiàn)在正在上初中久信,戀愛(ài)了。 “我的...
    公子琴卿閱讀 1,764評(píng)論 0 0