自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

程序員必備高級(jí)技術(shù)之函數(shù)調(diào)用棧

系統(tǒng) Linux
本文將介紹一下在Linux平臺(tái)下函數(shù)棧是如何實(shí)現(xiàn)的。有些同學(xué)可能覺(jué)得沒(méi)必要了解這么深入,其實(shí)非也。根據(jù)本號(hào)多年的經(jīng)驗(yàn),了解系統(tǒng)深層次的原理對(duì)分析疑難問(wèn)題有很好的幫助。

大家都知道函數(shù)調(diào)用是通過(guò)棧來(lái)實(shí)現(xiàn)的,而且知道在棧中存放著該函數(shù)的局部變量。但是對(duì)于棧的實(shí)現(xiàn)細(xì)節(jié)可能不一定清楚。

圖0 函數(shù)棧

就像熟悉抓包是解決網(wǎng)絡(luò)通信問(wèn)題的高級(jí)武器一樣,熟悉函數(shù)調(diào)用棧則是分析程序內(nèi)存問(wèn)題的高級(jí)武器。本文以Linux 64位操作系統(tǒng)下C語(yǔ)言開(kāi)發(fā)為例,介紹應(yīng)用程序調(diào)用棧的實(shí)現(xiàn)原理,并通過(guò)一個(gè)實(shí)例和GDB工具具體分析一下某個(gè)程序的調(diào)用棧內(nèi)容。在介紹具體的調(diào)用棧之前,我們先介紹一些基礎(chǔ)知識(shí),這些知識(shí)是理解后續(xù)函數(shù)調(diào)用棧的基礎(chǔ)。

X86 CPU的寄存器

CPU的寄存器是需要了解的基礎(chǔ)知識(shí),這是因?yàn)樵赬64體系中函數(shù)的參數(shù)是通過(guò)寄存器傳遞的。如圖1是X86 CPU寄存器的列表及功能簡(jiǎn)要說(shuō)明。

圖1 Intel X86 CPU寄存器用途

我們知道Intel的CPU在設(shè)計(jì)的時(shí)候都是向前兼容的,也就是在新一代的CPU上可以運(yùn)行老一代CPU上的編譯的程序。為了保證兼容性,新一代CPU保留了老一代寄存器的別名。以16位寄存器AX為例,AL表示低8位,AH表示高8位。而32位CPU問(wèn)世之后,通過(guò)名為EAX的寄存器表示32位寄存器,AX仍然保留。以此類推,RAX表示一個(gè)64位寄存器。

圖2 不同的寄存器名稱

應(yīng)用程序的地址空間

操作系統(tǒng)通過(guò)虛擬內(nèi)存的方式為所有應(yīng)用程序提供了統(tǒng)一的內(nèi)存映射地址。如圖3所示,從上到下分別是用戶棧、共享庫(kù)內(nèi)存、運(yùn)行時(shí)堆和代碼段。當(dāng)然這個(gè)是一個(gè)大概的分段,實(shí)際分段比這個(gè)可能稍微復(fù)雜一些,但整個(gè)格局沒(méi)有大變化。

圖3 應(yīng)用程序的地址空間

從圖中可以看出用戶棧是從上往下生長(zhǎng)的。也就是用戶棧會(huì)先占用高地址的空間,然后占用低地址空間。目前我們可以大體上有個(gè)了解即可,后面我們?cè)谠敿?xì)分析用戶棧的細(xì)節(jié)。

函數(shù)調(diào)用及匯編指令

為了理解函數(shù)調(diào)用棧的細(xì)節(jié),有必要了解一下匯編程序中函數(shù)調(diào)用的實(shí)現(xiàn)。函數(shù)的調(diào)用主要分為2部分,一個(gè)是調(diào)用,另外一個(gè)是返回。在匯編語(yǔ)言中函數(shù)調(diào)用是通過(guò)call指令完成的,返回則是通過(guò)ret指令。

匯編語(yǔ)言的call指令相當(dāng)于執(zhí)行了2步操作,分別是,1)將當(dāng)前的IP或CS和IP壓入棧中;2)跳轉(zhuǎn),類似與jmp指令。同樣,ret指令也分2步,分別是,1)將棧中的地址彈出到IP寄存器;2)跳轉(zhuǎn)執(zhí)行后續(xù)指令。這個(gè)基本上就是函數(shù)調(diào)用的原理。

