基于Google Breakpad的Android Native崩溃监测及问题定位分析

针对Android平台的native崩溃问题,有很多工具可以跟踪检测,比如ndk-stack,腾讯bugly等,本文引入一种相对于前两者,更能清晰定位native崩溃问题的监测手段,及提供了一种封装好的JNI方法,方便开发者快速引入并使用的,加快native崩溃问题的分析和定位。

Google Breakpad是什么?

Google Breakpad 是一个开源的跨平台崩溃转储工具,由 Google 开发,旨在帮助开发者自动捕获应用程序崩溃时的关键信息,并生成便于分析的文件,从而提升软件的稳定性和用户体验。

GoogleBreakpad 支持 Windows、Linux、macOS、Android、iOS 等平台。Breakpad 具有三个主要组件:

  • client 在崩溃系统中负责抓取当前线程和当前载入的库生成 minidump 文件。
  • dump-syms 负责读取用户开发应用中的debug信息,并生成特定的符号文件。
  • processor 通过 minidump_stackwalk 读取 minidump 文件 找到合适的符号文件产生一个可读的 c/c++ 调用栈。
原理图

在默认情况下,当程序崩溃时 breakpad 会生成一个 minidump 文件,它在不同平台上的实现机制不一样:

在 Windows 平台上,使用微软提供的 SetUnhandledExceptionFilter() 方法来实现。
在 OS X 平台上,通过创建一个线程来监听 Mach Exception port 来实现。
在 Linux 平台上,通过设置一个信号处理器来监听 SIGILL SIGSEGV 等异常信号。

Breakpad 在所有的平台上都使用 minidump 文件格式,minidump 文件格式是由微软开发的用于崩溃上传,它包括:

当 dump 生成时进程中一系列 executable 和 shared libraries, 包括这些文件的文件名和版本号。
进程中的线程列表,对于每个线程,minidump 包含它在寄存器中的状态,线程的 stack memory 内容。这些数据都是未解析的字节流,Breakpad client 通常没有调试信息能生成函数名,行号,甚至无法确定 stack frame 的边界。
其他收集关于系统的信息,如处理器,操作系统高版本,dump 的原因等等。

如何在Android平台上使用Breakpad?

Google github源码地址:https://github.com/google/breakpad

1. 下载源码,将其集成到android工程中,结构如下:
工程结构图
2. 编写cmakeList.txt
cmake_minimum_required(VERSION 3.4.1)

set(BREAKPAD_ROOT include)
include_directories(${BREAKPAD_ROOT} ${BREAKPAD_ROOT}/common/android/include)
# 传递版本号
if(DEFINED VERSION_NAME)
    add_definitions(-DVERSION_NAME="${VERSION_NAME}")
endif()

file(GLOB BREAKPAD_SOURCES_COMMON
        ${BREAKPAD_ROOT}/../cpp/breakpad.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.c
        ${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/android/breakpad_getcontext.S )

set_source_files_properties(${BREAKPAD_ASM_SOURCE} PROPERTIES LANGUAGE C)

add_library( # Sets the name of the library.
             breakpad
             SHARED
             ${BREAKPAD_SOURCES_COMMON} ${BREAKPAD_ASM_SOURCE})

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

target_link_libraries( # Specifies the target library.
                       breakpad
                       ${log-lib} )
3. 修改build.gradle 文件
android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                arguments '-DVERSION_NAME=' + defaultConfig.versionName
                cppFlags ""
                abiFilters 'armeabi-v7a', 'arm64-v8a' // 指定目标ABI
            }
        }
    }

    externalNativeBuild {
        cmake {
            path "src/main/jni/CMakeLists.txt"
        }
    }
}
4. 开发JNI 接口的breakpad.cpp源文件
extern "C"
JNIEXPORT void JNICALL 
Java_com_cc_breakpad_Breakpad_init(
        JNIEnv *env,
        jclass type,
        jstring log_dir,jstring log_name) {
    const char *path = env->GetStringUTFChars(log_dir, 0);
    const char *env_log_name = env->GetStringUTFChars(log_name, 0);
    google_breakpad::MinidumpDescriptor descriptor(path);
    static google_breakpad::ExceptionHandler eh(descriptor, NULL, DumpCallback, NULL, true, -1);
    strcpy(logPath,path);
    strcpy(logNamePrefix,env_log_name);
    env->ReleaseStringUTFChars(log_dir, path);
    env->ReleaseStringUTFChars(log_name, path);
    #if defined(VERSION_NAME)
        ALOGD("breakpad init success, version:%s, compile time:%s %s\n",VERSION_NAME, __DATE__, __TIME__);
    #else
        ALOGD("breakpad init success, compile time:%s %s\n", __DATE__, __TIME__);
    #endif
}

