首頁 > 科技

學會這個絕招,讓 C++ 崩潰無處可逃!

2021-06-17 03:05:45

作者 | Waleon

來源 | 高效程式設計師

Breakpad 是 Google 用 C++ 編寫的一個開源、跨平臺的崩潰報告系統,它支援 Windows、Linux 和 macOS,並提供了一個上傳器,可以在程序崩潰時向一個配置好的 URL 提交 minidump 檔案。

目前,有很多大型項目都在使用 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

工作原理

BreakPad 工作原理:

其中,包含了三個主要元件:

  • Breakpad client:是一個庫(即:libbreakpad_client.a),將來要整合到我們的程式中。用於寫 minidump 檔案,捕獲當前執行緒的狀態,以及可執行檔案/共享庫的標識。

  • Breakpad 符號轉儲工具:是一個程式(即:dump_syms),用於讀取由編譯器產生的偵錯資訊,並以 Breakpad 自己的格式生成一個符號檔案。

  • Breakpad minidump 處理器:是一個程式(即:minidump_stackwalk),用於讀取 minidump 檔案和符號檔案,並生成一個可讀的 C/C++ 堆棧跟蹤。

編譯安裝

  1. 下載 Breakpad 源碼;

  2. 由於 Breakpad 依賴於 LSS,所以還需要下載它(地址:https://github.com/adelshokhy112/linux-syscall-support);

  3. 將 LSS 中的 linux_syscall_support.h 檔案放至breakpad/src/third_party/lss/ 目錄下;

  4. 編譯 Breakpad,步驟非常簡單:

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

成功之後,會生成 libbreakpad_client.a 庫檔案,以及dump_syms、minidump_stackwalk 等程式。

將 Breakpad 整合到程式中

和其他第三方庫的用法一樣,要將 Breakpad 整合到程式中,先要連結生成的庫 libbreakpad_client.a,並設定包含路徑為breakpad/src/,接下來就可以 include 標頭檔案了。

為了生成 mindump 檔案,我們需要例項化一個ExceptionHandler 物件,並提供一個儲存 mindump 的路徑,以及一個回撥函數來接收關於已寫入的 mindump 的資訊:

#include "client/linux/handler/exception_handler.h"
#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;
}

編譯運行這個程式,會在 /tmp/ 目錄下生成一個 minidump 檔案,並在退出之前列印該檔案的路徑。

注意:在使用 Breakpad 時,需要目標程式內包含偵錯資訊,這樣dump_syms 工具才能從中解析出偵錯符號。而在預設情況下,Release 模式編譯的程式是並不包含偵錯資訊,若想包含,需要在編譯程式時新增 -g 選項。

生成可讀的堆棧跟蹤

帶偵錯資訊的目標程式有了,minidump 檔案有了,dump_syms 和minidump_stackwalk 等工具也有了,現在要做的就是生成有用的堆棧跟蹤資訊了!

1. 生成符號檔案

Breakpad 要求我們將目標程式中的偵錯符號轉換為文字格式的符號檔案,這一步需要通過 dump_syms 工具來完成。

假設,我們的程式名為 test,執行以下命令,便會生成一個名為 test.sym 的符號檔案:

$ dump_syms ./test > test.sym

2. 將符號檔案放在特定目錄下

為了使用這些符號,需要將生成的符號檔案放在特定的目錄結構中:

檢視符號檔案,第一行包含了生成目錄結構所需的資訊:

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

創建以上目錄結構,並將符號檔案移動到該路徑下:

$ mkdir -p ./symbols/test/95E20E34BE203CB093B675D606A7D7D20
$ mv test.sym ./symbols/test/95E20E34BE203CB093B675D606A7D7D20/

3. 生成堆棧跟蹤資訊

當一切準備就緒,minidump_stackwalk 工具就派上用場了。

只需將 mindump 檔案和符號路徑作為命令列參數傳遞給它,便能生成堆棧跟蹤資訊了。為了便於分析,可以將輸出重定向至檔案中:

$ minidump_stackwalk /tmp/48596758-1d7a-4318-15edb4af-a9186ad7.dmp ./symbols > error.log

定位 Crash 所在位置

開啟 error.log,搜尋關鍵字「crashed」,一般與它最接近的一行,就是發生崩潰時程式的堆棧資訊:

可以很清楚地看到,崩潰發生在 main.cpp 的第 16 行。

檢視我們的測試程式,與預期結果一樣:

至此,Crash 所在位置已經準確地定位到了,趕緊抓緊時間修改吧!

將上述過程指令碼化

由於以上過程會經常執行,可以將其做成自動化指令碼,不僅可有效防止步驟命令的遺忘,而且極大的加快了操作速度。

新建一個指令碼 dumptool.sh,內容如下:

#!/bin/bash

if [ $# != 3 ] ; then
echo "USAGE: $0 TARGET_NAME DMP_NAME OUTPUT_NAME"
echo " e.g.: $0 test 48596758-1d7a-4318-15edb4af-a9186ad7.dmp error.log"
exit 1;
fi

#獲取輸入參數
target_file_name=$1
dmp_file_name=$2
output_file_name=$3

getSymbol() {
echo "@getSymbol: start get symbol"
dump_syms ./$target_file_name > $target_file_name'.sym'
}

getStackTrace() {
echo "@getStackTrace: start get StackTrace"
sym_file_name=$target_file_name'.sym'

#獲取符號檔案中的第一行
line1=`head -n1 $sym_file_name`

#從第一行字元串中獲取版本號
OIFS=$IFS; IFS=" "; set -- $line1; aa=$1;bb=$2;cc=$3;dd=$4; IFS=$OIFS

version_number=$dd

#創建特定的目錄結構,並將符號檔案移進去
mkdir -p ./symbols/$target_file_name/$version_number
mv $sym_file_name ./symbols/$target_file_name/$version_number

#將堆棧跟蹤資訊重定向到檔案中
minidump_stackwalk $dmp_file_name ./symbols > $output_file_name
}

main() {
getSymbol
if [ $? == 0 ]
then
getStackTrace
fi
}

#運行main
main

使用比較簡單,先為指令碼增加可執行許可權:

$ chmod +x ./dump_tool.sh

然後執行下述命令,將二進位制檔案、minidump 檔案、輸出日誌檔案作為參數傳遞給它:

$ ./dumptool.sh ./test /tmp/48596758-1d7a-4318-15edb4af-a9186ad7.dmp error.log

完成之後,所有的堆棧跟蹤資訊就會被輸出到 error.log 檔案中了。

很好用吧,學會這個絕招,讓 C++ 崩潰無處可逃!


IT145.com E-mail:sddin#qq.com