之前介绍过使用backtrace的方式定位程序崩溃问题,本篇来介绍另一种方式,通过生成core-dump文件,再通过gdb工具来定位程序崩溃问题。
1 使用core-dump分析崩溃的条件
1.1 开启core-dump文件的生成条件
解除core 文件大小的限制,有临时生效和永久生效两种方案,在本篇的例子中,在Ubuntu中使用临时生效的方式,嵌入式Linux板子中,使用永久生效的方式进行测试。
1.1.1 临时生效
sh
ulimit -c unlimited
1.1.2 永久生效
编辑 /etc/security/limits.conf,在文件末尾添加以下两行
sh
* soft core unlimited
* hard core unlimited
说明:
*表示对所有用户生效,若仅针对特定用户,可替换为用户名(如root soft core unlimited)soft为软限制(用户可临时调整)hard为硬限制(系统强制上限)
1.2 自定义core文件的名称与生成目录
1.2.1 临时生效
Ubuntu 系统默认通过 apport 接管了 core 文件,不会直接生成在程序运行目录。
若要生成core文件,需要如下指令:
sh
# 停止 apport 服务(临时生效,重启后恢复)
sudo service apport stop
# 重置 core_pattern 为默认文件格式(生成在程序运行目录)
echo "/tmp/core-%e-%p-%t" | sudo tee /proc/sys/kernel/core_pattern
说明:
/tmp/:core 文件存储路径,可按需求指定%e:程序名称,%p:进程 PID,%t:崩溃时间戳
1.2.2 永久生效
编辑 /etc/sysctl.conf,添加或修改以下参数:
sh
kernel.core_pattern = /tmp/core-%e-%p-%t
kernel.core_uses_pid = 0
说明:
/tmp/:core 文件存储路径,可按需求指定%e:程序名称,%p:进程 PID,%t:崩溃时间戳kernel.core_uses_pid = 0:关闭默认的 PID 后缀命名,使用自定义规则
执行 sysctl -p 使配置立即生效
sh
sysctl -p
可以再通过查看
sh
cat /proc/sys/kernel/core_pattern
1.3 需要配有gdb工具
查看版本号,确认有没有gdb工具
sh
gdb --version

1.4 编译时加上-g选项
用于调试的代码不需要修改,只需要在编译时加上-g的选项,例如:
sh
/gcc -g test.c -o test
2 在Ubuntu虚拟机上测试
2.1 测试代码
c
//gcc -g test.c -o test
#include <stdio.h>
void TestFun()
{
printf("[%s] in\n", __func__);
int a[2] = {123, 456};
printf("[%s] a[1]=%d\n", a[0]); // 这里会崩溃,少了__func__
printf("[%s] out\n", __func__);
}
int main()
{
TestFun();
return 0;
}
这里在printf格式化打印时,想打印函数名,但忘记写__func__,将会导致程序崩溃。
2.2 实测结果
2.2.1 编译、临时配置与运行
先在Ubuntu虚拟机中编译并测试:

程序运行后,生成了core文件
2.2.2 gdb调试core文件
使用Ubuntu中的gdb工具调试core,定位到了崩溃的行号:

3 在嵌入式Linux板子上测试
3.1 交叉编译代码
在Linux板子中运行,需要使用交叉编译的程序,例如我的这个板子:
sh
export PATH=/home/xxpcb/myTest/OK3568/gcc_aarch64/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH
aarch64-linux-gnu-gcc -g test.c -o test

3.2 运行
将编译好的程序放到板子中运行

板子里没有gdb工具,可以将core文件拷到Ubuntu中进行分析。
3.3 永久配置测试
在分析板子中的core文件之前,先来对板子的core文件生成进行永久配置:
3.3.1 配置limits.conf
编辑 /etc/security/limits.conf,在文件末尾添加以下两行
sh
* soft core unlimited
* hard core unlimited

3.3.2 配置sysctl.conf
编辑 /etc/sysctl.conf,添加或修改以下参数:
sh
kernel.core_pattern = /tmp/core-%e-%p-%t
kernel.core_uses_pid = 0

3.3.3 初步验证
重启板子,看下是否是默认开始了core。

实际是ulimit和core文件的名称和生成位置都没有生效,需要再使用其它方式继续配置。
3.3.4 将ulimit指令添加到/etc/profile
对于ulimit没生效,可以在/etc/profile文件末尾添加:
bash
ulimit -c unlimited > /dev/null 2>&1

3.3.5 将sysctl加入开机启动
对于core文件的名称和生成位置未生效,可以在 /etc/rc.local 中配置:
sh
#!/bin/sh -e
/sbin/sysctl -p /etc/sysctl.conf > /dev/null 2>&1
exit 0
注:
-
-e表示脚本遇到错误时立即退出 -
sysctl -p是系统级初始化操作,应放在开机启动脚本(rc.local/systemd)中 -
ulimit -c是用户会话级配置 ,放在/etc/profile更合理(或也放 rc.local 实现全局生效)
然后给rc.local可执行权限,并运行检查是否报错:
sh
chmod +x rc.local
/etc/rc.local
echo $? # 输出 0 表示执行成功
最后将rc.local添加到开机启动:
sh
ln -s /etc/rc.local /etc/init.d/rc.local

rc.local还需要通过rcS脚本在开机时调用,在rcS脚本中添加调用的逻辑:
sh
# 新增:调用 rc.local
if [ -x /etc/rc.local ]; then
/etc/rc.local
fi

然后重启板子
3.3.6 再次验证
这次重启板子后,默认配置就对了

运行程序,在指定位置生成了指定格式名称的core文件
3.3.7 将core文件拷回ubuntu中分析
因为是嵌入式Liunx中的程序的core文件,架构与Ubuntu不一样,需要使用交叉编译工具链的gdb进行core文件的调试
sh
export PATH=/home/xxpcb/myTest/OK3568/gcc_aarch64/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH
aarch64-linux-gnu-gcc -g -static test.c -o test
实测结果,定位不到具体的行号,应该是找不到相关的动态库

3.3.8 编译时静态链接,避免库依赖
一种解决方案是,在编译程序的时候,使用静态链接:

静态连接生成的可执行文件会比较大,比如使用scp将文件拷贝到板子中:
sh
scp test root@192.168.5.113:/home/mytest/cpp/test
再次在板子中运行,再将core文件拷回Ubuntu:

最终,使用交叉编译工具链的gdb进行core文件的调试,定位到了具体的行号:

4 总结
本篇文件,介绍了嵌入式Linux中如何利用core-dump文件和gdb工具来分析程序崩溃问题,并在Ubuntu虚拟机和嵌入式Linux板子中分别进行了实际的测试,演示了定位到程序崩溃的具体代码位置和行号。