1. 程式人生 > >Cortex-M3開發經驗(三):在HardFault中列印棧資訊

Cortex-M3開發經驗(三):在HardFault中列印棧資訊

Cortex-M3開發經驗(三):在HardFault中列印棧資訊

在《Cortex-M3開發經驗(二):確認發生HardFault的地方》中,我們提到如何查找出錯地方。但是這有一個問題,就是必須連結偵錯程式。那麼在某些情況下,我們無法連線偵錯程式,那麼就無法讀取到棧資訊了嗎?我們可以在進入HardFault時,獲取棧指標,然後通過串列埠的方式打印出來嗎?

說幹就幹,有好的想法,也必須有實際的行動驗證自己的想法。

如何獲取棧指標?

卡住我們的第一個問題就是如何獲取棧指標了。就是如何獲取SP,MSP(主堆疊指標),PSP(程序堆疊指標)的值了。

在《Cortex-M3權威指南》中,有這麼一段話:CM3微控制器核心中共有兩個堆疊指標,於是也就是支援兩個堆疊,當引用R13(寫作SP)時,引用到的是當前正在使用的那一個(MSP或PSP),另一個必須用特殊的指令來訪問(MRS和MSR指令)。

也就是說,我們需要用匯編的指令來獲取棧指標。

uint32_t get_msp_addr()
{
    __asm("mrs r0, msp");
    __asm("bx lr");
}

uint32_t get_psp_addr()
{
    __asm("mrs r0, psp");
    __asm("bx lr");
}

uint32_t get_sp_addr()
{
    __asm("mov r0, sp");
    __asm("bx lr");
}

注:每個編譯器所支援C嵌入彙編的方式不同,也可能一些編譯器不支援__asm指令。

通過棧指標獲取核心暫存器的值

uint32_t reg_buff[10];
uint32_t *sp = NULL;

void HardFault_Handler()
{
    sp = (uint32_t*)get_msp_addr();
    reg_buff[0] = *(sp++);
    reg_buff[1] = *(sp++);
    reg_buff[2] = *(sp++);
    reg_buff[3] = *(sp++);
    reg_buff[4] = *(sp++);
    reg_buff[5] = *(sp++);
    reg_buff[6] = *(sp++);
    reg_buff[7] = *(sp++);
    reg_buff[8] = *(sp++);
    reg_buff[9] = *(sp++);
    while(1){}
}

編譯,執行!

結果有點意料之外!

LR的值和PC的值跟我們之前單步除錯的不一樣!偏移了12個位元組。為什麼?後面單步看了一下後發現,我們在HardFault中呼叫了get_msp_addr這個函式,而呼叫函式就意味著使用棧空間。如果我把reg_buff放到HardFault中,這樣就不止偏移12個位元組了!

有沒有更好的方法啊!?

我們在進入HardFault_Handler函式之前就獲取SP指標的值,並作為引數傳入到HardFault_Handler中不就可以了嗎?

誰在呼叫中斷處理函式?

要解決上面的問題,我們就需要知道核心在哪裡呼叫中斷函式的,這樣我們才能修改對應的中斷處理函式,使其可以接收引數。

《Cortex-M3開發經驗(二):確認發生HardFault的地方》中,我們提到過,在發生中斷/異常時,核心會去中斷向量表中找到對應的中斷,找到中斷的入口地址。那麼我們就看看中斷向量表在哪?

最終,在startup_xxx.S檔案中找到了向量表的定義1。我們也找到了HardFault_Handler的定義

.weak   HardFault_Handler
.type   HardFault_Handler, %function

HardFault_Handler:
    B .

雖然可能不瞭解彙編,不知道什麼意思,但也能猜測出大概的意思,也可能查資料。發現B是跳轉指令,這應該就是跳轉到同名的C函式中。那麼我們可以修改為:

.weak   HardFault_Handler
.type   HardFault_Handler, %function

HardFault_Handler:
    MOV     r0, lr
    MOV     r1, sp
    BL      hardfault_handler

這樣就吧LR的值和SP的值傳入到hardfault_handler函式中去了2

編譯,執行!

這次的結果就是我們想要的了。


  1. 這是一個啟動檔案,裡面包含了該晶片啟動需要的一些過程。↩

  2. R0~R3暫存器儲存的是函式呼叫時所傳入的引數,同時也可作為函式返回值。hardfault_handler定義如下:void hardfault_handler(uint32_t lr, uint32_t sp)↩