偵錯 Android 原生平台程式碼

本節將概述在開發平台層級功能時,可用於偵錯、追蹤和剖析原生 Android 平台程式碼的實用工具和相關指令。

注意:本節和本網站其他位置的頁面建議您將 adbsetprop 引數搭配使用,以便對 Android 的特定部分進行偵錯。在 Android 7.x 以下版本中,屬性名稱的長度上限為 32 個半形字元。這表示,如果要使用應用程式名稱建立包裝屬性,就必須截斷名稱以便配合。在 Android 8.0 以上版本中,這個限制會大幅提高,因此不應需要截斷。

本頁面將說明 logcat 輸出內容中當機傾印的相關基本概念。 其他頁面提供更多詳細資訊,說明如何診斷原生異常、使用 dumpsys 探索系統服務、查看原生記憶體網路RAM 用量、使用 AddressSanitizer 偵測原生程式碼中的記憶體錯誤、評估效能問題 (包括 systrace),以及使用偵錯工具

當機傾印和墓碑

動態連結的可執行檔啟動時,系統會註冊多個訊號處理常式,在發生當機時,會將基本當機傾印寫入 logcat,並將更詳細的墓碑檔案寫入 /data/tombstones/。墓碑檔案是含有異常程序額外資料的檔案。特別是,它包含當機程序中所有執行緒的堆疊追蹤記錄 (而非僅限於擷取信號的執行緒)、完整記憶體對應圖和所有已開啟檔案描述元的清單。

在 Android 8.0 之前,系統會透過 debuggerddebuggerd64 守護程序處理當機情形。在 Android 8.0 以上版本中,系統會視需要產生 crash_dump32crash_dump64

當沒有其他項目已連結時,當機傾印程式才會連結,這表示使用 stracelldb 等工具可避免發生當機傾印。

輸出範例 (已移除時間戳記和其他不必要資訊):

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'Android/aosp_angler/angler:7.1.1/NYC/enh12211018:eng/test-keys'
Revision: '0'
ABI: 'arm'
pid: 17946, tid: 17949, name: crasher  >>> crasher <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xc
    r0 0000000c  r1 00000000  r2 00000000  r3 00000000
    r4 00000000  r5 0000000c  r6 eccdd920  r7 00000078
    r8 0000461a  r9 ffc78c19  sl ab209441  fp fffff924
    ip ed01b834  sp eccdd800  lr ecfa9a1f  pc ecfd693e  cpsr 600e0030

backtrace:
    #00 pc 0004793e  /system/lib/libc.so (pthread_mutex_lock+1)
    #01 pc 0001aa1b  /system/lib/libc.so (readdir+10)
    #02 pc 00001b91  /system/xbin/crasher (readdir_null+20)
    #03 pc 0000184b  /system/xbin/crasher (do_action+978)
    #04 pc 00001459  /system/xbin/crasher (thread_callback+24)
    #05 pc 00047317  /system/lib/libc.so (_ZL15__pthread_startPv+22)
    #06 pc 0001a7e5  /system/lib/libc.so (__start_thread+34)
Tombstone written to: /data/tombstones/tombstone_06

輸出內容的最後一行會顯示磁碟上完整墓碑的位置。

如果您有未經精簡的二進位檔,可以將堆疊貼到 development/scripts/stack 中,藉此取得更詳細的回溯資訊,並取得行號資訊:

development/scripts/stack

提示:為方便起見,如果您已執行 lunchstack 就會在 $PATH 中,因此您不需要提供完整路徑。

輸出內容範例 (根據上述 Logcat 輸出內容):

