一、Coredump概述
1. 背景
当用户进程发生native crash时,tombstone会抓取一些简单的backtrace信息,但是对于定位一些内存访问异常、内存被踩的疑难问题来说,tombstone信息量不充足导致无法精确定位分析问题,这个时候就需要使用到coredump分析这类问题。
2. 什么是coredump
我们经常听到大家说到程序core掉了,需要定位解决,这里说的大部分是指对应程序由于各种异常或者bug导致在运行过程中异常退出或者中止,并且在满足一定条件下会产生一个叫做core的文件。
在 Linux 系统开发领域中,core dump(核心转储)是一个不可或缺的工具,它为我们提供了在程序崩溃时分析程序状态的重要线索。当程序因为某种原因(如 段错误、 非法指令等)异常终止时,Linux 系统会尝试将程序在内存中的映像、程序计数器、寄存器状态等信息写入到一个名为 core 的文件中,这个文件就是所谓的 core dump。
对于开发者而言,core dump 文件如同一块宝藏,其中蕴含着程序崩溃时的现场信息。通过对 core dump 文件的分析,我们可以了解到程序在崩溃时的内存布局、函数调用栈、变量值等重要信息,从而帮助我们快速定位问题原因,优化代码,提高程序的健壮性。
二、coredump文件的存储位置
core文件默认的存储位置与对应的可执行程序在同一目录下,文件名是core,大家可以通过下面的命令看到core文件的存在位置:
cat /proc/sys/kernel/core_pattern
缺省值是|/usr/share/apport/apport %p %s %c %P
。
注意:这里是指在进程当前工作目录的下创建。通常与程序在相同的路径下。但如果程序中调用了
chdir
函数,则有可能改变了当前工作目录。这时core文件创建在chdir
指定的路径下。有好多程序崩溃了,我们却找不到core文件放在什么位置。和chdir
函数就有关系。当然程序崩溃了不一定都产生 core文件。
通过下面的命令可以更改coredump文件的存储位置,若你希望把core文件生成到/data/coredump/core
目录下:
echo "/data/coredump/core" > /proc/sys/kernel/core_pattern
1. core文件的命名
缺省情况下,内核在coredump时所产生的core文件放在与该程序相同的目录中,并且文件名固定为core。很显然,如果有多个程序产生core文件,或者同一个程序多次崩溃,就会重复覆盖同一个core文件,因此我们有必要对不同程序生成的core文件进行分别命名。
/proc/sys/kernel/core_uses_pid
可以控制core文件的文件名中是否添加pid作为扩展。文件内容为1,表示添加pid作为扩展名,生成的core文件格式为core.xxxx
;为0则表示生成的core文件同一命名为core。可通过以下命令修改此文件:
echo "1" > /proc/sys/kernel/core_uses_pid
/proc/sys/kernel/core_pattern
可以控制core文件保存位置和文件名格式,可通过以下命令修改此文件:
echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
2. 相关参数列表
标识符 | 含义 |
---|---|
%% | 单个%字符 |
%p | 添加pid |
%u | 添加当前uid |
%g | 添加当前gid |
%s | 添加导致产生core的信号 |
%t | 添加core文件生成时的时间 |
%h | 添加主机名 |
%e | 添加程序文件名 |
3. coredump实现原理
用户程序发生某些错误或异常时,在Linux内核会捕获到异常,并给用户进程发送signal异常信号,进程在返回用户空间之前处理信号,调用Linux内核coredump,生成elf格式的core文件,保存到指定的路径。
三、造成程序coredump的原因
1. 内存访问越界
a) 由于使用错误的下标,导致数组访问越界。
b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符。
c) 使用strcpy
, strcat
, sprintf
, strcmp
, strcasecmp
等字符串操作函数,将目标字符串读/写爆。应该使用strncpy
, strlcpy
, strncat
, strlcat
, snprintf
, strncmp
, strncasecmp
等函数防止读写越界。
2. 多线程程序使用了线程不安全的函数
应该使用下面这些可重入的函数,它们很容易被用错:
asctime_r(3c)
、gethostbyname_r(3n)
、getservbyname_r(3n)
、ctermid_r(3s)
、gethostent_r(3n)
、getservbyport_r(3n)
、
ctime_r(3c)
、getlogin_r(3c)
、getservent_r(3n)
、fgetgrent_r(3c)
、getnetbyaddr_r(3n)
、getspent_r(3c)
、
fgetpwent_r(3c)
、getnetbyname_r(3n)
、getspnam_r(3c)
、fgetspent_r(3c)
、getnetent_r(3n)
、gmtime_r(3c)
、
gamma_r(3m)
、getnetgrent_r(3n)
、lgamma_r(3m)
、getauclassent_r(3)
、getprotobyname_r(3n)
、
localtime_r(3c)
、getauclassnam_r(3)
、getprotobynumber_r(3n)
、nis_sperror_r(3n)
、getauevent_r(3)
、
getprotoent_r(3n)
、rand_r(3c)
、getauevnam_r(3)
、getpwent_r(3c)
、readdir_r(3c)
、getauevnum_r(3)
、
getpwnam_r(3c)
、strtok_r(3c)
、getgrgid_r(3c)
、getrpcbyname_r(3n)
、ttyname_r(3c)
、
getgrnam_r(3c)
、getrpcent_r(3n)
、gethostbyaddr_r(3n)
、getrpcbynumber_r(3n)
、getrpcbynumber_r(3n)
、
gethostbyaddr_r(3n)
、tmpnam_r(3s)
、getpwuid_r(3c)
。
3. 多线程读写的数据未加锁保护
对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成coredump。
4. 非法指针操作
(1)使用空指针
(2)随意使用指针转换
一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时就很容易因为bus error而core dump。
5. 堆栈溢出
不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。
四、Vela Coredump实现
配置说明
-
使能Coredump
SYSLOG方式和BLKDEV方式选择一种 -
安装
python-lzf
-
主动触发Coredump
设备端实现
1. Vela
Vela 中的实现方式需要考虑到参差不齐的硬件和资源形态:
- 资源限制问题:在资源受限的嵌入式设备中,
kexec boot
所需的独立代码和RAM区域难以实现,如何克服这一硬件约束? - 底层依赖问题:供应商提供的启动跳转处理通常过于简化,如何设计脱离底层限制的健壮崩溃恢复流程?
- 内存持久化问题:系统重启后RAM内容是否可保留?若RAM丢失,如何确保捕获的内核信息(如coredump)仍有效可用?
2. 触发Coredump
Coredump支持用户态和内核态两种形式:
a) CONFIG_BOARD_COREDUMP_SYSLOG
:
- 系统将在异常或断言时自动收集和生成Coredump,并通过syslog打印。
b) CONFIG_SYSTEM_COREDUMP
:
- 支持通过命令
coredump <pid>
来捕获系统在运行时的core信息,并通过syslog打印。
3. ELF Coredump
在Dump触发时,系统通过libelf coredump进行Core文件格式化,其中会收集相关线程的pid/name/registers保存在 NT_PRSTATUS / NT_PRPSINFO 段中,同时线程堆栈也会同步保存在program stack段中。
可以设置 CONFIG_BOARD_MEMORY_RANGE
将board的内存范围指定,这样coredump将会保存指定的内存。
例如:
CONFIG_BOARD_MEMORY_RANGE="0x1000,0x2000,0x7"
格式为:Start1,End1,RWX1,Start2,End2,RWX2...
- 起始地址
- 终止地址
- 类型(可读0x1,可写0x2,可执行0x4,可以相或)
输出和保存Coredump
1. Syslog
Coredump输出backend套用了Vela的stream框架,在输出时可以将stream组装成pipeline来进行多个组件的后处理:
当前Coredump的默认处理流程是:首先将数据通过LZF算法进行压缩,然后再将压缩后的二进制数据转换为十六进制(hex)或base64格式的可打印字符,最后通过Syslog进行输出。伪代码如下:
struct lib_hexdumpstream_s g_hexstream;
struct lib_lzfoutstream_s g_lzfstream;
struct lib_syslogstream_s g_syslogstream;
lib_syslogstream(&g_syslogstream, LOG_EMERG);
lib_hexdumpstream(&g_hexstream, &g_syslogstream);
lib_lzfoutstream(&g_lzfstream, &g_hexstream);
core_dump(NULL, &g_lzfstream, -1);
2. 块设备(BLKDEV)
除Syslog链路外,Coredump还支持文件、内存、块设备等驱动的流式后端,但是由于硬件场景和应用环境复杂度需要做相应的配置。
块设备需要打开CONFIG
相关配置。
CONFIG_BOARD_COREDUMP_DEVPATH
是对应保存块设备的路径。
在触发 fault 时:
- ELF 格式封装:Coredump 将系统完整状态(包括所有进程上下文、寄存器值及 RAM 内存快照)封装为标准 ELF(Executable and Linkable Format)文件,确保兼容主流调试工具。
- 块设备后端写入:通过预配置的 blockdriver 后端,系统在异常触发时直接将 ELF 数据流式写入指定块设备节点(如
/dev/coredump
),实现低延迟持久化。 - 元数据追加机制:ELF 数据写入完成后,coredump 服务在块设备的末端区块(last block)注入结构化元数据,包含:
- Magic number(标识文件有效性)
- 版本号(兼容性控制)
- 精确崩溃时间戳
- Coredump 文件大小及校验和
- 重启后持久化处理:设备重启时,系统自动从块设备解析元数据定位 ELF 文件,将其完整导出至持久存储目录(如
/data
),供离线诊断使用。
3. MTDDEV
MTD保存coredump功能和BLK类似,当使用NORFLASH时,如果使用BLK,需要经过FTL & BCH。
在部分资源紧张的平台中,需要直接将coredump保存到MTD设备中,节约中间层的内存开销。
使用MTDDEV可以节约掉FTL & BCH占用的内存(通常是1个erase blk, NOR FLASH的常规值4KB)。
五、多核coredump使用
方法一
在Vela中需要在某些项目中有多个核时,但是coredump只能在主核进行(AP),如果想要其他核的coredump,需要将其他核的内存区域添加到ap的dump区域,之后生成coredump文件需要和gdbserver配合使用。
方法二
core1上不带flash driver,需要借助mtd ram做中转;core0保持coredump原始流程
在gdb调试中也可以借助dump
命令将一段内存导出到文件中。