溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

LiteOS中backtrace函數(shù)的原理是什么

發(fā)布時(shí)間:2021-07-28 13:58:10 來源:億速云 閱讀:193 作者:Leah 欄目:互聯(lián)網(wǎng)科技

今天就跟大家聊聊有關(guān)LiteOS中backtrace函數(shù)的原理是什么,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。


匯編指令的執(zhí)行流程

LiteOS中backtrace函數(shù)的原理是什么

圖 1 匯編指令的執(zhí)行順序

上圖1所示,ARM的匯編指令執(zhí)行分三步:取值(fetch)、譯指(decode)、執(zhí)行(execute),按照流水線的方式執(zhí)行,即當(dāng)運(yùn)行指令節(jié)拍m時(shí),pc會指向n+2匯編指令地址進(jìn)行取指令操作,同時(shí)會將n+1處匯編指令翻譯成對應(yīng)機(jī)器碼,并執(zhí)行指令n。

內(nèi)存中棧的布局

LiteOS中backtrace函數(shù)的原理是什么

圖 2 棧在內(nèi)存中的布局

LiteOS Cortex-M架構(gòu)的棧布局如上圖2,棧區(qū)間在內(nèi)存中位于最末端,程序運(yùn)行時(shí)從內(nèi)存末端(棧頂)開始進(jìn)行遞減壓棧。LiteOS的內(nèi)存末端為主棧空間(msp_stack),LiteOS進(jìn)入任務(wù)前的初始化過程及中斷函數(shù)調(diào)用過程的棧數(shù)據(jù)保存在此區(qū)間內(nèi),主棧地址空間往下為任務(wù)??臻g(psp_stack),任務(wù)棧空間在每個(gè)任務(wù)被創(chuàng)建時(shí)指定,多個(gè)任務(wù)??臻g依次排列。一個(gè)任務(wù)中可能包含多個(gè)函數(shù),每個(gè)函數(shù)都有自己的棧空間,稱為棧幀。調(diào)用函數(shù)時(shí),會創(chuàng)建子函數(shù)的棧幀,同時(shí)將函數(shù)入?yún)?、局部變量、寄存器入棧。棧幀從高地址向低地址生長。

寄存器數(shù)據(jù)入棧流程

ARM為了維護(hù)棧中的數(shù)據(jù)設(shè)計(jì)了兩個(gè)寄存器,分別為fp寄存器(framepointer,幀指針寄存器)和sp寄存器(stack pointer,堆棧寄存器)。fp指向當(dāng)前函數(shù)的父函數(shù)的棧幀起始地址, sp指向當(dāng)前函數(shù)的棧頂。通過對sp寄存器的地址進(jìn)行偏移訪問可以得到棧中的數(shù)據(jù)內(nèi)容,通過訪問fp寄存器地址可以得到上一棧幀的起始位置,進(jìn)而計(jì)算出函數(shù)的返回地址。由于Cortex-M沒有fp寄存器,若想獲得函數(shù)入口地址只能通過sp地址偏移找到lr寄存器(link register,鏈接寄存器,指向當(dāng)前函數(shù)的返回地址),并結(jié)合函數(shù)入口的push指令計(jì)算得出。lr寄存器會在每次函數(shù)調(diào)用時(shí)壓入棧中,用以返回到函數(shù)調(diào)用前的位置繼續(xù)執(zhí)行。函數(shù)調(diào)用執(zhí)行流程引用自Joseph Yiu的《Cortex-M3 權(quán)威指南》,如下圖3所示。

LiteOS中backtrace函數(shù)的原理是什么

圖 3 函數(shù)調(diào)用執(zhí)行流程

如函數(shù)調(diào)用執(zhí)行流程所示,程序進(jìn)入一個(gè)子函數(shù)后,通常都會使用push指令先將寄存器的值壓入棧中,執(zhí)行完業(yè)務(wù)邏輯后再使用pop指令將棧中保存的寄存器數(shù)據(jù)出棧并按順序存入對應(yīng)的寄存器。當(dāng)程序執(zhí)行bl跳轉(zhuǎn)指令時(shí),pc中的值為bl指令后的第二條指令的地址,減去一條匯編指令的長度后為bl后第一條指令的地址,即lr值。程序在進(jìn)入Fx1前,bl或blx指令會將此lr值保存到lr寄存器,并在進(jìn)入Fx1函數(shù)時(shí)將其壓入棧中。例如有如下匯編指令:

800780e:  6078        str  r0, [r7, #4]
8007810:  f7ff ffe0   bl  80077d4 <test_div>
8007814:  f7f9 fe68   bl  80014e8 <OsTickStart>

當(dāng)程序執(zhí)行到地址0x8007810時(shí),在bl指令跳轉(zhuǎn)到函數(shù)test_div之前,bl指令會將此時(shí)的pc地址(0x8007818)減去一條匯編指令的長度(這里為4),將計(jì)算得到的值0x8007814(本條指令僅執(zhí)行到譯指,尚未完成全部執(zhí)行過程,返回后需重新取指)保存到lr寄存器。

實(shí)現(xiàn)思路

根據(jù)函數(shù)調(diào)用執(zhí)行流程的原理,當(dāng)程序跳入異常時(shí),傳入當(dāng)前位置sp指針,通過對sp指針進(jìn)行循環(huán)自增訪問操作獲取棧中的內(nèi)容,sp指向棧頂,循環(huán)自增的邊界即任務(wù)棧的棧底,由于Cortex-M使用的thum-2指令集,匯編指令長度為2字節(jié),因此可通過判斷棧中的數(shù)據(jù)是否兩字節(jié)對齊及位于代碼段區(qū)間內(nèi)篩選出當(dāng)前棧中的匯編指令地址。并通過判斷上一條是否為bl指令或blx指令(b、bx指令不將lr寄存器入棧,不對其進(jìn)行處理)對上一條指令進(jìn)行計(jì)算。跳轉(zhuǎn)指令的機(jī)器碼構(gòu)成如下圖4所示:

LiteOS中backtrace函數(shù)的原理是什么

圖 4 thum跳轉(zhuǎn)指令機(jī)器碼構(gòu)成

如果為bl指令地址(特征碼0xf000),通過該地址中存儲的機(jī)器碼計(jì)算出偏移地址(原理見下圖5),從而獲得跳轉(zhuǎn)指令目標(biāo)函數(shù)入口地址,如果為blx指令(這里為blx 寄存器n指令,其特征碼0x4700),由于目標(biāo)偏移地址保存在寄存器中,無法通過機(jī)器碼計(jì)算偏移地址,則需要根據(jù)被調(diào)用幀保存的lr地址推算其所在的函數(shù)入口地址,直到入口處的push指令。

LiteOS中backtrace函數(shù)的原理是什么

圖 5 bl指令偏移地址計(jì)算規(guī)則

設(shè)計(jì)實(shí)現(xiàn)分析

LiteOS在運(yùn)行過程中出現(xiàn)異常時(shí),會自動轉(zhuǎn)入異常處理函數(shù)。LiteOS提供了backtrace函數(shù)用于跟蹤函數(shù)的堆棧信息,通過系統(tǒng)注冊的異常處理函數(shù)來調(diào)用backtrace函數(shù)實(shí)現(xiàn)系統(tǒng)異常時(shí)自動打印函數(shù)的調(diào)用棧。

設(shè)計(jì)思路

由于Cortex-M架構(gòu)無fp寄存器,sp寄存器分為msp寄存器(用于主棧)和psp寄存器(用于任務(wù)棧),因此只能通過匯編指令機(jī)器碼計(jì)算及l(fā)r地址自增查找函數(shù)入口處的push指令特征碼計(jì)算函數(shù)入口。

詳細(xì)設(shè)計(jì)

LiteOS中backtrace函數(shù)的原理是什么

圖 6 backtrace代碼框架

當(dāng)調(diào)用Cortex-M架構(gòu)的ArchBackTrace接口時(shí),該函數(shù)會通過ArchGetSp獲取當(dāng)前sp指針,如果在初始化或中斷過程發(fā)生異常,sp指向msp,在任務(wù)中發(fā)生異常,sp指向psp。將獲取的sp指針傳入BackTraceWithSp進(jìn)行調(diào)用棧分析,該函數(shù)通過FindSuitableStack函數(shù)進(jìn)行棧邊界確認(rèn),找到合適的任務(wù)棧邊界或主棧(未區(qū)分中斷棧及初始化棧)邊界。再通過邊界值控制循環(huán)查找次數(shù),從而確保將對應(yīng)棧空間內(nèi)所有棧幀的lr地址過濾出來。最后將lr地址傳入CalculateTargetAddress函數(shù)計(jì)算出lr前一條指令(即跳轉(zhuǎn)指令)要跳轉(zhuǎn)到的函數(shù)入口地址。

代碼路徑

以上代碼在LiteOS 5.0版本中已經(jīng)發(fā)布,核心代碼路徑如下:

https://gitee.com/LiteOS/LiteOS/blob/master/arch/arm/cortex_m/src/fault.c

Backtrace效果演示

  • 演示demo

LiteOS中backtrace函數(shù)的原理是什么

圖 7 除0錯(cuò)誤用例函數(shù)

演示demo設(shè)計(jì)了一個(gè)會導(dǎo)致除0錯(cuò)誤的函數(shù)(如上圖圖7),分別在初始化、中斷、任務(wù)三個(gè)場景下調(diào)用該函數(shù),將會觸發(fā)異常并打印相應(yīng)的信息,觀察相應(yīng)的fp(此處指函數(shù)入口地址,非棧幀寄存器的值)地址是否與實(shí)際代碼的反匯編地址一致。

可以通過menuconfig菜單使能backtrace功能,菜單項(xiàng)為:Debug--> Enable Backtrace。同時(shí)為避免編譯優(yōu)化造成的影響,還需配置編譯優(yōu)化選項(xiàng)為不優(yōu)化:Compiler--> Optimize Option --> Optimize None。

  • 演示效果

下面所示圖中,左圖為異常接管打印的日志,右圖為反匯編代碼??梢钥吹阶髨D中出現(xiàn)異常的pc指令值,對應(yīng)于右圖中的匯編代碼為sdiv r3, r2, r3,即為test_div函數(shù)中的int z = a / b代碼行。左圖中打印的backtrace信息,其fp值和右圖中的函數(shù)入口地址一致。

任務(wù)中觸發(fā)異常:

LiteOS中backtrace函數(shù)的原理是什么

圖 8 backtrace任務(wù)演示效果

中斷處理函數(shù)中觸發(fā)異常:

LiteOS中backtrace函數(shù)的原理是什么

圖 9 backtrace中斷演示效果

初始化函數(shù)中觸發(fā)異常:

LiteOS中backtrace函數(shù)的原理是什么

圖 10 backtrace初始化演示效果

看完上述內(nèi)容,你們對LiteOS中backtrace函數(shù)的原理是什么有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI