STM32上的backtrace原理與分析
-
1.說明
-
2.cortex-m上的棧布局
-
2.1 cortex-m上的寄存器
-
2.2 cortex-m上的自動壓棧
-
2.3 cortex-m上的函數(shù)執(zhí)行流程
-
-
3.cmbacktrace原理分析
-
3.1 問題分析
-
-
4.實(shí)際應(yīng)用
-
5.總結(jié)
1.說明
對于一個嵌入式產(chǎn)品的開發(fā)流程來說,一般都需要經(jīng)過如下幾個階段:
1.方案預(yù)研
2.產(chǎn)品功能設(shè)計(jì)
3.開發(fā)調(diào)試
4.工廠測試
5.產(chǎn)品上線售后
一般來說,1,2,3板子都是在開發(fā)者手上,一旦遇到bug,只要可以復(fù)現(xiàn),基本上都可以排查出來,然后修復(fù)或者規(guī)避。但一旦進(jìn)入到4,5階段,產(chǎn)品已經(jīng)成型之后,再想排查BUG就比較麻煩了。例如工廠測試階段,有可能連續(xù)運(yùn)行好幾天或者好幾個星期才能復(fù)現(xiàn)的問題,排查起來就十分的復(fù)雜。對于這種情況,backtrace是十分必要的??梢栽陔x線的狀態(tài)下分析系統(tǒng)的關(guān)鍵信息,通過函數(shù)的?;厮?,從而找到出錯的對應(yīng)的執(zhí)行函數(shù),然后結(jié)合程序設(shè)計(jì),基本上大部分的bug基本上也可以找到。我之前寫過一篇文章arm上backtrace的分析與實(shí)現(xiàn)原理。分析了在cortex-a上的分析情況。但是對于cortex-m來說,問題就會復(fù)雜許多,因?yàn)閏ortex-m對于固件的體積的限制以及特殊的架構(gòu),讓backtrack的方案占用了過大的flash。這是設(shè)計(jì)者所不能接受的,而且更加難受的是cortex-m并沒有棧回溯指針。這就讓棧的深度的計(jì)算變的十分復(fù)雜。本文主要分析cortex-m的棧布局以及一些?;厮莸牡讓釉砗头桨浮?/p>
2.cortex-m上的棧布局
在cortex-m上弄清楚棧的布局,就必須理解cortex-m上的壓棧入棧的機(jī)制和原理。下面從該體系架構(gòu)上說說cortex-m上比較重要的細(xì)節(jié)。
2.1 cortex-m上的寄存器
一旦涉及到C語言函數(shù),必須要考慮到的問題就是函數(shù)的入棧出棧的問題,也就是SP指針的增加或者減少。下面還是來復(fù)習(xí)一下arm cortex-m上的寄存器。
按照arm cortex-m的設(shè)計(jì),一共有32個寄存器。
-
13個通用寄存器,r0-r12 -
2個不同模式下使用的SP, PSP(SP_process) 和MSP(SP_main) -
1個鏈接寄存器LR(r14) -
1個程序計(jì)數(shù)器(PC) -
1個程序狀態(tài)寄存器(xPSR)
在不同的模式下,R0-R12、SP、LR是各有一份的,所以這樣算下來,總共是32個寄存器,但是在不同的模式下,并不能完全看到這32個寄存器的狀態(tài),只能看到其中的一部分。
通用寄存器R0-R12
上圖將通用寄存器分為low register和high registers就是根據(jù)指令集來說的,對于thumb指令,是16位的,只能訪問到low register,也就是R0-R7,而對于32位的arm指令,是所有的指令都可以訪問到。所以有這樣的劃分。
棧指針SP
一旦涉及到參數(shù)的壓棧與入棧,或者函數(shù)的執(zhí)行返回的時候,必須會涉及到棧指針的變化。在cortex-m由于涉及到兩種不同的sp的切換,所以在使用SP的時候要格外的小心。
程序鏈接寄存器LR
程序的鏈接寄存器在函數(shù)返回的時候會被使用到,比如一個函數(shù)A中執(zhí)行的另外一個函數(shù)B,如下
void?fun_A()
{
?fun_B()
}
那么當(dāng)執(zhí)行到fun_B的時候,首先編譯器編譯的匯編代碼會將func_A的地址自動存放LR壓棧,然后壓入其他的參數(shù)。待func_B執(zhí)行完成之后,會彈出LR到PC,此時就會返回到fun_A函數(shù)去執(zhí)行了。
程序計(jì)數(shù)寄存器
該寄存器會自動指向當(dāng)前指向的程序地址。
2.2 cortex-m上的自動壓棧
不同于其他的處理器架構(gòu),cortex-m的定位一開始就是為實(shí)時性、小體積容量的設(shè)計(jì)考慮的,所以在中斷處理這一塊,也做了一個十分有意思的設(shè)計(jì)--自動壓棧處理。
一般的CPU進(jìn)入中斷后都會去進(jìn)行壓棧操作,因?yàn)闂>褪呛瘮?shù)的現(xiàn)場,保護(hù)了棧內(nèi)容,中斷退出的時候只需要恢復(fù)棧數(shù)據(jù)就可以恢復(fù)到程序執(zhí)行的狀態(tài)了。以往這個階段都是通過人工操作寫程序完成的,在cortex-m上,將部分棧由硬件自動壓入。其壓入棧的順序一般如下:
xPSR->PC(返回地址)->LR->R12->R3->R2->R1->R0
這些寄存器硬件自動壓入,效率上應(yīng)該有較大的提升。另外的一些寄存器可以手動處理。
2.3 cortex-m上的函數(shù)執(zhí)行流程
在分析函數(shù)的執(zhí)行的時候,主要是想弄清楚底層的硬件寄存器做了哪些操作,這就需要進(jìn)行匯編翻譯進(jìn)行。此處我們用arm gcc編譯出cortex-m的elf固件,通過objdump隨便看一個函數(shù)體的執(zhí)行。
對于一個arm函數(shù)的匯編代碼,基本上都是上面的執(zhí)行邏輯。根據(jù)指令機(jī)器碼,得到對應(yīng)的指令。
我們知道,在函數(shù)執(zhí)行的時候,保存在內(nèi)存上的都是機(jī)器碼,只有在通過objdump工具的時候,才會將這些機(jī)器碼變成程序。也就是說,在程序執(zhí)行時,如果此時查看0x8004794這個地址,看到的數(shù)據(jù)是80b5 84b0這樣的內(nèi)容。那么這些又該如何進(jìn)行翻譯呢?該函數(shù)的sp指針到底該如何計(jì)算。
PUSH指令分析
PUSH指令所對應(yīng)的機(jī)器碼如下:
1011?010R?rrrr?rrrr?--?PUSH?reg_list?
按照解析,R表示的是LR寄存器,后面的是R0-R7寄存器的列表。所以解釋起來機(jī)器碼b580翻譯成二進(jìn)制b1011 0101 1000 0000。對應(yīng)的實(shí)際含義就是壓入LR與R7寄存器,當(dāng)執(zhí)行PUSH后,SP指針會自動減去兩個寄存器的大小,也就是8個字節(jié)。
SUB指令分析
SUB指令對應(yīng)的機(jī)器碼如下:
1011?0000?1vvv?vvvv?--?SUB?Sp,#immed_7*4
根據(jù)含義,v表示分別乘以4。也就是最低位為4,第二位是8,第三位是16,第四位為32,以此類推,得到其偏移的立即數(shù)。目前的機(jī)器碼為b084 翻譯成二進(jìn)制為b1011 0000 1000 0100,所以表示的立即數(shù)為16.
兩者結(jié)合,得到當(dāng)前函數(shù)會使得sp指針的值減少16+8=24。
3.cmbacktrace原理分析
在做cortex-m上的backtrace的時候,查閱了一些資料,其中發(fā)現(xiàn)一個CmBacktrace。
https://github.com/armink/CmBacktrace
設(shè)計(jì)的目的:針對 ARM Cortex-M 系列 MCU 的錯誤代碼自動追蹤、定位,錯誤原因自動分析的開源庫。
其實(shí)現(xiàn)的機(jī)理是利用cortex-m的壓棧特性所決定的。當(dāng)指定好棧地址后,sp指針就會在自己的??臻g內(nèi)進(jìn)行偏移。函數(shù)入棧的時候,會壓入?yún)?shù),也會壓入lr寄存器,利用lr寄存器的值就可以找到是誰調(diào)用該函數(shù)的。
對于裸機(jī)情況,棧地址指向一個
當(dāng)程序出現(xiàn)異常的時候,只需知道當(dāng)前的棧頂以及當(dāng)前的sp的偏移量,這些在程序中是很好得到的。然后開始便利棧中的數(shù)據(jù),每四個字節(jié)遍歷一次得到地址,該地址不一定是函數(shù)地址,有可能是參數(shù)的地址,人工去審閱這些地址的時候,只要細(xì)心一點(diǎn)是可以找到線索的。在CmBacktrace上通過判斷地址的前面2個字節(jié)的thumb指令的機(jī)器碼是否為BL或者bLx來進(jìn)行判斷該地址是否為函數(shù)。這樣也是可以的。
如果在cortex-m上使用了操作系統(tǒng),原理上基本上是類似的,由于每個線程都會有自己的線程棧,所以會有多個線程棧的情況。要想得到當(dāng)前運(yùn)行的線程棧的backtrack,原理上是和裸機(jī)一樣。但是如果想要分析其他的線程的棧的backtrace,則需要注意操作系統(tǒng)的壓棧問題。
例如在rt-thread中,進(jìn)行線程切換的時候,會調(diào)用pendsv進(jìn)行自動壓棧一次,然后在手動壓棧其他的寄存器。如果要做解析,首先去掉前面操作系統(tǒng)壓棧的部分。rt-thread操作系統(tǒng)前面壓棧的數(shù)據(jù)
#??xPSR->PC->LR->R12->R3->R2->R1->R0
#??R11?R10?R9?R8?R7?R6?R5?R4?FLAG??
一共壓了16個寄存器,如果不做處理,解析到的PC為rt_hw_interrupt_enable,解析到的LR為rt_schedule。
3.1 問題分析
在對棧的解析過程中,我們往往會涉及到一些臟數(shù)據(jù)來破壞我們的分析。比如,參數(shù)中傳遞東西是函數(shù)的地址,這是讀到的可能會誤以為這是LR,這樣分析起來會有一定的風(fēng)險(xiǎn),雖然說在大多數(shù)情況下CmBacktrace的解析可以做的很好,但是遇到參數(shù)是函數(shù)地址的時候,就很難去做分析了,此時可能會借助人工來做分析。需要一定的工作量。那么有沒有比較想的辦法,不需要便利,直接跳轉(zhuǎn)到下一個LR去執(zhí)行呢?
根據(jù)在《2.3 cortex-m上的函數(shù)執(zhí)行流程》的分析,我們基本上可以算出來一個函數(shù)的棧數(shù)據(jù)偏移,這樣就可以順利的解決這個問題了。每次都會跳轉(zhuǎn)到固定的函數(shù)中,結(jié)合當(dāng)前的數(shù)據(jù)棧的內(nèi)容,從而得到想要的結(jié)果。
4.實(shí)際應(yīng)用
上述的分析是有實(shí)際應(yīng)用的價(jià)值的,在每次出錯的情況下,我們可以保存棧的數(shù)據(jù)到掉電非易失性存儲介質(zhì)的某個特定的地址處,因?yàn)闂5拇笮〔⒉粫艽螅话?12字節(jié)或者1k或者2k等等數(shù)據(jù)量,問題出現(xiàn)后,取出棧里面的內(nèi)容,然后通過外部工具例如python腳本進(jìn)行分析,與對應(yīng)的elf文件結(jié)合起來,就能很準(zhǔn)確的定位函數(shù)的backtrace了。然后對于問題的查詢也會變得有跡可循,大大減少后期調(diào)試工作的復(fù)雜性。
5.總結(jié)
未雨綢繆是設(shè)計(jì)中必須考慮的問題,做出的產(chǎn)品都不能保證一點(diǎn)問題都不會出現(xiàn),當(dāng)出現(xiàn)問題的時候,也不用怕,因?yàn)橛辛朔治龅氖侄魏蛿?shù)據(jù)。這樣也能夠減少產(chǎn)品設(shè)計(jì)的風(fēng)險(xiǎn),做出更好用的嵌入式產(chǎn)品。
原文標(biāo)題:STM32上的backtrace原理與分析
文章出處:【微信公眾號:嵌入式IoT】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
評論
查看更多