ARM下C語言棧幀機制

choubou發表於2021-09-09

背景:

    最近在某個RTOS上遇到一個系統BUG,幾經折騰,終於將其斬於馬下。結局美好,過程卻很曲折,在分析定位問題的時候,順便把ARM上C函式呼叫stack frame機制捋了一遍,記錄並分享一下。


概念:

棧:

1)從資料結構的角度來理解,棧是一種描述先進後出的資料結構;

2)從程式的記憶體空間角度來理解,棧是一種特殊的記憶體段,用於存放區域性變數、函式引數、返回值等;

第一種角度,用來描述本身的特性,第二種角度,是將這種資料結構的特性用於實際的記憶體空間中。

棧幀:每個程式都會有自己的棧空間,而程式中的各個函式也會維護自己本身的一個棧的區域,這個區域就是棧幀。那麼一個函式的棧幀的區域是如何來界定的呢?當然,我首先會普及ARM的幾個特殊暫存器功能。

R11:frame pointer,FP暫存器

R12:IP暫存器,用於暫存SP

R13:stack pointer,SP暫存器

R14:link register,LR暫存器

R15:PC暫存器

而在ARM上,函式的棧幀是由SP暫存器和FP暫存器來界定的,相信你應該見過下邊這張比較經典的圖了:


圖片描述

    上圖描述的是main函式呼叫func1函式的棧幀情況,從圖可知,當main函式呼叫func1函式時,func1函式會先將PC、LR、SP、FP四個暫存器壓到棧上邊,其中SP和FP的值分別指向main函式棧幀的兩個邊界,LR的值儲存的是func1呼叫結束之後的返回值,PC值表示的是當前執行到的指令地址,放置的是進入func1後的指令地址。緊接著就會在棧上分配一片區域,用於放置區域性變數等。

    如果func1中還呼叫了func2子函式,那麼也會為func2建立一個棧幀,並且func2的SP和FP會指向func1棧幀的兩個邊界。這樣當函式返回的時候,引數進行出棧,也能找到Caller函式,這個也就是backtrace的原理了。


示例:

    反彙編分析某段程式碼,如下圖所示:


圖片描述

紅色部分,表明進入到函式時先將幾個特殊的暫存器壓棧

黃色部分,sub sp, sp, #16,表明開闢一個4 x 32bit大小的棧區域

藍色部分,將傳入的引數壓棧,在ARM ATPCS中規定,暫存器R0-R3用來傳參

綠色部分,呼叫子函式

    那麼,我們順道看看子函式的棧幀區域吧:


圖片描述

    從圖中可以看出,機制是一樣的,當最終queue_push函式呼叫結束後,棧上的資料進行出棧,根據fp和ip,便能找到workflow_gather_input函式的棧幀了。

    當然,並不是所有函式呼叫都需要先push  {fp, ip, lr, pc},當子函式呼叫過程中,並不會去改變這些值的時候,就不需要壓棧,說白了,壓棧的目的就是為了在使用完的時候能恢復原來的狀態。我會再次提供一個例子:


圖片描述

    strlen函式中沒有子函式的呼叫,所以進入函式後,直接就在棧上分配4 * 32bit大小的區域了。

    棧裡邊能分析出每個引數的值,以及函式呼叫時的傳參,這對分析與定位問題很有幫助。成熟的系統可能會提供一堆工具來dump stack,並去分析呼叫關係,但是在RTOS上,很多卻並不完善,需要一定的低層知識去分析才能解決問題。



作者:Loyen
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4548/viewspace-2820959/,如需轉載,請註明出處,否則將追究法律責任。

相關文章