本文基于转载文章编写。如侵权请告知,将立马删除
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_syms
、minidump_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