解讀鴻蒙輕核心的監控器:異常鉤子函式

華為雲開發者社群 發表於 2021-10-14
摘要:本篇先介紹下支援的異常鉤子函式的型別,異常鉤子函式的註冊、執行等內部操作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,以及介紹了支援的異常鉤子函式型別等。

 

點選關注,第一時間瞭解華為雲新鮮技術~