前言
这里墓碑指的是在 Android 下应用程序发生了 Native Crash 的情况下由系统自动生成的文件。具体的墓碑文件格式以及 Coredump 文件的核心部分可以翻阅之前的文章 《如何理解Native Crash问题》。
原理

部件 | 依赖 |
---|---|
ELF Header | 唯一需要计算的参数 "Number of program headers"。 |
Program Headers | 解析 memory map 获得进程所有 vma 数量,以及 Fake VMA 和 PT_NOTE 组成。 |
Thread Registers | 解析 Crash thread registers 即可获得相应的内容。 |
AUXV | 实际上是个 Fake AUXV,见具体内容。 |
Segment(PT_LOAD) | 通过解析 memory near xx 获得相应的内存值,墓碑文件没有记录的内存地址则补 0 填充即可。 |
图中右侧为一个墓碑文件的基本内容,左侧是一个 Coredump 文件的基本组成,绝大部分信息均可来自墓碑文件,但要想 GDB、LLDB 上能够进行调试,加载符号表文件,我们需要 Fake AUXV 以及 Fake VMA 来得到一个 FakeCore 的文件。
FakeCore 文件格式

FAKE AUXV
像 GDB、LLDB 这种调试工具,要获得该进程依赖的动态链接库信息所依赖的基本参数。
数值 | 类型 | 描述 | 用途 |
---|---|---|---|
3 | AT_PHDR | Program headers for program | 找到执行程序的 Program Headers 首地址,这里我们让该地址指向 FAKE PHDR 即可 |
4 | AT_PHENT | Size of program header entry | 遍历的步长,大小为 sizeof(Elfxx_Phdr) |
5 | AT_PHNUM | Number of program headers | 数量为 1 即可,具体原因见 FAKE PHDR。 |
FAKE PHDR
调试工具得到 AT_PHDR 地址下一步的目的是检索 Program headers 表得到 PT_DYNAMIC 的位置。
ini
typedef struct {
uint32_t p_type;
uint32_t p_flags;
Elf64_Off p_offset;
Elf64_Addr p_vaddr;
Elf64_Addr p_paddr;
uint64_t p_filesz;
uint64_t p_memsz;
uint64_t p_align;
} Elf64_Phdr;
成员 | 值 |
---|---|
p_type | PT_DYNAMIC |
p_flags | PF_R | PF_W |
p_vaddr | 该地址指向 FAKE DYNAMIC 即可 |
FAKE DYNAMIC
调试工具得到 PT_DYNAMIC 地址下一步的目的是检索 DEBUG 地址,比如我们用 readelf -d 来解析 ELF 可执行文件,可以看到 DYNAMIC 上的内容,其中有一项即为 DEBUG,当前地址为 0x0,实际上程序真正运行时会生成一个真正的 DYNAMIC 内存,其中 DEBUG 地址指向 r_debug 地址。
yaml
# llvm-readelf -d app_process64
Dynamic section at offset 0x30c0 contains 41 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libandroid_runtime.so]
0x0000000000000001 (NEEDED) Shared library: [libbinder.so]
0x0000000000000001 (NEEDED) Shared library: [libcutils.so]
0x0000000000000001 (NEEDED) Shared library: [libhidlbase.so]
0x0000000000000001 (NEEDED) Shared library: [liblog.so]
0x0000000000000001 (NEEDED) Shared library: [libnativeloader.so]
0x0000000000000001 (NEEDED) Shared library: [libsigchain.so]
0x0000000000000001 (NEEDED) Shared library: [libutils.so]
0x0000000000000001 (NEEDED) Shared library: [libwilhelm.so]
0x0000000000000001 (NEEDED) Shared library: [libc++.so]
0x0000000000000001 (NEEDED) Shared library: [libc.so]
0x0000000000000001 (NEEDED) Shared library: [libm.so]
0x0000000000000001 (NEEDED) Shared library: [libdl.so]
0x000000000000001e (FLAGS) BIND_NOW
0x000000006ffffffb (FLAGS_1) NOW PIE
0x0000000000000015 (DEBUG) 0x0
0x0000000060000011 (ANDROID_RELA) 0xe38
0x0000000060000012 (ANDROID_RELASZ) 18 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x0000000000000024 (RELR) 0xe50
0x0000000000000023 (RELRSZ) 24 (bytes)
0x0000000000000025 (RELRENT) 8 (bytes)
0x0000000000000017 (JMPREL) 0xe68
0x0000000000000002 (PLTRELSZ) 1152 (bytes)
0x0000000000000003 (PLTGOT) 0x3360
0x0000000000000014 (PLTREL) RELA
0x0000000000000006 (SYMTAB) 0x310
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000005 (STRTAB) 0x8a4
0x000000000000000a (STRSZ) 1427 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x888
0x0000000000000020 (PREINIT_ARRAY) 0x3000
0x0000000000000021 (PREINIT_ARRAYSZ) 16 (bytes)
0x0000000000000019 (INIT_ARRAY) 0x3010
0x000000000000001b (INIT_ARRAYSZ) 16 (bytes)
0x000000000000001a (FINI_ARRAY) 0x3020
0x000000000000001c (FINI_ARRAYSZ) 16 (bytes)
0x000000006ffffff0 (VERSYM) 0x7c0
0x000000006ffffffe (VERNEED) 0x824
0x000000006fffffff (VERNEEDNUM) 3
0x0000000000000000 (NULL) 0x0
arduino
typedef struct elf64_dynamic {
uint64_t d_type;
uint64_t d_val;
} Elf64_dynamic;
struct r_debug {
int r_version;
struct link_map *r_map;
};
成员 | |
---|---|
d_type | DT_DEBUG |
d_val | FAKE_DYNAMIC 地址 + sizeof(Elf64_dynamic) |
r_version | 1 (SYSV) 标准 |
r_map | 该地址指向 FAKE LINK MAP 即可 |
FAKE LINK MAP
该部分才是 GDB、LLDB 找到进程依赖动态库的链表,而我们不需要全部链接库,因为墓碑文件本身记录的信息非常有限,只要 Crash thread backtrace 中出现的链接库即可,如下才是我们需要的。
sql
backtrace:
#00 pc 00000000000edcc8 /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+8)
#01 pc 0000000000018a4c /system/lib64/libutils.so (android::Looper::pollInner(int)+188)
#02 pc 000000000001892c /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+124)
#03 pc 000000000018d03c /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
#04 pc 0000000000217094 /system/framework/arm64/boot-framework.oat (art_jni_trampoline+116)
... ...
arduino
struct link_map {
uint64_t l_addr;
char * l_name;
uint64_t l_ld;
struct link_map *l_next, *l_prev;
};
成员 | |
---|---|
l_addr | 动态链接库的加载地址,可从 memory map 中获得。 |
l_name | 文件路径的字符串均在 FAKE STRTAB 即可。 |
l_ld | 关乎 GDB、LLDB 能否正常解析堆栈的核心参数,DYNAMIC 虚拟地址 + 加载地址 |
l_next | 下一个 link_map 地址,我们可以像数组一样堆叠即可。 |
l_prev | 上一个 link_map 地址。 |
关于 l_ld 的值来源,依赖源 ELF 文件解析 DYNAMIC 获得虚拟地址。

