【STM32】Fault 類異常_hardfault定位方法(二)

目錄

1. 案例1 

1.1 STM32出現硬件錯誤可能有以下原因:

1.2 出現問題時排查的方法:

1.3 STM32出現HardFault_Handler故障的原因主要有兩個方面:

2. 案例2

2.1 方法1   如何精確定位出問題代碼的所在位置:

2.2 方法2:最簡單,最明顯

2.3 方法3:此方法和方法一大致相同



1. 案例1 

    在用Keil對STM32的程序進行仿真時程序有時會跑飛,停止仿真程序會停在HardFault_Handler函數裏的死循環while(1)中。這說明STM32出現了硬件錯誤。

/**
  * @brief  This function handles Hard Fault exception.
  * @param  None
  * @retval None
  */
void HardFault_Handler(void)
{
  /* Go to infinite loop when Hard Fault exception occurs */
  while (1)
  {
  }
}

1.1 STM32出現硬件錯誤可能有以下原因:

  • 數組越界操作;
  • 內存溢出,訪問越界;
  • 堆棧溢出,程序跑飛;
  • 中斷處理錯誤;
  • 內存溢出或者訪問越界。這個需要自己寫程序的時候規範代碼,遇到了需要慢慢排查。
  • 堆棧溢出。增加堆棧的大小。
  •  

1.2 出現問題時排查的方法:

1、發生異常之後可首先查看LR寄存器中的值,確定當前使用堆棧爲MSP或PSP,然後找到相應堆棧的指針,並在內存中查看相應堆棧裏的內容。由於異常發生時,內核將R0~R3、R12、Return address、PSR、LR寄存器依次入棧,其中Return address即爲發生異常前PC將要執行的下一條指令地址,因此在堆棧中反數第三個字即爲出錯位置。

2、默認的HardFault_Handler處理方法是B .將它改成BX LR直接返回的形式。然後在這條語句打個斷點,一旦在斷點中停下來,說明出錯了,然後再返回,就可以返回到出錯的位置的下一條語句那兒。
這個有時候可能需要在反彙編模式下調試,因爲可以是程序跑飛一會兒纔出現HardFault_Handler。

3、還是將中斷函數修改,打印中斷時的一些信息:

//HardFault_Hander()定義如下:
void HardFault_Handler(void)
{
  uint32_t r_sp ;

  r_sp = __get_PSP(); //獲取SP的值
  PERROR(ERROR,Memory Access Error!);
  Panic(r_sp);
  while (1);

}

CPSR   當前程序狀態寄存器 (Current Program State Register)

SPSR    保存的程序狀態寄存器 (Saved Program State Register), 有6個,主要是在處理異常的時候使用.

每一種處理器模式下都有一個專用的物理寄存器作爲備份的程序狀態寄存器SPSR , 當特定的異常發生時,這個物理寄存器負責保存CPSR當前程序狀態寄存器的內容, 當異常處理程序返回時,再將內容恢復到當前程序狀態器中,繼續向下執行原來程序.

PC    程序計數器,是用來計數的,指示指令在存儲器的存放位置,也就是個地址信息.

 

1.3 STM32出現HardFault_Handler故障的原因主要有兩個方面:

出現問題時排查的方法:

      發生異常之後可首先查看LR寄存器中的值,確定當前使用堆棧爲MSP或PSP,然後找到相應堆棧的指針,並在內存中查看相應堆棧裏的內容。由於異常發生時,內核將R0~R3、R12、LR、PC、XPRS 寄存器依次入棧,其中LR即爲發生異常前PC將要執行的下一條指令地址。

注意:寄存器均是32位,且STM32是小端模式。(參考Cortex-M3權威)

SP值爲0x20008560,查看堆棧裏面的值依次爲R0~R3、R12、LR、PC、XPRS, 例如R0(10 27 00 00),  顯然堆棧後第21個字節到24字節即爲LR,該地址0x08001FFD即爲異常前PC將要執行的下一條指令地址(即StackFlow()後面的語句處 RCC->CR &= (uint32_t)0xFFFBFFFF)

void StackFlow(void)
{
    uint32_t a[3], i;
    for (i = 0; i < 10000; i++)
    {
        a[i] = 1;
    }
}

/**
  * @brief  Demo state machine.
  * @param  None
  * @retval None
  */
void Menu_Init(void)
{
    uint8_t *pdata;
    StackFlow();

    USBH_UsrLog("Starting MSC Demo");

    /* Create Menu Semaphore */
    osSemaphoreDef(osSem);

    MenuEvent = osSemaphoreCreate(osSemaphore(osSem), 1);
    //*pdata = 10;


    /* Force menu to show Item 0 by default */
    osSemaphoreRelease(MenuEvent);

    /* Menu task */
    osThreadDef(Menu_Thread, MSC_MenuThread, osPriorityHigh, 0,
                8 * configMINIMAL_STACK_SIZE);
    osThreadCreate(osThread(Menu_Thread), NULL);
}

SP值爲0x20001198,查看堆棧裏面的值依次爲R0~R3、R12、LR、PC、XPRS, 例如R0(10 27 00 00),  顯然堆棧後第21個字節到24字節即爲LR,該地址0x08003B61即爲異常前PC將要執行的下一條指令地址(即StackFlow()後面的語句處 RCC->CR &= (uint32_t)0xFFFBFFFF)
 

 

2. 案例2

發生異常後我們可以首先查看LR寄存器的值,確認當前使用的堆棧是MSP還是PSP,然後找到相對應的堆棧指針,並在內存中查看相對應堆棧的內容,內核將R0~R3,R12,LR,PC,XPRS寄存器依次入棧,其中LR即爲發生異常前PC將要執行的下一條指令地址。

那麼Cortex-M3 內核HardFault錯誤調試定位方法有:

