鴻蒙輕核心原始碼分析:異常鉤子模組系統中斷異常,如何轉儲異常資訊

語言: CN / TW / HK
摘要:本篇介紹下鴻蒙輕核心中異常鉤子模組發生系統中斷異常時如何轉儲異常資訊。

本文分享自華為雲社群《鴻蒙輕核心M核原始碼分析系列十七(3) 異常資訊ExcInfo》,作者: zhushy。

ExcHook異常鉤子模組是OpenHarmony LiteOS-M核心的一個可選元件,提供註冊鉤子函式LOS_RegExcHook、解除註冊鉤子函式LOS_UnRegExcHook等操作介面。發生系統時,支援儲存異常上下文、任務資訊、佇列資訊、中斷暫存器狀態、任務切換資訊、記憶體分配等資訊。由於異常鉤子模組內容較多,我們分為幾篇進行分析原始碼,分別介紹異常鉤子函式的型別,如何註冊和解除註冊鉤子函式,如何轉儲異常資訊等。本篇介紹下異常鉤子模組發生系統中斷異常時如何轉儲異常資訊。

本文中所涉及的原始碼,以OpenHarmony LiteOS-M核心為例,均可以在開源站點https://gitee.com/openharmony/kernel_liteos_m 獲取。鴻蒙輕核心異常鉤子模組程式碼主要在components\exchook目錄下。

1、異常資訊的巨集定義、列舉和結構體

在檔案components\exchook\los_exc_info.h定義了異常資訊的相關巨集定義、列舉和結構體。如下所示的巨集定義為各種異常資訊的大小,可以參考下面的異常資訊儲存區域分佈圖進行直觀的理解,前4位元組儲存異常資訊儲存區域的大小,然後分別是異常上下文、任務、佇列,中斷暫存器,任務切換,記憶體分配情況的資料資訊,最後4位元組儲存的是異常型別的最大值。

異常上下文儲存區域的詳細分佈如下圖所示,儲存異常資訊型別和資訊大小,然後分別儲存ExcInfo和上下文資訊。其他異常資訊類似,不再提供。

#define INFO_TYPE_AND_SIZE      8

#define MAX_SCENE_INFO_SIZE     (INFO_TYPE_AND_SIZE + sizeof(ExcInfo) + sizeof(EXC_CONTEXT_S))
#define MAX_TSK_INFO_SIZE       (INFO_TYPE_AND_SIZE + sizeof(TSK_INFO_S) * (LOSCFG_BASE_CORE_TSK_LIMIT + 1))

#if (LOSCFG_BASE_IPC_QUEUE == 1)
#define MAX_QUEUE_INFO_SIZE     (INFO_TYPE_AND_SIZE + sizeof(QUEUE_INFO_S) * LOSCFG_BASE_IPC_QUEUE_LIMIT)
#else
#define MAX_QUEUE_INFO_SIZE     (0)
#endif

#if (LOSCFG_BASE_CORE_EXC_TSK_SWITCH == 1)
#define MAX_SWITCH_INFO_SIZE    (INFO_TYPE_AND_SIZE + (sizeof(UINT32) + sizeof(CHAR) * LOS_TASK_NAMELEN) * OS_TASK_SWITCH_INFO_COUNT)
#else
#define MAX_SWITCH_INFO_SIZE    (0)
#endif

#define MAX_MEM_INFO_SIZE       (INFO_TYPE_AND_SIZE + sizeof(MemInfoCB) * OS_SYS_MEM_NUM)
#define MAX_EXC_MEM_SIZE        (INFO_TYPE_AND_SIZE + MAX_SCENE_INFO_SIZE + MAX_TSK_INFO_SIZE + MAX_QUEUE_INFO_SIZE + MAX_INT_INFO_SIZE + MAX_SWITCH_INFO_SIZE + MAX_MEM_INFO_SIZE)

從檔案中定義的列舉,支援的異常資訊型別包含上下文、任務、對外、中斷暫存器、任務切換和記憶體分配資訊。列舉定義如下:

typedef enum {
    OS_EXC_TYPE_CONTEXT     = 0,
    OS_EXC_TYPE_TSK         = 1,
    OS_EXC_TYPE_QUE         = 2,
    OS_EXC_TYPE_NVIC        = 3,
    OS_EXC_TYPE_TSK_SWITCH  = 4,
    OS_EXC_TYPE_MEM         = 5,
    OS_EXC_TYPE_MAX         = 6
} ExcInfoType;

