Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

네이티브 충돌 진단

다음 섹션에는 네이티브 충돌의 일반적인 유형, 샘플 크래시 덤프의 분석, 이력 표시에 관한 설명이 포함되어 있습니다. 각 충돌 유형에는 강조표시된 주요 증거가 있는 예시 debuggerd 출력이 포함되어 있어 특정 유형의 충돌을 구분할 수 있습니다.

중단

중단은 의도적인 작업이라는 점에서 흥미롭습니다. abort(3) 호출, assert(3) 실패, Android와 관련된 치명적인 로깅 유형 중 하나 사용 등 여러 가지 방법으로 중단할 수 있지만, 모든 작업은 abort 호출을 수반합니다. abort 호출은 SIGABRT가 포함된 호출 스레드 신호를 보내므로 이러한 경우를 인식하기 위해서는libc.so에 'abort'를 표시하는 프레임과 SIGABRT를 debuggerd 출력에서 찾아야 합니다.

명시적인 '중단 메시지' 행이 있을 수도 있습니다. 또한 assert(3) 또는 높은 수준의 치명적인 로깅 기능과 달리, abort(3)는 메시지를 수락하지 않으므로 logcat 출력을 검토하여 스레드가 의도적으로 자체 중단되기 전에 남긴 로그를 확인해야 합니다.

현재 Android 버전에서는 tgkill(2) 시스템 호출을 인라인 함수로 제공하므로 맨 위에 있는 abort(3) 호출을 포함하여 스택을 가장 쉽게 읽을 수 있습니다.

    pid: 4637, tid: 4637, name: crasher  >>> crasher <<<
    signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
    Abort message: 'some_file.c:123: some_function: assertion "false" failed'
        r0  00000000  r1  0000121d  r2  00000006  r3  00000008
        r4  0000121d  r5  0000121d  r6  ffb44a1c  r7  0000010c
        r8  00000000  r9  00000000  r10 00000000  r11 00000000
        ip  ffb44c20  sp  ffb44a08  lr  eace2b0b  pc  eace2b16
    backtrace:
        #00 pc 0001cb16  /system/lib/libc.so (abort+57)
        #01 pc 0001cd8f  /system/lib/libc.so (__assert2+22)
        #02 pc 00001531  /system/bin/crasher (do_action+764)
        #03 pc 00002301  /system/bin/crasher (main+68)
        #04 pc 0008a809  /system/lib/libc.so (__libc_init+48)
        #05 pc 00001097  /system/bin/crasher (_start_main+38)
    

이전 Android 버전에서는 원래의 중단 호출(여기서는 프레임 4)과 신호의 실제 전송(여기서는 프레임 0) 사이의 복잡한 경로를 따랐습니다. __libc_android_abort(여기서는 프레임 3)를 다른 플랫폼의 raise/pthread_kill/tgkill 시퀀스에 추가한 32비트 ARM의 경우 특히 그러했습니다.

    pid: 1656, tid: 1656, name: crasher  >>> crasher <<<
    signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
    Abort message: 'some_file.c:123: some_function: assertion "false" failed'
        r0 00000000  r1 00000678  r2 00000006  r3 f70b6dc8
        r4 f70b6dd0  r5 f70b6d80  r6 00000002  r7 0000010c
        r8 ffffffed  r9 00000000  sl 00000000  fp ff96ae1c
        ip 00000006  sp ff96ad18  lr f700ced5  pc f700dc98  cpsr 400b0010
    backtrace:
        #00 pc 00042c98  /system/lib/libc.so (tgkill+12)
        #01 pc 00041ed1  /system/lib/libc.so (pthread_kill+32)
        #02 pc 0001bb87  /system/lib/libc.so (raise+10)
        #03 pc 00018cad  /system/lib/libc.so (__libc_android_abort+34)
        #04 pc 000168e8  /system/lib/libc.so (abort+4)
        #05 pc 0001a78f  /system/lib/libc.so (__libc_fatal+16)
        #06 pc 00018d35  /system/lib/libc.so (__assert2+20)
        #07 pc 00000f21  /system/xbin/crasher
        #08 pc 00016795  /system/lib/libc.so (__libc_init+44)
        #09 pc 00000abc  /system/xbin/crasher
    

crasher abort을 사용하면 이러한 유형의 충돌 인스턴스를 재현할 수 있습니다.

순수 null 포인터 역 참조

이 유형은 기본적인 네이티브 충돌로 다음 충돌 유형의 특수한 경우일 뿐이지만 최소한의 지식은 필요하기 때문에 별도로 언급할 만한 가치가 있습니다.

