linux, windows, mac 的c/c++程序使用 breakpad 來進行崩潰匯報


crashpad是一個支持mac和windows的崩潰報告庫,google還有一個breakpad,已經不建議使用了。編譯 crashpad 只能用 gn 來生成 ninja 文件,gn 的下載方法: git clone https://gn.googlesource.com/gn

因此,編譯crashpad同時需要gn和ninja。  ninja的下載地址:  https://github.com/ninja-build/ninja

crashpad編譯麻煩,直接使用breakpad吧。

 

 

breakpad的代碼見:

https://github.com/google/breakpad

Breakpad是谷歌開源的一個跨平台崩潰處理框架,內含崩潰轉儲、上報、分析一套工作流程框架。

主要的工作流程為:client以library的方式嵌入自己的程序,並設置handler,將會在程序崩潰時將會把一系列的線程列表、調用堆棧和一些系統信息寫入minidump文件。 得到minidump文件后,分析minidump文件可以使用dump_syms將編譯器生成的含調試信息的可執行文件生成符號文件,然后再使用minidump_walker生成可以閱讀的stack trace。

 

編譯:

1、下載breakpad的代碼;

2、克隆 https://chromium.googlesource.com/linux-syscall-support 

3、將 linux-syscall-support 里面的 linux_syscall_support.h 頭文件放到 breakpad/src/third_party/lss/ 目錄下。

4、必須用支持 c++11 的編譯器來編譯breakpad,生成 libbreakpad.a  libbreakpad_client.a 庫及一些分析用的 tool,主要使用 dump_syms 和 minidump_stackwalk 兩個工具來分析崩潰報告文件。

 

 

崩潰分析過程:

參考: https://chromium.googlesource.com/breakpad/breakpad/+/master/docs/linux_starter_guide.md

示例代碼如下(a.cpp):

#include <unistd.h>
#include <thread>
#include <iostream>
#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; }

void task1(string msg)
{
    std::cout << "task1 says: " << msg << std::endl;
    crash();
}

int main(int argc, char* argv[]) {
  google_breakpad::MinidumpDescriptor descriptor("/tmp/hzh");
  google_breakpad::ExceptionHandler eh(descriptor, NULL, dumpCallback, NULL, true, -1);
  std::thread t1(task1, "Hello");
  t1.detach();
  sleep(2);
  return 0;
}

 

編譯,必須用 -g:

$ g++ -g a.cpp -I/home/hzh/soft/softy/breakpad/include/breakpad -L/home/hzh/soft/softy/breakpad/lib -lbreakpad -lbreakpad_client -pthread -o test

 

1,運行test,會崩潰並產生 179cac63-2e41-4de0-09e8b58c-56069f80.dmp 文件。

2,從可執行程序生成符號表:

$ /home/hzh/soft/softy/breakpad/bin/dump_syms test >> test.sym

3,建立一個目錄結構,目錄名必須為“可執行程序的名字”,然后再該目錄里面建立一個目錄,名字為 test.sym 的第一行的某個數據,具體如下:

$ head -n1 test.sym

得到:  MODULE Linux x86_64 A35260606902350047A2A3559926FE410 test  ,我們就要  A35260606902350047A2A3559926FE410  作為目錄名。

$  mkdir -p ./symbols/test/A35260606902350047A2A3559926FE410

4,將 test.sym 移動到目錄里:

$  mv test.sym symbols/test/A35260606902350047A2A3559926FE410/

5,開始分析:

$  /home/hzh/soft/softy/breakpad/bin/minidump_stackwalk 179cac63-2e41-4de0-09e8b58c-56069f80.dmp ./symbols

 

 

 

在qt中直接使用 breakpad 原則上可行的,但是這樣breakpad的調用在每個平台上代碼會有些差異,因此可以使用打包過的 QBreakpad 來實現在每個平台上代碼都一樣,代碼見:

https://github.com/buzzySmile/qBreakpad

 

 

 

 

 

-------------------------------------------------------------

windows 下怎么使用:

什么是gyp 
GYP(Generate Your Projects)是由 Chromium 團隊開發的跨平台自動化項目構建工具,Chromium 便是通過 GYP 進行項目構建管理。

