1. 程式人生 > >ARM下C語言棧幀機制

ARM下C語言棧幀機制

https://www.jianshu.com/p/91c5dc0a8bb9

背景:
最近在某個RTOS上遇到一個系統BUG,幾經折騰,終於將其斬於馬下。結局美好,過程卻很曲折,在分析定位問題的時候,順便把ARM上C函式呼叫stack frame機制捋了一遍,記錄並分享一下。概念:棧:1)從資料結構的角度來理解,棧是一種描述先進後出的資料結構;2)從程序的記憶體空間角度來理解,棧是一種特殊的記憶體段,用於存放區域性變數、函式引數、返回值等;第一種角度,用來描述本身的特性,第二種角度,是將這種資料結構的特性用於實際的記憶體空間中。棧幀:每個程序都會有自己的棧空間,而程序中的各個函式也會維護自己本身的一個棧的區域,這個區域就是棧幀。那麼一個函式的棧幀的區域是如何來界定的呢?當然,我首先會普及ARM的幾個特殊暫存器功能。R11:frame pointer,FP暫存器R12:IP暫存器,用於暫存SPR13: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
連結:https://www.jianshu.com/p/91c5dc0a8bb9
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。