使用google breakpad 定位linux C++ 程序崩溃点(嵌入式平台)

本文基于转载文章编写。如侵权请告知,将立马删除

Breakpad 是 Google 用 C++ 编写的一个开源、跨平台的崩溃报告系统,它支持 Windows、Linux 和 macOS。进程崩溃时可以产生一个较小的.dmp文件, 根据这个.dmp文件和原始程序(未strip去除调试信息前)生成的符号文件,可以打印出堆栈信息和线程执行情况。
目前,有很多大型项目都在使用 Breakpad,例如:Google Chrome、Firefox、Google Picasa、Camino、Google Earth 等。

主页: https://chromium.googlesource.com/breakpad/breakpad/
文档: https://chromium.googlesource.com/breakpad/breakpad/+/HEAD/docs
GitHub 地址: https://github.com/google/breakpad

使用流程

  • 流程图:
    在这里插入图片描述

简略版:在发布程序前,(未strip)把刚编出来热乎的程序使用dump_syms生成 项目名.sym 的符号文件(图上绿色部分)跟随版本保存起来,等程序发生崩溃获取到.dmp时,用minidump_stackwalk 和.sym符号文件生成堆栈信息调试。

其中,包含了三个主要组件:

  • Breakpad client:是一个静态库(即:libbreakpad_client.a),要编译集成到我们的程序中。用于在我们的程序中写 minidump 文件、捕获当前线程的状态、以及可执行文件/共享库的标识,并通过注册的回调通知我们。

  • Breakpad 符号转储工具:是一个程序(即:dump_syms),用于读取由编译器产生的调试信息,并以 Breakpad 自己的格式生成一个符号文件。

  • Breakpad minidump 处理器:是一个程序(即:minidump_stackwalk),用于读取 minidump 文件和符号文件,并生成一个可读的 C/C++ 堆栈跟踪。

编译

下载 Breakpad 源码;

由于 Breakpad 依赖于 LSS,所以还需要下载它(地址:https://github.com/adelshokhy112/linux-syscall-support);

转载并编辑此文时,我使用的是gitee的de-user/breakpad, LSS使用的是abuyoyo的linux_syscall_support.h,可以直接去搜他们的gitee。这个版本在 GNClinux gcc v4.8.4和arm-linux gcc v9.2.1 都可以编译。如果arm编译器过于老旧需要往前翻旧版本breakpad和改函数mmap mmap2

将 LSS 中的 linux_syscall_support.h 文件放至 breakpad/src/third_party/lss/ 目录下。

编译 Breakpad,步骤非常简单:

$ cd breakpad
$ ./configure && make
$ make
$ sudo make install

如果需要交叉编译,则编写一个make.sh去配置configure, 生成对应的Makefile后直接make即可,make install后会安装到 --prefix=YOUPATH 处

  1 #!/bin/bash
  2
  3 ROOT=$(pwd)
  4
  5 rm -rf $ROOT/_NEED
  6 mkdir $ROOT/_NEED
  7 ./configure \
  8 --prefix=$ROOT/_NEED \
  9 --exec-prefix=$ROOT/_NEED \
 10 --host=arm-linux \
 11 CC=/home/os07/device_platform/8368u_v08_4/build/tools/arm-9.2_eabihf/bin/arm-none-linux-gnueabihf-gcc \
 12 CXX=/home/os07/device_platform/8368u_v08_4/build/tools/arm-9.2_eabihf/bin/arm-none-linux-gnueabihf-g++
 13 CFLAGS=std=c++11
 14 CXXFLAGS=std=c++11

如果你指定了prefix,会安装到你指定的目录,并在该目录的/bin生成 dump_symsminidump_stackwalk 等程序, include 和 lib有整合进你的主程序时需要的库和头文件

整合进你的程序

如果你在上一步没有指定–prefix,那命令和include会安装到默认 /usr/local处。要整合进你的程序,需要把对应的include 和lib 路径包含进你的编译脚本,并增加编译选项-lbreakpad-lbreakpad_client
这里为了方便测试,编写一个小的测试程序去测试效果:

#include "client/linux/handler/exception_handler.h"// 在breakpad编译输出的/include/breakpad/下可以找到这个文件
#include <iostream>

//当崩溃发生的时候,会回调这个函数
static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
                         void* context,
                         bool succeeded)
{
    std::cout << "Dump path:" << descriptor.path() << std::endl;

    return succeeded;
}