獲取gyp

git clone https://chromium.googlesource.com/external/gyp

安裝gyp

cd gyp
python setup.py install

然后,拷貝gyp文件夾到breakpad\src\tools文件夾下

然后,生成Breakpad的sln文件,步驟 :
進入剛剛拷貝的gyp目錄,然后執行: 

gyp.bat --no-circular-check  "../../client/windows/breakpad_client.gyp"

程序輸出為:

$ gyp.bat --no-circular-check "../../client/windows/breakpad_client.gyp"
Warning: Missing input files:
..\..\client\windows\unittests\..\..\..\testing\src\gmock-all.cc
..\..\client\windows\unittests\..\..\..\testing\gtest\src\gtest-all.cc
..\..\client\windows\unittests\..\..\..\testing\src\gmock_main.cc

 

這里要注意,一定不能使用絕對路徑,要使用相對路徑,所以為什么要拷貝gyp文件夾到tools文件夾下面。

使用vs2015編譯 
剛才我看看到了提示,missing幾個文件,所以我們這里不能編譯unittest下的兩個工程,暫時不理會 

編譯后,在debug文件夾下會生成:

.
├── common.lib
├── crash_generation_client.lib
├── crash_generation_server.lib
├── crash_report_sender.lib
├── exception_handler.lib
└── processor_bits.lib

 

然后,使用Breakpad生成dump文件得步驟:
把之前生成的幾個lib,包含進來
common.lib
exception_handler.lib
crash_generation_server.lib
crash_generation_client.lib

頭文件目錄導進來:

breakpad/src/client
breakpad/src/client/windows/common
breakpad/src/client/windows/crash_generation
breakpad/src/client/windows/handler

編寫測試代碼:

#include <cstdio>  
#include "client/windows/handler/exception_handler.h"  
 
namespace {
 
  static bool callback(const wchar_t *dump_path, const wchar_t *id,
    void *context, EXCEPTION_POINTERS *exinfo,
    MDRawAssertionInfo *assertion,
    bool succeeded) {
    if (succeeded) {
      printf("dump guid is %ws\n", id);
    }
    else {
      printf("dump failed\n");
    }
    fflush(stdout);
 
    return succeeded;
  }
 
  static void CrashFunction() {
    int *i = reinterpret_cast<int*>(0x45);
    *i = 5;  // crash!  
  }
 
}  // namespace  
 
int main(int argc, char **argv) {
  google_breakpad::ExceptionHandler eh(
    L".", NULL, callback, NULL,
    google_breakpad::ExceptionHandler::HANDLER_ALL);
  CrashFunction();
  printf("did not crash?\n");
  return 0;
}

編譯可能出現的錯誤:
common.lib(guid_string.obj) : error LNK2038: 檢測到“RuntimeLibrary”的不匹配項: 值“MTd_StaticDebug”不匹配值“MDd_DynamicDebug”(main.obj 中)

解決方法:
就是編譯庫的時候 和現在使用庫的工程 選擇的代碼生成方式不一致:

在工程屬性頁里,選擇:  代碼生成 -> 運行庫,     將運行庫改成“多線程調試DLL(/MDd)

 

 

如何根據生成的dump定位錯誤代碼?
文件->打開->文件,找到剛生成的dump文件,然后點擊“使用僅限本機進行調試”

 

 

windows 下使用 git 和 breakpad 將可執行文件對應到代碼版本庫以及使用breakpad 將崩潰日志和現場保存起來的比較好的方法:

首先使用git的hook將每次commit和merge的hash版本自動給代碼打上版本,方法如下:

添加兩個git的hook,hook的文件內容一模一樣,名字分別為 post-commit 和 post-merge:    (注意,這里沒有考慮 git reset 和 git revert 回退版本帶來的影響,這兩個命令不常用,因此沒考慮它們的解決方案)

#!/bin/bash
commit_short_hash=$(git log -1 --pretty=%h)      # c7618bf
branch_simple=$(git symbolic-ref HEAD 2>/dev/null | cut -d"/" -f 3)   # master

