跨平台coredump生成器——breakpad

跨平台coredump生成器——breakpad

介绍

google/breakpad: Mirror of Google Breakpad project

breakpad是google开发的一个跨平台C/C++ dump捕获开源库,崩溃文件使用微软的minidump格式存储,也支持发送这个dump文件到服务器,breakpad可以在程序崩溃时触发dump写入操作,也可以在没有触发dump时主动写dump文件。breakpad支持windows、linux、macos、android、ios等。目前已有Google Chrome, Firefox, Google Picasa, Camino, Google Earth等项目使用。

工作原理

集成breakpad的工程会有额外链接一个 Breakpad Client 静态库(libbreakpad_client.a)。在Build System编译生成产物时strip剥离符号前,使用 Breadpad symbol dumperdump_syms)生成出调试包 Debugging Infomation (google自己的格式,不是gnu的调试信息)并自行留存,然后可以发布程序(当然还会先strip剥离符号信息)。

等到崩溃发生时,触发 Breakpad Client 回调,生成 minidump 文件(微软的格式)到指定目录并发送到远端服务器。然后再服务器上就可以与构建时留存的 Debugging Infomation 结合传递给 Breakpad minidump processorminidump_stackwalk)来得到人类可读的崩溃堆栈。

这个生成minidump的回调, 在 Windows 上,是通过 SetUnhandledExceptionFilter() 实现的;在 OS X 上,是通过创建一个在 Mach 异常端口上等待的线程实现的;在 Linux 上,是通过为各种异常(如 SIGILL, SIGSEGV 等)安装信号处理程序实现的。

image

主要组件

breakpad有三个主要的组件:

  • breakpad client:breakpad的客户端静态库(libbreakpad_client.a)。它的主要作用是在程序崩溃后,接管程序的异常处理,具体来说,它主要做了两方面的事情。
    • 响应程序崩溃时接收到的signal,包括:SIGSEGVSIGABRTSIGFPESIGILLSIGBUS。 (另外两个SIGSTOPSIGKILL无法处理)
    • 获取程序崩溃那一刻的运行时信息,保存为一个minidump格式的文件。
  • symbol dumper:调试信息文件生成程序(dump_syms)。主要是用来从可执行程序中提取与符号相关的信息,并保存为一种特定格式的文件。
  • processor module:minidump 处理程序(minidump_stackwalk),它的作用就是根据coredump及symbol file,构建出可读的call stack。

Minidump格式

Minidump是微软开发的一种用于崩溃时记录的小存储器转储文件,它类似与linux下的core fileminidump中包含以下信息:

  • 进程装载的驱动程序和共享库列表,这个列表中包含了指定的名称和版本号
  • 进程中存在的线程列表。对于每个线程,小型转储包括处理器寄存器的状态和线程堆栈内存的内容。这些数据是未解释的字节流,因为 Breakpad 客户端通常没有可用于生成函数名称或行号,甚至标识堆栈帧边界的调试信息。
  • 已停止的处理器的上下文 (PRCB)
  • 已停止的进程的信息和内核上下文 (EPROCESS)
  • 有关收集转储的系统的其他信息:处理器和操作系统版本、转储的原因等。

Breakpad 在所有平台上使用 Windows 小型转储文件,而不是传统核心文件,原因如下:

  • 核心文件可能非常大,因此无法通过网络将其发送到收集器进行处理。小型转储更小,因为它们被设计为这样使用。
  • 核心文件格式记录得很差。例如,Linux 标准基础不描述寄存器如何存储在段中。
  • 说服 Windows 计算机生成核心转储文件比说服其他计算机编写小型转储文件更难。
  • 它简化了 Breakpad 处理器,仅支持一种文件格式。

集成

客户端的典型示例是:受监控的应用程序链接到Breakpad客户端库,在其主函数中安装一个 Breakpad 处理程序,并提供一个回调来启动一个小型崩溃报告程序。崩溃报告程序将链接到发送程序库,并在启动时发送崩溃转储。由于从崩溃的进程中执行任何大量工作都存在固有的不可靠性,因此建议为该功能使用单独的进程。

编译

目前只在Linux上和WSL中成功编译。在Powershell和mingw的shell下执行均出错。

