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 true:BuildConfig.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
对应示意图:

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
对照示意图:

结果能对上(示意):

输出通常类似:
Java_com_xx_xxx_NativeCrash_crashSegv
E:/Project/xxxx/android/app/src/main/cpp/crashlib.cpp:9
到这里就完成了“Bugly/tombstone -> pc -> so 方法 -> 源码行”的闭环。
八、offset 计算什么时候需要
常见两种情况:
- 日志已经给的是 so 内
pc:直接查。 - 日志是
...!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。 - 强制崩溃代码务必只在调试环境启用,避免误进生产。