2、異常資訊初始化

在檔案kernel\src\los_init.c中的函式UINT32 LOS_KernelInit(VOID)內會呼叫OsExcMsgDumpInit()函式進行初始化,程式碼片段如下。該初始化程式碼被巨集LOSCFG_PLATFORM_EXC包圍,需要開啟該巨集才能生效。

#if (LOSCFG_PLATFORM_EXC == 1)
    OsExcMsgDumpInit();
#endif

在分析函式OsExcMsgDumpInit程式碼之前,我們先看下函式OsExcRegister的程式碼。函式比較簡單,⑴處的g_excArray[]異常資訊轉儲函式陣列,支援發生異常時呼叫這些函式儲存任務、記憶體、中斷暫存器等資訊,⑵處標記異常資訊轉儲函式是否有效,每個型別的異常資訊轉儲函式只能設定一次。

VOID OsExcRegister(ExcInfoType type, EXC_INFO_SAVE_CALLBACK func, VOID *arg)
{
    ExcInfoArray *excInfo = NULL;
    if ((type >= OS_EXC_TYPE_MAX) || (func == NULL)) {
        PRINT_ERR("HalExcRegister ERROR!\n");
        return;
    }
⑴  excInfo = &(g_excArray[type]);
    if (excInfo->valid == TRUE) {
        return;
    }

    excInfo->type = type;
    excInfo->fnExcInfoCb = func;
    excInfo->arg = arg;
⑵  excInfo->valid = TRUE;
}

函式OsExcMsgDumpInit程式碼定義在components\exchook\los_exc_info.c,程式碼如下所示。⑴處的OS_SYS_MEM_NUM來自kernel\include\los_config.h配置檔案,在記錄記憶體資訊時,會記錄每個記憶體塊記憶體節點的資訊,該配置數值表示可以記錄的記憶體塊記憶體節點的數量。

⑵處的g_excMsgArray是個位元組陣列用於儲存異常資訊,g_excContent是執行位元組陣列的指標,儲存異常資訊時該指標不斷指向位元組陣列的後面的位置。⑶處開始的程式碼呼叫OsExcRegister()函式分別設定上下文、任務、佇列、中斷、任務切換、記憶體等異常資訊轉儲函式。具體的異常資訊轉儲函式在後文分析。⑷處程式碼為中斷異常型別註冊異常鉤子函式OsExcMsgDump()。

VOID OsExcMsgDumpInit(VOID)
{
    g_excQueueMaxNum = LOSCFG_BASE_IPC_QUEUE_LIMIT;
⑴  g_excMemMaxNum = OS_SYS_MEM_NUM;
⑵  g_excContent = (VOID *)g_excMsgArray;

⑶  OsExcRegister(OS_EXC_TYPE_CONTEXT, OsExcContentGet, NULL);
    OsExcRegister(OS_EXC_TYPE_TSK, OsExcTaskMsgGet, &g_taskMaxNum);
#if (LOSCFG_BASE_IPC_QUEUE == 1)
    OsExcRegister(OS_EXC_TYPE_QUE, OsExcQueueMsgGet, &g_excQueueMaxNum);
#endif
    OsExcRegister(OS_EXC_TYPE_NVIC, OsExcSaveIntStatus, NULL);
#if (LOSCFG_BASE_CORE_EXC_TSK_SWITCH == 1)
    OsExcRegister(OS_EXC_TYPE_TSK_SWITCH, OsExcTskSwitchMsgGet, &g_taskSwitchInfo);
#endif
    OsExcRegister(OS_EXC_TYPE_MEM, OsExcMemMsgGet, &g_excMemMaxNum);

⑷  (VOID)LOS_RegExcHook(EXC_INTERRUPT, (ExcHookFn)OsExcMsgDump);
}

3、中斷異常鉤子函式OsExcMsgDump

函式OsExcMsgDump()是註冊的對應中斷異常型別的異常鉤子函式。當發生中斷異常時,會執行該函式轉儲異常資訊到g_excMsgArray陣列,轉儲前執行⑴把該記憶體區域初始化為0xFF。⑵處把轉儲區的前4個位元組儲存異常資訊的大小,然後g_excContent指標往後移動4個位元組。然後遍歷g_excArray[]異常資訊轉儲函式陣列迴圈執行,會依次都各類資訊轉儲到g_excMsgArray陣列。轉儲資訊後執行⑷把指定區域設定異常資訊型別的最大值,然後g_excContent指標往後移動4個位元組。

