棧回溯

首先必須明確一點也是非常重要的一點,棧是向下生長的,所謂向下生長是指從內存高地址->低地址的路徑延伸,那麼就很明顯了,棧有棧底和棧頂,那麼棧頂的地址要比棧底低。對x86體系的CPU而言,其中:

---> 寄存器ebp(base pointer )可稱爲“幀指針”或“基址指針”,其實語意是相同的。

---> 寄存器esp(stack pointer)可稱爲“ 棧指針”。

要知道的是:

--->ebp 在未受改變之前始終指向棧幀的開始,也就是棧底,所以ebp的用途是在堆棧中尋址用的。

—>esp是會隨着數據的入棧和出棧移動的,也就是說,esp始終指向棧頂。

寄存器ebp指向當前的棧幀的底部(高地址),寄存器esp指向當前的棧幀的頂部(地地址)

這是來自apue裏一張經典的c程序內存分佈圖:

 

基於多核異構處理器B4860平臺的實例,對棧回溯功能做如下描述,僅供參考。假設函數A調用函數B,我們稱A函數爲"調用者",B函數爲“被調用者”則函數調用過程:

(1)先將調用者(A)的堆棧的基址(ebp)入棧,以保存之前任務的信息;

(2)然後將調用者(A)的棧頂指針(esp)的值賦給ebp,作爲新的基址(即被調用者B的棧底);

(3)然後在這個基址(被調用者B的棧底)上開闢(一般用sub指令)相應的空間用作被調用者B的棧空間;

(4)函數B返回後,從當前棧幀的ebp即恢復爲調用者A的棧頂(esp),使棧頂恢復函數B被調用前的位置;然後調用者A再從恢復後的棧頂可彈出之前的ebp值(可以這麼做是因爲這個值在函數調用前一步被壓入堆棧)。這樣,ebp和esp就都恢復了調用函數B前的位置,也就是棧恢復函數B調用前的狀態。

以DSP側異常爲例說明,如下所示:

1.Task創建時,會爲每個Task分配好Task的棧,並指定當前Task棧的size。

其中,棧是向下生長(高地址 -> 低地址),因此文中所描述的“top_of_stack”實則爲棧的底(EBP)。

2.DSP出現異常時,會記錄當前函數的上下文信息,如PC,SP,os_context_info,top_of_stack,etc.可結合這些關鍵信息及Map文件進行棧回溯,找到出現異常函數的上下文。

3.PC指針,通常是用來指向當前運行指令的下一條指令的指針。計算機取指令也就是根據PC指針所指向的那條指令來進行取指的,接着就是譯碼等操作。異常時的PC,歸屬於當前棧幀,要想獲取到異常函數的多級調用關係,就需要知道當前棧幀的棧頂(根據函數的入棧會動態變化)和棧底。

4.得到完全任務棧的棧幀,以及當前錯誤的PC,以及Map文件,就可以獲取到函數調用的上下文,描述如下:

(1)錯誤的PC不一定指向當前出現異常時函數,但一定會指向出現異常函數的棧幀內。PC缺省是uint32_t類型,這裏我們對PC截斷處理,PC的高16bit+offset就會指向出現異常時的函數,offset表示當函數壓棧時的偏移地址。

(2)函數的壓棧操作是從高地址到低地址方向的一個生長,換句話說,函數的調用關係是從高地址往低地址方向調入的一個過程,因此,爲了找到出現異常時函數調用的上下文,就要從低地址往高地址方向反推。

注:在一體機SC3900中,函數的壓棧是從低地址到高地址。其中,嵌彙編操作描述如下:

以CPU側異常爲例說明,結合coredump文件即核心轉儲,進行GDB調試即可,本文不過多描述。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章