2.1 方法1   如何精確定位出問題代碼的所在位置:


以訪問越界爲例:(對STM32F103C8T6內部flash模擬EEPROM)

#define STM32_FLASH_SIZE 64                                         

#define STM32_FLASH_WREN 1           

#define FLASH_SAVE_ADDR  0X08078000            

#define FLASH_HIS_ADDR  0X08078002      

...

       FLASH_SAVE_ADDR是開始存儲的基地址,STM32F103C8T6內部flash大小是64K,在STM32的內部閃存(FLASH)地址起始於0x08000000,一般情況下,程序就從此地址開始寫入。因此STM32F103C8T6的結束地址應該是64*1024轉換成16進制後加上單片機flash的基地址得到的結果就是0x08010000,那麼以上代碼設置了FLASH_SAVE_ADDR爲0X08078000已經超出了該單片機的範圍,因此如果在用此單片機操作flash是如果對這個地址進行寫和讀的會發生錯誤。現在假設你在不知情的狀況下對這個地址進行了操作,然後程序運行時進入

HardFault_Handler中斷中。那麼要找出錯誤代碼在哪個地方,可以使用以下方法(調試軟件MDK):

1:進入調試調試界面在HardFault_Handler的while(1)處打上斷點。

3:等待代碼運行到此,這時查看LR寄存器的

如果是正常運行那麼顯示的寄存器類似下圖所示:

     發生異常之後可首先查看LR寄存器中的值,確定當前使用堆棧爲MSP或PSP,然後找到相應堆棧的指針,並在內存中查看相應堆棧裏的內容。在Cortex_M3權威指南中可以看到如下圖所示:

 

     由這張圖可見,這個按位或的作用是把R14寄存器的低4位設爲D,在這個異常返回後進入線程模式,使用線程堆棧PSP,因爲任務運行時要確保使用的是線程模式,只有發生中斷或異常時,才讓系統進入Handle模式並使用MSP。

     在xPortPendSVHandler裏之所以沒有這一行,是因爲在進入這個異常前,系統正在跑任務,使用的就是線程模式和PSP,進入異常後變成Handle模式和MSP,但在異常返回時會自動回到這個異常發生前的模式也就是線程模式與PSP。

在vPortSVCHandler這個函數被調用之前,系統一直是處於Handle模式並使用MSP的。(因爲復位後就是Handle模式,因此大部分沒用上系統的STM32的工程,都是讓STM32處於Handle這個最高模式下運行的。)

      看到LR寄存器中的值是0xFFFFFFFD,因此我應該去看PSP的地址,找到該地址的地址然後如下圖所示打開內存,輸入上面找到的寄存器地址,右鍵選擇以long型查看地址如下所示:

然後查看這個地址向下數六個long地址,爲什麼是6個long地址呢,因爲由於異常發生時,內核將R0~R3、R12、Returnaddress、PSR、LR寄存器依次入棧,其中Return address即爲發生異常前PC將要執行的下一條指令地址;

大概是0x08xxxxxx這樣開始的即爲出錯的代碼位置,然後可以反彙編查看,如下圖所示:

可以看到是對應的C語言程序是在讀FLASH函數中發生了錯誤,因此可以判斷爲訪問越界的問題。

 

 

2.2 方法2:最簡單,最明顯

在調試狀態下,當進入HardFault斷點後,菜單欄Peripherals >Core Peripherals >FaultReports打開異常發生的報告,查看發生異常的原因。

上面的報告發生了BUS FAULT,並將Fault的中斷服務轉向Hard Fault

相對於檢測發生了什麼異常,定位異常發生位置顯得更重要。
(1)打開Call Stack窗口(如下圖,斷點停在Hard Fault服務程序中)

 

 

2.3 方法3:此方法和方法一大致相同

下面介紹怎麼找出程序中的異常。

接下來在keil_MDK工程中,編譯代碼,並debug,之後全速運行,可以看到如下圖所示程序進入HardFault異常。

如下所示我們找到SP寄存器,0x200045B8即爲棧地址,棧裏面的值依次爲R0~R3、R12、PC(Return address)、xPSR(CPSR或SPSR)、LR。如圖我們看到劃紅線的地方,注意從右往左看。分別爲0x0800427D和0x08004BFA。

在show code at address中輸入0x08004BFA,點擊go to即找到出現異常的代碼段附近下面要執行的程序

我們用同樣的方法在show code at address中輸入0x0800427D,找到如下代碼段

可以發現異常代碼就在uart_send_noackdata這個函數裏,這個函數裏我們定義了一個指針,沒有給他分配空間便開始使用了。由此我們掌握了第一種查找異常的方法。只要記錄棧裏面第21~24以及25到28字節的內容即可方便的找到異常代碼。下面介紹使用.map文件查找異常。.map文件在keil工程裏面隨着程序的編譯會自動生成。
 

在.map文件裏我們查找0x08004BFA,找到了0x08004bd8指示是uart_send_noackdata函數,到此我們找到了異常代碼所在的位置。

 由此我們知道我們只要找到棧裏面PC(Return address)、xPSR(CPSR或SPSR)寄存器裏的內存地址便可以找到異常代碼。

CPSR

當前程序狀態寄存器 (Current Program State Register)

SPSR

保存的程序狀態寄存器 (Saved Program State Register), 有6個,主要是在處理異常的時候使用.

每一種處理器模式下都有一個專用的物理寄存器作爲備份的程序狀態寄存器SPSR , 當特定的異常發生時,這個物理寄存器負責保存CPSR當前程序狀態寄存器的內容, 當異常處理程序返回時,再將內容恢復到當前程序狀態器中,繼續向下執行原來程序.

PC

程序計數器,是用來計數的,指示指令在存儲器的存放位置,也就是個地址信息.
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章