아래의 예에서는 충돌 함수가 libc.so에 있더라도 문자열 함수는 주어진 포인터만 연산하므로 strlen(3)이 null 포인터로 호출되었으며 이 충돌이 호출 코드의 작성자에게 곧바로 전달되어야 한다는 것을 추론할 수 있습니다. 이 경우 프레임 #01은 잘못된 호출자입니다.

    pid: 25326, tid: 25326, name: crasher  >>> crasher <<<
    signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
        r0 00000000  r1 00000000  r2 00004c00  r3 00000000
        r4 ab088071  r5 fff92b34  r6 00000002  r7 fff92b40
        r8 00000000  r9 00000000  sl 00000000  fp fff92b2c
        ip ab08cfc4  sp fff92a08  lr ab087a93  pc efb78988  cpsr 600d0030

    backtrace:
        #00 pc 00019988  /system/lib/libc.so (strlen+71)
        #01 pc 00001a8f  /system/xbin/crasher (strlen_null+22)
        #02 pc 000017cd  /system/xbin/crasher (do_action+948)
        #03 pc 000020d5  /system/xbin/crasher (main+100)
        #04 pc 000177a1  /system/lib/libc.so (__libc_init+48)
        #05 pc 000010e4  /system/xbin/crasher (_start+96)
    

crasher strlen-NULL을 사용하면 이러한 유형의 충돌 인스턴스를 재현할 수 있습니다.

낮은 주소 null 포인터 역참조

대개 오류 주소는 0 이외의 낮은 수입니다. 특히 2자리 또는 3자리 주소는 매우 일반적이며 6자리 주소는 1MiB 오프셋이 필요하기 때문에 대개 null 포인터 역참조가 아닙니다. 이러한 경우는 일반적으로 유효한 구조인 것처럼 null 포인터를 역참조하는 코드가 있을 때 발생합니다. 일반적인 함수는 fprintf(3)(또는 FILE*을 취하는 다른 함수) 및 readdir(3)입니다. 코드가 fopen(3) 또는 opendir(3) 호출이 실제로 먼저 성공했는지 확인하는 데 실패하는 경우가 많기 때문입니다.

다음은 readdir의 예시입니다.

    pid: 25405, tid: 25405, name: crasher  >>> crasher <<<
    signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xc
        r0 0000000c  r1 00000000  r2 00000000  r3 3d5f0000
        r4 00000000  r5 0000000c  r6 00000002  r7 ff8618f0
        r8 00000000  r9 00000000  sl 00000000  fp ff8618dc
        ip edaa6834  sp ff8617a8  lr eda34a1f  pc eda618f6  cpsr 600d0030

    backtrace:
        #00 pc 000478f6  /system/lib/libc.so (pthread_mutex_lock+1)
        #01 pc 0001aa1b  /system/lib/libc.so (readdir+10)
        #02 pc 00001b35  /system/xbin/crasher (readdir_null+20)
        #03 pc 00001815  /system/xbin/crasher (do_action+976)
        #04 pc 000021e5  /system/xbin/crasher (main+100)
        #05 pc 000177a1  /system/lib/libc.so (__libc_init+48)
        #06 pc 00001110  /system/xbin/crasher (_start+96)
    

여기에서 충돌의 직접적인 원인은 pthread_mutex_lock(3)이 주소 0xc(프레임 0)에 액세스를 시도했기 때문입니다. 하지만 가장 먼저 pthread_mutex_lock이 실행하는 작업은 주어진 pthread_mutex_t*state 요소를 역참조하는 것입니다. 소스를 살펴보면 요소가 구조의 오프셋 0에 있으며, 이를 통해 pthread_mutex_lock에 잘못된 포인터인 0xc가 지정되었음을 알 수 있습니다. 프레임 1에서는 주어진 DIR*에서 mutex_ 필드를 추출하는 readdir에 의해 포인터가 주어졌음을 확인할 수 있습니다. 이 구조를 보면 mutex_가 오프셋 sizeof(int) + sizeof(size_t) + sizeof(dirent*) = struct DIR(32비트 기기에서 4 + 4 + 4 = 12 = 0xc)에 있으므로 호출자가 readdir에 null 포인터를 전달했다는 버그를 확인할 수 있습니다. 이때 스택 도구에 스택을 붙여 logcat에서 이러한 버그가 발생한 위치를 찾을 수 있습니다.

      struct DIR {
        int fd_;
        size_t available_bytes_;
        dirent* next_;
        pthread_mutex_t mutex_;
        dirent buff_[15];
        long current_pos_;
      };
    

대부분의 경우 이 분석을 건너뛰어도 됩니다. 오류 주소가 매우 낮다는 것은 일반적으로 개발자가 스택의 libc.so 프레임을 건너뛰고 직접 호출 코드를 확인할 수 있다는 것을 의미합니다. 하지만 항상 그런 것은 아니며 이런 방식으로 설득력 있는 케이스를 제시할 수 있습니다.

crasher fprintf-NULL 또는 crasher readdir-NULL을 사용하면 이러한 유형의 충돌 인스턴스를 재현할 수 있습니다.

FORTIFY 실패

