W tej sekcji znajdziesz podsumowanie przydatnych narzędzi i powiązanych poleceń do debugowania, śledzenia i profilowania natywnego kodu platformy Android podczas tworzenia funkcji na poziomie platformy.
Uwaga: na stronach w tej sekcji i w innych miejscach na tej stronie zalecamy używanie parametru adb
w połączeniu z argumentem setprop
do debugowania niektórych aspektów Androida.
W Androidzie 7.x i starszych nazwy właściwości miały limit długości 32 znaków. Oznacza to, że aby utworzyć właściwość zawinięcia z nazwą aplikacji, konieczne było skrócenie tej nazwy. W Androidzie 8.0 i nowszych ten limit jest znacznie większy i nie wymaga skracania.
Ta strona zawiera podstawowe informacje dotyczące zrzutów pamięci znalezionych w wyjściu logcat.
Na innych stronach znajdziesz więcej informacji o diagnozowaniu awarii kodu natywnego, eksplorowaniu usług systemowych za pomocą
dumpsys
, wyświetlaniu pamięci natywnej, sieci i pamięci RAM, korzystaniu z AddressSanitizer do wykrywania błędów w pamięci w kodzie natywnym, ocenie problemów z wydajnością (w tym systrace) oraz korzystaniu z debugerów.
Zrzuty pamięci i nagrobki
Podczas uruchamiania pliku wykonywalnego z linkami dynamicznymi rejestrowanych jest kilka modułów obsługi sygnałów, które w przypadku awarii powodują zapisanie podstawowego zrzutu awarii w pliku logcat oraz bardziej szczegółowego pliku tombstone w pliku /data/tombstones/
.
Plik ten zawiera dodatkowe dane o uszkodzonym procesie. Zawiera ona między innymi ścieżki stosu dla wszystkich wątków w procesie, który uległ awarii (a nie tylko ten, który przechwycił sygnał), pełną mapę pamięci oraz listę wszystkich otwartych deskryptorów plików.
Przed Androidem 8.0 awarie były obsługiwane przez demony debuggerd
i debuggerd64
. W Androidzie 8.0 i nowszych funkcje crash_dump32
i crash_dump64
są wywoływane w miarę potrzeby.
Narzędzie do tworzenia zrzutów pamięci może się załączyć tylko wtedy, gdy nie jest już nic innego, co oznacza, że narzędzia takie jak strace
lub lldb
uniemożliwiają tworzenie zrzutów pamięci.
Przykładowy wynik (bez sygnatur czasowych i nieistotnych informacji):
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 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
Ostatni wiersz danych wyjściowych zawiera lokalizację pełnego nagrobka na dysku.
Jeśli masz nieobcięte binarne, możesz uzyskać bardziej szczegółowe informacje o rozwinięciu, wklejając stos do:development/scripts/stack
development/scripts/stack
Wskazówka: jeśli polecenie lunch
zostało już wykonane, stack
jest już w folderze $PATH
, więc nie musisz podawać pełnej ścieżki.
Przykładowe dane wyjściowe (na podstawie danych wyjściowych 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)
Możesz użyć stack
do całego komunikatu. Przykład:
stack < FS/data/tombstones/tombstone_05
Jest to przydatne, jeśli właśnie rozpakowałeś raport o błędzie w bieżącym katalogu. Więcej informacji o diagnozowaniu awarii i nagrobków natywnych znajdziesz w artykule Diagnozowanie awarii natywnych.
Pobieranie ścieżki stosu lub nagłówka z bieżącego procesu
Aby uzyskać zrzut stosu z działającego procesu, możesz użyć narzędzia debuggerd
.
W wierszu poleceń wywołaj debuggerd
, podając identyfikator procesu (PID), aby wygenerować pełny komunikat o zakończeniu procesu i przekazać go do stdout
. Aby uzyskać tylko ścieżkę dla każdego wątku w procesie, dodaj flagę -b
lub --backtrace
.
Zrozumienie złożonego odwijania
Gdy aplikacja ulega awarii, stos jest zwykle dość złożony. Poniżej znajdziesz szczegółowy przykład, który pokazuje wiele złożonych aspektów:
#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)
Ramki 00–03 pochodzą z natywnego kodu JNI, który został zapisany w nieskompresowanej postaci w pliku APK, aby zaoszczędzić miejsce na dysku, zamiast wyodrębnić go do osobnego pliku .so
. Rozwijanie stosu w Androidzie 9 i nowszych nie wymaga wyodrębnionego pliku .so
, aby poradzić sobie z tym typowym przypadkiem dotyczącym Androida.
Ramki 00–02 nie mają nazw symboli, ponieważ zostały one usunięte przez dewelopera.
Ramka 03 pokazuje, że gdy są dostępne symbole, program do odwijania z nich korzysta.
#04 pc 0000000000117550 /data/dalvik-cache/arm64/system@priv-app@Velvet@Velvet.apk@classes.dex (offset 0x108000) (com.google.speech.recognizer.AbstractRecognizer.nativeRun+160)
Ramka #04 to z góry skompilowany kod Java. Stary dekompozytor zatrzymałby się tutaj, nie mogąc odwinąć kodu przez Javę.
#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)
Ramki 05–10 pochodzą z implementacji interpretera ART.
Rozwijanie stosu w wersjach starszych niż Android 9 wyświetliłoby te klatki bez kontekstu klatki #11, który wyjaśnia, jaki kod interpretował interpreter. Te ramki są przydatne, jeśli debugujesz sam ART. Jeśli debugujesz aplikację, możesz je zignorować. Niektóre narzędzia, takie jak simpleperf
, automatycznie pomijają te klatki.
#11 pc 00000000001992d6 /system/priv-app/Velvet/Velvet.apk (offset 0x26cf000) (com.google.speech.recognizer.AbstractRecognizer.run+18)
Ramka 11 to interpretowany kod 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)
Ramki 12–16 to sama implementacja interpretera.
#17 pc 00000000002454a0 /system/priv-app/Velvet/Velvet.apk (offset 0x1322000) (com.google.android.apps.gsa.speech.e.c.c.call+28)
Ramka 17 to interpretowany kod Java. Ta metoda Java odpowiada ramkom tłumaczacza #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)
Ramki 18–20 to sama maszyna wirtualna, czyli kod służący do przejścia z skompilowanego kodu Java na interpretowany kod Java.
#21 pc 00000000002ce44c /system/framework/arm64/boot.oat (offset 0xdc000) (java.util.concurrent.FutureTask.run+204)
Ramka 21 to skompilowana metoda Java, która wywołuje metodę Java w ramce 17.
#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)
Ramki 22–27 to implementacja interpretera, która powoduje wywołanie metody z interpretowanego kodu do skompilowanej metody.
#28 pc 00000000003ed69e /system/priv-app/Velvet/Velvet.apk (com.google.android.apps.gsa.shared.util.concurrent.b.e.run+22)
Ramka 28 to interpretowany kod 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)
Ramki 29–31 to kolejny przejście między skompilowanym kodem a interpretowanym kodem.
#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)
Ramki 32–34 to skompilowane ramki Java, które wywołują się nawzajem bezpośrednio. W tym przypadku natywny stos wywołań jest taki sam jak stos wywołań w Javie.
#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)
Ramki 35–40 to sam tłumacz.
#41 pc 00000000003ed8e0 /system/priv-app/Velvet/Velvet.apk (com.google.android.apps.gsa.shared.util.concurrent.b.i.run+20)
Ramka 41 to interpretowany kod 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)
Ramki 42–49 to sama maszyna wirtualna. Tym razem jest to kod, który uruchamia Java na nowym wątku.
#50 pc 0000000000082e24 /system/lib64/libc.so (__pthread_start(void*)+36) #51 pc 00000000000233bc /system/lib64/libc.so (__start_thread+68)
Ramki 50–51 to punkt, w którym powinny się rozpoczynać wszystkie wątki. To libc
nowy kod rozpoczęcia wątku.