在 Android C/C++ 代码中接入 breakpad

本文概述在 Android 的 C++ 代码中使用 Breakpad 的方法。

与其它平台接入 Breakpad 的方法类似,主要有如下几步:

  1. 编译 breakpad 客户端库。
  2. 在代码中集成 breakpad 客户端库。在这一步中配置生成的 minidump 文件的保存目录路径。
  3. 生成符号文件。通过 breakpad 提供的 dump_syms 工具,为要分析的二进制文件(动态链接库或可执行文件)生成符号文件。
  4. 程序崩溃时,生成 minidump 文件。
  5. 利用 breakpad 提供的 minidump_stackwalk 工具,以前面生成的符号文件和程序崩溃时生成的 minidump 文件为输入,获得符号化的堆栈。

构建 Breadpad

这里说明基于 WebRTC / OpenRTCClient 的 GN + ninja 构建系统的构建方法。

通过 Git 下载 OpenRTCClient 的源码。然后在 OpenRTCClient 的源码根目录中执行如下命令:

OpenRTCClient$ ./build_system/webrtc_build gen android arm64 debug
OpenRTCClient$ ./build_system/webrtc_build build:crash_catch_system android arm64 debug

这将为 Android 生成 ARM64 debug 版的客户端静态库文件 build/android/arm64/debug/obj/third_party/breakpad/libbreakpad_client.a,同时还会生成用于生成符号文件和将生成的 minidump 转为符号的工具,它们位于 build/mac/x64/debug

OpenRTCClient$ ls -alh build/android/arm64/debug/                         
total 24792
drwxr-xr-x  21 zhangsan  staff   672B  7  8 16:28 .
drwxr-xr-x   3 zhangsan  staff    96B  7  8 16:18 ..
-rw-r--r--   1 zhangsan  staff    45K  7  8 16:26 .ninja_deps
-rw-r--r--   1 zhangsan  staff    15K  7  8 16:28 .ninja_log
-rw-r--r--   1 zhangsan  staff   939B  7  8 16:18 args.gn
-rw-r--r--   1 zhangsan  staff   1.9M  7  8 16:20 build.ninja
-rw-r--r--   1 zhangsan  staff    49K  7  8 16:20 build.ninja.d
-rw-r--r--   1 zhangsan  staff   777B  7  8 16:19 build_vars.json
drwx------   9 zhangsan  staff   288B  7  8 16:28 clang_x64
-rwxr-xr-x   1 zhangsan  staff    55K  7  8 16:24 core-2-minidump
lrwxr-xr-x   1 zhangsan  staff    19B  7  8 16:25 dump_syms -> clang_x64/dump_syms
drwxr-xr-x   4 zhangsan  staff   128B  7  8 16:24 exe.unstripped
drwx------  12 zhangsan  staff   384B  7  8 16:19 gen
drwx------   5 zhangsan  staff   160B  7  8 16:20 gen.runtime
lrwxr-xr-x   1 zhangsan  staff    29B  7  8 16:27 microdump_stackwalk -> clang_x64/microdump_stackwalk
-rwxr-xr-x   1 zhangsan  staff    29K  7  8 16:24 minidump-2-core
lrwxr-xr-x   1 zhangsan  staff    23B  7  8 16:28 minidump_dump -> clang_x64/minidump_dump
lrwxr-xr-x   1 zhangsan  staff    28B  7  8 16:27 minidump_stackwalk -> clang_x64/minidump_stackwalk
drwx------  29 zhangsan  staff   928B  7  8 16:19 obj
lrwxr-xr-x   1 zhangsan  staff    19B  7  8 16:26 symupload -> clang_x64/symupload
-rw-r--r--   1 zhangsan  staff   9.1M  7  8 16:20 toolchain.ninja

只能在 Linux 操作系统下为 Android 生成 dump_symsminidump_stackwalk 这样的工具,因而尽管可以 Mac 平台下为 Android 生成 breakpad 客户端静态库文件,但上面的命令的成功完整执行需要在 Linux 平台下进行。

把 Breakpad 客户端库集成进你的程序

首先,配置构建过程把 libbreakpad_client.a 的目录地址添加到链接库文件的搜索路径,同时为链接添加 breakpad_client 库,把 libbreakpad_client.a 链接进你的二进制文件,然后设置 include 路径包含 breakpad 源码树中的 src 目录。

Google breakpad 本身提供的 ExceptionHandler 类接口在各个平台上虽然差别也不大,但也不完全一样。这给 breakpad 的接入带来了一定的负担。

OpenRTCClient 中,笔者实现了一个简单的封装,即 open_rtc::InstallCrashHandler()open_rtc::UninstallCrashHandler()。要使用这个接口,包含崩溃处理器的头文件:

#include "client/crash_handler.h"

调用 open_rtc::InstallCrashHandler() 安装崩溃处理器。之后在调用 open_rtc::UninstallCrashHandler() 之前,异常处理都是激活的,因此你应该在你的程序启动过程中,尽可能早地调用 open_rtc::InstallCrashHandler(),并使其尽可能接近关闭状态一直保持激活。要做任何有用的事情,open_rtc::InstallCrashHandler() 都需要一个可以写入 minidump 的路径,以及一个回调函数来接收有关已写入的 minidump 的信息:

#include <stdlib.h>

#include "client/crash_handler.h"

bool minidumpCallback(const char* dump_dir,
                      const char* minidump_id,
                      void* context,
                      bool succeeded) {
//  auto thread = std::make_unique<std::thread>([&]() {
      char dump_path[512] = { 0 };
      snprintf(dump_path, sizeof(dump_path), "%s/%s.dmp", dump_dir, minidump_id);
//      RTC_LOG(INFO) << "Minidump file path: " << dump_path;
      chmod(dump_path, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
//  });
//
//  thread->join();
  return succeeded;
}

static void crashfunc() {
  volatile int* a = (int*)(NULL);
  *a = 1;
}

extern "C" jint JNIEXPORT JNICALL JNI_OnLoad(JavaVM* jvm, void* reserved) {
  jint ret = InitGlobalJniVariables(jvm);
  RTC_DCHECK_GE(ret, 0);
  if (ret < 0)
    return -1;

   . . . . . .
  open_rtc::InstallCrashHandler("/sdcard/Android/data/com.example.cpp/cache", nullptr, minidumpCallback, nullptr);
  crashfunc();
  return ret;
}

extern "C" void JNIEXPORT JNICALL JNI_OnUnLoad(JavaVM* jvm, void* reserved) {
  open_rtc::UnInstallCrashHandler();
}

编译并运行这个示例程序,应该会在手机的 /sdcard/Android/data/com.example.cpp/cache 目录下生成一个 minidump 文件,而且它应该会在退出之前打印出 minidump 文件的目录路径和 id,id 即为 minidump 文件的文件名,实际的 minidump 文件带有文件扩展名 ".dmp"。

也可以直接使用 Google breakpad 提供的 ExceptionHandler 类来接入 breakpad。此时首先包含头文件:

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

然后创建 ExceptionHandler 类对象。ExceptionHandler 对象的整个生命周期中异常处理都是激活的,因此应该在程序启动过程中,尽可能早实例化它,并使其尽可能接近关闭状态一直保持激活。要做任何有用的事情,ExceptionHandler 构造函数都需要一个可以写入 minidump 的路径,以及一个回调函数来接收有关已写入的 minidump 的信息:

#include <stdlib.h>

#include <unistd.h>

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

static bool DumpCallback(const google_breakpad::MinidumpDescriptor &descriptor,
    void *context, bool success) {
//  auto thread = std::make_unique<std::thread>([&]() {
    if (!success) {
      static const char msg[] = "Failed to write minidump\n";
      // RTC_LOG(INFO) << msg;
      return false;
    } else {
//        RTC_LOG(INFO) << "Minidump file path: " << descriptor.path();
        chmod(descriptor.path(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
    }
//  });
//
//  thread->join();
  return succeeded;
}

static void DoSomethingWhichCrashes() {
  int local_var = 1;
  *reinterpret_cast<volatile char*>(NULL) = 1;
}

int main(int argc, const char *argv[]) {
  google_breakpad::MinidumpDescriptor minidump("/sdcard/Android/data/com.example.cpp/cache");
  google_breakpad::ExceptionHandler breakpad(minidump, NULL, DumpCallback, NULL,
      true, -1);
  DoSomethingWhichCrashes();
  return 0;
}

编译并运行这个程序与前面那个程序的运行结果相同。

Android 接入 breakpad 客户端时,需要注意传入的 minidump 文件目录,需要应用程序对其有写权限。此外,为了使生成的 minidump 文件能被成功地从 Android 设备中 pull 出来,在回调函数中修改了 minidump 文件的权限。

另外,在发生崩溃,回调被调用时,在回调中不能通过 JNI 调用 Java 代码。对于这个问题,StackOverflow 上有讨论,Unable to make JNI call from c++ to java in android lollipop using jni,这是一个已知问题,Android 的 ART 为信号处理使用了备用的栈导致了这个问题。如果想在回调中通过 JNI 调用 Java 代码,需要开专门的线程来执行。

Android 中各种不同的本地层崩溃捕获方案都采用了相同的机制,具体来说是覆盖默认的信号处理器,来获得关于崩溃的详细信息。不同的本地层崩溃捕获方案可能会相互干扰。同一个应用中最好只集成一个崩溃捕获方案。

为程序生成符号

如上面的说明,通过 OpenRTCClient 构建 breakpad 会一并生成各种工具。如果集成了 breakpad 的动态链接库是由 Android Studio 编译的,则相应的带符号动态链接库位于 build/intermediates/cmake/debug/obj/arm64-v8a。通过上面生成的 dump_syms 来生成符号文件。这个过程相对于其它平台没有什么特别的地方,与 Linux 的基本相同。

同样,生成的符号文件要按特定的目录结构放置,如 在 Linux 程序中使用 breakpad

处理 minidump 生成栈追踪

Android 程序发生了崩溃之后,从 Android 设备获取 minidump 文件。随后通过 minidump_stackwalk 生成栈追踪的方法也与 Linux 的相同,如 在 Linux 程序中使用 breakpad

它在 stderr 上产生详细输出,在 stdout 上产生堆栈跟踪,因此你可能需要重定向 stderr。

参考文档

在 Linux 程序中使用 breakpad

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355

推荐阅读更多精彩内容