extern "C"
JNIEXPORT void JNICALL 
Java_com_cc_breakpad_Breakpad_crashTest(
        JNIEnv *env,
        jclass type) {
    volatile int *a=(int*)(NULL);
    *a=1;
}

5. 开发JNI 接口的Kotlin源文件Breakpad.kt
public class Breakpad {

    static {
        System.loadLibrary("breakpad");
    }

    /**
     * 初始化
     * @param logPath 日志目录
     * @param logNamePrefix 日志文件名前缀,前缀+时间.dmp
     */
    public static native void init(String logPath,String logNamePrefix);
    public static native void crashTest();
}
6. 应用集成使用

至此,开发集成工作开发完成,可在应用中通过Breakpad.init方法进行初始化,即可实现native崩溃监测。

class MainActivity : AppCompatActivity() {
    private var TAG = "MainActivity"
    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

 Breakpad.init(Environment.getExternalStorageDirectory().absolutePath,"Blib")

        binding.button.setOnClickListener {
            Breakpad.crashTest()
        }
    }
}

运行程序,调用Breakpad.crashTest()可捕获native崩溃异常,具体信息如下:

2025-12-11 11:09:41.040 3037-3062/com.android.cc.nlib D/breakpad: 
breakpad dump path: /storage/emulated/0/463d081f-9d7b-4029-b137e5bc-1822af04.dmp

7. 崩溃堆栈dmp信息解析

将dmp文件导出到PC,通过dump_symsminidump_stackwalk 对dmp文件进行解析,libbreakpad.so 不能用apk里集成的,需要用未strip的so,具体路径为:

dump_syms 在 jni/include/tools/linux/dump_syms/dump_syms,
minidump_stackwalk 在  jni/include/processor/minidump_stackwalk 
libbreakpad.so 在 build/intermediates/cmake/release/obj/(release的版本),
build/intermediates/cmake/debug/obj/(debug的版本)

具体操作如下:

cc@linux ~/b/breakpaddemo> ls
463d081f-9d7b-4029-b137e5bc-1822af04.dmp   libbreakpad.so  
cc@linux ~/b/breakpaddemo> dump_syms libbreakpad.so > libbreakpad.so.sym
cc@linux ~/b/breakpaddemo> head -n1 libbreakpad.so.sym
MODULE Linux arm64 BE286F546A6E957F7A240604B230BE7B0 libbreakpad.so
cc@linux ~/b/breakpaddemo> mkdir -p symbols/libbreakpad.so/BE286F546A6E957F7A240604B230BE7B0 
cc@linux ~/b/breakpaddemo> mv libbreakpad.so.sym symbols/libbreakpad.so/BE286F546A6E957F7A240604B230BE7B0/
cc@linux ~/b/breakpaddemo> minidump_stackwalk 463d081f-9d7b-4029-b137e5bc-1822af04.dmp symbols > 463d081f-9d7b-4029-b137e5bc-1822af04.txt

至此,dump 监测的native 崩溃的详细堆栈信息被解析到了463d081f-9d7b-4029-b137e5bc-1822af04.txt文件中,具体堆栈信息如下:

Operating system: Android  0.0.0 Linux 4.4.138 
#187 SMP PREEMPT Tue Aug 26 09:27:58 CST 2025 aarch64
CPU: arm64
     4 CPUs
GPU: UNKNOWN
Crash reason:  SIGSEGV /SEGV_MAPERR
Crash address: 0x0
Process uptime: not available
Thread 0 (crashed)
 0  libbreakpad.so!Java_com_cc_breakpad_BreakPad_crashTest [breakpad.cpp : 48 + 0x0]
     x0 = 0x00000074494711c0    x1 = 0x0000007fc84e6974
     x2 = 0x0000000000000000    x3 = 0x000000744945aa00

对照breakpad.cpp Breakpad.crashTest()代码查看:

extern "C"
JNIEXPORT void JNICALL Java_com_cc_breakpad_Breakpad_crashTest(
        JNIEnv *env,
        jclass type) {
    volatile int *a=(int*)(NULL);
    *a=1;
}

崩溃点:
libbreakpad.so!Java_com_cc_breakpad_BreakPad_crashTest(breakpad.cpp第48行)
错误类型:SEGV_MAPERR(访问无效内存地址)
关键线索:Crash address: 0x0(尝试访问地址0x0,即空指针)
寄存器状态:x8 = 0x0(可能表示传递的指针参数为NULL)

备注:
如果用apk中的libbreakpad.so,由于经过strip去除调试符号信息,只能解析到崩溃的函数,无法定位到具体行数。

breakpad的Android项目JNI开发及使用基本完成了,如果需要集成到实际项目中,可以直接集成libbreakpad.so的JNI接口,这样就不用每次都编译breakpad源码了。

8. 附录

breakpad JNI项目源码地址:https://gitee.com/carmanshaw/blib

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容