摘要:本篇先介紹下支援的異常鉤子函式的型別,異常鉤子函式的註冊、執行等內部操作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,以及介紹了支援的異常鉤子函式型別等。