FORTIFY 실패는 C 라이브러리가 보안 취약점을 일으킬 수 있는 문제를 발견했을 때 발생하는 특수한 중단 케이스입니다. 많은 C 라이브러리 함수는 강화되어 버퍼가 실제로 얼마나 큰지 알려주는 추가적인 인수를 갖게 되며 런타임 시 처리하려는 작업이 실제로 적합한지도 확인합니다. 실제 10바이트 길이에 불과한 버퍼로의 read(fd, buf, 32)를 처리하는 코드의 예시는 다음과 같습니다.

    pid: 25579, tid: 25579, name: crasher  >>> crasher <<<
    signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
    Abort message: 'FORTIFY: read: prevented 32-byte write into 10-byte buffer'
        r0 00000000  r1 000063eb  r2 00000006  r3 00000008
        r4 ff96f350  r5 000063eb  r6 000063eb  r7 0000010c
        r8 00000000  r9 00000000  sl 00000000  fp ff96f49c
        ip 00000000  sp ff96f340  lr ee83ece3  pc ee86ef0c  cpsr 000d0010

    backtrace:
        #00 pc 00049f0c  /system/lib/libc.so (tgkill+12)
        #01 pc 00019cdf  /system/lib/libc.so (abort+50)
        #02 pc 0001e197  /system/lib/libc.so (__fortify_fatal+30)
        #03 pc 0001baf9  /system/lib/libc.so (__read_chk+48)
        #04 pc 0000165b  /system/xbin/crasher (do_action+534)
        #05 pc 000021e5  /system/xbin/crasher (main+100)
        #06 pc 000177a1  /system/lib/libc.so (__libc_init+48)
        #07 pc 00001110  /system/xbin/crasher (_start+96)
    

crasher fortify을 사용하면 이러한 유형의 충돌 인스턴스를 재현할 수 있습니다.

-fstack-protector에 의해 스택 손상 감지

컴파일러의 -fstack-protector 옵션은 버퍼 오버런을 방지하기 위해 스택에 버퍼가 있는 함수에 검사를 삽입합니다. 이 옵션은 기본적으로 플랫폼 코드에 사용되고 앱에는 사용되지 않습니다. 이 옵션을 사용하면 컴파일러는 스택의 마지막 로컬을 방금 지난 임의의 값을 함수 말미에 쓰고 이 값을 다시 읽어들여 변경되지 않았는지 확인하도록 함수 서두에 명령을 추가합니다. 값이 변경되면 이 값은 버퍼 오버런으로 인해 덮어쓰기되므로 말미에서 __stack_chk_fail가 호출되어 메시지를 로깅하고 중단합니다.

    pid: 26717, tid: 26717, name: crasher  >>> crasher <<<
    signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
    Abort message: 'stack corruption detected'
        r0 00000000  r1 0000685d  r2 00000006  r3 00000008
        r4 ffd516d8  r5 0000685d  r6 0000685d  r7 0000010c
        r8 00000000  r9 00000000  sl 00000000  fp ffd518bc
        ip 00000000  sp ffd516c8  lr ee63ece3  pc ee66ef0c  cpsr 000e0010

    backtrace:
        #00 pc 00049f0c  /system/lib/libc.so (tgkill+12)
        #01 pc 00019cdf  /system/lib/libc.so (abort+50)
        #02 pc 0001e07d  /system/lib/libc.so (__libc_fatal+24)
        #03 pc 0004863f  /system/lib/libc.so (__stack_chk_fail+6)
        #04 pc 000013ed  /system/xbin/crasher (smash_stack+76)
        #05 pc 00001591  /system/xbin/crasher (do_action+280)
        #06 pc 00002219  /system/xbin/crasher (main+100)
        #07 pc 000177a1  /system/lib/libc.so (__libc_init+48)
        #08 pc 00001144  /system/xbin/crasher (_start+96)
    

이러한 중단 유형은 backtrace 및 특정 중단 메시지 내 __stack_chk_fail의 유무를 통해 다른 중단 유형과 구분할 수 있습니다.

crasher smash-stack을 사용하면 이러한 유형의 충돌 인스턴스를 재현할 수 있습니다.

허용되지 않은 시스템 호출의 Seccomp SIGSYS

