Zephyr的Coredump机制

Creative Commons
本作品采用知识共享署名

本文简要介绍Zephyr的Coredump机制和使用方法。

作为一个多线程的操作系统Zephyr在Crash的时候你会看到Zephyr会提示如下的一些fault信息

从上面的信息你可以知道出问题的thread是shell_uart,出问题的地址是0x600093a4, 通过反编译可以找到在代码里面对应到哪一行导致了Crash。但是这足够了吗? 同一个thread可能会有几个路径走到这一行代码,或者想更进一步知道是不是其它的变量操作导致了这一行代码出错。在Linux下有Coredump机制,在Process crash后会产生core file,使用gdb分析core file可以很轻松的知道crash调用backtrace,也很容易查看相关变量。但在Zephyr这种非多进程操作系统下就无计可施吗?当然不是! 大佬dcpleung在上个月为Zephyr添加了CoreDump机制,这里我们来看如何使用Zephyr的Coredump机制。

功能简介

Zephyr的Coredump机制是在上个月由https://github.com/zephyrproject-rtos/zephyr/pull/27589引入, 目前只是一个很基础的版本,提供了以下功能:

  1. 支持x86,x86_64,cortex-m架构的coredump
  2. 只支持dump单线程堆栈: 可以通过gdb查看crash线程的堆栈信息
  3. 支持dump data,bss,noinit 段:可以通过gdb查看全局变量

使用

1.配置

使用Coredump需要进行配置, 必选项如下

1
2
3
4
CONFIG_LOG=y
CONFIG_LOG_MINIMAL=y
CONFIG_DEBUG_COREDUMP=y
CONFIG_DEBUG_COREDUMP_BACKEND_LOGGING=y

注意一定要配置CONFIG_LOG_MINIMAL,这让fault的时候将数据字节送到printk,不启用该项会导致Coredump log错乱。
可选项为

1
CONFIG_DEBUG_COREDUMP_MEMORY_DUMP_MIN=y

当配置了该选项后将不会dump data,bss,noinit段内存

2.产生corefile

按照上面配置后重新编译,运行程序,当触发crash时,会将寄存器的地址和内存通过送到log backend,最常用的就是我们的串口,如下图:

将串口中所有log全部保存为一个文件,例如叫coredump.log,使用zephyr的script将coredump.log转换为coredump.bin文件

1
/mnt/e/westz/zephyrmaster/zephyr/scripts/coredump/coredump_serial_log_parser.py coredump.log coredump.bin

转换完后会提示

1
2
3
Input file coredump.log
Output file coredump.bin
Bytes written 8776

3.调试

由于这并不是标准的corefile文件,所以zephyr提供了一个py脚本读取该coredump.bin文件,并在py脚本中建立一个gdbserver

1
/mnt/e/westz/zephyrmaster/zephyr/scripts/coredump/coredump_gdbserver.py build/zephyr/zephyr.elf coredump.bin

此时,使用gdb对zephyr.elf进行调试

1
/mnt/e/westz/zephyr-sdk-0.11.3/arm-zephyr-eabi/bin/arm-zephyr-eabi-gdb-no-py build/zephyr/zephyr.elf

执行下面命令链接gdbserver

1
target remote 127.0.0.1:1234

连接后按照正常的gdb操作,执行bt就可以看到crash的位置和backtrace了

例如上图可以看到是操作*nullptr = 0引起的crash,那么我们可以再看下nullptr是什么值

这里看到nullptr是0地址,在rt1052上对该地址进行写操作是非法的,因此会crash。
由于我们没有配置CONFIG_DEBUG_COREDUMP_MEMORY_DUMP_MIN,因此也可以通过gdb查看全局变量

原理简介

Zephyr和Linux不太一样,大多数时候可能没有FS,因此不太可能直接生成corefile, 因此将寄存器信息和内存数据输出到log终端,然后再转化为特定的coredump进行分析。
在Zephyr crash发生时会触发z_irq_spurious,以cortex为例:
z_irq_spurious->z_arm_fatal_error

1
2
3
4
5
6
7
8
void z_arm_fatal_error(unsigned int reason, const z_arch_esf_t *esf)
{

if (esf != NULL) {
esf_dump(esf); //这里dump出寄存器信息
}
z_fatal_error(reason, esf);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
void z_fatal_error(unsigned int reason, const z_arch_esf_t *esf)
{
···
LOG_ERR(">>> ZEPHYR FATAL ERROR %d: %s on CPU %d", reason,
reason_to_str(reason), get_cpu());

LOG_ERR("Current thread: %p (%s)", thread,
log_strdup(thread_name_get(thread)));

//coredump 生成
z_coredump(reason, esf, thread);
···
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void z_coredump(unsigned int reason, const z_arch_esf_t *esf,
struct k_thread *thread)
{
error = 0;
//开始coredump,只是输出BEGIN到终端
z_coredump_start();

//写Header信息到终端
dump_header(reason);

//这里将寄存器信息写到终端
if (esf != NULL) {
arch_coredump_info_dump(esf);
}

//这里将线程的堆栈写到终端
if (thread != NULL) {
dump_thread(thread);
}

//这里将data,bss,noinit写到终端
process_memory_region_list();

if (error != 0) {
z_coredump_error();
}

z_coredump_end();
}

调试时通过脚本coredump_serial_log_parser.py将文本格式的Coredump.log转化为二进制格式的Coredump.bin。然后通过coredump_gdbserver.py读取分别coredump.bin,并在脚本中建立gdbserver,提供标准的gdb remote接口。之后就可以gdb进行coredump分析了,脚本具体不在分析,大家可以参考scripts/coredump/下的内容。
目前Zephyr已经有了这个Coredump框架,未来加入多线程堆栈dump和heap dump可以期待,基本可以达到Linux上coredump的水平。从这种形式上来看Zephyr被叫做是一个小型嵌入式设备的Linux,不仅仅在代码架构上,也体现在调试工具上。
最后需要注意的是Zephyr的Coredump和Linux下的Coredump文件结构并不一样,因此是无法直接使用gdb进行调试的,关于文件结构可以参考文末链接。这里也附上我调试用coredump文件,方便大家尝鲜Coredump.7z

参考

https://docs.zephyrproject.org/latest/guides/debugging/coredump.html