echo "--------"
echo "${branch_simple}_${commit_short_hash}"
echo "--------\n"

versionfilename=$(git config hooks.versionfilename)
if [[ -z $versionfilename ]]
then
  versionfilename="./YCAISecurity/version_git.h"
fi

echo -n "static std::string version_hash=\"${branch_simple}_${commit_short_hash}\";" > $versionfilename

就是在commit和merge之后(pull之后如果代碼沒沖突,則一定有個merge;如果pull之后有沖突則沒有merge,但是一定有個commit)自動調用hook產生一個版本文件version_git.h,將這個文件包含在代碼種,可執行文件就可以和代碼版本關聯起來了。以后可執行文件奔潰了之后,就可以通過該hash調出該可執行文件在版本庫里對應的代碼。

然后將version_git.h加入到 .gitignore里,不用跟蹤它,因為每次都會自動生成。

然后在代碼里使用 version_git.h 和 breakpad 將崩潰的日志dump文件對應到該hash。我自己的使用示例如下:

#include "client\windows\handler\exception_handler.h"
#include "version_git.h"

static bool crash_callback(const wchar_t *dump_path, const wchar_t *id, void *context, 
    EXCEPTION_POINTERS *exinfo,    MDRawAssertionInfo *assertion,    bool succeeded) {
    if (succeeded) {
        QString exeFilePath = QCoreApplication::applicationDirPath();
        QString exeFilePathName = QCoreApplication::applicationFilePath();
        QString exeName = QFileInfo(exeFilePathName).fileName();
        QString exeNameWithoutExtension = QFileInfo(exeFilePathName).completeBaseName();
        QString pdbFilePathName = exeFilePath + "/" + exeNameWithoutExtension + ".pdb";

        QString dumpPath(QString::fromUtf16(reinterpret_cast<const unsigned short *>(dump_path)));
        QString exeFileInDumpPath = dumpPath + "/" + exeName;
        QString pdbFileInDumpPath = dumpPath + "/" + exeNameWithoutExtension + ".pdb";

        QFileInfo exeFileInDumpPathExist(exeFileInDumpPath);
        QFileInfo pdbFileInDumpPathExist(pdbFileInDumpPath);
        if (!exeFileInDumpPathExist.exists()) QFile::copy(exeFilePathName, exeFileInDumpPath);
        if (!pdbFileInDumpPathExist.exists()) QFile::copy(pdbFilePathName, pdbFileInDumpPath);
    }
    else {
        qDebug() << "dump failed" << endl;
    }
    fflush(stdout);

    return succeeded;
}

QString createCrashLogsDir(const wchar_t *dump_path_parent, std::string dump_path_subdir)
{
    QString dumpPathParent(QString::fromUtf16(reinterpret_cast<const unsigned short *>(dump_path_parent)));
    QString dumpPathSubdir = QString::fromLocal8Bit(dump_path_subdir.c_str());
    QString dumpDir = dumpPathParent + "/" + dumpPathSubdir;
    bool success = QDir().mkpath(dumpDir);
    return (success ? dumpDir : ".");
}

int main(int argc, char *argv[])
{
    const wchar_t *dump_path_parent = L"./crash_logs";
    QString dump_path = createCrashLogsDir(dump_path_parent, version_hash);
    wchar_t wchar_array[128];
    dump_path.toWCharArray(wchar_array);
    google_breakpad::ExceptionHandler eh(wchar_array, NULL, crash_callback, NULL, google_breakpad::ExceptionHandler::HANDLER_ALL);

...
}

 

注意,每次生成可執行文件時,必須提交你所有更改的代碼(不然以后版本check出來的代碼就和你編譯的代碼不一樣),然后再執行生成。

必須將可以行文件和起pdb文件一起拷貝到目標機器,如果不拷貝pdb文件,以后崩潰的時候你都不知道到哪里去找這個文件。

如果可執行文件在客戶那里崩潰,則會在可執行文件目錄里創建 crash_logs/version_hash 目錄,然后將可執行文件和"可執行文件.pdb"一起拷貝到這個目錄,這個目錄還有崩潰時的dmp文件。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM