摘要:本篇先介紹下支持的異常鈎子函數的類型,異常鈎子函數的注冊、執行等內部操作API接口,並介紹下使用異常鈎子函數的操作接口。
本文分享自華為雲社區《鴻蒙輕內核M核源碼分析系列十七(1) 異常鈎子函數類型介紹》,作者:zhushy 。
ExcHook異常鈎子模塊是OpenHarmony LiteOS-M內核的一個可選組件,提供注冊鈎子函數LOS_RegExcHook、解除注冊鈎子函數LOS_UnRegExcHook等操作接口。發生系統時,支持保存異常上下文、任務信息、隊列信息、中斷寄存器狀態、任務切換信息、內存分配等信息。由於異常鈎子模塊內容較多,我們分為幾篇進行分析源碼,分別介紹異常鈎子函數的類型,如何注冊和解除注冊鈎子函數,如何轉儲異常信息等。本篇先介紹下支持的異常鈎子函數的類型,異常鈎子函數的注冊、執行等內部操作API接口,並介紹下使用異常鈎子函數的操作接口。異常鈎子函數的注冊、執行,異常鈎子類型定義在utils\los_debug.h|.c。
本文中所涉及的源碼,以OpenHarmony LiteOS-M內核為例,均可以在開源站點https://gitee.com/openharmony/kernel_liteos_m 獲取。鴻蒙輕內核異常鈎子模塊代碼主要在components\exchook目錄下。
1、異常鈎子類型枚舉EXC_TYPE
在文件utils\los_debug.h定義異常鈎子類型枚舉EXC_TYPE。EXC_REBOOT用於標記系統重啟時的鈎子函數,發生重啟時調用注冊的重啟鈎子函數;EXC_ASSERT用於標記斷言函數,發生斷言時調用注冊的斷言鈎子函數;EXC_STACKOVERFLOW用於標記任務棧溢出鈎子函數,發生任務棧溢出時調用注冊的任務棧溢出鈎子函數;EXC_INTERRUPT用於標記中斷異常時的鈎子函數,發生中斷異常時調用注冊的中斷異常鈎子函數。
typedef enum { EXC_REBOOT, EXC_ASSERT, EXC_STACKOVERFLOW, EXC_INTERRUPT, EXC_TYPE_END } EXC_TYPE;
2、如何注冊和執行異常鈎子函數
本節我們先看下如何調用和注冊異常鈎子函數,異常鈎子函數的注冊和調用的函數API定義在utils\los_debug.c,代碼如下。⑴處定義的函數OsExcHookRegister用於注冊異常鈎子函數到全局變量g_excHook。它的傳入的參數ExcHookFn excHookFn是個異常鈎子函數,這個鈎子函數是定義在文件components\exchook\los_exchook.c中的STATIC VOID DoExcHook(EXC_TYPE excType)后文會詳細分析。另外,從代碼上可以看出異常鈎子函數只有一個,也只能注冊一次。⑵處定義的異常鈎子執行函數OsDoExcHook,根據傳入的枚舉類型EXC_TYPE來判斷執行什么類型的異常鈎子函數。
可以看出這2個函數都是內部函數,用函數OsExcHookRegister注冊的也是全局的異常鈎子函數,它實質上對應的其實是個異常鈎子函數數組。后文會分析如何通過定義在components\exchook\los_exchook.c的LOS_RegExcHook函數如何分別注冊不同類型的異常鈎子函數。下文也會詳細分析其他對外函數如何調用OsDoExcHook來處理異常。
⑴ VOID OsExcHookRegister(ExcHookFn excHookFn) { UINT32 intSave = LOS_IntLock(); if (!g_excHook) { g_excHook = excHookFn; } LOS_IntRestore(intSave); } ⑵ VOID OsDoExcHook(EXC_TYPE excType) { UINT32 intSave = LOS_IntLock(); if (g_excHook) { g_excHook(excType); } LOS_IntRestore(intSave); }
3、使用異常鈎子函數的操作
我們從上文知道,注冊的全局異常鈎子函數只有一個,那就是全局異常鈎子函數變量g_excHook,它根據不同的異常鈎子類型來分別處理。我們看下具體如何異常鈎子函數的,關於全局異常鈎子函數底層的細節后文會詳細分析。
3.1 重啟LOS_Reboot
該函數可以在發生系統重啟異常時調用,程序僵死在此處等待看門狗watchdog等。⑴處根據參數類型EXC_REBOOT調用對應的重啟異常鈎子函數。需要在系統初始化時執行LOS_RegExcHook(EXC_REBOOT, (ExcHookFn)YourRebootFunction)注冊異常鈎子函數,才能執行重啟異常鈎子函數。YourRebootFunction需要自行定義實現在系統重啟異常時執行什么操作。如果沒有注冊過重啟鈎子函數則跳過不執行任何操作。
LITE_OS_SEC_TEXT_INIT VOID LOS_Reboot(VOID)
{
⑴ OsDoExcHook(EXC_REBOOT);
HalSysExit();
}
3.2 斷言LOS_ASSERT
該函數可以用於驗證函數的參數合法性,該函數宏定義在文件utils\los_debug.h。可以看出,如果設置的打印級別數值太低,時不支持斷言功能的。如⑴處代碼所示,該函數宏需要一個參數judge。如果參數為假時會執行⑵處的代碼,根據參數類型EXC_ASSERT調用對應的斷言異常鈎子函數。需要在系統初始化時執行LOS_RegExcHook(EXC_ASSERT, (ExcHookFn)YourAssertFunction)注冊異常鈎子函數,才能執行斷言異常鈎子函數。YourAssertFunction需要自行定義實現在斷言異常時執行什么操作。如果沒有注冊過斷言鈎子函數則跳過不執行任何操作。LOS_ASSERT后續的⑶處的代碼會關閉中斷,打印斷言錯誤信息ASSERT ERROR...。
#if PRINT_LEVEL < LOG_ERR_LEVEL #define LOS_ASSERT(judge) #else #define LOS_ASSERT(judge) \ do { \ ⑴ if ((judge) == 0) { \ ⑵ OsDoExcHook(EXC_ASSERT); \ ⑶ (VOID)LOS_IntLock(); \ PRINT_ERR("ASSERT ERROR! %s, %d, %s\n", __FILE__, __LINE__, __func__); \ while (1) { } \ } \ } while (0) #endif
3.3 任務棧溢出OsDoExcHook(EXC_STACKOVERFLOW)
任務棧溢出OsDoExcHook(EXC_STACKOVERFLOW)被OsHandleRunTaskStackOverflow函數和OsHandleNewTaskStackOverflow函數調用,這2個函數定義在文件kernel\src\los_task.c,分別在當前運行任務,要調度運行的新任務發生任務棧溢出時調用。當執行到⑴、⑵處的代碼時,根據參數類型EXC_STACKOVERFLOW調用對應的異常鈎子函數。需要在系統初始化時執行LOS_RegExcHook(EXC_STACKOVERFLOW, (ExcHookFn)YourStackOverflowFunction)注冊異常鈎子函數,才能執行異常鈎子函數。YourStackOverflowFunction需要自行定義實現在任務棧溢出異常時執行什么操作。如果沒有注冊過鈎子函數則跳過不執行任何操作。
LITE_OS_SEC_TEXT STATIC VOID OsHandleRunTaskStackOverflow(VOID) { PRINT_ERR("CURRENT task ID: %s:%d stack overflow!\n", g_losTask.runTask->taskName, g_losTask.runTask->taskID); ⑴ OsDoExcHook(EXC_STACKOVERFLOW); } ...... LITE_OS_SEC_TEXT STATIC VOID OsHandleNewTaskStackOverflow(VOID) { ...... tmp = g_losTask.runTask; g_losTask.runTask = g_losTask.newTask; ⑵ OsDoExcHook(EXC_STACKOVERFLOW); g_losTask.runTask = tmp; }
3.4 中斷異常HalExcHandleEntry
該函數在發生中斷異常時匯編代碼中調用執行,用於處於系統異常,該函數宏定義在不同芯片架構實現的文件los_interrupt.c中,如kernel\arch\arm\cortex-m7\gcc\los_interrupt.c。處理系統中斷異常時,執行到⑴處代碼時,會根據參數類型EXC_INTERRUPT調用對應的異常鈎子函數。和上述幾個異常類型的鈎子函數不一樣,中斷異常鈎子函數不需要用戶來注冊,內核已經注冊了中斷異常鈎子函數。相應的代碼在文件components\exchook\los_exc_info.c中,注冊代碼語句為(VOID)LOS_RegExcHook(EXC_INTERRUPT, (ExcHookFn)OsExcMsgDump);,當發生系統中斷異常時會調用(ExcHookFn)OsExcMsgDump函數,后文會詳細分析都包含哪些異常信息。
LITE_OS_SEC_TEXT_INIT VOID HalExcHandleEntry(UINT32 excType, UINT32 faultAddr, UINT32 pid, EXC_CONTEXT_S *excBufAddr) { ...... ⑴ OsDoExcHook(EXC_INTERRUPT); OsExcInfoDisplay(&g_excInfo); HalSysExit(); }
小結
本文介紹了異常鈎子函數的注冊函數OsExcHookRegister和異常鈎子函數的調用函數OsDoExcHook,以及介紹了支持的異常鈎子函數類型等。