除了在代碼間的跳動(dòng)外,函數(shù)的調(diào)用往往還需要傳遞一個(gè)參數(shù),而處理完成后還可能有返回值。這些數(shù)據(jù)的傳遞都是通過(guò)寄存器進(jìn)行的。在函數(shù)調(diào)用之前通過(guò)上文介紹的寄存器存儲(chǔ)參數(shù),函數(shù)返回之前通過(guò)RAX寄存器(32位系統(tǒng)為EAX)存儲(chǔ)返回結(jié)果。

另外一個(gè)比較重要的知識(shí)點(diǎn)是函數(shù)調(diào)用過(guò)程中與堆棧相關(guān)的寄存器RSP和RBP,兩個(gè)寄存器主要實(shí)現(xiàn)對(duì)棧位置的記錄,具體作用如下:

RSP:棧指針寄存器(reextended stack pointer),其內(nèi)存放著一個(gè)指針,該指針永遠(yuǎn)指向系統(tǒng)棧最上面一個(gè)棧幀的棧頂。

RBP:基址指針寄存器(reextended base pointer),其內(nèi)存放著一個(gè)指針,該指針永遠(yuǎn)指向系統(tǒng)棧最上面一個(gè)棧幀的底部。

寄存器的名稱跟體系結(jié)構(gòu)是相關(guān)的,本文是64位系統(tǒng),因此寄存器是RSP和RBP。如果是32位系統(tǒng)則寄存器的名稱為ESP和EBP。

應(yīng)用程序調(diào)用棧

我們先從整體上來(lái)看一下函數(shù)調(diào)用棧的主要內(nèi)容,如圖4所示。在函數(shù)棧中主要包括函數(shù)參數(shù)表、局部變量表、棧的基址和函數(shù)返回地址。這里棧的基址是上一個(gè)棧幀的基址,因?yàn)樵诒竞瘮?shù)中需要使用該基址訪問(wèn)棧中的內(nèi)容,因此需要首先將上一個(gè)棧幀中的基址壓棧。

圖4 函數(shù)調(diào)用棧概覽

為了便于理解,我們以一個(gè)具體的程序作為示例。本程序非常簡(jiǎn)單,主要是模擬了多個(gè)函數(shù)的函數(shù)調(diào)用關(guān)系和參數(shù)傳遞。另外,在函數(shù)func_2中定義了2個(gè)形參,以模擬多參數(shù)傳遞的過(guò)程。

圖5 函數(shù)棧匯編分析

在本示例中,main函數(shù)調(diào)用func_1函數(shù)。我們從main函數(shù)開(kāi)始分析,可以先看一下右側(cè)的C語(yǔ)言代碼。首先是函數(shù)參數(shù)的準(zhǔn)備過(guò)程。在main函數(shù)調(diào)用func_1時(shí)依次傳入的參數(shù)為1、2、3和4+g,其中最后一個(gè)參數(shù)是需要計(jì)算的。按照紅色方框的虛線,我們可以看到對(duì)應(yīng)的匯編程序,在匯編程序中首先處理最后一個(gè)參數(shù),然后是倒數(shù)第二個(gè),以此類推(函數(shù)參數(shù)的處理順序在日常開(kāi)發(fā)中是需要注意的內(nèi)容重點(diǎn))。同時(shí),我們看到存儲(chǔ)參數(shù)的寄存器名稱與前文是一致。

當(dāng)準(zhǔn)備完參數(shù)之后,就是調(diào)用func_1函數(shù),這個(gè)在匯編語(yǔ)言中就是call func_1這一行。雖然只是一行匯編指令,但其實(shí)內(nèi)部做了一些事情,這個(gè)我們?cè)谇拔慕榻Bcall指令的時(shí)候有所介紹,大家可以參考一下前文。

之后就進(jìn)入func_1函數(shù)的處理邏輯。最一開(kāi)始是pushq %rbp匯編程序,這句指令的作用是將RBP壓入函數(shù)棧中。這句壓棧及后面的更新RBP的值(moveq %rsp, %rbp)是構(gòu)建本函數(shù)的棧幀頭,后續(xù)對(duì)本棧幀的內(nèi)容的訪問(wèn)都是通過(guò)幀頭(RBP)進(jìn)行的。接下來(lái)是對(duì)參數(shù)壓棧的過(guò)程和局部變量初始化的過(guò)程,具體分布參考圖5中的綠色方框和紅色方框。