seccomp 시스템(특히 seccomp-bpf)은 시스템 호출에 대한 액세스를 제한합니다. 플랫폼 개발자용 seccomp에 관한 자세한 내용은 Android O의 Seccomp 필터 블로그 게시물을 참조하세요. 제한된 시스템 호출을 호출하는 스레드는 SYS_SECCOMP 코드가 있는 SIGSYS 신호를 받습니다. 시스템 호출 번호는 아키텍처와 함께 원인 행에 표시됩니다. 시스템 호출 번호는 아키텍처에 따라 다르다는 점에 유의해야 합니다. 예를 들어 readlinkat(2) 시스템 호출은 x86에서 305이지만 x86-64에서는 267입니다. arm과 arm64에서도 호출 번호가 다릅니다. 시스템 호출 번호는 아키텍처에 따라 다르므로 헤더에서 시스템 호출 번호를 찾는 것보다는 스택 추적을 사용하여 어떤 시스템 호출이 허용되지 않았는지 확인하는 편이 일반적으로 더 쉽습니다.

    pid: 11046, tid: 11046, name: crasher  >>> crasher <<<
    signal 31 (SIGSYS), code 1 (SYS_SECCOMP), fault addr --------
    Cause: seccomp prevented call to disallowed arm system call 99999
        r0 cfda0444  r1 00000014  r2 40000000  r3 00000000
        r4 00000000  r5 00000000  r6 00000000  r7 0001869f
        r8 00000000  r9 00000000  sl 00000000  fp fffefa58
        ip fffef898  sp fffef888  lr 00401997  pc f74f3658  cpsr 600f0010

    backtrace:
        #00 pc 00019658  /system/lib/libc.so (syscall+32)
        #01 pc 00001993  /system/bin/crasher (do_action+1474)
        #02 pc 00002699  /system/bin/crasher (main+68)
        #03 pc 0007c60d  /system/lib/libc.so (__libc_init+48)
        #04 pc 000011b0  /system/bin/crasher (_start_main+72)
    

허용되지 않은 시스템 호출은 신호 행의 SYS_SECCOMP 유무와 원인 행에 있는 설명을 통해 다른 충돌과 구분할 수 있습니다.

crasher seccomp를 사용하면 이러한 유형의 충돌 인스턴스를 재현할 수 있습니다.

실행 전용 메모리 위반

Android 10 이상의 arm64의 경우 바이너리 및 라이브러리의 실행 가능 세그먼트는 코드 재사용 공격에 대한 강화 기술로서 메모리 실행 전용(읽기 불가)으로 매핑됩니다. 읽기 프리미티브는 실행 가능한 메모리에서 알려진 함수를 검색하거나 메모리 레이아웃에 관한 사전 지식 없이 가젯 체인을 생성하여 주소 공간 레이아웃 임의 추출(ASLR)을 우회할 수 있습니다. 실행 가능 코드를 읽을 수 없는 것으로 표시하면 읽기 프리미티브가 실행 가능 메모리에 액세스할 수 없으므로 악용 기법 전체를 사용할 수 없습니다. 유효한 대상이 더 이상 읽기 프리미티브를 가진 공격자에게 노출될 수 없으므로 이 방법은 ASLR 효과뿐만 아니라 제어 흐름 무결성(CFI)도 개선합니다.

코드를 읽을 수 없게 하면 실행 전용으로 표시된 메모리 세그먼트에 대한 의도적인 읽기와 의도하지 않은 읽기가 SEGV_ACCERR 코드를 통해 SIGSEGV를 발생시킵니다. 이는 버그, 취약점, 코드와 혼합된 데이터(예: 리터럴 풀) 또는 의도적인 메모리 검사의 결과로 발생할 수 있습니다.

컴파일러는 코드와 데이터가 상호 혼합되지 않은 것으로 가정하지만 직접 작성한 어셈블리에서는 문제가 발생할 수 있습니다. 대부분의 경우 상수를 .data 섹션으로 이동하기만 하면 문제를 해결할 수 있습니다. 실행 코드 섹션에 코드 검사가 반드시 필요한 경우 먼저 mprotect(2)를 호출하여 코드를 읽을 수 있는 것으로 표시한 다음 작업이 완료된 후에 다시 읽을 수 없도록 표시해야 합니다.

    pid: 2938, tid: 2940, name: crasher64  >>> crasher64 <<<
    signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x5f2ced24a8
    Cause: execute-only (no-read) memory access error; likely due to data in .text.
        x0  0000000000000000  x1  0000005f2cecf21f  x2  0000000000000078  x3  0000000000000053
        x4  0000000000000074  x5  8000000000000000  x6  ff71646772607162  x7  00000020dcf0d16c
        x8  0000005f2ced24a8  x9  000000781251c55e  x10 0000000000000000  x11 0000000000000000
        x12 0000000000000014  x13 ffffffffffffffff  x14 0000000000000002  x15 ffffffffffffffff
        x16 0000005f2ced52f0  x17 00000078125c0ed8  x18 0000007810e8e000  x19 00000078119fbd50
        x20 00000078125d6020  x21 00000078119fbd50  x22 00000b7a00000b7a  x23 00000078119fbdd8
        x24 00000078119fbd50  x25 00000078119fbd50  x26 00000078119fc018  x27 00000078128ea020
        x28 00000078119fc020  x29 00000078119fbcb0
        sp  00000078119fba40  lr  0000005f2ced1b94  pc  0000005f2ced1ba4

    backtrace:
          #00 pc 0000000000003ba4  /system/bin/crasher64 (do_action+2348)
          #01 pc 0000000000003234  /system/bin/crasher64 (thread_callback+44)
          #02 pc 00000000000e2044  /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+36)
          #03 pc 0000000000083de0  /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64)
    