FAKE STRTAB
此部分存放所有字符串,如前面 link_map 中的动态链接库地址。
FakeCore 实验效果
例如在手机中主动 kill -11 一个进程来获得相应的 tombstone 文件。
shell
# kill -11 `pidof com.android.settings`
# ./data/tomb2core --input=/data/tombstones/tombstone_17 --output=/data/tombstones/tombstone_17.core
less
// 注意制作 link_map 要在头部塞入一个 fake,因为 GDB 会认为第一个是进程的执行文件给过滤掉了。
art-parser> map
0x2002000 [0x2000000, 0x2001000) [fake] [*]
0x2002028 [0x7bcdf53000, 0x7bce0b4000) [/apex/com.android.art/lib64/libart.so] [empty]
0x2002050 [0x7c6d6cb000, 0x7c6d719000) [/apex/com.android.runtime/lib64/bionic/libc.so] [empty]
0x2002078 [0x71c98000, 0x71eae000) [/system/framework/arm64/boot-framework.oat] [empty]
0x20020a0 [0x7189e000, 0x7193a000) [/system/framework/arm64/boot.oat] [empty]
0x20020c8 [0x7c6100a000, 0x7c610e9000) [/system/lib64/libandroid_runtime.so] [empty]
0x20020f0 [0x7c5fb5d000, 0x7c5fb6c000) [/system/lib64/libutils.so] [empty]
bash
(gdb) core-file tombstone_17.core
(gdb) info sharedlibrary
warning: Could not load shared library symbols for 6 libraries, e.g. /apex/com.android.art/lib64/libart.so.
Use the "info sharedlibrary" command to see the complete listing.
Do you need "set solib-search-path" or "set sysroot"?
From To Syms Read Shared Object Library
No /apex/com.android.art/lib64/libart.so
No /apex/com.android.runtime/lib64/bionic/libc.so
No /system/framework/arm64/boot-framework.oat
No /system/framework/arm64/boot.oat
No /system/lib64/libandroid_runtime.so
No /system/lib64/libutils.so
bash
(gdb) set sysroot symbols/
Reading symbols from symbols/apex/com.android.art/lib64/libart.so...
Reading symbols from symbols/apex/com.android.runtime/lib64/bionic/libc.so...
Reading symbols from symbols/system/framework/arm64/boot-framework.oat...
Reading symbols from symbols/system/framework/arm64/boot.oat...
Reading symbols from symbols/system/lib64/libandroid_runtime.so...
Reading symbols from symbols/system/lib64/libutils.so...
rust
(gdb) bt
#0 __epoll_pwait () at /bionic/libc/syscalls-arm64/gen/syscalls-arm64.S:2370
#1 0x0000007c5fb75a50 in android::Looper::pollInner (this=<optimized out>, timeoutMillis=19008) at system/core/libutils/Looper.cpp:249
#2 0x0000007c5fb75930 in android::Looper::pollOnce (this=0xb400007bd2ad4100, timeoutMillis=19008, outFd=0x0, outEvents=0x0, outData=0x0) at system/core/libutils/Looper.cpp:217
#3 0x0000007c61197040 in android::Looper::pollOnce (this=0xfffffffffffffffc, timeoutMillis=<optimized out>) at system/core/libutils/include/utils/Looper.h:270
#4 android::NativeMessageQueue::pollOnce (env=0xb400007bd2a8ce00, pollObj=<optimized out>, timeoutMillis=<optimized out>, this=<optimized out>) at frameworks/base/core/jni/android_os_MessageQueue.cpp:125
#5 android::android_os_MessageQueue_nativePollOnce (env=0xb400007bd2a8ce00, obj=<optimized out>, ptr=-5476376615066225088, timeoutMillis=-811281936) at frameworks/base/core/jni/android_os_MessageQueue.cpp:225
#6 0x0000000071eaf098 in android::os::MessageQueue::nativePollOnce (this=...)
from symbols/system/framework/arm64/boot-framework.oat
#7 0x00000000722bab8c in android::os::MessageQueue::next (this=...) at android/os/MessageQueue.java:341
#8 0x00000000722b82c4 in android::os::Looper::loopOnce (me=..., ident=<optimized out>, thresholdOverride=<optimized out>) at android/os/Looper.java:176
#9 0x00000000722b81d0 in android::os::Looper::loop ()
from symbols/system/framework/arm64/boot-framework.oat
#10 0x0000000072041448 in android::app::ActivityThread::main (args=...) at android/app/ActivityThread.java:8667
#11 0x0000007bce163c84 in art_quick_invoke_static_stub () at art/runtime/arch/arm64/quick_entrypoints_arm64.S:688
#12 0x0000007bce1a6b44 in art::ArtMethod::Invoke (this=0x710e6b58, self=0xb400007bd2a42c00, args=0x16, args_size=<optimized out>, result=0x7fcfa4d7b0, shorty=0x7bcd4e00ca "") at art/runtime/art_method.cc:425
#13 0x0000007bce59e4e8 in art::(anonymous namespace)::InvokeWithArgArray(art::ScopedObjectAccessAlreadyRunnable const&, art::ArtMethod*, art::(anonymous namespace)::ArgArray*, art::JValue*, char const*) [clone .__uniq.245181933781456475607640333933569312899] (soa=..., method=0x710e6b58, arg_array=0x7fcfa4d820, result=0x7fcfa4d7b0, shorty=0x7bcd4e00ca "") at art/runtime/reflection.cc:458
#14 art::(anonymous namespace)::InvokeMethodImpl(art::ScopedObjectAccessAlreadyRunnable const&, art::ArtMethod*, art::ArtMethod*, art::ObjPtr<art::mirror::Object>, art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object> >, char const**, art::JValue*) [clone .__uniq.245181933781456475607640333933569312899] (soa=..., m=0x710e6b58, np_method=0x710e6b58, receiver=..., objects=..., result=0x7fcfa4d7b0,
shorty=<optimized out>) at art/runtime/reflection.cc:493
#15 art::InvokeMethod<(art::PointerSize)8> (soa=..., javaMethod=<optimized out>, javaReceiver=<optimized out>, javaArgs=<optimized out>, num_frames=<optimized out>) at art/runtime/reflection.cc:772
#16 0x0000007bce5165b4 in art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*) [clone .__uniq.165753521025965369065708152063621506277] (env=<optimized out>, javaMethod=<optimized out>,
javaReceiver=<optimized out>, javaArgs=<optimized out>) at art/runtime/native/java_lang_reflect_Method.cc:86
#17 0x000000007194057c in java::lang::Class::getDeclaredMethodInternal (this=...)
from symbols/system/framework/arm64/boot.oat
#18 0x0000000072495388 in com::android::internal::os::RuntimeInit$MethodAndArgsCaller::run (this=...) at com/android/internal/os/RuntimeInit.java:565
#19 0x000000007249ff78 in com::android::internal::os::ZygoteInit::main (argv=...) at com/android/internal/os/ZygoteInit.java:1081
#20 0x0000007bce163c84 in art_quick_invoke_static_stub () at art/runtime/arch/arm64/quick_entrypoints_arm64.S:688
#21 0x0000007bce1a6b44 in art::ArtMethod::Invoke (this=0x7116f378, self=0xb400007bd2a42c00, args=0x16, args_size=<optimized out>, result=0x7fcfa4dc48, shorty=0x7bcba45177 "") at art/runtime/art_method.cc:425
#22 0x0000007bce59ed6c in art::(anonymous namespace)::InvokeWithArgArray(art::ScopedObjectAccessAlreadyRunnable const&, art::ArtMethod*, art::(anonymous namespace)::ArgArray*, art::JValue*, char const*) [clone .__uniq.245181933781456475607640333933569312899] (soa=..., method=0x7116f378, arg_array=0x7fcfa4dbd0, result=0x7fcfa4dc48, shorty=0x7bcba45177 "") at art/runtime/reflection.cc:458
#23 art::InvokeWithVarArgs<art::ArtMethod*> (soa=..., obj=0x0, method=0x7116f378, args=...) at art/runtime/reflection.cc:550
#24 0x0000007bce59f344 in art::InvokeWithVarArgs<_jmethodID*> (soa=..., obj=0x0, mid=<optimized out>, args=...) at art/runtime/reflection.cc:565
#25 0x0000007bce4690f8 in art::JNI<true>::CallStaticVoidMethodV (env=<optimized out>, mid=0x7116f378, args=...) at art/runtime/jni/jni_internal.cc:1963
#26 0x0000007c610eacac in _JNIEnv::CallStaticVoidMethod (this=<optimized out>, clazz=<optimized out>, methodID=<optimized out>) at libnativehelper/include_jni/jni.h:779
#27 0x0000007c610f7438 in android::AndroidRuntime::start (this=0x7fcfa4e020, className=0x55d055a394 "", options=..., zygote=true) at frameworks/base/core/jni/AndroidRuntime.cpp:1350
#28 0x00000055d055b590 in ?? ()
这样许多计算问题以及反编译问题都可在 GDB、LLDB 上完成,节省许多计算量,其中 tomb2core 也是离线内存分析项目中的组成部分。