Reading native crash info from stdin
03-02 23:53:49.477 17951 17951 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
03-02 23:53:49.477 17951 17951 F DEBUG   : Build fingerprint: 'Android/aosp_angler/angler:7.1.1/NYC/enh12211018:eng/test-keys'
03-02 23:53:49.477 17951 17951 F DEBUG   : Revision: '0'
03-02 23:53:49.477 17951 17951 F DEBUG   : ABI: 'arm'
03-02 23:53:49.478 17951 17951 F DEBUG   : pid: 17946, tid: 17949, name: crasher  >>> crasher <<<
03-02 23:53:49.478 17951 17951 F DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xc
03-02 23:53:49.478 17951 17951 F DEBUG   :     r0 0000000c  r1 00000000  r2 00000000  r3 00000000
03-02 23:53:49.478 17951 17951 F DEBUG   :     r4 00000000  r5 0000000c  r6 eccdd920  r7 00000078
03-02 23:53:49.478 17951 17951 F DEBUG   :     r8 0000461a  r9 ffc78c19  sl ab209441  fp fffff924
03-02 23:53:49.478 17951 17951 F DEBUG   :     ip ed01b834  sp eccdd800  lr ecfa9a1f  pc ecfd693e  cpsr 600e0030
03-02 23:53:49.491 17951 17951 F DEBUG   :
03-02 23:53:49.491 17951 17951 F DEBUG   : backtrace:
03-02 23:53:49.492 17951 17951 F DEBUG   :     #00 pc 0004793e  /system/lib/libc.so (pthread_mutex_lock+1)
03-02 23:53:49.492 17951 17951 F DEBUG   :     #01 pc 0001aa1b  /system/lib/libc.so (readdir+10)
03-02 23:53:49.492 17951 17951 F DEBUG   :     #02 pc 00001b91  /system/xbin/crasher (readdir_null+20)
03-02 23:53:49.492 17951 17951 F DEBUG   :     #03 pc 0000184b  /system/xbin/crasher (do_action+978)
03-02 23:53:49.492 17951 17951 F DEBUG   :     #04 pc 00001459  /system/xbin/crasher (thread_callback+24)
03-02 23:53:49.492 17951 17951 F DEBUG   :     #05 pc 00047317  /system/lib/libc.so (_ZL15__pthread_startPv+22)
03-02 23:53:49.492 17951 17951 F DEBUG   :     #06 pc 0001a7e5  /system/lib/libc.so (__start_thread+34)
03-02 23:53:49.492 17951 17951 F DEBUG   :     Tombstone written to: /data/tombstones/tombstone_06
Reading symbols from /huge-ssd/aosp-arm64/out/target/product/angler/symbols
Revision: '0'
pid: 17946, tid: 17949, name: crasher  >>> crasher <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xc
     r0 0000000c  r1 00000000  r2 00000000  r3 00000000
     r4 00000000  r5 0000000c  r6 eccdd920  r7 00000078
     r8 0000461a  r9 ffc78c19  sl ab209441  fp fffff924
     ip ed01b834  sp eccdd800  lr ecfa9a1f  pc ecfd693e  cpsr 600e0030
Using arm toolchain from: /huge-ssd/aosp-arm64/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/bin/

Stack Trace:
  RELADDR   FUNCTION                   FILE:LINE
  0004793e  pthread_mutex_lock+2       bionic/libc/bionic/pthread_mutex.cpp:515
  v------>  ScopedPthreadMutexLocker   bionic/libc/private/ScopedPthreadMutexLocker.h:27
  0001aa1b  readdir+10                 bionic/libc/bionic/dirent.cpp:120
  00001b91  readdir_null+20            system/core/debuggerd/crasher.cpp:131
  0000184b  do_action+978              system/core/debuggerd/crasher.cpp:228
  00001459  thread_callback+24         system/core/debuggerd/crasher.cpp:90
  00047317  __pthread_start(void*)+22  bionic/libc/bionic/pthread_create.cpp:202 (discriminator 1)
  0001a7e5  __start_thread+34          bionic/libc/bionic/clone.cpp:46 (discriminator 1)

您可以在整個墓碑上使用 stack。例子:

stack < FS/data/tombstones/tombstone_05

如果您剛剛在目前目錄中解壓縮錯誤報告,這項功能就很實用。如要進一步瞭解如何診斷原生程式碼當機和墓碑,請參閱「診斷原生程式碼當機」。

從執行中的程序取得堆疊追蹤或墓碑

您可以使用 debuggerd 工具,從執行中的程序取得堆疊轉儲。在指令列中,使用處理程序 ID (PID) 叫用 debuggerd,將完整的墓碑資料轉儲至 stdout。如要只取得程序中每個執行緒的堆疊,請加入 -b--backtrace 標記。

瞭解複雜的解開程序

