摘要:本篇介紹下鴻蒙輕內核中異常鈎子模塊發生系統中斷異常時如何轉儲異常信息。
本文分享自華為雲社區《鴻蒙輕內核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; }
小結
本文介紹了異常信息的轉儲區域分布情況,介紹異常信息如何初始化,並介紹了兩個主要的異常信息轉儲函數。感謝閱讀,如有任何問題、建議,都可以博客下留言給我,謝謝。