원인 행을 통해 실행 전용 메모리 위반을 다른 충돌과 구분할 수 있습니다.

crasher xom을 사용하면 이러한 유형의 충돌 인스턴스를 재현할 수 있습니다.

fdsan에 의해 오류 감지

Android의 fdsan 파일 설명자 sanitizer를 통해 종료 후 사용 및 이중 종료와 같은 파일 설명자의 일반적인 실수를 감지할 수 있습니다. 이러한 오류의 디버깅 및 방지에 관한 자세한 내용은 fdsan 문서를 참조하세요.

    pid: 32315, tid: 32315, name: crasher64  >>> crasher64 <<<
    signal 35 (), code -1 (SI_QUEUE), fault addr --------
    Abort message: 'attempted to close file descriptor 3, expected to be unowned, actually owned by FILE* 0x7d8e413018'
        x0  0000000000000000  x1  0000000000007e3b  x2  0000000000000023  x3  0000007fe7300bb0
        x4  3033313465386437  x5  3033313465386437  x6  3033313465386437  x7  3831303331346538
        x8  00000000000000f0  x9  0000000000000000  x10 0000000000000059  x11 0000000000000034
        x12 0000007d8ebc3a49  x13 0000007fe730077a  x14 0000007fe730077a  x15 0000000000000000
        x16 0000007d8ec9a7b8  x17 0000007d8ec779f0  x18 0000007d8f29c000  x19 0000000000007e3b
        x20 0000000000007e3b  x21 0000007d8f023020  x22 0000007d8f3b58dc  x23 0000000000000001
        x24 0000007fe73009a0  x25 0000007fe73008e0  x26 0000007fe7300ca0  x27 0000000000000000
        x28 0000000000000000  x29 0000007fe7300c90
        sp  0000007fe7300860  lr  0000007d8ec2f22c  pc  0000007d8ec2f250

    backtrace:
          #00 pc 0000000000088250  /bionic/lib64/libc.so (fdsan_error(char const*, ...)+384)
          #01 pc 0000000000088060  /bionic/lib64/libc.so (android_fdsan_close_with_tag+632)
          #02 pc 00000000000887e8  /bionic/lib64/libc.so (close+16)
          #03 pc 000000000000379c  /system/bin/crasher64 (do_action+1316)
          #04 pc 00000000000049c8  /system/bin/crasher64 (main+96)
          #05 pc 000000000008021c  /bionic/lib64/libc.so (_start_main)
    

이러한 중단 유형은 backtrace 및 특정 중단 메시지 내 fdsan_error의 유무를 통해 다른 중단 유형과 구분할 수 있습니다.

crasher fdsan_file 또는 crasher fdsan_dir을 사용하면 이러한 유형의 충돌 인스턴스를 재현할 수 있습니다.

크래시 덤프 조사

현재 조사 중인 특정 충돌이 없는 경우 플랫폼 소스에는 crasher라고 하는 debuggerd 테스트 도구가 포함됩니다. system/core/debuggerd/mm이 있는 경우 경로에 crashercrasher64를 모두 얻게 됩니다. 후자의 경우는 64비트 충돌을 테스트할 수 있습니다. crasher는 개발자가 제공하는 명령줄 인수에 기반하여 여러 가지 흥미로운 방법으로 충돌시킬 수 있습니다. crasher --help를 사용하여 현재 지원되는 선택항목을 확인할 수 있습니다.

크래시 덤프의 여러 부분을 소개하고자 다음 크래시 덤프 예시를 살펴보겠습니다.

    *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
    Build fingerprint: 'Android/aosp_flounder/flounder:5.1.51/AOSP/enh08201009:eng/test-keys'
    Revision: '0'
    ABI: 'arm'
    pid: 1656, tid: 1656, name: crasher  >>> crasher <<<
    signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
    Abort message: 'some_file.c:123: some_function: assertion "false" failed'
        r0 00000000  r1 00000678  r2 00000006  r3 f70b6dc8
        r4 f70b6dd0  r5 f70b6d80  r6 00000002  r7 0000010c
        r8 ffffffed  r9 00000000  sl 00000000  fp ff96ae1c
        ip 00000006  sp ff96ad18  lr f700ced5  pc f700dc98  cpsr 400b0010
    backtrace:
        #00 pc 00042c98  /system/lib/libc.so (tgkill+12)
        #01 pc 00041ed1  /system/lib/libc.so (pthread_kill+32)
        #02 pc 0001bb87  /system/lib/libc.so (raise+10)
        #03 pc 00018cad  /system/lib/libc.so (__libc_android_abort+34)
        #04 pc 000168e8  /system/lib/libc.so (abort+4)
        #05 pc 0001a78f  /system/lib/libc.so (__libc_fatal+16)
        #06 pc 00018d35  /system/lib/libc.so (__assert2+20)
        #07 pc 00000f21  /system/xbin/crasher
        #08 pc 00016795  /system/lib/libc.so (__libc_init+44)
        #09 pc 00000abc  /system/xbin/crasher
    Tombstone written to: /data/tombstones/tombstone_06
    *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
    

