
LiteOS-M小型系统内核——异常调测
异常调测
基本概念
OpenHarmony LiteOS-M提供异常接管调测手段,帮助开发者定位分析问题。异常接管是操作系统对运行期间发生的异常情况进行处理的一系列动作,例如打印异常发生时异常类型、发生异常时的系统状态、当前函数的调用栈信息、CPU现场信息、任务调用堆栈等信息。
运行机制
栈帧用于保存函数调用过程中的函数参数、变量、返回值等信息。调用函数时,会创建子函数的栈帧,同时将函数入参、局部变量、寄存器入栈。栈帧从高地址向低地址生长。以ARM32 CPU架构为例,每个栈帧中都会保存PC、LR、SP和FP寄存器的历史值。LR链接寄存器(Link Register)指向函数的返回地址,FP帧指针寄存器(Frame Point)指向当前函数的父函数的栈帧起始地址。利用FP寄存器可以得到父函数的栈帧,从栈帧中获取父函数的FP,就可以得到祖父函数的栈帧,以此类推,可以追溯程序调用栈,得到函数间的调用关系。
当系统发生异常时,系统打印异常函数的栈帧中保存的寄存器内容,以及父函数、祖父函数的栈帧中的LR链接寄存器、FP帧指针寄存器内容,用户就可以据此追溯函数间的调用关系,定位异常原因。
堆栈分析原理如下图所示,实际堆栈信息根据不同CPU架构有所差异,此处仅做示意。
图 1 堆栈分析原理示意图
图中不同颜色的寄存器表示不同的函数。可以看到函数调用过程中,寄存器的保存。通过FP寄存器,栈回溯到异常函数的父函数,继续按照规律对栈进行解析,推出函数调用关系,方便用户定位问题。
接口说明
OpenHarmony LiteOS-M内核的回溯栈模块提供下面几种功能,接口详细信息可以查看API参考。
表 1 回溯栈模块接口
功能分类 |
||
---|---|---|
使用指导
开发流程
开启异常调测的典型流程如下:
-
配置异常接管相关宏。
需要在target_config.h头文件中修改配置:
-
使用示例中有问题的代码,编译、运行工程,在串口终端中查看异常信息输出。示例代码模拟异常代码,实际产品开发时使用异常调测机制定位异常问题。
本示例演示异常输出,包含1个任务,该任务入口函数模拟若干函数调用,最终调用一个模拟异常的函数。代码实现如下:
- 上述代码串口终端输出异常信息如下:
定位流程
异常接管一般的定位步骤如下:
-
打开编译后生成的镜像反汇编(asm)文件。如果默认没有生成,可以使用objdump工具生成,命令为:
arm-none-eabi-objdump -S -l XXX.elf -
搜索PC指针(指向当前正在执行的指令)在asm中的位置,找到发生异常的函数。
PC地址指向发生异常时程序正在执行的指令。在当前执行的二进制文件对应的asm文件中,查找PC值0x80037da,找到当前CPU正在执行的指令行,反汇编如下所示:
UINT32 Get_Result_Exception_0(UINT16 dividend){ 80037c8: b480 push {r7} 80037ca: b085 sub sp, #20 80037cc: af00 add r7, sp, #0 80037ce: 4603 mov r3, r0 80037d0: 80fb strh r3, [r7, #6] kernel_liteos_m\targets\cortex-m7_nucleo_f767zi_gcc/Core/Src/exc_example.c:10 UINT32 divisor = 0; 80037d2: 2300 movs r3, #0 80037d4: 60fb str r3, [r7, #12] kernel_liteos_m\targets\cortex-m7_nucleo_f767zi_gcc/Core/Src/exc_example.c:11 UINT32 result = dividend / divisor; 80037d6: 88fa ldrh r2, [r7, #6] 80037d8: 68fb ldr r3, [r7, #12] 80037da: fbb2 f3f3 udiv r3, r2, r3 80037de: 60bb str r3, [r7, #8] -
可以看到:
- 异常时CPU正在执行的指令是udiv r3, r2, r3,其中r3取值为0,导致发生除零异常。
- 异常发生在函数Get_Result_Exception_0中。
-
根据LR值查找异常函数的父函数。
包含LR值0x80037fe的反汇编如下所示:
-
LR值80037fe上一行是bl 80037c8 <Get_Result_Exception_0>,此处调用了异常函数,调用异常函数的父函数为Get_Result_Exception_1()。
-
重复步骤3,解析异常信息中backtrace start至backtrace end之间的LR值,得到调用产生异常的函数调用栈关系,找到异常原因。
