Android集成Google Breakpad

1,967 阅读5分钟

Android集成Google Breakpad,捕捉Native层崩溃

介绍

崩溃是Android开发经常会碰到的问题,我们都知道,Android崩溃分为Java崩溃和Native崩溃。简单来说Java崩溃就是在Java代码中出现了未捕获异常,导致程序异常退出。那Native崩溃又是如何产生的?一般是因为Native代码中访问非法地址,也可能是地址对齐出现了问题,或者发生了程序主动abort,这些都会产生相应的signal信号,导致程序异常退出。
相比于Java崩溃,Native崩溃更难捕获和定位。事实上针对我们目前的项目,几乎大部分的崩溃都是Native 崩溃。分析项目的崩溃日志我们可以发现,有些崩溃是可以从backtrace中获得一些有用的信息。但是有些崩溃甚至连backtrace都不会出现,开发人员只能单步调试,无法快速定位。所以有没有一个系统的框架来收集和处理这些Native崩溃呢?当然有,Google开源的BreakPad就是其中之一。

Google Breakpad集成

源码下载

源码地址:breakpad源码地址
若无法下载,可通过:breakpad源码镜像地址
下载后的breakpad-main.zip解压后,目录如下:

image.png

这里只需要src目录下的源码:

image.png

so库编译

Android Studio新建工程

选用Native C++模板

源码目录拷贝

将上面src目录下的文件,拷入Android工程cpp目录下:

image.png

编写CMakeLists

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

set(BREAKPAD_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${BREAKPAD_ROOT} ${BREAKPAD_ROOT}/common/android/include)
file(GLOB BREAKPAD_SOURCES_COMMON
        native-lib.cpp
        ${BREAKPAD_ROOT}/client/linux/crash_generation/crash_generation_client.cc
        ${BREAKPAD_ROOT}/client/linux/dump_writer_common/thread_info.cc
        ${BREAKPAD_ROOT}/client/linux/dump_writer_common/ucontext_reader.cc
        ${BREAKPAD_ROOT}/client/linux/handler/exception_handler.cc
        ${BREAKPAD_ROOT}/client/linux/handler/minidump_descriptor.cc
        ${BREAKPAD_ROOT}/client/linux/log/log.cc
        ${BREAKPAD_ROOT}/client/linux/microdump_writer/microdump_writer.cc
        ${BREAKPAD_ROOT}/client/linux/minidump_writer/linux_dumper.cc
        ${BREAKPAD_ROOT}/client/linux/minidump_writer/linux_ptrace_dumper.cc
        ${BREAKPAD_ROOT}/client/linux/minidump_writer/minidump_writer.cc
        ${BREAKPAD_ROOT}/client/minidump_file_writer.cc
        ${BREAKPAD_ROOT}/common/convert_UTF.cc
        ${BREAKPAD_ROOT}/common/md5.cc
        ${BREAKPAD_ROOT}/common/string_conversion.cc
        ${BREAKPAD_ROOT}/common/linux/elfutils.cc
        ${BREAKPAD_ROOT}/common/linux/file_id.cc
        ${BREAKPAD_ROOT}/common/linux/guid_creator.cc
        ${BREAKPAD_ROOT}/common/linux/linux_libc_support.cc
        ${BREAKPAD_ROOT}/common/linux/memory_mapped_file.cc
        ${BREAKPAD_ROOT}/common/linux/safe_readlink.cc
)

file(GLOB BREAKPAD_ASM_SOURCE ${BREAKPAD_ROOT}/common/linux/breakpad_getcontext.S)
set_source_files_properties(${BREAKPAD_ASM_SOURCE} PROPERTIES LANGUAGE C)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
             breakpad

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             ${BREAKPAD_SOURCES_COMMON} ${BREAKPAD_ASM_SOURCE} )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                       breakpad

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

breakpad引用

//#include <jni.h>
//#include <string>
//
//extern "C" JNIEXPORT jstring JNICALL
//Java_com_devyk_breakpaddemo_MainActivity_stringFromJNI(
//        JNIEnv* env,
//        jobject /* this */) {
//    std::string hello = "Hello from C++";
//    return env->NewStringUTF(hello.c_str());
//}

#include <jni.h>
#include <string>
#include "client/linux/handler/exception_handler.h"
#include "client/linux/handler/minidump_descriptor.h"
#include "android/log.h"

const char *TAG = "Breakpad";
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)

// 崩溃回调
bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
                  void* context,
                  bool succeeded) {

    LOGE("Dump path: %s\n", descriptor.path());
    return succeeded;
}

