您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關LiteOS中backtrace函數的原理是什么,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
圖 1 匯編指令的執行順序
上圖1所示,ARM的匯編指令執行分三步:取值(fetch)、譯指(decode)、執行(execute),按照流水線的方式執行,即當運行指令節拍m時,pc會指向n+2匯編指令地址進行取指令操作,同時會將n+1處匯編指令翻譯成對應機器碼,并執行指令n。
圖 2 棧在內存中的布局
LiteOS Cortex-M架構的棧布局如上圖2,棧區間在內存中位于最末端,程序運行時從內存末端(棧頂)開始進行遞減壓棧。LiteOS的內存末端為主棧空間(msp_stack),LiteOS進入任務前的初始化過程及中斷函數調用過程的棧數據保存在此區間內,主棧地址空間往下為任務棧空間(psp_stack),任務棧空間在每個任務被創建時指定,多個任務棧空間依次排列。一個任務中可能包含多個函數,每個函數都有自己的棧空間,稱為棧幀。調用函數時,會創建子函數的棧幀,同時將函數入參、局部變量、寄存器入棧。棧幀從高地址向低地址生長。
ARM為了維護棧中的數據設計了兩個寄存器,分別為fp寄存器(framepointer,幀指針寄存器)和sp寄存器(stack pointer,堆棧寄存器)。fp指向當前函數的父函數的棧幀起始地址, sp指向當前函數的棧頂。通過對sp寄存器的地址進行偏移訪問可以得到棧中的數據內容,通過訪問fp寄存器地址可以得到上一棧幀的起始位置,進而計算出函數的返回地址。由于Cortex-M沒有fp寄存器,若想獲得函數入口地址只能通過sp地址偏移找到lr寄存器(link register,鏈接寄存器,指向當前函數的返回地址),并結合函數入口的push指令計算得出。lr寄存器會在每次函數調用時壓入棧中,用以返回到函數調用前的位置繼續執行。函數調用執行流程引用自Joseph Yiu的《Cortex-M3 權威指南》,如下圖3所示。
圖 3 函數調用執行流程
如函數調用執行流程所示,程序進入一個子函數后,通常都會使用push指令先將寄存器的值壓入棧中,執行完業務邏輯后再使用pop指令將棧中保存的寄存器數據出棧并按順序存入對應的寄存器。當程序執行bl跳轉指令時,pc中的值為bl指令后的第二條指令的地址,減去一條匯編指令的長度后為bl后第一條指令的地址,即lr值。程序在進入Fx1前,bl或blx指令會將此lr值保存到lr寄存器,并在進入Fx1函數時將其壓入棧中。例如有如下匯編指令:
800780e: 6078 str r0, [r7, #4] 8007810: f7ff ffe0 bl 80077d4 <test_div> 8007814: f7f9 fe68 bl 80014e8 <OsTickStart>
當程序執行到地址0x8007810時,在bl指令跳轉到函數test_div之前,bl指令會將此時的pc地址(0x8007818)減去一條匯編指令的長度(這里為4),將計算得到的值0x8007814(本條指令僅執行到譯指,尚未完成全部執行過程,返回后需重新取指)保存到lr寄存器。
根據函數調用執行流程的原理,當程序跳入異常時,傳入當前位置sp指針,通過對sp指針進行循環自增訪問操作獲取棧中的內容,sp指向棧頂,循環自增的邊界即任務棧的棧底,由于Cortex-M使用的thum-2指令集,匯編指令長度為2字節,因此可通過判斷棧中的數據是否兩字節對齊及位于代碼段區間內篩選出當前棧中的匯編指令地址。并通過判斷上一條是否為bl指令或blx指令(b、bx指令不將lr寄存器入棧,不對其進行處理)對上一條指令進行計算。跳轉指令的機器碼構成如下圖4所示:
圖 4 thum跳轉指令機器碼構成
如果為bl指令地址(特征碼0xf000),通過該地址中存儲的機器碼計算出偏移地址(原理見下圖5),從而獲得跳轉指令目標函數入口地址,如果為blx指令(這里為blx 寄存器n指令,其特征碼0x4700),由于目標偏移地址保存在寄存器中,無法通過機器碼計算偏移地址,則需要根據被調用幀保存的lr地址推算其所在的函數入口地址,直到入口處的push指令。
圖 5 bl指令偏移地址計算規則
LiteOS在運行過程中出現異常時,會自動轉入異常處理函數。LiteOS提供了backtrace函數用于跟蹤函數的堆棧信息,通過系統注冊的異常處理函數來調用backtrace函數實現系統異常時自動打印函數的調用棧。
由于Cortex-M架構無fp寄存器,sp寄存器分為msp寄存器(用于主棧)和psp寄存器(用于任務棧),因此只能通過匯編指令機器碼計算及lr地址自增查找函數入口處的push指令特征碼計算函數入口。
圖 6 backtrace代碼框架
當調用Cortex-M架構的ArchBackTrace接口時,該函數會通過ArchGetSp獲取當前sp指針,如果在初始化或中斷過程發生異常,sp指向msp,在任務中發生異常,sp指向psp。將獲取的sp指針傳入BackTraceWithSp進行調用棧分析,該函數通過FindSuitableStack函數進行棧邊界確認,找到合適的任務棧邊界或主棧(未區分中斷棧及初始化棧)邊界。再通過邊界值控制循環查找次數,從而確保將對應棧空間內所有棧幀的lr地址過濾出來。最后將lr地址傳入CalculateTargetAddress函數計算出lr前一條指令(即跳轉指令)要跳轉到的函數入口地址。
以上代碼在LiteOS 5.0版本中已經發布,核心代碼路徑如下:
https://gitee.com/LiteOS/LiteOS/blob/master/arch/arm/cortex_m/src/fault.c
圖 7 除0錯誤用例函數
演示demo設計了一個會導致除0錯誤的函數(如上圖圖7),分別在初始化、中斷、任務三個場景下調用該函數,將會觸發異常并打印相應的信息,觀察相應的fp(此處指函數入口地址,非棧幀寄存器的值)地址是否與實際代碼的反匯編地址一致。
可以通過menuconfig菜單使能backtrace功能,菜單項為:Debug--> Enable Backtrace。同時為避免編譯優化造成的影響,還需配置編譯優化選項為不優化:Compiler--> Optimize Option --> Optimize None。
下面所示圖中,左圖為異常接管打印的日志,右圖為反匯編代碼。可以看到左圖中出現異常的pc指令值,對應于右圖中的匯編代碼為sdiv r3, r2, r3,即為test_div函數中的int z = a / b代碼行。左圖中打印的backtrace信息,其fp值和右圖中的函數入口地址一致。
任務中觸發異常:
圖 8 backtrace任務演示效果
中斷處理函數中觸發異常:
圖 9 backtrace中斷演示效果
初始化函數中觸發異常:
圖 10 backtrace初始化演示效果
看完上述內容,你們對LiteOS中backtrace函數的原理是什么有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。