找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4648

积分

0

好友

605

主题
发表于 3 小时前 | 查看: 5| 回复: 0

嵌入式Linux开发中,程序崩溃问题如何高效定位?Google Breakpad 提供了一套跨平台的异常捕获与栈回溯机制。本篇将手把手带你完成 Breakpad 的编译安装、API 用法,并通过一个空指针崩溃的实例,演示从 dump 文件生成到符号解析、再转 core 文件用 GDB 调试的全流程。

1 Breakpad 的安装

在 Linux 下编译 Breakpad:

git clone https://github.com/google/breakpad.git
cd breakpad
./configure
make -j8
sudo make install

最新主线要求 C++20 支持,如果你的编译器版本较低会直接报错。

configure 检测编译器不支持 C++20 导致配置失败

如果不打算升级 C++ 工具链,可以下载一个旧版本的 Breakpad 源码(例如 2021 年的版本)。
不过旧版本在 make 后仍可能出现缺少第三方库的提示:

编译报错:缺失 third_party/lss/linux_syscall_support.h

此时需要手动下载 linux_syscall_support 源码,放到 src/third_party 目录下:

git clone https://github.com/cpp-pm/linux-syscall-support.git

注意调整文件夹名称使其与报错路径匹配,之后再执行 make 就可以顺利通过。

make 编译过程终端输出

编译后需要关注以下几个关键产物:

  • src/client/linux/libbreakpad_client.a(链接到自己的程序)
  • src/tools/linux/dump_syms/dump_syms(提取符号)
  • src/processor/minidump_stackwalk(栈回溯)

src 目录下列出的关键编译产物

make install 后,这些文件会被拷贝到系统路径,方便全局调用。

安装后 /usr/local 下的 Breakpad 工具

2 Breakpad 核心 API 简介

2.1 MinidumpDescriptor

用来描述 dump 输出位置(目录或文件描述符)。主要构造形式:

namespace google_breakpad {
class MinidumpDescriptor {
public:
    // 构造1:传入目录路径
    explicit MinidumpDescriptor(const char* directory);
    explicit MinidumpDescriptor(const std::string& directory);

    // 构造2:传入已打开的文件 fd
    explicit MinidumpDescriptor(int fd);

    // 启用 microdump 极简崩溃日志
    void set_microdump(bool microdump);

    // 获取接口
    std::string path() const;
    int fd() const;
    bool IsFD() const;
    bool microdump() const;
};
}  // namespace google_breakpad

简化记忆:

google_breakpad::MinidumpDescriptor(const char* dir);
google_breakpad::MinidumpDescriptor(const std::string& dir);
google_breakpad::MinidumpDescriptor(int fd);
  • path(字符串):minidump 文件写入的目录路径
  • fd(int):直接写入已打开的文件描述符(如 socket、管道、文件)

2.2 ExceptionHandler

异常处理器,接管崩溃信号。完整构造函数如下:

namespace google_breakpad {
class ExceptionHandler {
public:
    // 完整标准构造
    ExceptionHandler(const MinidumpDescriptor& descriptor,
                     FilterCallback filter,
                     MinidumpCallback callback,
                     void* callback_context,
                     bool install_handlers,
                     int handler_types);

    ~ExceptionHandler();
};
}  // namespace google_breakpad

简化:

google_breakpad::ExceptionHandler(
    const MinidumpDescriptor& descriptor,
    FilterCallback filter,
    MinidumpCallback callback,
    void* callback_context,
    bool install_handlers,
    int handler_types
);

各参数含义:

  • const MinidumpDescriptor& descriptor:minidump 输出描述符,统一管理 dump 文件生成策略。
  • FilterCallback filter崩溃前置过滤,决定本次崩溃是否要产生 minidump。  
    • 返回 true:允许生成 dump  
    • 返回 false:忽略,不抓栈  
    • 若传 nullptr,则默认放行。
  • MinidumpCallback callback后置回调,dump 写入完成后触发。可获得文件路径和写入结果,常用于上报或日志记录,不需要则传 nullptr
  • void* callback_context:自定义上下文,会原样透传给 filter 和 callback 回调。
  • bool install_handlers:是否立即安装全局信号处理器。  
    • true:构造时直接接管崩溃信号  
    • false:后续手动安装。
  • int handler_types:需要捕获的信号类型,按位组合:
enum HandlerType {
    // 不安装任何信号处理器
    HANDLER_NONE = 0,
    // 基础崩溃信号:SIGSEGV / SIGABRT / SIGFPE / SIGILL / SIGBUS
    HANDLER_CRASH_EXCEPTIONS = 1 << 0,
    // 捕获 SIGTERM
    HANDLER_TERMINATE = 1 << 1,
    // 捕获 SIGPIPE
    HANDLER_PIPE = 1 << 2,
    // 全部信号集合
    HANDLER_ALL = -1
};

3 实战示例

3.1 示例代码——DumpCallback 演示

编写测试程序 test1.cpp

#include <cstdio>
#include "client/linux/handler/exception_handler.h"

