此前我们介绍过使用backtrace的方式定位程序崩溃问题,本文再分享另一种方法——通过生成core-dump文件,结合GDB工具来定位崩溃。在实际开发中,掌握多种调试手段能让我们在云栈社区这样的技术氛围中交流时更加游刃有余。下面我们就从环境配置开始,一步步走完整个调试流程。
1 使用core-dump分析崩溃的条件
1.1 开启core-dump文件的生成
需要先解除 core 文件大小的限制。可以临时生效,也可以在嵌入式 Linux 板子上配置永久生效进行测试。
1.1.1 临时生效
ulimit -c unlimited
1.1.2 永久生效
编辑 /etc/security/limits.conf,在文件末尾添加以下两行:
* soft core unlimited
* hard core unlimited
说明:
* 表示对所有用户生效,若仅针对特定用户,可替换为用户名(如 root soft core unlimited)
soft 为软限制(用户可临时调整)
hard 为硬限制(系统强制上限)
1.2 自定义core文件的名称与生成目录
1.2.1 临时生效
Ubuntu 系统默认通过 apport 接管了 core 文件,不会直接生成在程序运行目录。
若要生成 core 文件,需要执行以下指令:
# 停止 apport 服务(临时生效,重启后恢复)
sudo service apport stop
# 指定 core 文件的保存路径和名称格式
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,添加或修改以下参数:
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 使配置立即生效:
sysctl -p
可以通过以下命令查看是否生效:
cat /proc/sys/kernel/core_pattern
1.3 需要配有GDB工具
查看版本号,确认是否有 GDB:
gdb --version

1.4 编译时加上-g选项
用于调试的代码不做改动,只需在编译时加上 -g 选项,例如:
gcc -g test.c -o test
2 在Ubuntu虚拟机上测试
2.1 测试代码
//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 虚拟机中编译并测试:

程序运行后,在 /tmp 目录下生成了 core 文件。
2.2.2 gdb调试core文件
使用 Ubuntu 中的 GDB 工具调试 core,可以快速定位到崩溃的行号:

3 在嵌入式Linux板子上测试
3.1 交叉编译代码
在板子上运行需要使用交叉编译的程序,例如我的板子用如下命令:
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,末尾添加:
* soft core unlimited
* hard core unlimited

3.3.2 配置sysctl.conf
编辑 /etc/sysctl.conf,添加:
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 文件末尾添加:
ulimit -c unlimited > /dev/null 2>&1

3.3.5 将sysctl加入开机启动
对于 core 文件的名称和生成位置未生效,可以在 /etc/rc.local 中配置:
#!/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 可执行权限,并运行检查是否报错:
chmod +x rc.local
/etc/rc.local
echo $? # 输出 0 表示执行成功
最后将 rc.local 添加到开机启动:
ln -s /etc/rc.local /etc/init.d/rc.local

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

然后重启板子。
3.3.6 再次验证
这次重启后,默认配置就正确了:

运行程序,在指定位置生成了符合名称格式的 core 文件。
3.3.7 将core文件拷回ubuntu中分析
因为嵌入式板子上是 ARM 架构的程序,与 Ubuntu 的 x86 架构不同,需要使用交叉编译工具链中的 GDB 进行调试:
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 将文件拷贝到板子中:
scp test root@192.168.5.113:/home/mytest/cpp/test
再次在板子中运行,再将 core 文件拷回 Ubuntu:

最终,使用交叉工具链的 GDB 调试 core 文件,成功定位到了具体的行号:

4 总结
本文详细介绍了嵌入式 Linux 中利用 core-dump 文件和 GDB 工具分析程序崩溃的方法,并在 Ubuntu 虚拟机和嵌入式 Linux 板子上分别进行了实际测试。从开启 core 生成、自定义文件路径,到交叉编译、静态链接避坑,一套完整的调试链路都展现了出来,最终能够精确地定位到代码中的崩溃位置和行号。希望这些实践经验能帮助你在日常调试中少走一些弯路。