應用程式當機時,堆疊通常會變得相當複雜。以下詳細範例會強調許多複雜性:

    #00 pc 00000000007e6918  /system/priv-app/Velvet/Velvet.apk (offset 0x346b000)
    #01 pc 00000000001845cc  /system/priv-app/Velvet/Velvet.apk (offset 0x346b000)
    #02 pc 00000000001847e4  /system/priv-app/Velvet/Velvet.apk (offset 0x346b000)
    #03 pc 00000000001805c0  /system/priv-app/Velvet/Velvet.apk (offset 0x346b000) (Java_com_google_speech_recognizer_AbstractRecognizer_nativeRun+176)

影格 #00 至 #03 來自原生 JNI 程式碼,該程式碼未經壓縮就儲存在 APK 中,以節省磁碟空間,而非擷取至個別的 .so 檔案。Android 9 以上版本的堆疊解開器不需要擷取的 .so 檔案,即可處理這類常見的 Android 專屬案例。

影格 #00 至 #02 沒有符號名稱,因為這些名稱已遭開發人員移除。

影格 #03 顯示,當符號可用時,解開器會使用這些符號。

    #04 pc 0000000000117550  /data/dalvik-cache/arm64/system@priv-app@Velvet@Velvet.apk@classes.dex (offset 0x108000) (com.google.speech.recognizer.AbstractRecognizer.nativeRun+160)

影格 #04 是提前編譯的 Java 程式碼。舊版 unwinder 會在此停止,無法透過 Java 解開。

    #05 pc 0000000000559f88  /system/lib64/libart.so (art_quick_invoke_stub+584)
    #06 pc 00000000000ced40  /system/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+200)
    #07 pc 0000000000280cf0  /system/lib64/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+344)
    #08 pc 000000000027acac  /system/lib64/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+948)
    #09 pc 000000000052abc0  /system/lib64/libart.so (MterpInvokeDirect+296)
    #10 pc 000000000054c614  /system/lib64/libart.so (ExecuteMterpImpl+14484)

影格 #05 至 #10 來自 ART 解譯器的實作。在 Android 9 以下版本中,堆疊解開工具會顯示這些影格,但不會顯示第 11 個影格的內容,因此無法說明解譯器正在解讀哪些程式碼。如果您要對 ART 本身進行偵錯,這些畫面就很實用。如果您正在對應用程式進行偵錯,可以忽略這些訊息。simpleperf 等部分工具會自動略過這些影格。

    #11 pc 00000000001992d6  /system/priv-app/Velvet/Velvet.apk (offset 0x26cf000) (com.google.speech.recognizer.AbstractRecognizer.run+18)

影格 #11 是正在解譯的 Java 程式碼。

    #12 pc 00000000002547a8  /system/lib64/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEb.llvm.780698333+496)
    #13 pc 000000000025a328  /system/lib64/libart.so (art::interpreter::ArtInterpreterToInterpreterBridge(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame*, art::JValue*)+216)
    #14 pc 000000000027ac90  /system/lib64/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+920)
    #15 pc 0000000000529880  /system/lib64/libart.so (MterpInvokeVirtual+584)
    #16 pc 000000000054c514  /system/lib64/libart.so (ExecuteMterpImpl+14228)

框架 #12 至 #16 是解譯器本身的實作項目。

    #17 pc 00000000002454a0  /system/priv-app/Velvet/Velvet.apk (offset 0x1322000) (com.google.android.apps.gsa.speech.e.c.c.call+28)

影格 #17 是正在解譯的 Java 程式碼。這個 Java 方法對應至轉譯器框架 #12 至 #16。

    #18 pc 00000000002547a8  /system/lib64/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEb.llvm.780698333+496)
    #19 pc 0000000000519fd8  /system/lib64/libart.so (artQuickToInterpreterBridge+1032)
    #20 pc 00000000005630fc  /system/lib64/libart.so (art_quick_to_interpreter_bridge+92)

幀 #18–#20 是 VM 本身,也就是從已編譯 Java 程式碼轉換為解譯 Java 程式碼的程式碼。

    #21 pc 00000000002ce44c  /system/framework/arm64/boot.oat (offset 0xdc000) (java.util.concurrent.FutureTask.run+204)