공백이 있는 별표 행은 로그에서 네이티브 충돌을 찾는 경우에 유용합니다. '*** ***' 문자열은 네이티브 충돌의 시작부가 아니면 로그에 표시되는 일이 거의 없습니다.

    Build fingerprint:
    'Android/aosp_flounder/flounder:5.1.51/AOSP/enh08201009:eng/test-keys'
    

지문을 사용하면 어떤 빌드에서 충돌이 발생했는지 정확히 식별할 수 있습니다. 이는 ro.build.fingerprint 시스템 속성과 완전히 동일합니다.

    Revision: '0'
    

Revision은 소프트웨어보다는 하드웨어를 지칭합니다. 이는 일반적으로는 사용되지 않지만, 잘못된 하드웨어로 인한 것으로 알려진 버그를 자동으로 무시하는 데 유용할 수 있습니다. 이는 ro.revision 시스템 속성과 완전히 동일합니다.

    ABI: 'arm'
    

ABI는 arm, arm64, x86 또는 x86-64 중 하나입니다. 이는 대부분의 경우 위에서 언급한 stack 스크립트에 유용하며, 이를 통해 어떤 도구 모음을 사용해야 할지 파악할 수 있습니다.

    pid: 1656, tid: 1656, name: crasher >>> crasher <<<
    

이 행은 프로세스에서 충돌이 발생한 특정 스레드를 식별합니다. 여기에서는 프로세스의 기본 스레드이므로 프로세스 ID와 스레드 ID가 일치합니다. 첫 번째 이름은 스레드 이름이며 >>> 및 <<<에 둘러싸인 이름은 프로세스 이름입니다. 앱의 경우, 프로세스 이름은 일반적으로 com.facebook.katana와 같은 정규화된 패키지 이름이며, 이는 버그를 신고하거나 Google Play에서 앱을 찾는 데 유용합니다. pid와 tid는 충돌이 발생하기 이전의 관련 로그 행을 찾는 데도 유용합니다.

    signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
    

이 행은 수신된 신호(SIGABRT)와 수신 방법(SI_TKILL)에 관한 정보를 제공합니다. debuggerd에 의해 보고된 신호는 SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP입니다. 신호별 코드는 특정 신호에 따라 다릅니다.

    Abort message: 'some_file.c:123: some_function: assertion "false" failed'
    

모든 충돌에 중단 메시지 행이 있는 것은 아니지만 모든 중단에는 중단 메시지 행이 있습니다. 중단 메시지는 이 pid/tid와 관련된 치명적인 logcat 출력의 마지막 행으로부터 자동으로 수집되며, 의도적인 중단의 경우에는 프로그램이 자체적으로 중단된 이유를 제공할 수 있습니다.

    r0 00000000 r1 00000678 r2 00000006 r3 f70b6dc8
    r4 f70b6dd0 r5 f70b6d80 r6 00000002 r7 0000010c
    r8 ffffffed r9 00000000 sl 00000000 fp ff96ae1c
    ip 00000006 sp ff96ad18 lr f700ced5 pc f700dc98 cpsr 400b0010
    

레지스터 덤프는 신호가 수신된 시점에 CPU 레지스터의 내용을 보여줍니다. 이 섹션은 ABI에 따라 매우 다양합니다. 이러한 기능의 유용성은 구체적인 충돌 내용에 따라 다릅니다.

    backtrace:
        #00 pc 00042c98 /system/lib/libc.so (tgkill+12)
        #01 pc 00041ed1 /system/lib/libc.so (pthread_kill+32)
        #02 pc 0001bb87 /system/lib/libc.so (raise+10)
        #03 pc 00018cad /system/lib/libc.so (__libc_android_abort+34)
        #04 pc 000168e8 /system/lib/libc.so (abort+4)
        #05 pc 0001a78f /system/lib/libc.so (__libc_fatal+16)
        #06 pc 00018d35 /system/lib/libc.so (__assert2+20)
        #07 pc 00000f21 /system/xbin/crasher
        #08 pc 00016795 /system/lib/libc.so (__libc_init+44)
        #09 pc 00000abc /system/xbin/crasher
    

backtrace는 충돌 시점의 코드 위치를 보여줍니다. 첫 번째 열은 가장 깊은 프레임이 0인 gdb의 스타일과 일치하는 프레임 숫자입니다. PC 값은 절대 주소가 아닌 공유 라이브러리의 위치를 기준으로 합니다. 다음 열은 매핑된 리전의 이름입니다. 이는 일반적으로 공유 라이브러리나 실행 파일이지만 JIT 컴파일 코드에는 적합하지 않을 수 있습니다. 마지막으로 기호를 사용할 수 있는 경우 PC 값에 해당하는 기호가 오프셋과 함께 바이트로 표시됩니다. 이 기호를 objdump(1)와 함께 사용하여 해당하는 어셈블러 명령을 찾을 수 있습니다.

