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解压后,目录如下:
这里只需要src目录下的源码:
so库编译
Android Studio新建工程
选用Native C++模板
源码目录拷贝
将上面src目录下的文件,拷入Android工程cpp目录下:
编写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…
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的工具链目录:
如果是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层崩溃