完成函數(shù)內(nèi)的運(yùn)算后,最后將運(yùn)算結(jié)果放入寄存器EAX中,然后調(diào)用指令leave和ret。這里面需要說(shuō)明的是leave指令,該指令相當(dāng)于下面兩條匯編指令??梢詫?duì)比一下函數(shù)入口的匯編指令,其實(shí)兩者是對(duì)稱的。leave指令將本幀的?;焚x值給棧指針(圖6中步驟2),然后將其中的內(nèi)容彈出到RBP中(圖6中步驟3)。其實(shí)就是RBP指向上一個(gè)幀(調(diào)用者)的棧幀,也即是一個(gè)復(fù)原的過(guò)程。

movl %ebp %esp
popl %ebp

圖6 函數(shù)返回示意圖

這樣,函數(shù)返回后寄存器RBP和RSP從被調(diào)用者的棧幀切換到了調(diào)用者的棧幀。

通過(guò)GDB分析函數(shù)調(diào)用棧

上面是通過(guò)反匯編的方式分析函數(shù)的調(diào)用棧和棧幀情況。我們還可以通過(guò)gdb動(dòng)態(tài)的分析函數(shù)棧和棧幀的使用情況。我們依然通過(guò)main函數(shù)調(diào)用func_1函數(shù)為例來(lái)分析。我們這里在函數(shù)func_1的入口處設(shè)置一個(gè)單點(diǎn),然后運(yùn)行程序,程序停止在斷點(diǎn)處。如圖7是我們逐步執(zhí)行是函數(shù)棧的變化過(guò)程,具體細(xì)節(jié)我們這里就不再贅述,大家可以實(shí)際操作一下。

圖7 函數(shù)棧變化過(guò)程

本文的目的是讓大家對(duì)函數(shù)調(diào)用棧有個(gè)整體的了解,這樣對(duì)以后程序的疑難雜癥就有更多的解決思路。因?yàn)樵趯?shí)際生產(chǎn)環(huán)境中與棧相關(guān)的問(wèn)題也是比較多的,比如局部變量太多導(dǎo)致的棧溢出,或者踩內(nèi)存問(wèn)題引起的棧破壞等等。因此,了解了函數(shù)棧的原理,在遇到所謂的莫名其妙問(wèn)題的時(shí)候就會(huì)有新的思路。往往很多問(wèn)題不是問(wèn)題本身莫名其妙,而是我們的知識(shí)儲(chǔ)備不夠,自己感覺(jué)莫名其妙而已。

責(zé)任編輯:龐桂玉 來(lái)源: 一口Linux
相關(guān)推薦

2019-06-23 17:37:58

Linux后端函數(shù)棧

2020-10-10 11:01:40

后端程序員技術(shù)

2020-10-09 14:44:57

程序員開(kāi)發(fā)技術(shù)

2009-06-25 09:33:43

Java API程序員

2011-06-11 20:59:12

程序員

2014-08-15 14:25:48

Android程序員資源

2014-08-20 10:28:29

Android

2019-09-25 11:39:07

程序員編程技術(shù)

2022-10-24 09:00:47

畫(huà)圖工具程序員XMind

2020-04-04 20:59:28

程序員技術(shù)開(kāi)發(fā)

2013-06-09 09:56:35

2011-07-19 13:27:35

2009-06-22 09:06:57

程序員技術(shù)升級(jí)

2020-05-06 15:59:07

JavaScript程序員技術(shù)

2020-07-20 07:46:01

程序員加簽驗(yàn)簽

2015-10-29 09:50:36

程序員免費(fèi)編程圖書(shū)

2019-04-23 13:51:43

程序員技能開(kāi)發(fā)者

2021-03-02 09:34:15

GitHub倉(cāng)庫(kù)代碼

2020-05-09 11:20:02

Java結(jié)構(gòu)圖虛擬機(jī)

2015-07-09 10:30:35

程序員必備經(jīng)驗(yàn)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)