除去上篇文章总结的通过maps文件和反汇编定位崩溃问题原因的办法外,在嵌入式项目中,还有一种常用的定位办法-coredump,在嵌入式Linux项目中启用coredump功能,相当于为程序安装了一个“黑匣子”,当程序意外崩溃时,它能保存崩溃瞬间的内存状态、寄存器值等关键信息,极大方便了事后调试。下面大概介绍下coredump以及如何在嵌入式项目中配置和使用coredump。

1.coredump概述

coredump(核心转储)是操作系统在程序异常终止(如段错误、非法指令等)时生成的内存快照文件,记录进程崩溃时的内存、寄存器状态、堆栈信息等。主要用于调试和分析程序崩溃原因。

2.coredump 生成条件

  • 程序触发严重错误(如 SIGSEGV、SIGABRT 等信号)。
  • 系统配置允许生成 coredump(通过 ulimit -c 设置大小,默认可能为 0 即禁用)。
  • 文件系统有足够空间,且进程对目标目录有写入权限。

3.配置coredump

(1)首先make menuconfig,查看内核是否支持并已开启coredump功能,如果没有,全局查找到coredump关键字,开启coredump功能,然后重新编译内核并烧写到设备上;

(2)内核支持后,需要在系统层面进行配置,以控制coredump的生成和保存。

  • 解除资源限制​:使用 ulimit -c unlimited命令,允许生成任意大小的coredump文件。这对于资源紧张的嵌入式设备很重要,你也可以根据存储空间指定文件大小上限,例如 ulimit -c 1024(单位为KB)。

    注意​:ulimit命令的作用范围仅限于当前shell会话及其派生的子进程。对于通过其他方式(如后台服务、开机自启动)启动的程序,需要采用其他方法设置。

  • 设置coredump路径和格式​:通过修改 /proc/sys/kernel/core_pattern文件来定制coredump文件的保存路径和文件名,

        # 示例:将coredump保存到/tmp目录,文件名包含程序名和PID
        echo "/tmp/core.%e.%p" > /proc/sys/kernel/core_pattern

        常用的文件名格式符包括:

        %e:可执行文件名

        %p:进程PID

        %t:时间戳

        %s:导致coredump的信号编号

  • 启用PID扩展名​(可选):执行 echo 1 > /proc/sys/kernel/core_uses_pid,可以让coredump文件名自动加上进程PID,便于区分。

  • 设置SUID程序dump​(如需要):如果你的程序设置了SUID权限,可能需要执行 echo 2 > /proc/sys/fs/suid_dumpable才能为其生成coredump。

4.对于后台守护进程或开机自启动程序,由于它们不继承当前shell的 ulimit设置,需要在程序代码中主动启用coredump功能。

下面是一个C语言的示例代码,可以在程序的main函数初始化部分调用:

#include <sys/resource.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

static int enable_coredump(void) {
    struct rlimit limit;
    
    // 将core文件的大小限制设置为无限制
    limit.rlim_cur = RLIM_INFINITY;
    limit.rlim_max = RLIM_INFINITY;
    
    if (setrlimit(RLIMIT_CORE, &limit) != 0) {
        fprintf(stderr, "Error: setrlimit failed: %s\n", strerror(errno));
        return -1;
    }
    
    // 可选:设置core_pattern,确保路径可写
    system("echo \"/media/mmcblk0p1/core.%e.%p.%t\" > /proc/sys/kernel/core_pattern");
    
    printf("Coredump enabled.\n");
    return 0;
}

// 在main函数早期调用enable_coredump()
int main() {
    enable_coredump();
    // ... 程序主要逻辑
}

这里需要说明的一点,在查找应用崩溃问题的时候,为了方便调试,第四点最好都是要的,因为ulimit设置都只是单次开机生效(适合./demo_app_test调试),机器重启后,会恢复默认设置,再次调试需要重新设置ulimit。

5.编译和调试准备

