Flutter Android 正常解决 so 崩溃并反查方法

在排查线上 Native 崩溃时,最常见的阻塞点是:Bugly 有崩溃记录,但不知道如何把 pc 精确落到某个 so 的具体方法和源码行
这篇按“正常解决 so 问题”的顺序,结合一次真实记录来走完整闭环。

设计目标

  • 崩溃触发要稳定、可重复。
  • backtrace 中必须出现我们自定义的 libcrashlib.so
  • 能用 llvm-addr2line 直接反查到 crashlib.cpp:line

一、先做一个可控崩溃 so(用于验证链路)

android/app/src/main/cpp/crashlib.cpp

#include <jni.h>
#include <signal.h>

extern "C" JNIEXPORT void JNICALL
Java_com_xx_xxxx_NativeCrash_crashSegv(JNIEnv*, jclass) {
  volatile int* p = reinterpret_cast<volatile int*>(0);
  *p = 1; // 真实空指针写,触发 SIGSEGV
}

extern "C" JNIEXPORT void JNICALL
Java_com_xx_xxxx_NativeCrash_crashAbort(JNIEnv*, jclass) {
  raise(SIGABRT); // 作为对照
}

这里保留两种崩溃方式:SIGSEGV(真实非法访问)和 SIGABRT(对照用)。

二、接入 CMake 编译

android/app/src/main/cpp/CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(crashlib)

add_library(crashlib SHARED crashlib.cpp)
find_library(log-lib log)
target_link_libraries(crashlib ${log-lib})

三、Kotlin 封装 JNI

android/app/src/main/kotlin/com/xx/xxx/NativeCrash.kt


object NativeCrash {
    init {
        System.loadLibrary("crashlib")
    }

    external fun crashSegv()
    external fun crashAbort()
}

四、在 onPause 触发(仅 Debug)

android/app/src/main/kotlin/com/xx/xxx/MainActivity.kt

override fun onPause() {
    super.onPause()
    if (BuildConfig.DEBUG) {
        NativeCrash.crashSegv()
    }
}

这样处理后,应用切到后台就会稳定触发一次 Native 崩溃,便于反复采集 tombstone。

五、Gradle 关键配置

android/app/build.gradle 需要包含:

android {
    buildFeatures {
        buildConfig true
    }

    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
        }
    }

    packagingOptions {
        jniLibs {
            keepDebugSymbols += ['**/libcrashlib.so']
        }
    }

    buildTypes {
        debug {
            ndk {
                debugSymbolLevel 'FULL'
            }
        }
        release {
            ndk {
                debugSymbolLevel 'FULL'
            }
        }
    }
}

为什么要这些配置:

  • buildConfig trueBuildConfig.DEBUG 才能用。
  • externalNativeBuild:让 CMake 进 Android 构建流程。
  • keepDebugSymbols + debugSymbolLevel FULL:确保后续可符号化到源码行。

六、构建与触发

cd android
gradlew.bat :app:assembleDebug

示例产物:

  • build/app/outputs/apk/debug/debug.apk

安装后,按 Home 或切后台触发 onPause,即可得到 tombstone/Bugly 记录。

七、结合真实记录做反查

1) 找到未 strip 的 so

示例路径(每次 hash 目录可能变化):

  • build/app/intermediates/cxx/Debug/361q5h28/obj/arm64-v8a/libcrashlib.so

你也可以把这个文件拷到固定目录,例如:

  • E:\Project\xxxxx\out\self_build\libcrashlib.so

对应示意图:

未 strip so 目录

2) 从 tombstone / Bugly 拿 pc

例如某帧:

#xx pc 00000000000006c0 .../libcrashlib.so

3) 用 llvm-addr2line 反查

E:\Android\Sdk\ndk\27.0.12077973\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-addr2line.exe ^
  -f -C ^
  -e E:\Project\cat\out\self_build\libcrashlib.so ^
  00000000000006c0

对照示意图:

addr2line 命令与结果

结果能对上(示意):

命中方法与源码行

输出通常类似:

Java_com_xx_xxx_NativeCrash_crashSegv
E:/Project/xxxx/android/app/src/main/cpp/crashlib.cpp:9

到这里就完成了“Bugly/tombstone -> pc -> so 方法 -> 源码行”的闭环。

八、offset 计算什么时候需要

常见两种情况:

  1. 日志已经给的是 so 内 pc:直接查。
  2. 日志是 ...!libX.so (offset 0x1d0000):先算
    addr_in_so = offset + pc,再 addr2line

九、排查时最常见坑

  • -e 用错文件:libcrashlib.so 的地址不能拿去喂 app.android-arm64.symbols
  • 符号与崩溃产物不一致:BuildId 不一致会导致错位或 ??:0
  • Bugly 接管信号后,栈顶可能先看到 kill/libBugly_Native.so,需要继续看后续帧或原始崩溃 PC。
  • 强制崩溃代码务必只在调试环境启用,避免误进生产。