В этом разделе приведены полезные инструменты и связанные с ними команды для отладки, трассировки и профилирования нативного кода платформы Android при разработке функций на уровне платформы.
Примечание. На страницах этого раздела и других частях сайта рекомендуется использовать adb вместе с аргументом setprop для отладки некоторых аспектов Android.
В Android 7.x и более ранних версиях длина названий свойств не должна была превышать 32 символа. Это означало, что для создания обернутого ресурса с названием приложения его нужно было сократить. В Android 8.0 и более поздних версий это ограничение значительно больше, поэтому обрезать текст не нужно.
На этой странице описаны основы работы с дампами памяти, которые можно найти в выходных данных logcat.
На других страницах вы найдете подробную информацию о том, как диагностировать сбои на уровне ОС, изучать системные сервисы с помощью
dumpsys, просматривать нативную память, сеть и ОЗУ, использовать AddressSanitizer для обнаружения ошибок памяти в нативном коде, оценивать проблемы производительности (включая Systrace) и использовать отладчики.
Дампы памяти и файлы tombstone
При запуске динамически связанного исполняемого файла регистрируется несколько обработчиков сигналов, которые в случае сбоя записывают базовый аварийный дамп в logcat и более подробный файл tombstone в /data/tombstones/.
Tombstone – это файл с дополнительными данными о процессе, который завершился со сбоем. В частности, в нем содержатся трассировки стека для всех потоков в процессе, который завершился с ошибкой (а не только для потока, который перехватил сигнал), полная карта памяти и список всех дескрипторов открытых файлов.
До Android 8.0 обработкой сбоев занимались демоны debuggerd и debuggerd64. В Android 8.0 и более поздних версиях crash_dump32 и crash_dump64 создаются по мере необходимости.
Сборщик дампов может подключиться, только если к процессу не подключено ничего другого. Это означает, что использование таких инструментов, как strace или lldb, предотвращает создание дампов.
Пример результата (с удаленными временными метками и лишней информацией):
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
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
В последней строке вывода указано местоположение полного файла tombstone на диске.
Если у вас есть нераспакованные двоичные файлы, вы можете получить более подробную информацию о стеке с номерами строк, вставив его в development/scripts/stack:
development/scripts/stack
Совет. Если вы уже запустили lunch, то stack уже находится в $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.
В командной строке вызовите debuggerd, используя идентификатор процесса (PID), чтобы сбросить полный файл tombstone в 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.
У фреймов 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, скомпилированный заранее. Старый декомпилятор остановился бы на этом этапе, поскольку не мог декомпилировать код 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)
Фреймы 5–10 относятся к реализации интерпретатора ART.
В версиях Android ниже 9.0 эти кадры показывались без контекста кадра 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 – это сама виртуальная машина, код для перехода от скомпилированного кода Java к интерпретированному коду Java.
#21 pc 00000000002ce44c /system/framework/arm64/boot.oat (offset 0xdc000) (java.util.concurrent.FutureTask.run+204)
Кадр 21 – это скомпилированный метод Java, который вызывает метод Java в кадре 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)
Фреймы 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 – это сама виртуальная машина. На этот раз код запускает 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.