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

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

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

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

충돌 덤프

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

Android 8.0 이전에는 debuggerddebuggerd64 데몬이 충돌을 처리했습니다. Android 8.0 이상에서는 필요에 따라 crash_dump32crash_dump64가 생성됩니다.

충돌 덤퍼는 이미 연결된 항목이 없는 경우에만 연결할 수 있습니다. 즉, strace 또는 gdb 등과 같은 도구가 충돌 덤프가 발생하지 않도록 합니다.

출력 예(타임스탬프 및 관련 없는 정보는 삭제됨):

    *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
    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 도구를 사용하여 실행 중인 프로세스에서 스택 덤프를 가져올 수 있습니다. 명령줄에서 PID(프로세스 ID)를 사용하여 전체 tombstone을 stdout으로 덤프해 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입니다.