이력 표시 읽기

    Tombstone written to: /data/tombstones/tombstone_06
    

이를 통해 debuggerd에서 추가 정보를 작성한 위치를 알 수 있습니다. debuggerd는 00에서 09 사이의 숫자를 순환하며 필요에 따라 기존의 이력 표시를 덮어씀으로써 최대 10개의 이력 표시를 유지합니다.

이력 표시에는 크래시 덤프와 동일한 정보 및 몇 가지 추가 정보가 포함됩니다. 예를 들어 충돌 스레드를 비롯한 모든 스레드, 부동 소수점 레지스터, 원시 스택 덤프, 레지스터 내 주소 주변의 메모리 덤프에 대한 backtrace를 포함합니다. 가장 유용하게는 /proc/pid/maps와 유사하게 전체 메모리 맵도 포함합니다. 다음은 32비트 ARM 프로세스 충돌의 주석 표시된 예시입니다.

    memory map: (fault address prefixed with --->)
    --->ab15f000-ab162fff r-x 0 4000 /system/xbin/crasher (BuildId:
    b9527db01b5cf8f5402f899f64b9b121)
    

여기에서 두 가지 사항을 확인할 수 있습니다. 첫 번째는 이 행의 접두어가 '--->'라는 점입니다. 맵은 충돌이 단지 null 포인터 역참조가 아닌 경우에 가장 유용합니다. 오류 주소가 작은 경우 이는 null 포인터 역참조의 일부 변형일 가능성이 높습니다. 그 외에도 오류 주소 주변 맵을 보면 어떤 일이 발생했는지에 관한 단서를 얻을 수 있는 경우가 많습니다. 맵을 살펴봄으로써 확인할 수 있는 몇 가지 문제는 다음과 같습니다.

  • 읽기/쓰기가 메모리 블록의 끝을 지난 다음에 이루어집니다.
  • 읽기/쓰기가 메모리 블록이 시작되기 전에 이루어집니다.
  • 코드가 아닌 것을 실행하려고 시도합니다.
  • 스택 끝을 벗어나서 실행됩니다.
  • 위 예시처럼 코드에 쓰기를 시도합니다.

두 번째로 참고할 사항은 실행 파일과 공유 라이브러리 파일은 Android 6.0 이상에서 BuildId(있는 경우)를 표시하므로 코드의 어떤 버전에서 충돌이 발생했는지 정확히 확인할 수 있다는 점입니다. Android 6.0 이후의 플랫폼 바이너리에는 기본적으로 BuildId가 포함되어 있습니다. NDK r12 이상에서는 또한 -Wl,--build-id를 자동으로 링커에 전달합니다.

    ab163000-ab163fff r--      3000      1000  /system/xbin/crasher
    ab164000-ab164fff rw-         0      1000
    f6c80000-f6d7ffff rw-         0    100000  [anon:libc_malloc]
    

Android에서 힙이 반드시 단일 리전인 것은 아닙니다. 힙 리전은 [anon:libc_malloc]으로 표시됩니다.

    f6d82000-f6da1fff r--         0     20000  /dev/__properties__/u:object_r:logd_prop:s0
    f6da2000-f6dc1fff r--         0     20000  /dev/__properties__/u:object_r:default_prop:s0
    f6dc2000-f6de1fff r--         0     20000  /dev/__properties__/u:object_r:logd_prop:s0
    f6de2000-f6de5fff r-x         0      4000  /system/lib/libnetd_client.so (BuildId: 08020aa06ed48cf9f6971861abf06c9d)
    f6de6000-f6de6fff r--      3000      1000  /system/lib/libnetd_client.so
    f6de7000-f6de7fff rw-      4000      1000  /system/lib/libnetd_client.so
    f6dec000-f6e74fff r-x         0     89000  /system/lib/libc++.so (BuildId: 8f1f2be4b37d7067d366543fafececa2) (load base 0x2000)
    f6e75000-f6e75fff ---         0      1000
    f6e76000-f6e79fff r--     89000      4000  /system/lib/libc++.so
    f6e7a000-f6e7afff rw-     8d000      1000  /system/lib/libc++.so
    f6e7b000-f6e7bfff rw-         0      1000  [anon:.bss]
    f6e7c000-f6efdfff r-x         0     82000  /system/lib/libc.so (BuildId: d189b369d1aafe11feb7014d411bb9c3)
    f6efe000-f6f01fff r--     81000      4000  /system/lib/libc.so
    f6f02000-f6f03fff rw-     85000      2000  /system/lib/libc.so
    f6f04000-f6f04fff rw-         0      1000  [anon:.bss]
    f6f05000-f6f05fff r--         0      1000  [anon:.bss]
    f6f06000-f6f0bfff rw-         0      6000  [anon:.bss]
    f6f0c000-f6f21fff r-x         0     16000  /system/lib/libcutils.so (BuildId: d6d68a419dadd645ca852cd339f89741)
    f6f22000-f6f22fff r--     15000      1000  /system/lib/libcutils.so
    f6f23000-f6f23fff rw-     16000      1000  /system/lib/libcutils.so
    f6f24000-f6f31fff r-x         0      e000  /system/lib/liblog.so (BuildId: e4d30918d1b1028a1ba23d2ab72536fc)
    f6f32000-f6f32fff r--      d000      1000  /system/lib/liblog.so
    f6f33000-f6f33fff rw-      e000      1000  /system/lib/liblog.so
    

