JNI 崩潰與定位步驟

這個(gè)示例在主界面底部新增了一個(gè) JNI Crash 按鈕。點(diǎn)擊后 Kotlin 調(diào)用
NativeCrash.crashFromJni(),JNI 進(jìn)入 native-crash.cpp,最終在
crashOnPurpose() 中寫空指針并觸發(fā) SIGSEGV。

關(guān)鍵代碼位置:

  • Kotlin 入口:app/src/main/java/com/mobilefiction/svgexample/NativeCrash.kt
  • JNI 實(shí)現(xiàn):app/src/main/cpp/native-crash.cpp
  • CMake 配置:app/src/main/cpp/CMakeLists.txt
  • Gradle 接入:app/build.gradle.kts

1. 構(gòu)建 Debug 包

.\gradlew.bat :app:assembleDebug

Debug 包會(huì)保留未剝離的 native so,后面定位時(shí)優(yōu)先使用這個(gè)目錄里的文件:

app/build/intermediates/cxx/Debug/<hash>/obj/<abi>/libnative-crash.so

不要用 APK 中解出來的 so 或 merged_native_libs 中的 so 做精確行號(hào)定位,它們可能已經(jīng)被 strip。

2. 安裝并觸發(fā)崩潰

adb install -r .\app\build\outputs\apk\debug\app-debug.apk
adb logcat -c
adb shell am start -n com.mobilefiction.svgexample/.MainActivity

打開另一個(gè)終端開始收集日志:

adb logcat -v threadtime > crash.log

回到手機(jī)或模擬器點(diǎn)擊底部的 JNI Crash 按鈕。應(yīng)用閃退后,停止 logcat。

3. 從日志里找到 native backtrace

crash.log 中搜索 libnative-crash.so、SIGSEGVbacktrace。你會(huì)看到類似:

signal 11 (SIGSEGV), code 1 (SEGV_MAPERR)
backtrace:
  #00 pc 000000000000xxxx  .../libnative-crash.so (crashOnPurpose()+...)
  #01 pc 000000000000yyyy  .../libnative-crash.so (Java_com_mobilefiction_svgexample_NativeCrash_crashFromJni+...)

記下 #00 行的 pc 地址,例如 000000000000xxxx。Android tombstone 中這類
pc 通??梢灾苯幼鳛?so 內(nèi)偏移傳給 addr2line

4. 找到 ABI 與未剝離 so

$abi = (adb shell getprop ro.product.cpu.abi).Trim()
$so = Get-ChildItem .\app\build\intermediates\cxx\Debug -Recurse -Filter libnative-crash.so |
    Where-Object { $_.FullName -like "*\obj\$abi\*" } |
    Select-Object -First 1
$so.FullName

如果設(shè)備是 arm64-v8a,$so.FullName 應(yīng)該指向類似:

D:\project\ComposeSVGExample\app\build\intermediates\cxx\Debug\<hash>\obj\arm64-v8a\libnative-crash.so

5. 用 addr2line 定位源文件行

先找到 NDK:

$sdk = (Get-Content .\local.properties | Where-Object { $_ -like "sdk.dir=*" }).Substring(8).Replace("\:", ":").Replace("\\", "\")
$ndk = (Get-ChildItem "$sdk\ndk" | Sort-Object Name -Descending | Select-Object -First 1).FullName
$addr2line = "$ndk\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-addr2line.exe"

把第 3 步拿到的 pc 地址傳給 llvm-addr2line

& $addr2line -C -f -e $so.FullName 000000000000xxxx

預(yù)期輸出會(huì)落到 JNI 崩潰函數(shù)附近:

crashOnPurpose()
D:/project/ComposeSVGExample/app/src/main/cpp/native-crash.cpp:10

其中第 10 行就是:

*nullPointer = 42;

6. 用符號(hào)表粗定位函數(shù)

如果只有 symbol table,通常只能定位到函數(shù)名和函數(shù)內(nèi)偏移,不能穩(wěn)定給出源文件行??梢杂?br> llvm-nmllvm-readelf 看符號(hào)地址:

$nm = "$ndk\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-nm.exe"
& $nm -n -C $so.FullName | Select-String "crashOnPurpose|NativeCrash"

示例輸出形態(tài):

000000000000aaaa t crashOnPurpose()
000000000000bbbb T Java_com_mobilefiction_svgexample_NativeCrash_crashFromJni

把 tombstone 的 pc 地址和符號(hào)地址對(duì)比:

  • 如果 pc 落在 crashOnPurpose() 起始地址之后、下一個(gè)函數(shù)地址之前,崩潰就在這個(gè)函數(shù)內(nèi)。
  • pc - crashOnPurpose 起始地址 就是函數(shù)內(nèi)偏移;tombstone 有時(shí)也會(huì)直接顯示成 crashOnPurpose()+偏移。
  • 若要精確到 native-crash.cpp:10,使用未剝離 so 加 llvm-addr2line。

7. 用 ndk-stack 一次性符號(hào)化

ndk-stack 會(huì)讀取 logcat 中的 backtrace,并用 -sym 指向的未剝離 so 目錄批量符號(hào)化:

$symDir = $so.Directory.FullName
& "$ndk\ndk-stack.cmd" -sym $symDir -dump .\crash.log

如果符號(hào)匹配,輸出中會(huì)顯示 crashOnPurpose()
app/src/main/cpp/native-crash.cpp:10

8. Release 符號(hào)說明

app/build.gradle.kts 里已經(jīng)給 debugrelease 配置了:

ndk {
    debugSymbolLevel = "FULL"
}

FULL 能保留文件名和行號(hào)信息,適合配合 addr2line 精確定位。若只配置
SYMBOL_TABLE,通常只能還原函數(shù)名,適合確認(rèn)崩潰大致落在哪個(gè) native 函數(shù)。

這段 JNI 崩潰代碼只用于演示 native crash 定位,請(qǐng)不要保留在生產(chǎn)版本中。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容