手把手教你在 Windows 上编译并集成 Breakpad 至 Android 项目(不是

42 阅读6分钟

Breakpad是一个跨平台的开源崩溃收集与分析工具,用于在程序崩溃时生成minidump 崩溃转储文件,便于在本地或服务端还原出可读的 堆栈信息(stack trace)

本文介绍了在 Windows 环境下编译 Breakpad 并集成至 Android 项目的完整流程。

如在操作中遇到问题,欢迎阅读本文或在评论区交流。也可以直接使用已编译好的版本,快速集成至本地项目,地址如下:github.com/LING-0001/b…

一、准备工作

1.安装MSYS2:www.msys2.org

2.使用MSYS2安装make工具

pacman -S base-devel

3.使用MSYS2安装mingw编译器

pacman -S git make cmake python3 mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake

说明:主要是通过在 Windows 上模拟 Linux 编译环境来完成构建,也可以选择其他方式自行搭建 C++ 编译环境(如 MinGW-64 等)。

二、下载并编译breakpad

1.下载对应的github项目,动手能力强的可以直接去breakpad官网,链接如下:github.com/google/brea…

git clone https://chromium.googlesource.com/breakpad/breakpad
git clone https://chromium.googlesource.com/external/gyp breakpad/src/third_party/gyp
git clone https://chromium.googlesource.com/linux-syscall-support src/third_party/lss
//不下载编译会报错'third_party/lss/linux_syscall_support. h' file not found

2.MSYS2 MINGW64打开breakpad目录

3.保险起见编译前清除缓存

make clean

4.编译breakpad拿到minidump_stackwalk

./configure && make

⚠️这一步比较关键,如果直接在windows的shell环境下编译可能会出错。

三、集成Android项目

1.将breakpad编译产物的src文件复制到合适位置

企业微信截图_17482441714035.png

2.配置CMakeLists

cmake_minimum_required(VERSION 3.4.1)
​
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
​
include_directories(    ${CMAKE_CURRENT_SOURCE_DIR}/src
        ${CMAKE_CURRENT_SOURCE_DIR}/src/client/linux
        ${CMAKE_CURRENT_SOURCE_DIR}/src/client/linux/dump_writer_common
        ${CMAKE_CURRENT_SOURCE_DIR}/src/common
        ${CMAKE_CURRENT_SOURCE_DIR}/src/common/linux)
file(GLOB BREAKPAD_SOURCES_COMMON
        native-lib.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/src/client/linux/crash_generation/crash_generation_client.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/client/linux/dump_writer_common/thread_info.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/client/linux/dump_writer_common/ucontext_reader.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/client/linux/handler/exception_handler.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/client/linux/handler/minidump_descriptor.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/client/linux/log/log.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/client/linux/microdump_writer/microdump_writer.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/client/linux/minidump_writer/linux_dumper.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/client/linux/minidump_writer/linux_ptrace_dumper.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/client/linux/minidump_writer/minidump_writer.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/client/minidump_file_writer.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/common/convert_UTF.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/common/md5.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/common/string_conversion.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/common/linux/elfutils.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/common/linux/file_id.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/common/linux/guid_creator.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/common/linux/linux_libc_support.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/common/linux/memory_mapped_file.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/src/common/linux/safe_readlink.cc
)
​
file(GLOB BREAKPAD_ASM_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/src/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} )

3.编写jni代码

#include <jni.h>
#include <string>
#include "android/log.h"
#include "src/client/linux/handler/minidump_descriptor.h"
#include "src/client/linux/handler/exception_handler.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_example_jni_JniActivity_initBreakpad(JNIEnv *env, jobject,
                                              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_example_jni_JniActivity_crash(JNIEnv *env, jobject) {
    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;
}

4.安卓代码

class JniActivity : BaseActivity<ActivityJniBinding>() {

    companion object {
        init {
            System.loadLibrary("breakpad")
        }
    }

    private external fun initBreakpad(breakpadPath: String)

    external fun crash()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        initBreakpad(filesDir.absolutePath)
        initView()
    }

    private fun initView() {
        binding?.tvJni?.setOnClickListener {
            crash()
            startNativeThread()
        }
    }
}

5.运行,然后编译报错,因为windows下需要保留 ELF 分支,移除 PE 分支

找到minidump_writer.cc

//    RSDS_DEBUG_FORMAT rsds;
//    PEFileFormat file_format = PEFile::TryGetDebugInfo(file_path, &rsds);if (true) {
    保留
} else {
    //注释
}
​
注释选择分支逻辑,直接走elf分支

6.点击按钮闪退,找到储存的dump文件

7.执行shell脚本

自己的路径./minidump_stackwalk.exe 生成dump的路径.dmp > textLog.txt

生成信息如下,会告诉你一些机型信息,以及闪退的对应符号表

Operating system: Android
                  0.0.0 Linux 4.14.116 #1 SMP PREEMPT Wed Jan 24 00:41:44 CST 2024 aarch64
CPU: arm64
     8 CPUs

GPU: UNKNOWN

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

Thread 0 (crashed)
 0  libbreakpad.so + 0x5014c
     x0 = 0x00000073eccf3980    x1 = 0x0000007fed5cbbc4
     x2 = 0x0000000000000010    x3 = 0x000000000000001c
     x4 = 0x0000000000000001    x5 = 0x0000007fed5cbb80
     x6 = 0x0000007fed5cbbe8    x7 = 0x0000007fed5cbc00
     x8 = 0x0000000000000001    x9 = 0x0000000000000000
    x10 = 0x0000000000430000   x11 = 0x00000073ecbd3000
    x12 = 0x0000007470026560   x13 = 0x3e795d9ee7e38c19
    x14 = 0x0000000000000006   x15 = 0xffffffffffffffff
    x16 = 0x00000073e016f340   x17 = 0x00000073e011313c
    x18 = 0x000000747315c000   x19 = 0x00000073ecc10800
    x20 = 0x0000000000000000   x21 = 0x00000073ecc10800
    x22 = 0x0000007fed5cbe30   x23 = 0x000000746d633246
    x24 = 0x0000000000000004   x25 = 0x0000007472705020
    x26 = 0x00000073ecc108b0   x27 = 0x0000000000000001
    x28 = 0x0000007fed5cbbc0    fp = 0x0000007fed5cbb90

8.找到Android studio ndk自带的aarch64-linux-android-addr2line,以及编译成功的so文件,将报错符号解析成可读懂代码

自己的路径./aarch64-linux-android-addr2line.exe -f -C -e 生成的so文件的路径.so 0x5014c(对应报错的内容)

生成信息如下:

企业微信截图_17482470411869.png

完事,找到了报错的地方,Crash函数以及29行

五、更多的意义

1.可编写shell脚本,实现闪退信息的自动化解析与归档。

2.可hook关键节点,实现 dump文件的上传、汇报或接入监控平台。

3.学习