STATIC VOID OsExcMsgDump(VOID)
{
    UINT32 index;

    /* Ignore the return code when matching CSEC rule 6.6(4). */
⑴  (VOID)memset_s(g_excMsgArray, g_excArraySize, EXC_MSG_ARRAY_INIT_VALUE, g_excArraySize);

⑵  *((UINT32 *)g_excContent) = MAX_EXC_MEM_SIZE;  /* The total length of exception information. */
    g_excContent = (UINT8 *)g_excContent + sizeof(UINT32);

    for (index = 0; index < OS_EXC_TYPE_MAX; index++) {
        if (!g_excArray[index].valid) {
            continue;
        }
⑶      g_excArray[index].fnExcInfoCb(g_excArray[index].type, g_excArray[index].arg);
    }

⑷  *((UINT32 *)g_excContent) = OS_EXC_TYPE_MAX;
    g_excContent = (UINT8 *)g_excContent + sizeof(UINT32);
    return;
}

4、支援的異常資訊轉儲函式

從列舉型別ExcInfoType,可以得知支援轉儲的異常資訊有6類,對應的轉儲函式在VOID OsExcMsgDumpInit(VOID)函式中進行註冊。我們挑2個簡單看下這些轉儲函式是如何工作。

4.1 OsExcContentGet上下文轉儲

上下文轉儲是第一塊要轉儲的資訊,儲存異常上下文資訊。⑴處獲取儲存區域的結束地址。⑵處儲存異常資訊型別,然後g_excContent指標往後移動4個位元組。⑶處儲存資訊大小,然後g_excContent指標往後移動4個位元組。⑷處把g_excInfo異常資訊複製到儲存區域當前指向的位置,其中excContentEnd - (UINTPTR)g_excContent用於保證複製不會越界溢位,然後繼續後移指標。⑸處轉儲上下文資訊,然後繼續後移指標,完成上下文資訊轉儲。

STATIC UINT32 OsExcContentGet(UINT32 type, VOID *arg)
{
⑴  UINTPTR excContentEnd = MAX_EXC_MEM_SIZE + (UINTPTR)g_excMsgArray;
    errno_t ret;

    (VOID)arg;

    /* save exception info */
⑵  *((UINT32 *)g_excContent) = type;
    g_excContent = (UINT8 *)g_excContent + sizeof(UINT32);
⑶  *((UINT32 *)g_excContent) = sizeof(ExcInfo) + sizeof(EXC_CONTEXT_S);
    g_excContent = (UINT8 *)g_excContent + sizeof(UINT32);

⑷  ret = memcpy_s((VOID *)g_excContent, excContentEnd - (UINTPTR)g_excContent,
                   (VOID *)&g_excInfo, sizeof(ExcInfo));
    if (ret != EOK) {
        return LOS_NOK;
    }
    g_excContent = (UINT8 *)g_excContent + sizeof(ExcInfo);

⑸  ret = memcpy_s((VOID *)g_excContent, excContentEnd - (UINTPTR)g_excContent,
                   g_excInfo.context, sizeof(EXC_CONTEXT_S));
    if (ret != EOK) {
        return LOS_NOK;
    }
    g_excContent = (UINT8 *)g_excContent + sizeof(EXC_CONTEXT_S);

    return LOS_OK;
}

4.2 OsExcSaveIntStatus中斷暫存器資訊轉儲

OsExcSaveIntStatus()函式用於轉儲中斷暫存器的資料,⑴、⑵和⑶和其他轉儲函式類似,分別是獲取儲存區域的結束地址,設定型別和大小資訊,並後移g_excContent指標。⑷處的OS_NVIC_SETENA_BASE定義在kernel\arch\arm\cortex-m7\gcc\los_arch_interrupt.h,是Interrupt enable register中斷使能暫存器的地址,它的大小由OS_NVIC_INT_ENABLE_SIZE定義。後續的程式碼分別轉儲其他中斷暫存器,比如Interrupt Set-Pending Registers中斷設定請求暫存器的地址OS_NVIC_SETPEND_BASE、Interrupt Active Bit Register中斷活躍暫存器的地址OS_NVIC_INT_ACT_BASE、Interrupt Priority Register中斷優先順序暫存器的地址OS_NVIC_PRI_BASE,這些中斷暫存器可以檢視官網瞭解更多,或者檢視下圖。