// 崩溃回调:告知 .dmp 路径
static bool DumpCallback(const google_breakpad::MinidumpDescriptor& desc,
                         void* context, bool succeeded)
{
    printf("Breakpad dump saved: %s\n", desc.path());
    return succeeded;
}

// 触发崩溃(空指针写)
void TestCrash()
{
    volatile int* p = nullptr;
    *p = 1;
}

int main()
{
    // 初始化 Breakpad:dump 存 ./log/test1
    google_breakpad::MinidumpDescriptor md("./log/test1");
    google_breakpad::ExceptionHandler eh(md, nullptr, DumpCallback,
                                         nullptr, true, -1);

    printf("TestCrash...\n");
    TestCrash();  // 触发崩溃
    printf("TestCrash done\n");
    return 0;
}

3.2 编译、运行,生成 .dmp 文件

g++ -g test1.cpp -o test1 -I/usr/local/include/breakpad -lbreakpad_client -lpthread
./test1

程序崩溃并保存 Breakpad dump 的终端输出

3.3 解析 .dmp 文件

3.3.1 使用 dump_syms 生成符号表

dump_syms test1 > log/test1/test1.sym

dump_syms 导出符号到 .sym 文件

查看符号文件的前几行,提取模块 ID,并按照 Breakpad 的符号目录规范存放:

head test1.sym 
# 根据首行 MODULE 信息建立 symbols 目录结构
mkdir -p symbols/test1/BAED630B5595363664CC4251DE58FAFA0
mv test1.sym symbols/test1/BAED630B5595363664CC4251DE58FAFA0/

head 与 mv 操作构建 symbols 符号目录

3.3.2 使用 minidump_stackwalk 解析 dump

minidump_stackwalk fd49a534-53da-4c18-6479f988-5acc949e.dmp symbols/

3.3.3 解析结果分析

minidump_stackwalk 输出崩溃线程栈

崩溃原因

Crash reason:  SIGSEGV /SEGV_MAPERR
Crash address: 0x0
Process uptime: not available
  • SIGSEGV:段错误(最常见的崩溃信号)
  • SEGV_MAPERR:访问了未映射的非法地址
  • Crash address: 0x0:访问地址 0x0 — 明显的空指针解引用!

崩溃栈回溯

Thread 0 (crashed)
 0  test1!TestCrash() [test1.cpp : 16 + 0x4]
    rax = 0x0000000000000000   rdx = 0x00007fb10cd838c0
    ...
    rip = 0x00005609e111f7e0
    Found by: given as instruction pointer in context
 1  test1!main [test1.cpp : 26 + 0x5]
    rbx = 0x0000000000000000   rbp = 0x00007fff1fcc2120
    ...
    rip = 0x00005609e111f8b0
    Found by: call frame info

解读:

  • 第一行指出崩溃直接位置:test1!TestCrash() 第 16 行(即 *p = 1 所在行),解引用空指针导致崩溃。
  • 第二行指出调用者:main 函数第 26 行调用了 TestCrash(),随后 TestCrash() 内部崩溃。

对照源码可以清楚看到对应行关系:

test1.cpp 代码中空指针写所在行与 main 调用位置

3.4 将 dmp 转为 core 文件并用 GDB 调试

有时我们想用 GDB 复现崩溃现场,可通过 minidump-2-core 工具将 .dmp 转为 core 文件。

使用 minidump-2-core 将 dump 转为 core,并用 GDB 加载

minidump-2-core fd49a534-53da-4c18-6479f988-5acc949e.dmp > fd49a534-53da-4c18-6479f988-5acc949e.core
gdb ../test1 fd49a534-53da-4c18-6479f988-5acc949e.core

进入 GDB 后,常用的调试命令:

  • bt:查看崩溃调用栈
  • bt full:显示完整堆栈及局部变量

GDB bt 与 bt full 输出,明确指向 test1.cpp:16 空指针写入

如果希望在程序内自动完成 dmp 到 core 的转换,可以在 DumpCallback 中调用系统命令:

static bool DumpCallback(const google_breakpad::MinidumpDescriptor& desc,
                         void* context, bool succeeded)
{
    printf("Breakpad dump saved: %s\n", desc.path());

    std::string corePath = std::string(desc.path(), strlen(desc.path()) - 4) + ".core";
    std::string cmd = "minidump-2-core " + desc.path() + " > " + corePath;
    printf("do cmd: %s\n", cmd.c_str());
    system(cmd.c_str());

    return succeeded;
}

4 总结

本文从 Google Breakpad 的编译安装开始,梳理了 MinidumpDescriptorExceptionHandler 的核心 API,并通过一个经典的 空指针 崩溃案例,完整演示了 dump 生成、符号提取及栈回溯的流程;最后还展示了如何将 dump 转为 core 文件,借助 GDB 进行深度调试。掌握了这套方法,再遇到嵌入式 Linux 程序的崩溃问题,你就能快速定位根源,而不再“盲猜”。更多技术分享可访问云栈社区。




上一篇:Linux TCP连接最大65535?详解端口复用与单机百万并发真相
下一篇:ss / netstat 秒级诊断:线上故障排查从 TCP 连接状态入手
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-5-7 23:54 , Processed in 0.633155 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表