为了后续能进行有效的调试,在编译程序时需要加上调试符号信息。

例如,使用GCC编译时,务必添加 -g选项,例如 gcc -g -o my_program my_program.c;这样生成的coredump文件才能显示详细的符号和代码信息;

如果是使用makefile编译,需要在包含目标文件的makefile中添加CFLAGS+=-g  -o0;-O0(禁用优化),避免编译器优化改变代码结构,导致调试信息与源代码行号错位。

6.分析coredump文件

当程序崩溃生成coredump文件后,可以使用GDB进行分析。

  • 使用GDB分析​:将coredump文件复制到你的开发环境(如果嵌入式设备与开发机架构不同,需要使用交叉编译工具链中的GDB,例如 arm-linux-gnueabihf-gdb)。

    # 格式:gdb <带调试信息的程序> <coredump文件>
    arm-buildroot-linux-uclibcgnueabihf-gdb ./my_program /tmp/core.my_program.1234
  • 常用GDB命令​:

    • btwhere:查看崩溃时的函数调用堆栈(backtrace),这是定位问题最关键的指令。

    • info registers:查看寄存器状态。

    • print variable:打印变量的值。

    • list:查看崩溃点附近的源代码。

7.实用技巧与注意事项

  • 存储空间管理​:嵌入式设备存储空间有限。可以通过 core_pattern将coredump文件保存到特定分区(如SD卡或扩展存储),或使用管道符号 |将其直接发送到网络或压缩脚本,以避免占满根文件系统。

  • 交叉调试​:确保使用与目标板架构匹配的交叉调试器(如 arm-linux-gnueabihf-gdb)来分析coredump文件。

  • 符号文件​:如果GDB无法直接解析动态库符号,可以使用 set solib-search-pathset sysroot命令指定动态库的路径。

8.常见问题和解决方向

问题现象

主要可能原因

核心解决思路

提示 No symbol table is loaded

可执行文件在编译时未包含调试信息(缺少 -g选项)。

使用 -g选项重新编译程序,并确保使用新版本重现问题生成coredump。

堆栈信息显示为问号 ??

1. 缺少对应的调试符号库(如系统库或动态库)。

2. 调试符号的版本与生成coredump时的库版本不匹配。

1. 安装匹配的调试符号包(如使用 debuginfo-install)。

2. 确保调试环境中的库版本与目标设备一致。

动态库中的函数和变量无法查看

动态库本身在编译时未使用 -g选项,导致其不包含调试符号。

使用 -g选项重新编译所有用到的动态库。

可执行文件与coredump文件不匹配

用于调试的可执行文件或动态库,与当初生成coredump文件时的程序版本不同。

确保用于调试的可执行文件和动态库与生成coredump时的版本完全一致。

堆栈本身可能已损坏

内存越界等问题破坏了调用堆栈,导致GDB无法正常解析。

尝试手动检查堆栈内存,或分析其他线程的堆栈来寻找线索。

9.如何验证Makefile编译生成的可执行文件确实包含了完整的调试符号?

要验证 Makefile 编译生成的可执行文件是否包含了完整的调试符号,你可以通过以下几种方法进行系统性检查。下面这个表格汇总了核心的验证手段和它们能告诉你什么。

验证方法

关键命令

成功标志(表明包含调试符号)

file命令快速检查

file <你的可执行文件>

输出中包含 ​with debug_info​ 或 ​not stripped

objdump工具深入分析

objdump --debugging <你的可执行文件>

能输出大量 DWARF 格式的调试信息,而非提示 no debug information found

gdb直接加载验证

gdb <你的可执行文件>

GDB 欢迎信息显示 ​Reading symbols from <文件>...​ 或 ​(no debugging symbols found)

检查编译日志

查看 make命令的输出

每个编译命令(如 gcc -c ...)都包含 ​-g​ 选项

个人感觉file命令是最快可以辨别可执行文件是否含有调试信息的,它同时也可以用于辨别动态库是否已加入调试信息。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