네이티브 Android 플랫폼 코드 디버깅

이 섹션에서는 플랫폼 수준 기능을 개발할 때 네이티브 Android 플랫폼 코드를 디버깅, 추적 및 프로파일링하는 데 유용한 도구와 관련 명령어를 요약합니다.

참고: 이 섹션과 이 사이트 내 다른 페이지에서는 Android의 특정 측면을 디버깅하기 위해 setprop 인수와 함께 adb의 사용을 권장합니다. Android 7.x 이하에서는 속성 이름 길이가 32자로 제한되었습니다. 즉, 앱 이름으로 wrap 속성을 만들려면 이름을 잘라서 맞춰야 했습니다. Android 8.0 이상에서는 이 제한이 훨씬 더 크기 때문에 이름을 자를 필요가 없습니다.

이 페이지에서는 logcat 출력에 있는 크래시 덤프와 관련된 기본사항을 다룹니다. 다른 페이지에는 네이티브 충돌 진단, dumpsys를 사용하여 시스템 서비스 탐색, 네이티브 메모리, 네트워크RAM 사용량 보기, AddressSanitizer를 사용하여 네이티브 코드에서 메모리 버그 감지, 성능 문제(systrace 포함) 평가, 디버거 사용에 관해 자세히 나와 있습니다.

크래시 덤프 및 Tombstone

동적으로 연결된 실행 파일이 시작되면 여러 신호 핸들러가 등록되는데, 비정상 종료 시 logcat에 기본 크래시 덤프가 기록되고 보다 자세한 Tombstone 파일은 /data/tombstones/에 기록됩니다. tombstone은 비정상 종료된 프로세스에 대한 추가 데이터가 포함된 파일입니다. 특히, 여기에는 (신호를 포착한 스레드뿐만이 아니라) 비정상 종료 프로세스 내 모든 스레드, 전체 메모리 맵 및 열려 있는 모든 파일 설명자의 스택 트레이스가 포함되어 있습니다.

Android 8.0 이전에는 debuggerddebuggerd64 데몬이 비정상 종료를 처리했습니다. Android 8.0 이상에서는 필요에 따라 crash_dump32crash_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)

전체 tombstone에서 stack을 사용할 수 있습니다. 예:

stack < FS/data/tombstones/tombstone_05

이는 현재 디렉터리에 버그 보고서를 압축을 푼 경우에 유용합니다. 네이티브 충돌 및 Tombstone 진단에 관한 자세한 내용은 네이티브 충돌 진단을 참조하세요.

실행 중인 프로세스에서 스택 트레이스/tombstone 가져오기

debuggerd 도구를 사용하여 실행 중인 프로세스에서 스택 덤프를 가져올 수 있습니다. 전체 Tombstone을 stdout으로 덤프하려면 명령줄에서 PID(프로세스 ID)를 사용하여 debuggerd를 호출합니다. 프로세스의 모든 스레드에 대한 스택만 가져오려면 -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 프레임은 별도의 .so 파일로 추출되지 않고 디스크 공간을 절약하기 위해 APK에 압축되지 않은 상태로 저장된 네이티브 JNI 코드에서 온 것입니다. Android 9 이상 버전의 스택 언와인더에는 이와 같이 일반적인 Android 관련 문제를 해결하기 위해 추출된 .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 프레임은 사전 컴파일된 자바 코드입니다. 이전 언와인더는 여기에서 중지되어 자바를 통해 해제할 수 없습니다.

    #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 프레임은 해석 중인 자바 코드입니다.

    #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 메서드는 인터프리터 프레임 #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 자체이며, 컴파일된 자바 코드에서 해석된 자바 코드로 전환하는 코드입니다.

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

프레임 #21은 #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은 해석 중인 자바 코드입니다.

    #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 프레임은 서로를 직접 호출하는 컴파일된 자바 프레임입니다. 이 경우 네이티브 호출 스택은 자바 호출 스택과 동일합니다.

    #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은 해석 중인 자바 코드입니다.

    #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 자체입니다. 이번에는 새 스레드에서 자바 실행을 시작하는 코드입니다.

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

#50~#51 프레임은 모든 스레드를 시작해야 하는 방법입니다. 이 코드는 새 스레드 시작 코드인 libc입니다.