breakpad由于是chromium的子项目,使用devopt_tools工具管理源码。但是这个工具在外网,所以这里采用手动下载依赖的方法。

  1. 下载breakpad源码:

    git clone https://github.com/google/breakpad.git
    
  2. 下载并集成依赖:

    git clone https://github.com/adelshokhy112/linux-syscall-support.git
    

    然后将 LSS 中的linux_syscall_support.h文件放至breakpad/src/third_party/lss/目录下。

  3. 编译breakpad:

    ./configure
    bear -- make
    

    中途可能说 linux_syscall_support.h 中使用了标记为 deprecated 的做法,所以需要使用 -Wno-deprecated 来避免将警告视为错误。

    # 在项目生成的makefile中修改
    -WARN_CXXFLAGS =  -Wmissing-braces -Wnon-virtual-dtor -Woverloaded-virtual -Wreorder -Wsign-compare -Wunused-local-typedefs -Wunused-variable -Wvla -Werror
    +WARN_CXXFLAGS =  -Wmissing-braces -Wnon-virtual-dtor -Woverloaded-virtual -Wreorder -Wsign-compare -Wunused-local-typedefs -Wunused-variable -Wvla -Werror -Wno-deprecated
    
  4. 收集编译产物:

    #!/bin/sh
    
    set -e
    
    os=$(uname -s)
    artifacts_dir=artifacts_$(date +%Y%m%d%H%M%S)
    
    mkdir -p $artifacts_dir
    
    cp src/client/$os/libbreakpad_client.a $artifacts_dir
    cp src/tools/$os/dump_syms/dump_syms $artifacts_dir
    cp src/processor/minidump_stackwalk $artifacts_dir
    cp src/tools/$os/symupload/sym_upload $artifacts_dir
    cp src/tools/$os/md2core/minidump-2-core $artifacts_dir
    

提供一个CMake脚本:

# FindBreakpad.cmake
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    set(BREAKPAD_LIBRARY ${PROJECT_SOURCE_DIR}/breakpad/libbreakpad_client.a)
    # set(BREAKPAD_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/../src/client/linux)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
    set(BREAKPAD_LIBRARY ${PROJECT_SOURCE_DIR}/breakpad/breakpad_client.lib)
    # set(BREAKPAD_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/../src/client/windows)
else()
    message(FATAL_ERROR "Unsupported platform")
endif()
set(BREAKPAD_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/../src)

if(BREAKPAD_LIBRARY AND BREAKPAD_INCLUDE_DIR)
    add_library(Breakpad STATIC IMPORTED GLOBAL)
    set_target_properties(Breakpad PROPERTIES IMPORTED_LOCATION ${BREAKPAD_LIBRARY})
    target_include_directories(Breakpad INTERFACE ${BREAKPAD_INCLUDE_DIR})
else()
    message(FATAL_ERROR "Breakpad library not found, skip creating Breakpad target")
endif()

生成Minidump

#include "client/linux/handler/exception_handler.h"

static bool DumpCallback(const google_breakpad::MinidumpDescriptor &descriptor, void *context, bool succeeded) {
    std::cout << "Dump path: " << descriptor.path() << std::endl;
    return succeeded;
}
void crash() { volatile int* a = (int*)(NULL); *a = 1; }

int main() {
    std::cout.setf(std::ios::unitbuf);

    google_breakpad::MinidumpDescriptor descriptor("/tmp");
    // 在 ExceptionHandler 对象的整个生命周期内,异常处理都是启用的,因此应尽可能早地实例化它,并在尽可能保持其存活。
    google_breakpad::ExceptionHandler eh(descriptor, nullptr, DumpCallback, nullptr, true, -1);

    crash();
    return 0;
}

注意:回调函数中应该尽可能少的做工作。因为当该函数回调时,程序处于不安全状态。从其他函数库分配内存或调用函数可能并不安全。如果你必须要在回调函数中实现一些功能,最安全的操作是forkexec一个新的进程来执行你需要做的功能。Breakpad源码中包含libc函数(用的是musl)的一些简单重新实现,同样,在src/third_party/lss中包含一些用于进行Linux系统调用的函数,应该避免直接调用libc和一些其他的动态库。

上传Minidump

可以使用编译得到的 sym_upload 程序,这个程序是用 curl 实现的。

生成调试符号并得到崩溃堆栈

要生成有用的堆栈,就得让breakpad看到程序二进制对应的调试符号。

  1. 编译时使用 -g 来生成调试符号

  2. 使用 dump_syms 来生成breakpad所需的符号文件(是文本格式的)

    dump_syms ./test > test.sym
    
  3. 然后需要将这个符号文件放置在特定的目录中,才能被 minidump_stackwalk 解析。这个符号文件的第一行说明了要放在哪个目录,例如:

    head -n1 test.sym MODULE Linux x86_64 6EDC6ACDB282125843FD59DA9C81BD830 test
    mkdir -p ./symbols/test/6EDC6ACDB282125843FD59DA9C81BD830 # 创建目录
    mv test.sym ./symbols/test/6EDC6ACDB282125843FD59DA9C81BD830
    
  4. 使用 minidump_stackwalk 生成堆栈:

    minidump_stackwalk minidump.dmp ./symbols 2>/dev/null # 会在stderr中打日志,在stdout中输出堆栈
    

如果没有把符号文件放在指定目目录,也能得到输出,但是信息不全!

image

其他

异常安全

