您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關STM32上的backtrace原理與分析是怎樣的,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
對于一個嵌入式產品的開發流程來說,一般都需要經過如下幾個階段:
1.方案預研
2.產品功能設計
3.開發調試
4.工廠測試
5.產品上線售后
一般來說,1,2,3板子都是在開發者手上,一旦遇到bug,只要可以復現,基本上都可以排查出來,然后修復或者規避。但一旦進入到4,5階段,產品已經成型之后,再想排查BUG就比較麻煩了。例如工廠測試階段,有可能連續運行好幾天或者好幾個星期才能復現的問題,排查起來就十分的復雜。對于這種情況,backtrace是十分必要的。可以在離線的狀態下分析系統的關鍵信息,通過函數的棧回溯,從而找到出錯的對應的執行函數,然后結合程序設計,基本上大部分的bug基本上也可以找到。我之前寫過一篇文章arm上backtrace的分析與實現原理。分析了在cortex-a上的分析情況。但是對于cortex-m來說,問題就會復雜許多,因為cortex-m對于固件的體積的限制以及特殊的架構,讓backtrack的方案占用了過大的flash。這是設計者所不能接受的,而且更加難受的是cortex-m并沒有棧回溯指針。這就讓棧的深度的計算變的十分復雜。本文主要分析cortex-m的棧布局以及一些棧回溯的底層原理和方案。
在cortex-m上弄清楚棧的布局,就必須理解cortex-m上的壓棧入棧的機制和原理。下面從該體系架構上說說cortex-m上比較重要的細節。
一旦涉及到C語言函數,必須要考慮到的問題就是函數的入棧出棧的問題,也就是SP指針的增加或者減少。下面還是來復習一下arm cortex-m上的寄存器。
按照arm cortex-m的設計,一共有32個寄存器。
在不同的模式下,R0-R12、SP、LR是各有一份的,所以這樣算下來,總共是32個寄存器,但是在不同的模式下,并不能完全看到這32個寄存器的狀態,只能看到其中的一部分。
通用寄存器R0-R12
上圖將通用寄存器分為low register和high registers就是根據指令集來說的,對于thumb指令,是16位的,只能訪問到low register,也就是R0-R7,而對于32位的arm指令,是所有的指令都可以訪問到。所以有這樣的劃分。
棧指針SP
一旦涉及到參數的壓棧與入棧,或者函數的執行返回的時候,必須會涉及到棧指針的變化。在cortex-m由于涉及到兩種不同的sp的切換,所以在使用SP的時候要格外的小心。
程序鏈接寄存器LR
程序的鏈接寄存器在函數返回的時候會被使用到,比如一個函數A中執行的另外一個函數B,如下
void fun_A()
{
fun_B()
}
那么當執行到fun_B的時候,首先編譯器編譯的匯編代碼會將func_A的地址自動存放LR壓棧,然后壓入其他的參數。待func_B執行完成之后,會彈出LR到PC,此時就會返回到fun_A函數去執行了。
程序計數寄存器
該寄存器會自動指向當前指向的程序地址。
不同于其他的處理器架構,cortex-m的定位一開始就是為實時性、小體積容量的設計考慮的,所以在中斷處理這一塊,也做了一個十分有意思的設計--自動壓棧處理。
一般的CPU進入中斷后都會去進行壓棧操作,因為棧就是函數的現場,保護了棧內容,中斷退出的時候只需要恢復棧數據就可以恢復到程序執行的狀態了。以往這個階段都是通過人工操作寫程序完成的,在cortex-m上,將部分棧由硬件自動壓入。其壓入棧的順序一般如下:
xPSR->PC(返回地址)->LR->R12->R3->R2->R1->R0
這些寄存器硬件自動壓入,效率上應該有較大的提升。另外的一些寄存器可以手動處理。
在分析函數的執行的時候,主要是想弄清楚底層的硬件寄存器做了哪些操作,這就需要進行匯編翻譯進行。此處我們用arm gcc編譯出cortex-m的elf固件,通過objdump隨便看一個函數體的執行。
對于一個arm函數的匯編代碼,基本上都是上面的執行邏輯。根據指令機器碼,得到對應的指令。
我們知道,在函數執行的時候,保存在內存上的都是機器碼,只有在通過objdump工具的時候,才會將這些機器碼變成程序。也就是說,在程序執行時,如果此時查看0x8004794這個地址,看到的數據是80b5 84b0這樣的內容。那么這些又該如何進行翻譯呢?該函數的sp指針到底該如何計算。
PUSH指令分析
PUSH指令所對應的機器碼如下:
1011 010R rrrr rrrr -- PUSH reg_list
按照解析,R表示的是LR寄存器,后面的是R0-R7寄存器的列表。所以解釋起來機器碼b580翻譯成二進制b1011 0101 1000 0000。對應的實際含義就是壓入LR與R7寄存器,當執行PUSH后,SP指針會自動減去兩個寄存器的大小,也就是8個字節。
SUB指令分析
SUB指令對應的機器碼如下:
1011 0000 1vvv vvvv -- SUB Sp,#immed_7*4
根據含義,v表示分別乘以4。也就是最低位為4,第二位是8,第三位是16,第四位為32,以此類推,得到其偏移的立即數。目前的機器碼為b084 翻譯成二進制為b1011 0000 1000 0100,所以表示的立即數為16.
兩者結合,得到當前函數會使得sp指針的值減少16+8=24。
在做cortex-m上的backtrace的時候,查閱了一些資料,其中發現一個CmBacktrace。
https://github.com/armink/CmBacktrace
設計的目的:針對 ARM Cortex-M 系列 MCU 的錯誤代碼自動追蹤、定位,錯誤原因自動分析的開源庫。
其實現的機理是利用cortex-m的壓棧特性所決定的。當指定好棧地址后,sp指針就會在自己的棧空間內進行偏移。函數入棧的時候,會壓入參數,也會壓入lr寄存器,利用lr寄存器的值就可以找到是誰調用該函數的。
對于裸機情況,棧地址指向一個
當程序出現異常的時候,只需知道當前的棧頂以及當前的sp的偏移量,這些在程序中是很好得到的。然后開始便利棧中的數據,每四個字節遍歷一次得到地址,該地址不一定是函數地址,有可能是參數的地址,人工去審閱這些地址的時候,只要細心一點是可以找到線索的。在CmBacktrace上通過判斷地址的前面2個字節的thumb指令的機器碼是否為BL或者bLx來進行判斷該地址是否為函數。這樣也是可以的。
如果在cortex-m上使用了操作系統,原理上基本上是類似的,由于每個線程都會有自己的線程棧,所以會有多個線程棧的情況。要想得到當前運行的線程棧的backtrack,原理上是和裸機一樣。但是如果想要分析其他的線程的棧的backtrace,則需要注意操作系統的壓棧問題。
例如在rt-thread中,進行線程切換的時候,會調用pendsv進行自動壓棧一次,然后在手動壓棧其他的寄存器。如果要做解析,首先去掉前面操作系統壓棧的部分。rt-thread操作系統前面壓棧的數據
# 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。
在對棧的解析過程中,我們往往會涉及到一些臟數據來破壞我們的分析。比如,參數中傳遞東西是函數的地址,這是讀到的可能會誤以為這是LR,這樣分析起來會有一定的風險,雖然說在大多數情況下CmBacktrace的解析可以做的很好,但是遇到參數是函數地址的時候,就很難去做分析了,此時可能會借助人工來做分析。需要一定的工作量。那么有沒有比較想的辦法,不需要便利,直接跳轉到下一個LR去執行呢?
根據在《2.3 cortex-m上的函數執行流程》的分析,我們基本上可以算出來一個函數的棧數據偏移,這樣就可以順利的解決這個問題了。每次都會跳轉到固定的函數中,結合當前的數據棧的內容,從而得到想要的結果。
上述的分析是有實際應用的價值的,在每次出錯的情況下,我們可以保存棧的數據到掉電非易失性存儲介質的某個特定的地址處,因為棧的大小并不會很大,一般512字節或者1k或者2k等等數據量,問題出現后,取出棧里面的內容,然后通過外部工具例如python腳本進行分析,與對應的elf文件結合起來,就能很準確的定位函數的backtrace了。然后對于問題的查詢也會變得有跡可循,大大減少后期調試工作的復雜性。
看完上述內容,你們對STM32上的backtrace原理與分析是怎樣的有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。