⑸處的程式碼是System Handler Priority Register系統處理優先順序暫存器的地址OS_NVIC_EXCPRI_BASE,⑹處是System Handler Control and State Register系統處理控制和狀態暫存器的地址OS_NVIC_SHCSR、⑺處是Interrupt Control and State Register中斷控制和狀態暫存器的地址OS_NVIC_INT_CTRL,有關這些暫存器的資訊可以訪問官網https://developer.arm.com/documentation/ddi0489/f/system-control/register-summary。

STATIC UINT32 OsExcSaveIntStatus(UINT32 type, VOID *arg)
{
    UINT32 ret;
⑴  UINTPTR excContentEnd = (UINTPTR)MAX_INT_INFO_SIZE + (UINTPTR)g_excContent;

    (VOID)arg;

⑵  *((UINT32 *)g_excContent) = type;
    g_excContent = (UINT8 *)g_excContent + sizeof(UINT32);

⑶  *((UINT32 *)g_excContent) = EXC_INT_STATUS_LEN;
    g_excContent = (UINT8 *)g_excContent + sizeof(UINT32);
    /* save IRQ ENABLE reg group */
⑷  ret = memcpy_s(g_excContent, excContentEnd - (UINTPTR)g_excContent,
                   (const VOID *)OS_NVIC_SETENA_BASE, OS_NVIC_INT_ENABLE_SIZE);
    if (ret != EOK) {
        return LOS_NOK;
    }
    g_excContent = (UINT8 *)g_excContent + OS_NVIC_INT_ENABLE_SIZE;

    /* save IRQ PEND reg group */
    ret = memcpy_s(g_excContent, excContentEnd - (UINTPTR)g_excContent,
                   (const VOID *)OS_NVIC_SETPEND_BASE, OS_NVIC_INT_PEND_SIZE);
    if (ret != EOK) {
        return LOS_NOK;
    }
    g_excContent = (UINT8 *)g_excContent + OS_NVIC_INT_PEND_SIZE;

    /* save IRQ ACTIVE reg group */
    ret = memcpy_s(g_excContent, excContentEnd - (UINTPTR)g_excContent,
                   (const VOID *)OS_NVIC_INT_ACT_BASE, OS_NVIC_INT_ACT_SIZE);
    if (ret != EOK) {
        return LOS_NOK;
    }
    g_excContent = (UINT8 *)g_excContent + OS_NVIC_INT_ACT_SIZE;

    /* save IRQ Priority reg group */
    ret = memcpy_s(g_excContent, excContentEnd - (UINTPTR)g_excContent,
                   (const VOID *)OS_NVIC_PRI_BASE, OS_NVIC_INT_PRI_SIZE);
    g_excContent = (UINT8 *)g_excContent + OS_NVIC_INT_PRI_SIZE;

    /* save Exception Priority reg group */
⑸  ret = memcpy_s(g_excContent, excContentEnd - (UINTPTR)g_excContent,
                   (const VOID *)OS_NVIC_EXCPRI_BASE, OS_NVIC_EXCPRI_SIZE);
    if (ret != EOK) {
        return LOS_NOK;
    }
    g_excContent = (UINT8 *)g_excContent + OS_NVIC_EXCPRI_SIZE;

    /* save IRQ Handler & SHCSR */
⑹  ret = memcpy_s(g_excContent, excContentEnd - (UINTPTR)g_excContent,
                   (const VOID *)OS_NVIC_SHCSR, OS_NVIC_SHCSR_SIZE);
    if (ret != EOK) {
        return LOS_NOK;
    }
    g_excContent = (UINT8 *)g_excContent + OS_NVIC_SHCSR_SIZE;

    /* save IRQ Control & ICSR */
⑺  ret = memcpy_s(g_excContent, excContentEnd - (UINTPTR)g_excContent,
                   (const VOID *)OS_NVIC_INT_CTRL, OS_NVIC_INT_CTRL_SIZE);
    if (ret != EOK) {
        return LOS_NOK;
    }
    g_excContent = (UINT8 *)g_excContent + OS_NVIC_INT_CTRL_SIZE;

    return LOS_OK;
}

小結

本文介紹了異常資訊的轉儲區域分佈情況,介紹異常資訊如何初始化,並介紹了兩個主要的異常資訊轉儲函式。感謝閱讀,如有任何問題、建議,都可以部落格下留言給我,謝謝。

 

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