// 模拟Crash
void Crash() {
    volatile int* a = reinterpret_cast<volatile int*>(NULL);
    *a = 1;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_devyk_breakpaddemo_BreakpadInit_initBreakpadNative(JNIEnv *env, jclass type, jstring path_) {
    const char *path = env->GetStringUTFChars(path_, 0);

    google_breakpad::MinidumpDescriptor descriptor(path);
    static google_breakpad::ExceptionHandler eh(descriptor, NULL, DumpCallback, NULL, true, -1);

    env->ReleaseStringUTFChars(path_, path);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_devyk_breakpaddemo_BreakpadInit_crash(JNIEnv *env, jclass clazz) {
    Crash();
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}

编译文件缺失:third_party/lss/linux_syscall_support.h

'third_party/lss/linux_syscall_support.h' file not found

源码地址:chromium.googlesource.com/linux-sysca…

国内地址:gitee.com/For_Love/go…

window下解析dmp

获取dmp

运行项目 ->点击崩溃 得到如下日志

2022-02-18 13:05:54.031 1457-1457/com.devyk.breakpaddemo E/Breakpad: Dump path: /storage/emulated/0/crashDump/4787d8a7-b653-4d03-9cb4c1a2-d33a00df.dmp

dmp解析

有了.dump文件,我们就需要对其进行解析,工具的名称是minidump_stackwalk。其实,Android Studio的安装目录下bin\lldb\bin就存在这么一个执行文件(我用的是Windows 10)。好,那么来试试(将目录下的.dump文件拷贝到当前目录并重新命名了):

minidump_stackwalk.exe crash.dmp > crashLog.txt

成功解析文件。

Operating system: Android
                  0.0.0 Linux 5.4.86-qgki-g9ccef2df83e2 #1 SMP PREEMPT Thu Nov 25 20:53:25 CST 2021 aarch64
CPU: arm64
     8 CPUs

GPU: UNKNOWN

Crash reason:  SIGSEGV
Crash address: 0x0
Process uptime: not available

Thread 0 (crashed)
 0  libbreakpad.so + 0x29240
     x0 = 0xb400006d87f903f0    x1 = 0x0000007fd9cb7494
     x2 = 0x0000000000000006    x3 = 0x0000006fbe5e5960
     x4 = 0x0000000200020003    x5 = 0x0000000400000003
     x6 = 0x0000000200000001    x7 = 0x0000000000000000
     x8 = 0x0000000000000000    x9 = 0x0000000000000001
    x10 = 0x0000000000430000   x11 = 0xb135000000030103
    x12 = 0x0000000000000000   x13 = 0x00000000006601c0
    x14 = 0x00000000005301c0   x15 = 0xffffffffffffffff

从.dump文件解析出来的信息中,根据文章Android 平台 Native 代码的崩溃捕获机制及实现的介绍,我们可知Crash reason: SIGSEGV代表哪种类型的错误

20:53:25 CST 2021 aarch64
CPU: arm64
     8 CPUs

GPU: UNKNOWN

Crash reason:  SIGSEGV //是当一个进程执行了一个无效的内存引用,或发生段错误时发送给它的信号。
Crash address: 0x0
Process uptime: not available

Thread 0 (crashed)  //crash 发生时候的线程

 0  libbreakpad.so + 0x29240 // 发生 crash 的位置和寄存器信息 

符号反解

有了具体的寄存器信息,我们进行符号解析,可以使用Android NDK中提供的addr2line来根据地址进行一个符号反解的过程,该工具在Android SDK目录下可以找到。
工具链的选择要根据.so的类型来决定,看解析后的文件,有显示CPU信息。下面是NDK 18的工具链目录:

image.png
如果是arm-64位的so,解析是需要使用aarch64-linux-android-4.9下的工具链。
如果是arm-32位的so,解析是需要使用arm-linux-androideabi-4.9下的工具链。
如果是x86-64位的so,解析是需要使用x86_64-4.9下的工具链。
如果是x86-32位的so,解析是需要使用x86-4.9下的工具链。
这里,因为CPU信息是x86,所以选择x86-4.9下的工具链。我们将项目中build目录下的x86对应的libcrash-lib.so(app\build\intermediates\transforms\mergeJniLibs\debug\0\lib\x86\libcrash-lib.so)拷贝到x86-4.9下的工具链目录(x86-4.9\prebuilt\windows-x86_64\bin)中,然后执行如下命令:

aarch64-linux-android-addr2line.exe -f -C -e libbreakpad.so 0x29240

得出如下结果

Crash()
C:/work/Android/breakpadDemo/app/src/main/cpp/native-lib.cpp:35

这样就找到具体哪一行报错了!!!

感谢

Android在Windows下无需编译Breakpad源码就能使用

Android集成Google Breakpad,捕捉Native层崩溃

Android 平台 Native 代码的崩溃捕获机制及实现

Android.mk