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

摘要:本篇先介紹下支援的異常鉤子函數的類型,異常鉤子函數的註冊、執行等內部操作API介面,並介紹下使用異常鉤子函數的操作介面。

本文分享自華為雲社區《鴻蒙輕內核M核源碼分析系列十七(1) 異常鉤子函數類型介紹》,作者:zhushy 。

ExcHook異常鉤子模組是OpenHarmony LiteOS-M內核的一個可選組件,提供註冊鉤子函數LOS_RegExcHook、解除註冊鉤子函數LOS_UnRegExcHook等操作介面。發生系統時,支援保存異常上下文、任務資訊、隊列資訊、中斷暫存器狀態、任務切換資訊、記憶體分配等資訊。由於異常鉤子模組內容較多,我們分為幾篇進行分析源碼,分別介紹異常鉤子函數的類型,如何註冊和解除註冊鉤子函數,如何轉儲異常資訊等。本篇先介紹下支援的異常鉤子函數的類型,異常鉤子函數的註冊、執行等內部操作API介面,並介紹下使用異常鉤子函數的操作介面。異常鉤子函數的註冊、執行,異常鉤子類型定義在utils\los_debug.h|.c。

本文中所涉及的源碼,以OpenHarmony LiteOS-M內核為例,均可以在開源站點//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,以及介紹了支援的異常鉤子函數類型等。

 

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