일반적으로 공유 라이브러리에는 3개의 인접 항목이 있습니다. 하나는 읽기 가능 및 실행 가능 코드이며, 하나는 읽기 전용 데이터이고, 다른 하나는 읽기-쓰기(변경 가능) 데이터입니다. 첫 번째 열은 매핑의 주소 범위를 표시하고, 두 번째 열은 일반적인 Unix ls(1) 스타일의 권한이며, 세 번째 열은 파일에 대한 오프셋(16진수 형식), 네 번째 열은 리전의 크기(16진수 형식), 다섯 번째 열은 파일 또는 다른 리전의 이름입니다.

    f6f34000-f6f53fff r-x         0     20000  /system/lib/libm.so (BuildId: 76ba45dcd9247e60227200976a02c69b)
    f6f54000-f6f54fff ---         0      1000
    f6f55000-f6f55fff r--     20000      1000  /system/lib/libm.so
    f6f56000-f6f56fff rw-     21000      1000  /system/lib/libm.so
    f6f58000-f6f58fff rw-         0      1000
    f6f59000-f6f78fff r--         0     20000  /dev/__properties__/u:object_r:default_prop:s0
    f6f79000-f6f98fff r--         0     20000  /dev/__properties__/properties_serial
    f6f99000-f6f99fff rw-         0      1000  [anon:linker_alloc_vector]
    f6f9a000-f6f9afff r--         0      1000  [anon:atexit handlers]
    f6f9b000-f6fbafff r--         0     20000  /dev/__properties__/properties_serial
    f6fbb000-f6fbbfff rw-         0      1000  [anon:linker_alloc_vector]
    f6fbc000-f6fbcfff rw-         0      1000  [anon:linker_alloc_small_objects]
    f6fbd000-f6fbdfff rw-         0      1000  [anon:linker_alloc_vector]
    f6fbe000-f6fbffff rw-         0      2000  [anon:linker_alloc]
    f6fc0000-f6fc0fff r--         0      1000  [anon:linker_alloc]
    f6fc1000-f6fc1fff rw-         0      1000  [anon:linker_alloc_lob]
    f6fc2000-f6fc2fff r--         0      1000  [anon:linker_alloc]
    f6fc3000-f6fc3fff rw-         0      1000  [anon:linker_alloc_vector]
    f6fc4000-f6fc4fff rw-         0      1000  [anon:linker_alloc_small_objects]
    f6fc5000-f6fc5fff rw-         0      1000  [anon:linker_alloc_vector]
    f6fc6000-f6fc6fff rw-         0      1000  [anon:linker_alloc_small_objects]
    f6fc7000-f6fc7fff rw-         0      1000  [anon:arc4random _rsx structure]
    f6fc8000-f6fc8fff rw-         0      1000  [anon:arc4random _rs structure]
    f6fc9000-f6fc9fff r--         0      1000  [anon:atexit handlers]
    f6fca000-f6fcafff ---         0      1000  [anon:thread signal stack guard page]
    

Android 5.0부터 C 라이브러리는 익명으로 매핑된 리전 대부분의 이름을 지정하므로 알 수 없는 리전이 더 적습니다.

    f6fcb000-f6fccfff rw- 0 2000 [stack:5081]
    

[stack:tid]로 명명된 리전은 주어진 스레드의 스택입니다.

    f6fcd000-f702afff r-x         0     5e000  /system/bin/linker (BuildId: 84f1316198deee0591c8ac7f158f28b7)
    f702b000-f702cfff r--     5d000      2000  /system/bin/linker
    f702d000-f702dfff rw-     5f000      1000  /system/bin/linker
    f702e000-f702ffff rw-         0      2000
    f7030000-f7030fff r--         0      1000
    f7031000-f7032fff rw-         0      2000
    ffcd7000-ffcf7fff rw-         0     21000
    ffff0000-ffff0fff r-x         0      1000  [vectors]
    

[vector] 또는 [vdso]의 표시 여부는 아키텍처에 따라 다릅니다. ARM에서는 [vector]를 사용하고 다른 모든 아키텍처에서는 [vdso]를 사용합니다.