void crash(){
    int* a = nullptr;
    *a = 1;
}

int main(int argc, char* argv[])
{
    google_breakpad::MinidumpDescriptor descriptor("/tmp");
    google_breakpad::ExceptionHandler eh(descriptor, nullptr, dumpCallback, nullptr, true, -1);
    crash();

    return 0;

编译命令

arm-none-linux-gnueabihf-g++ -g -std=c++11 $ROOT/test.cpp \
-I/YOU_PATH/_NEED/include/breakpad \
-L/YOU_PATH/_NEED/lib -lbreakpad -lbreakpad_client -pthread -o test

编译运行这个程序,会在 /tmp/ 目录下生成一个 minidump 文件,并在退出之前打印该文件的路径。


生成可读的堆栈跟踪

阅读奇小葩文章的第3点,了解到还有另一种方法。这里根据上面的例子详细介绍第二种方法。

调试符号文件 .sym、崩溃文件 minidump、minidump_stackwalk,有这三样就可以打印堆栈信息。

  • 生成符号调试文件
    需要目标程序内包含调试信息(未执行strip之前),这样 dump_syms 工具才能从中解析出调试符号。
    假设,我们的程序名为 test,执行以下命令,便会生成一个名为 test.sym 的符号文件 (一定要是未strip的包含所有调试信息的程序文件)
$ dump_syms ./test > test.sym
  • 符号调试文件.sym需要放到固定目录下

这一步很关键,否则minidump_stackwalk无法打印正确的堆栈信息。
在这里插入图片描述
查看符号文件,第一行包含了生成目录结构所需的信息:

$ head -n1 test.sym 
MODULE Linux x86_64 95E20E34BE203CB093B675D606A7D7D20 test

创建以上目录结构,并将符号文件移动到该路径下:

$ mkdir -p ./symbols/test/95E20E34BE203CB093B675D606A7D7D20
$ mv test.sym ./symbols/test/95E20E34BE203CB093B675D606A7D7D20/
  • 生成堆栈跟踪信息
    当一切准备就绪,minidump_stackwalk 工具就派上用场了。
    只需将 mindump 文件和符号路径作为命令行参数传递给它,便能生成堆栈跟踪信息了。为了便于分析,可以将输出重定向至文件中,或stdout:
$ minidump_stackwalk /tmp/48596758-1d7a-4318-15edb4af-a9186ad7.dmp ./symbols > error.log

定位崩溃地点

打开上一步输出的error.log或stdout,搜索crashed,可以查看崩溃点
在这里插入图片描述
可以很清楚地看到,崩溃发生在 main.cpp 的第 16 行。
查看我们的测试程序,与预期结果一样:
在这里插入图片描述

将上述过程脚本化

方便崩溃发生即时在stdout输出,我的思路是直接在dumpCallback里保存error.log并输出stdout。
在我的程序中,当dumpCallback 被调用时,它会后台执行这个脚本:

  1 #!/bin/sh
  2
  3 if [ $# != 2 ] ; then
  4         echo "USAGE: $0 TARGET_NAME DMP_NAME OUTPUT_NAME"
  5         echo " e.g.: $0 _path/Launcher _path/48596758-xxx-xxx-xxxxx.dmp"
  6         exit 1;
  7 fi
  8
  9 #获取输入参数
 10 WR_PATH=/YOU_PATH #放.sym error.log 文件和symbols/目录的地方
 11 app_file_name=$1
 12 dmp_file_name=$2
 13 sym_file_name=Launcher# 我用了固定名称,只需要符合目录结构就可以解析。跟程序名称没关系
 14 #output_file_name=$3
 15
 16 getSymbol() {
 17     echo "@getSymbol: start get symbol"
 18     ./dump_syms ${app_file_name} >> ${sym_file_name}.sym
 19 }
 20
 21 getStackTrace() {
 25     #从第一行字符串中获取版本号
 26     version_number=$(head -n1 ${sym_file_name}.sym | cut -d ' ' -f 4)
 27     echo -e "\e[43;30;1m version=[${version_number}] \e[0m"
 28
 30     #创建特定的目录结构,并将符号文件移进去
 31     mkdir -p  ${WR_PATH}/symbols/${sym_file_name}/${version_number}
 32     mv ${sym_file_name}.sym  ${WR_PATH}/symbols/${sym_file_name}/${version_number}/
 33
 34
 35     #将堆栈跟踪信息重定向到文件中
 36     ./minidump_stackwalk ${dmp_file_name} ${WR_PATH}/symbols >> ${WR_PATH}/out.log
 37     cat ${WR_PATH}/out.log
 38 }
 39
 40 main() {
 41     getSymbol
 42     if [ $? == 0 ]
 43     then
 44         getStackTrace
 45     fi
 46 }
 47
 48 #运行main
 49 main

这个脚本执行的命令是

./printfTrack.sh   _Path/Launcher   _path/48596758-xxx-xxx-xxxxx.dmp

参考文章
https://blog.csdn.net/u012489236/article/details/107923924
https://blog.csdn.net/robert_cysy/article/details/105395799

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Breakpad 是一个开源的崩溃报告库,它可以捕获应用程序的崩溃信息,生成崩溃报告,以便于开发人员进行分析和修复。 要捕获 C++ 标准库的崩溃,需要使用 Breakpad 提供的异常处理机制。在 C++ 中,异常是通过 `try-catch` 语句块来处理的,当程序遇到异常时,会跳转到相应的 `catch` 块中进行处理。Breakpad 利用这个机制,在 `catch` 块中捕获异常并生成崩溃报告。 具体的实现步骤如下: 1. 在应用程序使用 Breakpad 库,将崩溃信息保存到指定的文件中。 2. 在程序使用 `try-catch` 语句块捕获异常。 3. 在 `catch` 块中,调用 Breakpad 提供的函数,将崩溃信息写入文件。 以下是一个使用 Breakpad 捕获 C++ 标准库崩溃的示例代码: ```c++ #include <stdexcept> #include "client/linux/handler/exception_handler.h" bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) { // 将崩溃信息写入文件 return succeeded; } int main() { // 创建 Breakpad 异常处理器 google_breakpad::MinidumpDescriptor descriptor("."); google_breakpad::ExceptionHandler eh(descriptor, NULL, DumpCallback, NULL, true, -1); try { // 在这里写下可能会抛出异常的代码 throw std::runtime_error("C++ 标准库崩溃"); } catch (...) { // 在 catch 块中调用 Breakpad 的 WriteMinidump 函数 eh.WriteMinidump(); } return 0; } ``` 在上面的代码中,`DumpCallback` 函数用于将崩溃信息写入文件,`main` 函数中创建了一个 Breakpad 异常处理器,并在 `try-catch` 语句块中捕获异常。当程序遇到异常时,会跳转到 `catch` 块中,调用 Breakpad 的 `WriteMinidump` 函数将崩溃信息写入文件。 需要注意的是,使用 Breakpad 捕获崩溃信息时,需要在编译时链接 Breakpad 库,并将 Breakpad 的头文件包含在程序中。同时,需要在程序中注册异常处理器,以便于在发生异常时能够捕获崩溃信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值