前言
Breakpad是一组实现崩溃报告系统的客户机和服务器组件。我们可以在windows、linux、mac os上使用它生成dump文件。
放一张 Breakpad 官方的架构图:
Google Breakpad 源码解析中写到,Breakpad 主要由三个部分组成:
- Client,当端上发生崩溃时,会默认生成 minidump 文件。
- Symbol Dumper,这个工具用于生成 Breakpad 专属的符号表,要作用在带有调试信息原始库才行。
- Processor,这个工具通过读取 Client 生成的 minidump 文件,再去匹配 Symbol Dumper 生成的对应符号表,最后生成人类可读的 C/C++ 堆栈跟踪。
关于client部分,我读了下官方文档的client_design。它的一般使用过程是:1)将client库链接到需要监测崩溃的程序。2)给监测程序安装一个异常处理程序,并提供回调函数,生成minidump文件… 安装异常处理程序因操作系统而异。Linux上通过信号的传递获知异常。
知道一个大概过程后,我们做些实验,来简单了解下这个工具的使用。
Linux上breakpad使用
下面,我按照这个代码的组织结构,尝试使用breakpad。
注:下面所有目录在/mnt/data/others
路径下。每个人的路径是各种各样的。
# 按照下面结构组织代码
.
├── breakpad #源码
├── breakpad_output #breakpad经过编译后生成的头文件和库
├── breakpad_test #自己的代码,用于测试breakpad的使用
├── depot_tools #拉取breakpad源码的工具
└── doc #文档
编译breakpad
如果下载breakpad
的源码后,直接进行编译,会报一些错误,处理过程可参考:breakpad简单使用。或者可以尝试这个仓库代码,当它可能比较旧,也不可信:google-breakpad可编译通过的原始代码
我没有尝试上面的过程。我参考google/breakpad-README,使用depot_tools,进行代码拉取(这对网络有些要求)。
# depot_tools目录
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH=/mnt/data/others/depot_tools/:$PATH
# breakpad目录
mkdir breakpad && cd breakpad
fetch breakpad
cd src
./configure && make # 先测试下能否编过
# 安装到指定的breakpad_output目录
./configure --prefix=/mnt/data/others/breakpad_output
make install
我们看下编译生成的头文件和库。
➜ breakpad_output tree -L 2
.
├── bin
│ ├── core2md
│ ├── dump_syms
│ ├── dump_syms_mac
│ ├── microdump_stackwalk
│ ├── minidump-2-core
│ ├── minidump_dump
│ ├── minidump_stackwalk
│ ├── minidump_upload
│ ├── pid2md
│ └── sym_upload
├── include
│ └── breakpad
├── lib
│ ├── libbreakpad.a
│ ├── libbreakpad_client.a
│ └── pkgconfig
├── libexec
│ └── core_handler
└── share
└── doc
项目中集成breakpad
breakpad编译完之后,我们看下,如何将其集成到现有的项目中。参考链接:linux_starter_guide.md、breakpad崩溃日志收集
C++没有包管理器,使用一个外部库,需要添加头文件和库的搜索路径,对库进行链接。breakpad亦是如此。
我们使用的测试用例,是链接中提供的demo。
#include "client/linux/handler/exception_handler.h"
static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
void* context, bool succeeded) {
printf("Dump path: %s\n", descriptor.path());
return succeeded;
}
void crash() { volatile int* a = (int*)(NULL); *a = 1; }
int main(int argc, char* argv[]) {
google_breakpad::MinidumpDescriptor descriptor("./");
google_breakpad::ExceptionHandler eh(descriptor, NULL, dumpCallback, NULL, true, -1);
crash();
return 0;
}
上面代码中,我们创建了一个ExceptionHandler
实例。其生命周期,即是可以处理的崩溃的时期。所以,我们应该尽早的创建,尽量晚的销毁这个对象。ExceptionHandler
有多个参数。第一个参数指定写入minidumps的位置;第三个参数是个回掉函数,用于接收关于写入的迷你转储的信息。
更多信息见本节的参考链接,如:ExceptionHandler
更多的参数信息;通过http发送minidumps信息;
接下来,我们在CMakeLists.txt中,指定头文件和库的位置,并链接库。(如果make install
在标准路径中安装了breakpad,可以使用pkg-config来找到具体安装位置和链接库。)
cmake_minimum_required(VERSION 3.11)
project("breakpad_test")
include_directories(${CMAKE_SOURCE_DIR}/../breakpad_output/include/breakpad)
link_directories(${CMAKE_SOURCE_DIR}/../breakpad_output/lib)
message(STATUS "breakpad header path: ${CMAKE_SOURCE_DIR}/../breakpad_output/include/breakpad")
add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} breakpad_client pthread)
编译debug版本的程序
从二进制文件的debugging symbols中生成text-format symbol files是个比较“正常“的过程。(后面,我们也尝试下从release的二进制文件中生成text-format symbol files,看下是什么现象。不了解原理,就只好多尝试了。)
# breakpad_test目录
# 编译debug版本
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=release ..
make
# 根据二进制程序的调试信息,导出 text-format symbol files
../../breakpad_output/bin/dump_syms breakpad_test > breakpad_test.sym
# 为了在minidump_stackwalk中使用上面生成的symbol file, 我们需要将其放在特定的目录下。
head -n1 breakpad_test.sym
MODULE Linux x86_64 2CE6C807FC66681B9855B67570D894310 breakpad_test
mkdir -p ./symbols/breakpad_test/2CE6C807FC66681B9855B67570D894310
mv breakpad_test.sym ./symbols/breakpad_test/2CE6C807FC66681B9855B67570D894310
# ----------------------------------------------#
# 上面这个过程可能有点麻烦。可以使用[symbolstore.py](https://searchfox.org/mozilla-central/source/toolkit/crashreporter/tools/symbolstore.py)。
# 这个脚本对版本有要求,3.7.3上mozbuild不满足安装条件
# pip3 install buildconfig pyyaml mozbuild
# symbolstore.py <params> <dump_syms path> <symbol store path> <debug info files or dirs>
接下来,我们运行程序,触发崩溃。在运行之前,我们去除程序的符号信息。这里使用strip
命令,或者重新编译下release版本也行。
# 去除符号信息
strip breakpad_test
# 产生minidump文件
./breakpad_test
Dump path: .//4d890aed-5948-46dd-18df4bb4-4bbe01bb.dmp
[1] 13091 segmentation fault ./breakpad_test
接下来,我们查看下这个dump文件。
方法一,将这个minidunp转换成coredump,然后使用gdb。此时,不再需要上面的symbol file。按照coredump进行调试即可。
# minidunp转换成coredump
../../breakpad_output/bin/minidump-2-core 4d890aed-5948-46dd-18df4bb4-4bbe01bb.dmp > core
# 由于此时的breakpad_test没有符号信息,看不出更详细的内容
gdb breakpad_test core
Core was generated by `./breakpad_test'.
Program terminated with signal SIGSEGV, Segmentation fault.
# 重新编译一份breakpad_test的debug版本,也可以看到错误信息
Core was generated by `./breakpad_test'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x0000000000402bab in crash () at /mnt/data/others/breakpad_test/main.cpp:9
9 void crash() { volatile int* a = (int*)(NULL); *a = 1; }
因为breakpad是跨平台的库,所以上面过程可行。下面我们看下通用(跨平台)的做法。
# 从minidump中生成stack trace
../../breakpad_output/bin/minidump_stackwalk 4d890aed-5948-46dd-18df4bb4-4bbe01bb.dmp ./symbols > stack_trace.txt 2>&1
# 查看下堆栈信息
.....
Crash reason: SIGSEGV /SEGV_MAPERR
Crash address: 0x0
Process uptime: not available
Thread 0 (crashed)
0 breakpad_test!crash() [main.cpp : 9 + 0x4]
.....
1 breakpad_test!main [main.cpp : 14 + 0x5]
....
编译release版本的程序
对release版本的程序,重复上面过程。无法得到有效的信息。
cmake -DCMAKE_BUILD_TYPE=Release ..
make
../../breakpad_output/bin/dump_syms breakpad_test > breakpad_test.sym
head -n1 breakpad_test.sym
mkdir -p ./symbols/breakpad_test/D9F881C0A5EA59DF847F6AA6317737090
mv breakpad_test.sym ./symbols/breakpad_test/D9F881C0A5EA59DF847F6AA6317737090
./breakpad_test
Dump path: .//70067b88-cc34-4008-b018be8b-b5841440.dmp
[2] 30645 segmentation fault ./breakpad_test
../../breakpad_output/bin/minidump_stackwalk 70067b88-cc34-4008-b018be8b-b5841440.dmp ./symbols > stack_trace.txt 2>&1
# 可以看到,无法得到有效的信息
.....
Crash reason: SIGSEGV /SEGV_MAPERR
Crash address: 0x0
Process uptime: not available
....
Thread 0 (crashed)
0 breakpad_test!main + 0xe2
...
1 libc.so.6 + 0x2409b
windows上breakpad的使用
PS: 下面过程,我没实验过。
windows上和Linux上程序的调试信息,在存储上有所区别。Linux上调试信息和二进制文件绑定在一起。
windows可以编译生成release版本的时候,将调试信息存储在pdb文件中,需要在编译的时候加上/Zi参数
使用VS进行编译,可以参考:VS2015 让Release程序生成pdb文件
使用cmake,可以参考:how to generate pdb files for Release build with cmake flags?、CMAKE_BUILD_TYPE == Release should set /Zi (or /Z7) for MSVC++