本文概述在 Linux 平臺(tái)的可執(zhí)行程序或動(dòng)態(tài)鏈接庫(kù)中使用 Breakpad 的方法。
構(gòu)建 Breakpad 庫(kù)
Breakpad 提供了一個(gè) Autotools 構(gòu)建系統(tǒng),它將構(gòu)建 Breakpad 的 Linux 客戶端庫(kù)和處理程序。通過 git 從 breakpad 下載 Breakpad 的源碼。然后在 Breakpad 的源碼目錄中運(yùn)行 ./configure && make,這將生成客戶端靜態(tài)庫(kù)文件 src/client/linux/libbreakpad_client.a,它包含了為應(yīng)用程序或動(dòng)態(tài)鏈接庫(kù)生成 minidumps 所需的所有代碼。構(gòu)建過程除了客戶端庫(kù)之外,還生成了必須的工具。
Google 原始的 Breakpad 源碼倉(cāng)庫(kù) 沒有包含 Linux syscall support 庫(kù),這在構(gòu)建 Breakpad 時(shí)會(huì)報(bào)出如下錯(cuò)誤:
~/data/opensource/breakpad$ make
depbase=`echo src/tools/linux/core2md/core2md.o | sed 's|[^/]*$|.deps/&|;s|\.o$||'`;\
g++ -DHAVE_CONFIG_H -I. -I./src -I./src -Wmissing-braces -Wnon-virtual-dtor -Woverloaded-virtual -Wreorder -Wsign-compare -Wunused-local-typedefs -Wunused-variable -Wvla -Werror -fPIC -g -O2 -MT src/tools/linux/core2md/core2md.o -MD -MP -MF $depbase.Tpo -c -o src/tools/linux/core2md/core2md.o src/tools/linux/core2md/core2md.cc &&\
mv -f $depbase.Tpo $depbase.Po
In file included from ./src/client/linux/dump_writer_common/thread_info.h:37,
from ./src/client/linux/minidump_writer/linux_dumper.h:54,
from ./src/client/linux/minidump_writer/minidump_writer.h:42,
from src/tools/linux/core2md/core2md.cc:34:
./src/common/memory_allocator.h:50:10: fatal error: third_party/lss/linux_syscall_support.h: 沒有那個(gè)文件或目錄
50 | #include "third_party/lss/linux_syscall_support.h"
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
make: *** [Makefile:5701:src/tools/linux/core2md/core2md.o] 錯(cuò)誤 1
Breakpad 這樣的調(diào)試診斷代碼有時(shí)不太方便訪問封裝的 C 庫(kù),但它依然會(huì)有訪問系統(tǒng)功能的需要,如打開文件,讀寫文件等,此時(shí)它會(huì)通過系統(tǒng)調(diào)用直接訪問系統(tǒng)功能。Linux syscall support 庫(kù)為直接通過系統(tǒng)調(diào)用訪問系統(tǒng)功能提供支持。Linux syscall support 庫(kù)的源碼倉(cāng)庫(kù)地址為 https://chromium.googlesource.com/linux-syscall-support.git。筆者在 GitHub 上的 Breakpad fork 倉(cāng)庫(kù) breakpad 已經(jīng)包含了 Linux syscall support 庫(kù),無需做其它什么事情,即可直接編譯通過。
把 Breakpad 集成進(jìn)你的程序
首先,配置你的構(gòu)建過程把 libbreakpad_client.a 的目錄地址添加到鏈接庫(kù)文件的搜索路徑,同時(shí)為鏈接添加 breakpad_client 庫(kù),把 libbreakpad_client.a 鏈接進(jìn)你的二進(jìn)制文件,然后設(shè)置 include 路徑包含 google-breakpad 源碼樹中的 ** src** 目錄。breakpad_client 庫(kù)依賴 pthread 庫(kù),因而也需要添加對(duì) pthread 庫(kù)的依賴。接下來,包含異常處理器的頭文件:
#include "client/linux/handler/exception_handler.h"
現(xiàn)在你可以實(shí)例化一個(gè) ExceptionHandler 對(duì)象。ExceptionHandler 對(duì)象的整個(gè)生命周期中異常處理都是激活的,因此你應(yīng)該在你的程序啟動(dòng)過程中,盡可能早實(shí)例化它,并使其盡可能接近關(guān)閉狀態(tài)一直保持激活。要做任何有用的事情,ExceptionHandler 構(gòu)造函數(shù)都需要一個(gè)可以寫入 minidump 的路徑,以及一個(gè)回調(diào)函數(shù)來接收有關(guān)已寫入的 minidump 的信息:
#include <stdlib.h>
#include <unistd.h>
#include "src/client/linux/handler/exception_handler.h"
#include "src/common/linux/linux_libc_support.h"
#include "src/third_party/lss/linux_syscall_support.h"
static bool DumpCallback(const google_breakpad::MinidumpDescriptor &descriptor,
void *context, bool success) {
if (!success) {
static const char msg[] = "Failed to write minidump\n";
sys_write(2, msg, sizeof(msg) - 1);
return false;
}
static const char msg[] = "Wrote minidump: ";
sys_write(2, msg, sizeof(msg) - 1);
sys_write(2, descriptor.path(), strlen(descriptor.path()));
sys_write(2, "\n", 1);
return true;
}
static void DoSomethingWhichCrashes() {
int local_var = 1;
*reinterpret_cast<volatile char*>(NULL) = 1;
}
int main(int argc, const char *argv[]) {
google_breakpad::MinidumpDescriptor minidump(".");
google_breakpad::ExceptionHandler breakpad(minidump, NULL, DumpCallback, NULL,
true, -1);
DoSomethingWhichCrashes();
return 0;
}
編譯并運(yùn)行這個(gè)示例程序,應(yīng)該會(huì)在當(dāng)前目錄下生成一個(gè) minidump 文件,而且它應(yīng)該會(huì)在退出之前打印出 minidump 文件的文件名。你可以在 exception_handler.h 的源文件中讀到更多關(guān)于 ExceptionHandler 構(gòu)造函數(shù)的其它參數(shù)的內(nèi)容。
注意:你應(yīng)該在回調(diào)函數(shù)中執(zhí)行盡可能少的操作。你的程序已經(jīng)處于了不安全的狀態(tài)。分配內(nèi)存或調(diào)用其它共享庫(kù)的函數(shù)可能是不安全的。最安全的做法是 fork 并 exec 一個(gè)新的進(jìn)程來完成你需要做的任何工作。如果你必須要在回調(diào)中做些事情,Breakpad 源碼包含了 一些 libc 函數(shù)的簡(jiǎn)單重實(shí)現(xiàn),來避免直接調(diào)用 libc,以及 一個(gè)執(zhí)行 Linux 系統(tǒng)調(diào)用的頭文件(在 src/third_party/lss 中)以避免調(diào)用其它共享庫(kù)。
發(fā)送 minidump 文件
在實(shí)際的應(yīng)用中,你將想要以某種方式處理 minidump,比如把它發(fā)送到服務(wù)器以做分析。Breakpad 源碼樹包含了一些可能有點(diǎn)用的 HTTP 上傳源碼,以及一個(gè) minidump 上傳工具。
為你的程序生成符號(hào)
為了生成有用的棧追蹤,Breakpad 需要你把你的二進(jìn)制中的調(diào)試符號(hào)轉(zhuǎn)為 文本格式的符號(hào)文件。首先,確保你在編譯你的二進(jìn)制時(shí)帶了 -g 參數(shù)以包含調(diào)試符號(hào)。接下來,通過在 Breakpad 源碼目錄下運(yùn)行 configure && make 編譯 dump_syms 工具 。接下來,對(duì)你的二進(jìn)制文件運(yùn)行 dump_syms 以生成文本格式的符號(hào)。比如,如果你的主二進(jìn)制名稱為 HelloBreakpad:
$ breakpad/src/tools/linux/dump_syms/dump_syms ./HelloBreakpad > HelloBreakpad.sym
為了通過 minidump_stackwalk 工具使用這些符號(hào),你將需要把它們放在一個(gè)特定的目錄結(jié)構(gòu)下。符號(hào)文件的第一行包含了你生成這種目錄結(jié)構(gòu)所需的信息,比如(你的輸出可能不太一樣):
~/workspace/HelloBreakpad/Debug$ head -n5 HelloBreakpad.sym
MODULE Linux x86_64 467289A80132830FD70182B041DC6F960 HelloBreakpad
INFO CODE_ID A889724632010F83D70182B041DC6F96F093FBF2
FILE 0 /home/hanpfei/data/opensource/breakpad/./src/client/linux/handler/exception_handler.h
FILE 1 /home/hanpfei/data/opensource/breakpad/./src/client/linux/handler/microdump_extra_info.h
FILE 2 /home/hanpfei/data/opensource/breakpad/./src/client/linux/handler/minidump_descriptor.h
~/workspace/HelloBreakpad/Debug$ mkdir -p symbols/HelloBreakpad/467289A80132830FD70182B041DC6F960
~/workspace/HelloBreakpad/Debug$ mv HelloBreakpad.sym symbols/HelloBreakpad/467289A80132830FD70182B041DC6F960/
你也可以使用 Mozilla 代碼倉(cāng)庫(kù)中的 symbolstore.py 腳本,它封裝了這些步驟。
處理 minidump 生成棧追蹤
Breakpad 包含了一個(gè)稱為 minidump_stackwalk 的工具,它接收一個(gè) minidump 及它對(duì)應(yīng)的文本格式的符號(hào),生成一個(gè)符號(hào)化的棧追蹤。如果你按照上面的指示編譯了 Breakpad 源碼,它應(yīng)該位于 breakpad/src/processor 目錄下。簡(jiǎn)單地把 minidump 和符號(hào)路徑作為命令行參數(shù)傳入:
~/workspace/HelloBreakpad/Debug$ minidump_stackwalk efebac08-3b27-40b1-456cd9b4-33b33260.dmp symbols/
Operating system: Linux
0.0.0 Linux 5.13.0-40-generic #45~20.04.1-Ubuntu SMP Mon Apr 4 09:38:31 UTC 2022 x86_64
CPU: amd64
family 6 model 158 stepping 10
1 CPU
GPU: UNKNOWN
Crash reason: SIGSEGV /SEGV_MAPERR
Crash address: 0x0
Process uptime: not available
Thread 0 (crashed)
0 HelloBreakpad!DoSomethingWhichCrashes [HelloBreakpad.cpp : 28 + 0x0]
rax = 0x0000000000000000 rdx = 0x0000000000000000
rcx = 0x00005586cc34bf80 rbx = 0x00005586cb3276d0
rsi = 0x0000000000000000 rdi = 0x00005586cb32f6c0
rbp = 0x00007fffc54a2b50 rsp = 0x00007fffc54a2b50
r8 = 0x0000000000000000 r9 = 0x00005586cc34bf78
r10 = 0x0000000000000008 r11 = 0x00007f3735f21be0
r12 = 0x00005586cb314df0 r13 = 0x00007fffc54a2e00
r14 = 0x0000000000000000 r15 = 0x0000000000000000
rip = 0x00005586cb314ff7
Found by: given as instruction pointer in context
1 HelloBreakpad!main [HelloBreakpad.cpp : 35 + 0x5]
rbx = 0x00005586cb3276d0 rbp = 0x00007fffc54a2d10
rsp = 0x00007fffc54a2b60 r12 = 0x00005586cb314df0
r13 = 0x00007fffc54a2e00 r14 = 0x0000000000000000
r15 = 0x0000000000000000 rip = 0x00005586cb3150c9
Found by: call frame info
2 libc.so.6 + 0x240b3
rbx = 0x00005586cb3276d0 rbp = 0x0000000000000000
rsp = 0x00007fffc54a2d20 r12 = 0x00005586cb314df0
r13 = 0x00007fffc54a2e00 r14 = 0x0000000000000000
r15 = 0x0000000000000000 rip = 0x00007f3735d590b3
Found by: call frame info
3 HelloBreakpad!DoSomethingWhichCrashes [HelloBreakpad.cpp : 29 + 0x3]
rsp = 0x00007fffc54a2d40 rip = 0x00005586cb314ffd
Found by: stack scanning
4 HelloBreakpad + 0x156d0
rbp = 0x00005586cb314ffd rsp = 0x00007fffc54a2d48
rip = 0x00005586cb3276d0
Found by: call frame info
5 HelloBreakpad!_start + 0x2e
rsp = 0x00007fffc54a2df0 rip = 0x00005586cb314e1e
Found by: stack scanning
6 0x7fffc54a2df8
rsp = 0x00007fffc54a2df8 rip = 0x00007fffc54a2df8
Found by: call frame info
Loaded modules:
0x5586cb312000 - 0x5586cb327fff HelloBreakpad ??? (main)
0x7f3735be6000 - 0x7f3735c99fff libm.so.6 ???
0x7f3735d35000 - 0x7f3735ecefff libc.so.6 ??? (WARNING: No symbols, libc.so.6, E774DB9F17B26CD093172A8243F8547F0)
0x7f3735f27000 - 0x7f3735f3bfff libgcc_s.so.1 ???
0x7f3735f42000 - 0x7f37360c8fff libstdc++.so.6 ???
0x7f3736124000 - 0x7f373613afff libpthread.so.0 ???
0x7f3736161000 - 0x7f3736184fff ld-linux-x86-64.so.2 ???
0x7fffc54e3000 - 0x7fffc54e4fff linux-gate.so ???
它在 stderr 上產(chǎn)生詳細(xì)輸出,在 stdout 上產(chǎn)生堆棧跟蹤,因此你可能需要重定向 stderr。
參考文檔