影格 21 是編譯的 Java 方法,會呼叫 #17 中的 Java 方法。

    #22 pc 0000000000559f88  /system/lib64/libart.so (art_quick_invoke_stub+584)
    #23 pc 00000000000ced40  /system/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+200)
    #24 pc 0000000000280cf0  /system/lib64/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+344)
    #25 pc 000000000027acac  /system/lib64/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+948)
    #26 pc 0000000000529880  /system/lib64/libart.so (MterpInvokeVirtual+584)
    #27 pc 000000000054c514  /system/lib64/libart.so (ExecuteMterpImpl+14228)

影格 #22 至 #27 是解譯器實作,將方法呼叫從解譯程式碼轉換為已編譯的方法。

    #28 pc 00000000003ed69e  /system/priv-app/Velvet/Velvet.apk (com.google.android.apps.gsa.shared.util.concurrent.b.e.run+22)

影格 #28 是正在解譯的 Java 程式碼。

    #29 pc 00000000002547a8  /system/lib64/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEb.llvm.780698333+496)
    #30 pc 0000000000519fd8  /system/lib64/libart.so (artQuickToInterpreterBridge+1032)
    #31 pc 00000000005630fc  /system/lib64/libart.so (art_quick_to_interpreter_bridge+92)

影格 #29 至 #31 是編譯程式碼和解譯程式碼之間的另一個轉換。

    #32 pc 0000000000329284  /system/framework/arm64/boot.oat (offset 0xdc000) (java.util.concurrent.ThreadPoolExecutor.runWorker+996)
    #33 pc 00000000003262a0  /system/framework/arm64/boot.oat (offset 0xdc000) (java.util.concurrent.ThreadPoolExecutor$Worker.run+64)
    #34 pc 00000000002037e8  /system/framework/arm64/boot.oat (offset 0xdc000) (java.lang.Thread.run+72)

影格 #32 至 #34 是彼此直接呼叫的編譯 Java 影格。在這種情況下,原生呼叫堆疊會與 Java 呼叫堆疊相同。

    #35 pc 0000000000559f88  /system/lib64/libart.so (art_quick_invoke_stub+584)
    #36 pc 00000000000ced40  /system/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+200)
    #37 pc 0000000000280cf0  /system/lib64/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+344)
    #38 pc 000000000027acac  /system/lib64/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+948)
    #39 pc 0000000000529f10  /system/lib64/libart.so (MterpInvokeSuper+1408)
    #40 pc 000000000054c594  /system/lib64/libart.so (ExecuteMterpImpl+14356)

影格 #35 至 #40 是轉譯器本身。

    #41 pc 00000000003ed8e0  /system/priv-app/Velvet/Velvet.apk (com.google.android.apps.gsa.shared.util.concurrent.b.i.run+20)

影格 #41 是正在解譯的 Java 程式碼。

    #42 pc 00000000002547a8  /system/lib64/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEb.llvm.780698333+496)
    #43 pc 0000000000519fd8  /system/lib64/libart.so (artQuickToInterpreterBridge+1032)
    #44 pc 00000000005630fc  /system/lib64/libart.so (art_quick_to_interpreter_bridge+92)
    #45 pc 0000000000559f88  /system/lib64/libart.so (art_quick_invoke_stub+584)
    #46 pc 00000000000ced40  /system/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+200)
    #47 pc 0000000000460d18  /system/lib64/libart.so (art::(anonymous namespace)::InvokeWithArgArray(art::ScopedObjectAccessAlreadyRunnable const&, art::ArtMethod*, art::(anonymous namespace)::ArgArray*, art::JValue*, char const*)+104)
    #48 pc 0000000000461de0  /system/lib64/libart.so (art::InvokeVirtualOrInterfaceWithJValues(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jmethodID*, jvalue*)+424)
    #49 pc 000000000048ccb0  /system/lib64/libart.so (art::Thread::CreateCallback(void*)+1120)

影格 #42 至 #49 是 VM 本身。這次是程式碼在新的執行緒上開始執行 Java。

    #50 pc 0000000000082e24  /system/lib64/libc.so (__pthread_start(void*)+36)
    #51 pc 00000000000233bc  /system/lib64/libc.so (__start_thread+68)

影格 #50 至 #51 是所有執行緒的啟動方式。這是 libc 新執行緒的啟動程式碼。