目录
- 分析系统打印日志
- 利用系统内嵌的分析工具
- 任务状态管理器
- 主动断点宏
- KGDB调试
- 打印崩溃时刻现场.
- 内存泄漏检查
- 基于调试器进行裸机调试
- 栈回溯backtrace
- 设置断点,观察点
Melis3.0一共支持五个打印级别,分别对应__log, __err, __wrn, __msg, __inf五个打印接口,接口定义在log.h头文件中, 通过控制打印级别,可以过滤掉无关打印,只输出主要的日志信息。Log.h中的定义如下图所示。
OPTION_LOG_LEVEL_CLOSE: 值为0,禁止所有打印日志输出.
OPTION_LOG_LEVEL_ERROR: 值为1,允许__err日志.
OPTION_LOG_LEVEL_WARNING: 值为2,允许__wrn, __err日志输出
OPTION_LOG_LEVEL_INFO:值为3,允许__inf, __wrn, __err日志输出
OPTION_LOG_LEVEL_MESSAGE: 值为4,允许__msg, __inf, __wrn, __err日志输出。
数值越小,优先级越高, 优先级高于设定打印级别的消息将被打印至控制台,
__log是无条件打印接口,任何级别下都可以输出日志,相当于 printk.
配置打印级别的方式,执行make menuconfig.
进入
Melis3.0 SDK Configuration -->
--> Environment Setup-->
-->(1) Default log level
修改默认的打印级别即可。
终端输出的日志信息是携带颜色的,依据不同颜色,可以比较容易的区分日志的严重程度, 如下图:
数值越小,优先级越高, 日志颜色也会发生变化。
注:编写代码时,在进行逻辑判断的场合可按照异常的严重程度,使用不同级别的日志宏输出异常信息。调试候可根据输出的异常日志去分析问题。
Melis3.0的任务状态管理器有点类似于Windows下的任务管理器或Linux下的PS命令,可以实时更新系统当前的任务状态信息,包括任务数目,运行状态,处理器占用率,剩余运行时间, 堆栈利用率等关键信息,为系统调试调优提供分析研判依据。
执行make menuconfig,进入到配置界面, 依次进入:
Melis3.0 SDK Configuration -->
Kernel Setup --->
RTOS Kernel Setup --->
RT-Thread Kernel Setup --->
[*] Use task performance monitor
配置结束后,重新编译,刷机启动即可。
配置结束后,重新编译内核,然后打包烧写如目标平台。 连接串口启动平台即可看到每隔10s刷新一次当前任务状态信息:.
图中的信息有助于分析系统整体性能,优化应用,调试调优等场合。一般来讲,在系统健康运行的情况下,idle线程的CPU占用率能达到90%以上, 但上图中只有%87的时间运行在idle里面,再仔细看,任务名mlstsk_006的应用任务占用了%12的CPU时间,说明在上一个10的统计周期内,mlstsk_006比较忙.
主动断点由程序员编码时主动添加,一般添加在错误的执行路径处,用于检测程序是否进入到错误的执行流,其作用类似于assert.
例如:
当执行SW_BKPT()之后,处理器流水线停止执行指令,处理器进入debug模式,等待JTAG调试信号的连接。 此时串口终端将会打印如下输出,提示客户错误条件已经成立,需要进行排错处理。
KGDB功能通过menuconfig进行配置,在下面所示路径选中KGDB功能
Melis3.0 SDK Configuration-->
Kernel Setup --->
Subsystem support --->
[*] KGDB Debug Method Support
另外需要在程序中主动调用gdb_start函数插入首个GDB断点,作用是触发系统进入到KGDB的调试桩内等待与GDB的连接,位置越早越好。如图所示.
通常的基于GDB裸机调试需要用到调试器,调试器负责将GDB的调试命令翻译成片上调试电路能够理解的JTAG命令。 调试成本较高,melis3.0支持基于串行链路的KGDB调试手段,只需要平台提供TX, RX, GND三个串口引脚,便可以进行裸机级别的调试。举例如下:
- 在当前工作空间下,执行sudo arm-melis-eabi-gdb -se melis30.elf
- 设置串行口的波特率为115200, 执行set serial baud 115200
- 连接目标平台,执行target remote /dev/ttyUSBx, x具体数值要根据实际的设备节点名称确定,这里是0。
经过以上步骤后,GDB和KGDB之间已经建立了调试连接,接下来就可以执行正常的GDB调试指令了,比如观察变量,设置断电,反汇编,查看寄存器等等。
注意:KGDB作为嵌入到系统中的调试代理(stub), 只能以第三方的角色对系统其他部分进行调试,无法调试自身. 还有,在gdb_start断点中,不能执行backtrace命令,经测试,必须在重新建立连接才能恢复。
-
-
-
- KGDB调试实例-分析死机点调用路径.
-
-
思路:当前上下文的调用路径信息都会保留在堆栈中,例如参数和返回值点,如果读取当前断点处的r13寄存器(sp)的值,然后根据此值dump出堆栈的内容,就可以根据堆栈内的信息推断出大概的返回点的地址,根据返回点地址,结合反编译代码,就可以推断出当前点的完整调用路径。
以rt_free处的死机现场为例,首先,在进入到rt_free断电后,通过info reg命令获取当前CPU寄存器的内容, sp为0xc214af40
接着,dump出以sp开始的一段长度的内存内容,并打印输出,具体长度多少,要根据当前调用链的深度和堆栈大小决定。
获取当前栈顶位置,方式是通过当前任务指针,得到当前任务的TCB结构,在RTT的实现中,每个任务栈的基地址和长度都记录在内。经过计算, 当前任务的栈顶位置在
Top = 0xc2147000 + 16384 = 0xc214b000.
所以,上文获取的堆栈数据,有效数据只需要关注0xc214b000之前的内容即可。
上图的数据中,有些是堆栈内的临时数据,有些是调用路径中保存的父函数的调用现场, 包括lr返回地址在内,这里,可以将所有位于代码段的地址在返汇编文件中逐个比对,找到最合理的,即是调用路径。
查找地址0xc206a31c, 调用进入esDEV_close
查找地址0xc2023370,是esFSYS_mntparts的返回地址
查找地址0xc2023600, 为esDEV_DevReg调用的返回地址.
查找地址0xc2022cf4, 得到dmsdev_init的返回地址.
查找地址0xc200e188,在awos_init_thread内,是一个线程入口函数,回溯过程到此结束。
所以,完整的调用路径为:
awos_init_thread->dev_init->dmsdev_init->esDEV_DevReg->esFSYS_mntparts->esDEV_Close->rt_free.
在Load符号表后,通过GDB的backtrace命令得到的调用链是:
我们推倒出的结论和GDB的输出完全一样。
通过上面的例子,我们得到了以下信息, 系统崩溃时,执行在awos_init_thread线程内,更具体的说,他是通过以上路径层层传参进入的,所以,只需要把焦点放到上述的调用路径排查。通过KGDB,给调试一些容易复现的问题带来了极大方便。
注意:KGDB本身是串行链路连接,并且属于系统固件的一部分运行,与调试器方式相比,与仿真器相比,稳定性较差。适合容易复现的规律性问题。
注意:使用KGDB调试前,需要关闭PC串口终端(如secureCRT)与平台的连接,
KGDB适用于比较容易复现的规律性问题,但大多数时候遇到的崩溃问题并不满足这个条件,比如老化过程中的崩溃,3.0提供了一种能力,可以将崩溃点前一刻的CPU寄存器现场全部打印出来。开发者可以得到系统崩溃前一刻的所有信息, 为分析问题提供重要的参考依据。
以某个死机现场为例,如下图所示:
根据图中提供的寄存器信息我们可以研判出关于崩溃前一刻的重要信息:
- 原因:MA, Memory Access Abort, 内存越界访问,说白了就是访问了非法地址。
- 崩溃环境:tilde, 表明崩溃发生在tidle线程,tilde线程是Melis3.0的idle线程。
- 问题指令地址:PC的值在0xc200e0cc,表示崩溃的直接原因是由于执行这个地方的指令,需要重点关注。
- 非法地址值:abt_dfar, 0x000000,前面道明了崩溃的原因是CPU访问了非法地址,这里给出的是非法地址的具体数值。
综上所述,上图内容可以概括为,在tidle线程的上下文中发生了内存非法访问错误, 非法地址是NULL(空指针), 位置在 0xc200e0cc指令处。
崩溃点的地址地址在0xc200e0cc, 结合反编译文件,得到具体的函数位置信息:
为了定位指令具体对应的源码位置,可以通过将源码和汇编同时显示到文件中交叉参考, 执行arm-melis-eabi-objdump -S xxx.elf->xxx.dis得到源码和汇编同时显示的文件输出 .
所以,系统崩溃的原因是上述红色箭头位置的l指针为空。
Melis3.0对内存分配模块进行了较大改动,堆管理器从原来的多套变为只支持1套slab管理器。 统一了内存管理接口,同时彻底解决了内存碎片问题, 针对内存泄漏问题, 系统提供了“malloc-free 登记”机制,为分析解决此类问题提供了极大方便。
“malloc-free登记制度”的原理如图所示:
图中,内存泄漏池中的节点可能发生了内存泄漏, 内核提供了一个接口用来操作泄漏池,如下表所示:
void esKRNL_MemLeakChk(__u32 en) | en, 1:打开内存泄漏检查,0,关闭内存泄漏检查,并输出泄漏池中的内容 |
通过此功能可以有效的排查内存泄漏问题。
从segger官网获取jlink gdb server 安装包文件,获取后,以ubuntu为例,执行
sudo dpkg -i ozone_2.56.7_x86_64.deb
启动安装.
安装完成后,执行Ozone命令即可启动调试IDE. 经过简单配置后,即可通过Jlink仿真器调试目标平台:
经过测试,这个IDE不如GDB好用,建议使用GDB方式调试。
执行sudo dpkg -i JLink_Linux_V630j_x86_64.deb安装JLinkGDBServer调试代理。
安装完成后,执行
sudo JLinkGDBServer -device arm9
启动GDBServer服务,一切顺利的话,你会看到如下信息:
下一步,连接GDB
执行
arm-melis-eabi-gdb -se melis30.elf
在GDB会话终端下,执行
tar remote localhost:2331
连接到JLinkGDBServer.
连接成功后,会显示当前运行位置的函数信息。
下面分别举例介绍设置断点,堆栈回溯和设置观察点几个调试命令。
以在rt_free函数入口处设置断点为例, 执行
break rt_free
进入断点后,可以观察变量或者结构体内容:
以观察rt_interrupt_nest为例,此值表示中断嵌套的层数。
执行GDB命令
watch rt_interrupt_nest
以后每次rt_interrupt_nest的内容被修改,系统都会自动进入断点.
对分析数据敏感的问题非常有帮助。
除了支持JLinkGDBServer作为 GDBServer, 3.0系统也同时支持开源调试代理OpenOCD,并集成了AW自己开发的Os Aware能力,使用方式相同. 习惯使用IDE的用户也可以使用我们基于Eclipse+GDB + OpenOCD/JLinkGDBServer + Jink开发的用来进行Melis3.0系统调试的IDE环境。 使用方式有专门文档做介绍,这里不再赘述.