今天就跟大家聊聊有關(guān)STM32上的backtrace原理與分析是怎樣的,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
超過十余年行業(yè)經(jīng)驗(yàn),技術(shù)領(lǐng)先,服務(wù)至上的經(jīng)營(yíng)模式,全靠網(wǎng)絡(luò)和口碑獲得客戶,為自己降低成本,也就是為客戶降低成本。到目前業(yè)務(wù)范圍包括了:網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì),成都網(wǎng)站推廣,成都網(wǎng)站優(yōu)化,整體網(wǎng)絡(luò)托管,小程序開發(fā),微信開發(fā),手機(jī)APP定制開發(fā),同時(shí)也可以讓客戶的網(wǎng)站和網(wǎng)絡(luò)營(yíng)銷和我們一樣獲得訂單和生意!
對(duì)于一個(gè)嵌入式產(chǎn)品的開發(fā)流程來(lái)說,一般都需要經(jīng)過如下幾個(gè)階段:
1.方案預(yù)研
2.產(chǎn)品功能設(shè)計(jì)
3.開發(fā)調(diào)試
4.工廠測(cè)試
5.產(chǎn)品上線售后
一般來(lái)說,1,2,3板子都是在開發(fā)者手上,一旦遇到bug,只要可以復(fù)現(xiàn),基本上都可以排查出來(lái),然后修復(fù)或者規(guī)避。但一旦進(jìn)入到4,5階段,產(chǎn)品已經(jīng)成型之后,再想排查BUG就比較麻煩了。例如工廠測(cè)試階段,有可能連續(xù)運(yùn)行好幾天或者好幾個(gè)星期才能復(fù)現(xiàn)的問題,排查起來(lái)就十分的復(fù)雜。對(duì)于這種情況,backtrace是十分必要的??梢栽陔x線的狀態(tài)下分析系統(tǒng)的關(guān)鍵信息,通過函數(shù)的?;厮荩瑥亩业匠鲥e(cuò)的對(duì)應(yīng)的執(zhí)行函數(shù),然后結(jié)合程序設(shè)計(jì),基本上大部分的bug基本上也可以找到。我之前寫過一篇文章arm上backtrace的分析與實(shí)現(xiàn)原理。分析了在cortex-a上的分析情況。但是對(duì)于cortex-m來(lái)說,問題就會(huì)復(fù)雜許多,因?yàn)閏ortex-m對(duì)于固件的體積的限制以及特殊的架構(gòu),讓backtrack的方案占用了過大的flash。這是設(shè)計(jì)者所不能接受的,而且更加難受的是cortex-m并沒有?;厮葜羔?。這就讓棧的深度的計(jì)算變的十分復(fù)雜。本文主要分析cortex-m的棧布局以及一些棧回溯的底層原理和方案。
在cortex-m上弄清楚棧的布局,就必須理解cortex-m上的壓棧入棧的機(jī)制和原理。下面從該體系架構(gòu)上說說cortex-m上比較重要的細(xì)節(jié)。
一旦涉及到C語(yǔ)言函數(shù),必須要考慮到的問題就是函數(shù)的入棧出棧的問題,也就是SP指針的增加或者減少。下面還是來(lái)復(fù)習(xí)一下arm cortex-m上的寄存器。
按照arm cortex-m的設(shè)計(jì),一共有32個(gè)寄存器。
在不同的模式下,R0-R12、SP、LR是各有一份的,所以這樣算下來(lái),總共是32個(gè)寄存器,但是在不同的模式下,并不能完全看到這32個(gè)寄存器的狀態(tài),只能看到其中的一部分。
通用寄存器R0-R12
上圖將通用寄存器分為low register和high registers就是根據(jù)指令集來(lái)說的,對(duì)于thumb指令,是16位的,只能訪問到low register,也就是R0-R7,而對(duì)于32位的arm指令,是所有的指令都可以訪問到。所以有這樣的劃分。
棧指針SP
一旦涉及到參數(shù)的壓棧與入棧,或者函數(shù)的執(zhí)行返回的時(shí)候,必須會(huì)涉及到棧指針的變化。在cortex-m由于涉及到兩種不同的sp的切換,所以在使用SP的時(shí)候要格外的小心。
程序鏈接寄存器LR
程序的鏈接寄存器在函數(shù)返回的時(shí)候會(huì)被使用到,比如一個(gè)函數(shù)A中執(zhí)行的另外一個(gè)函數(shù)B,如下
void fun_A()
{
fun_B()
}
那么當(dāng)執(zhí)行到fun_B的時(shí)候,首先編譯器編譯的匯編代碼會(huì)將func_A的地址自動(dòng)存放LR壓棧,然后壓入其他的參數(shù)。待func_B執(zhí)行完成之后,會(huì)彈出LR到PC,此時(shí)就會(huì)返回到fun_A函數(shù)去執(zhí)行了。
程序計(jì)數(shù)寄存器
該寄存器會(huì)自動(dòng)指向當(dāng)前指向的程序地址。
不同于其他的處理器架構(gòu),cortex-m的定位一開始就是為實(shí)時(shí)性、小體積容量的設(shè)計(jì)考慮的,所以在中斷處理這一塊,也做了一個(gè)十分有意思的設(shè)計(jì)--自動(dòng)壓棧處理。
一般的CPU進(jìn)入中斷后都會(huì)去進(jìn)行壓棧操作,因?yàn)闂>褪呛瘮?shù)的現(xiàn)場(chǎng),保護(hù)了棧內(nèi)容,中斷退出的時(shí)候只需要恢復(fù)棧數(shù)據(jù)就可以恢復(fù)到程序執(zhí)行的狀態(tài)了。以往這個(gè)階段都是通過人工操作寫程序完成的,在cortex-m上,將部分棧由硬件自動(dòng)壓入。其壓入棧的順序一般如下:
xPSR->PC(返回地址)->LR->R12->R3->R2->R1->R0
這些寄存器硬件自動(dòng)壓入,效率上應(yīng)該有較大的提升。另外的一些寄存器可以手動(dòng)處理。
在分析函數(shù)的執(zhí)行的時(shí)候,主要是想弄清楚底層的硬件寄存器做了哪些操作,這就需要進(jìn)行匯編翻譯進(jìn)行。此處我們用arm gcc編譯出cortex-m的elf固件,通過objdump隨便看一個(gè)函數(shù)體的執(zhí)行。
對(duì)于一個(gè)arm函數(shù)的匯編代碼,基本上都是上面的執(zhí)行邏輯。根據(jù)指令機(jī)器碼,得到對(duì)應(yīng)的指令。
我們知道,在函數(shù)執(zhí)行的時(shí)候,保存在內(nèi)存上的都是機(jī)器碼,只有在通過objdump工具的時(shí)候,才會(huì)將這些機(jī)器碼變成程序。也就是說,在程序執(zhí)行時(shí),如果此時(shí)查看0x8004794這個(gè)地址,看到的數(shù)據(jù)是80b5 84b0這樣的內(nèi)容。那么這些又該如何進(jìn)行翻譯呢?該函數(shù)的sp指針到底該如何計(jì)算。
PUSH指令分析
PUSH指令所對(duì)應(yīng)的機(jī)器碼如下:
1011 010R rrrr rrrr -- PUSH reg_list
按照解析,R表示的是LR寄存器,后面的是R0-R7寄存器的列表。所以解釋起來(lái)機(jī)器碼b580翻譯成二進(jìn)制b1011 0101 1000 0000。對(duì)應(yīng)的實(shí)際含義就是壓入LR與R7寄存器,當(dāng)執(zhí)行PUSH后,SP指針會(huì)自動(dòng)減去兩個(gè)寄存器的大小,也就是8個(gè)字節(jié)。
SUB指令分析
SUB指令對(duì)應(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ù)會(huì)使得sp指針的值減少16+8=24。
在做cortex-m上的backtrace的時(shí)候,查閱了一些資料,其中發(fā)現(xiàn)一個(gè)CmBacktrace。
https://github.com/armink/CmBacktrace
設(shè)計(jì)的目的:針對(duì) ARM Cortex-M 系列 MCU 的錯(cuò)誤代碼自動(dòng)追蹤、定位,錯(cuò)誤原因自動(dòng)分析的開源庫(kù)。
其實(shí)現(xiàn)的機(jī)理是利用cortex-m的壓棧特性所決定的。當(dāng)指定好棧地址后,sp指針就會(huì)在自己的??臻g內(nèi)進(jìn)行偏移。函數(shù)入棧的時(shí)候,會(huì)壓入?yún)?shù),也會(huì)壓入lr寄存器,利用lr寄存器的值就可以找到是誰(shuí)調(diào)用該函數(shù)的。
對(duì)于裸機(jī)情況,棧地址指向一個(gè)
當(dāng)程序出現(xiàn)異常的時(shí)候,只需知道當(dāng)前的棧頂以及當(dāng)前的sp的偏移量,這些在程序中是很好得到的。然后開始便利棧中的數(shù)據(jù),每四個(gè)字節(jié)遍歷一次得到地址,該地址不一定是函數(shù)地址,有可能是參數(shù)的地址,人工去審閱這些地址的時(shí)候,只要細(xì)心一點(diǎn)是可以找到線索的。在CmBacktrace上通過判斷地址的前面2個(gè)字節(jié)的thumb指令的機(jī)器碼是否為BL或者bLx來(lái)進(jìn)行判斷該地址是否為函數(shù)。這樣也是可以的。
如果在cortex-m上使用了操作系統(tǒng),原理上基本上是類似的,由于每個(gè)線程都會(huì)有自己的線程棧,所以會(huì)有多個(gè)線程棧的情況。要想得到當(dāng)前運(yùn)行的線程棧的backtrack,原理上是和裸機(jī)一樣。但是如果想要分析其他的線程的棧的backtrace,則需要注意操作系統(tǒng)的壓棧問題。
例如在rt-thread中,進(jìn)行線程切換的時(shí)候,會(huì)調(diào)用pendsv進(jìn)行自動(dòng)壓棧一次,然后在手動(dòng)壓棧其他的寄存器。如果要做解析,首先去掉前面操作系統(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個(gè)寄存器,如果不做處理,解析到的PC為rt_hw_interrupt_enable,解析到的LR為rt_schedule。
在對(duì)棧的解析過程中,我們往往會(huì)涉及到一些臟數(shù)據(jù)來(lái)破壞我們的分析。比如,參數(shù)中傳遞東西是函數(shù)的地址,這是讀到的可能會(huì)誤以為這是LR,這樣分析起來(lái)會(huì)有一定的風(fēng)險(xiǎn),雖然說在大多數(shù)情況下CmBacktrace的解析可以做的很好,但是遇到參數(shù)是函數(shù)地址的時(shí)候,就很難去做分析了,此時(shí)可能會(huì)借助人工來(lái)做分析。需要一定的工作量。那么有沒有比較想的辦法,不需要便利,直接跳轉(zhuǎn)到下一個(gè)LR去執(zhí)行呢?
根據(jù)在《2.3 cortex-m上的函數(shù)執(zhí)行流程》的分析,我們基本上可以算出來(lái)一個(gè)函數(shù)的棧數(shù)據(jù)偏移,這樣就可以順利的解決這個(gè)問題了。每次都會(huì)跳轉(zhuǎn)到固定的函數(shù)中,結(jié)合當(dāng)前的數(shù)據(jù)棧的內(nèi)容,從而得到想要的結(jié)果。
上述的分析是有實(shí)際應(yīng)用的價(jià)值的,在每次出錯(cuò)的情況下,我們可以保存棧的數(shù)據(jù)到掉電非易失性存儲(chǔ)介質(zhì)的某個(gè)特定的地址處,因?yàn)闂5拇笮〔⒉粫?huì)很大,一般512字節(jié)或者1k或者2k等等數(shù)據(jù)量,問題出現(xiàn)后,取出棧里面的內(nèi)容,然后通過外部工具例如python腳本進(jìn)行分析,與對(duì)應(yīng)的elf文件結(jié)合起來(lái),就能很準(zhǔn)確的定位函數(shù)的backtrace了。然后對(duì)于問題的查詢也會(huì)變得有跡可循,大大減少后期調(diào)試工作的復(fù)雜性。
看完上述內(nèi)容,你們對(duì)STM32上的backtrace原理與分析是怎樣的有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝大家的支持。