zoukankan      html  css  js  c++  java
  • 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
    ", 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文件夹到breakpadsrc ools文件夹下

    然后,生成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:
    ....clientwindowsunittests......	estingsrcgmock-all.cc
    ....clientwindowsunittests......	estinggtestsrcgtest-all.cc
    ....clientwindowsunittests......	estingsrcgmock_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
    ", id);
        }
        else {
          printf("dump failed
    ");
        }
        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?
    ");
      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 "--------
    "
    
    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 "clientwindowshandlerexception_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文件。

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

    微信公众号:  共鸣圈
    欢迎讨论,邮件:  924948$qq.com       请把$改成@
    QQ群:263132197
    QQ:    924948

    良辰美景补天漏,风雨雷电洗地尘
    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  • 相关阅读:
    Android应用开发基础篇(16)-----ScaleGestureDetector(缩放手势检测)
    Android应用开发基础篇(15)-----URL(获取指定网址里的图片)
    Android应用开发基础篇(14)-----自定义标题栏
    Android应用开发提高篇(6)-----FaceDetector(人脸检测)
    Android应用开发提高篇(5)-----Camera使用
    Android应用开发提高篇(4)-----Socket编程(多线程、双向通信)
    Android应用开发基础篇(13)-----GestureDetector(手势识别)
    Android应用开发基础篇(12)-----Socket通信
    Android应用开发实例篇(1)-----简易涂鸦板
    Android应用开发提高篇(3)-----传感器(Sensor)编程
  • 原文地址:https://www.cnblogs.com/welhzh/p/14821817.html
Copyright © 2011-2022 走看看