摘要:本篇介绍下鸿蒙轻内核中异常钩子模块发生系统中断异常时如何转储异常信息。
本文分享自华为云社区《鸿蒙轻内核M核源码分析系列十七(3) 异常信息ExcInfo》,作者: zhushy。
ExcHook 异常钩子模块是 OpenHarmonyLiteOS-M 内核的一个可选组件,提供注册钩子函数 LOS_RegExcHook、解除注册钩子函数 LOS_UnRegExcHook 等操作接口。发生系统时,支持保存异常上下文、任务信息、队列信息、中断寄存器状态、任务切换信息、内存分配等信息。由于异常钩子模块内容较多,我们分为几篇进行分析源码,分别介绍异常钩子函数的类型,如何注册和解除注册钩子函数,如何转储异常信息等。本篇介绍下异常钩子模块发生系统中断异常时如何转储异常信息。
本文中所涉及的源码,以 OpenHarmonyLiteOS-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 中的函数 UINT32LOS_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,是 Interruptenable register 中断使能寄存器的地址,它的大小由 OS_NVIC_INT_ENABLE_SIZE 定义。后续的代码分别转储其他中断寄存器,比如 InterruptSet-Pending Registers 中断设置请求寄存器的地址 OS_NVIC_SETPEND_BASE、InterruptActive Bit Register 中断活跃寄存器的地址 OS_NVIC_INT_ACT_BASE、InterruptPriority Register 中断优先级寄存器的地址 OS_NVIC_PRI_BASE,这些中断寄存器可以查看官网了解更多,或者查看下图。
⑸处的代码是 SystemHandler Priority Register 系统处理优先级寄存器的地址 OS_NVIC_EXCPRI_BASE,⑹处是 SystemHandler Control and State Register 系统处理控制和状态寄存器的地址 OS_NVIC_SHCSR、⑺处是 InterruptControl 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;
}
复制代码
小结
本文介绍了异常信息的转储区域分布情况,介绍异常信息如何初始化,并介绍了两个主要的异常信息转储函数。感谢阅读,如有任何问题、建议,都可以博客下留言给我,谢谢。
点击关注,第一时间了解华为云新鲜技术~
评论