Once an application encounters an exception, it is in an indeterminate and possibly hazardous state. Consequently, any code that runs after an exception occurs must take extreme care to avoid performing operations that might fail, hang, or cause additional exceptions. This task is not at all straightforward, and the Breakpad handler library seeks to do it properly, accounting for all of the minute details while allowing other application developers, even those with little systems programming experience, to reap the benefits. All of the Breakpad handler code that executes after an exception occurs has been written according to the following guidelines for safety at exception time:一旦应用程序遇到异常,它就会处于不确定且可能危险的状态。因此,在异常发生后运行的任何代码都必须极其小心,避免执行可能会失败、挂起或引发其他异常的操作。这项任务绝非易事,而 Breakpad 处理程序库旨在妥善处理,考虑到所有细微之处,同时让其他应用程序开发人员,甚至是那些几乎没有系统编程经验的人,也能从中受益。所有在异常发生后执行的 Breakpad 处理程序代码都遵循以下异常处理时的安全准则:

  • Use of the application heap is forbidden. The heap may be corrupt or otherwise unusable, and allocators may not function.应用程序堆的使用被禁止。堆可能已损坏或无法使用,分配器可能无法运行。
  • Resource allocation must be severely limited. The handler may create a new file to contain the dump, and it may attempt to launch a process to continue handling the crash.资源分配必须严格限制。处理程序可以创建一个新文件来保存转储,并且可以尝试启动一个进程来继续处理崩溃。
  • Execution on the thread that caused the exception is significantly limited. The only code permitted to execute on this thread is the code necessary to transition handling to a dedicated preallocated handler thread, and the code to return from the exception handler.引发异常的线程的执行受到显著限制。 此线程上唯一允许执行的代码是将处理转移到专用的预分配处理线程所需的代码,以及从异常处理程序返回的代码。
  • Handlers shouldn’t handle crashes by attempting to walk stacks themselves, as stacks may be in inconsistent states. Dump generation should be performed by interfacing with the operating system’s memory manager and code module manager.处理程序不应通过自行遍历堆栈来处理崩溃,因为堆栈可能处于不一致的状态。应通过与操作系统的内存管理器和代码模块管理器进行交互来生成转储。
  • Library code, including runtime library code, must be avoided unless it provably meets the above guidelines. For example, this means that the STL string class may not be used, because it performs operations that attempt to allocate and use heap memory. It also means that many C runtime functions must be avoided, particularly on Windows, because of heap operations that they may perform.除非能证明符合上述准则,否则必须避免使用库代码,包括运行时库代码。例如,这意味着不能使用 STL 字符串类,因为它会尝试分配和使用堆内存。这也意味着必须避免使用许多 C 运行时函数,尤其是在 Windows 系统上,因为它们可能会执行堆操作。

A dedicated handler thread is used to preserve the state of the exception thread when an exception occurs: during dump generation, it is difficult if not impossible for a thread to accurately capture its own state. Performing all exception-handling functions on a separate thread is also critical when handling stack-limit-exceeded exceptions. It would be hazardous to run out of stack space while attempting to handle an exception. Because of the rule against allocating resources at exception time, the Breakpad handler library creates its handler thread when it installs its exception handler. On Mac OS X, this handler thread is created during the normal setup of the exception handler, and the handler thread will be signaled directly in the event of an exception. On Windows and Linux, the handler thread is signaled by a small amount of code that executes on the exception thread. Because the code that executes on the exception thread in this case is small and safe, this does not pose a problem. Even when an exception is caused by exceeding stack size limits, this code is sufficiently compact to execute entirely within the stack’s guard page without causing an exception.当发生异常时,会使用一个专用的处理线程来保存异常线程的状态:在生成转储期间,线程很难甚至不可能准确捕获自身状态。在处理超出堆栈限制的异常时,在单独的线程上执行所有异常处理功能也至关重要。在尝试处理异常时耗尽堆栈空间会很危险。由于在异常时分配资源是被禁止的,所以 Breakpad 处理程序库在安装其异常处理程序时就创建了其处理线程。在 Mac OS X 上,此处理线程是在正常设置异常处理程序期间创建的,并且在发生异常时会直接向该处理线程发送信号。在 Windows 和 Linux 上,异常线程上执行的一小段代码会向处理线程发送信号。由于在这种情况下在异常线程上执行的代码很小且安全,所以这不会造成问题。即使异常是由超出堆栈大小限制引起的,此代码也足够紧凑,可以在堆栈的保护页内完全执行而不会引发异常。

The handler thread may also be triggered directly by a user call, even when no exception occurs, to allow dumps to be generated at any point deemed interesting.即使未发生异常,处理程序线程也可能由用户调用直接触发,以便在任何被认为有趣的时刻生成转储。

进程的两种异常处理模式

breakpad/docs/exception_handling.md at main · google/breakpad

进程的异常处理分为In-processOut-process异常处理两种模式。因为从已经要崩溃的进程中生成dump文件是不安全的(进程的一些资源和状态已经不正常了,内存和堆栈可能破坏了,不应该执行太复杂、申请资源的操作),所以所有的OS都提供了Out-process模式fork额外的进程来处理异常。

Breakpad使用的是Out-process异常处理模式,dump文件的生成和写入在崩溃进程外进行,linux中进程间采用socketpair通信。

posted @ 2025-09-19 17:56  3的4次方  阅读(63)  评论(0)    收藏  举报