Android 7系统异常问题排查(三)Native层—Tombstone机制深度解析
系列目录:第一篇:异常机制全景图 | 第二篇:Kernel Panic 与系统重启 | 第三篇:Tombstone 机制 | 第四篇:System Server Watchdog | 第五篇:System Server 崩溃 | 第六篇:ANR 机制 | 第七篇:Java 层崩溃 | 第八篇:Trace 机制 | 第九篇:日志系统 | 第十篇:实战方法论
一、什么是 Tombstone?
你可能遇到过这些场景:
- 某个 native 进程悄无声息地消失,只在
/data/tombstones/留下一个二进制文件 - logcat 中出现
Fatal signal 11 (SIGSEGV),但不知道崩溃发生在哪一行代码 - 系统服务(如 surfaceflinger、mediaserver)突然重启,需要定位崩溃原因
Tombstone(墓碑)是 Native 进程因致命信号(SIGSEGV、SIGABRT 等)崩溃时,由debuggerd守护进程自动生成的现场快照文件。存放在/data/tombstones/,文件名tombstone_00到tombstone_09(最多保留 10 个,循环覆盖)。
Tombstone 包含崩溃瞬间的完整快照:进程 PID/TID、信号信息、全寄存器、各线程调用栈、内存映射表、栈内存 dump、logcat 片段。
二、Tombstone 生成流程
整个流程涉及三个角色:内核、debuggerd daemon、目标进程。
目标进程发生致命错误 → 内核发信号(SIGSEGV等) → Bionic libc 信号处理器介入 → 连接 debuggerd daemon socket,发送 PID/TID/UID → debuggerd fork 子进程 → ptrace 附加目标进程 → 收集寄存器、maps、backtrace、栈内存、logcat → 写入 /data/tombstones/tombstone_XX + dropbox → ptrace detach → 目标进程被内核终止关键源码路径
| 文件 | 功能 |
|---|---|
system/core/debuggerd/debuggerd.cpp | 主 daemon,socket 监听与请求分发 |
system/core/debuggerd/backtrace.cpp | 调用栈收集 |
bionic/libc/bionic/debugger.cpp | debuggerd 客户端信号处理器 |
system/core/libbacktrace/ | Unwind 库,支持 ARM/ARM64/x86 |
三、信号处理器的巧妙设计
AOSP 7 中 Bionic libc 为致命信号注册了信号处理器,当进程收到 SIGSEGV 等信号时,会主动连接 debuggerd:
源码路径:bionic/libc/bionic/debugger.cpp
// 简化的信号处理流程staticvoiddebuggerd_signal_handler(intsignal_number,siginfo_t*info,void*context){// 1. 创建 socket 连接到 debuggerd daemonintsocket_fd=socket_local_client("debuggerd",...);// 2. 发送 crash 信息结构体debugger_msg_t msg;msg.action=DEBUGGER_ACTION_CRASH;msg.tid=gettid();msg.abort_msg_address=...;TEMP_FAILURE_RETRY(write(socket_fd,&msg,sizeof(msg)));// 3. 进入阻塞等待 → debuggerd 通过 ptrace 控制本进程进行数据收集charack;TEMP_FAILURE_RETRY(read(socket_fd,&ack,1));// 4. 信号处理器返回 → 内核重新发送信号 → 进程终止}关键设计:信号处理器不"处理"信号,而是主动将进程"献祭"给 debuggerd,让它在进程真正死亡前完成现场采集。这种设计避免了在信号处理器中执行复杂的 dump 操作。
四、debuggerd daemon 源码解析
daemon 启动
源码路径:system/core/rootdir/init.rc
service debuggerd /system/bin/debuggerd class main主循环
源码路径:system/core/debuggerd/debuggerd.cpp
staticintdo_server(){// 创建 socket 监听ints=socket_local_server(SOCKET_NAME,...);// 启动 signal sender 子进程(用于向目标进程发送信号)start_signal_sender();ALOGI("debuggerd: starting\n");// 主循环:等待连接for(;;){intfd=accept4(s,addrp,&alen,SOCK_CLOEXEC);handle_request(fd);// 处理每个请求}}关键设计:debuggerd 采用 fork-per-request 模型,每个崩溃请求都由独立的子进程处理,避免一个崩溃影响其他崩溃的处理。
handle_request 处理流程
源码路径:system/core/debuggerd/debuggerd.cpp
staticvoidhandle_request(intfd){debugger_request_t request;read_request(fd,&request);// 读取 PID/TID/UID// 64位系统需要区分 32位/64位 进程if(is32bit(request.tid)){redirect_to_32(fd,&request);// 转发给 32位 debuggerdreturn;}// fork 子进程处理pid_t fork_pid=fork();if(fork_pid==0){worker_process(fd,request);// 子进程:实际处理}else{monitor_worker_process(fork_pid,request);// 父进程:监控超时}}worker_process 核心处理
源码路径:system/core/debuggerd/debuggerd.cpp
staticvoidworker_process(intfd,debugger_request_t&request){// 1. 打开 tombstone 文件inttombstone_fd=open_tombstone(&tombstone_path);// 2. ptrace 附加目标进程ptrace_attach_thread(request.pid,request.tid);// 3. 附加所有兄弟线程ptrace_siblings(request.pid,request.tid,siblings);// 4. 创建 BacktraceMapBacktraceMap*backtrace_map=BacktraceMap::Create(request.pid);// 5. 连接 Activity Manager(通知崩溃)intamfd=activity_manager_connect();// 6. 降级权限drop_privileges();// 7. 执行 dumpperform_dump(request,fd,tombstone_fd,backtrace_map,siblings,...);// 8. 通知 Activity Manageractivity_manager_write(request.pid,crash_signal,amfd,amfd_data);// 9. detach 并让目标进程继续(会被内核杀死)ptrace(PTRACE_DETACH,request.tid,0,0);send_signal(request.pid,request.tid,crash_signal);}关键设计:worker_process 在完成所有需要权限的操作(ptrace attach、连接 AMS)后,会主动降级权限(drop_privileges),减少安全风险。
五、调用栈收集
源码路径:system/core/debuggerd/backtrace.cpp
voiddump_backtrace(intfd,BacktraceMap*map,pid_t pid,pid_t tid,conststd::set<pid_t>&siblings,std::string*amfd_data){log_t log;log.tfd=fd;dump_process_header(&log,pid);// 输出 PID、时间、ABIdump_thread(&log,map,pid,tid);// 输出崩溃线程堆栈// 输出所有兄弟线程堆栈for(pid_t sibling:siblings){dump_thread(&log,map,pid,sibling);}dump_process_footer(&log,pid);}staticvoiddump_thread(log_t*log,BacktraceMap*map,pid_t pid,pid_t tid){// 获取线程名charpath[PATH_MAX];snprintf(path,sizeof(path),"/proc/%d/comm",tid);// ...// 创建 Backtrace 对象并 unwindstd::unique_ptr<Backtrace>backtrace(Backtrace::Create(pid,tid,map));if(backtrace->Unwind(0)){dump_backtrace_to_log(backtrace.get(),log," ");}}关键设计:Backtrace::Create 会根据目标进程的架构(ARM/ARM64/x86)选择合适的 unwind 方式,通过读取
/proc/pid/maps获取内存映射信息。
Unwind 方式
| 方法 | 原理 | 前提条件 |
|---|---|---|
| 基于帧指针 (FP) | 通过 R11(ARM)/x29(ARM64) 遍历栈帧链表 | -fno-omit-frame-pointer |
| 基于 .ARM.exidx | ARM 专用异常索引表 | ARM 架构默认 |
| 基于 .eh_frame | 解析 ELF 中的 DWARF 调试信息 | -funwind-tables |
六、Tombstone 文件格式逐段精读
6.1 Build Fingerprint
Build fingerprint: 'Android/aosp_arm64/generic:7.0/...' ABI: 'arm64' pid: 12345, tid: 12345, name: surfaceflinger >>> /system/bin/surfaceflinger <<< signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0- signal 11:SIGSEGV 信号
- code 1 (SEGV_MAPERR):访问了未映射的地址
- fault addr 0x0:空指针访问
6.2 寄存器 Dump(ARM64)
x0 0000000000000000 x1 0000007f8c3a4b10 ... sp 0000007f8ca0f000 lr 0000007f8c3a4b40 pc 0000007f8c3a4b50关键寄存器:
| 寄存器 | 定位价值 |
|---|---|
| x0 | 第一个参数,x0=0 可能表示传入空指针 |
| sp | 栈指针,当前线程栈顶 |
| lr | 返回地址(Link Register) |
| pc | 崩溃发生时的精确指令地址,最重要! |
6.3 Backtrace
#00 pc 0000000000004b50 /system/lib64/libsurfaceflinger.so (MyClass::process+32) #01 pc 0000000000004c20 /system/lib64/libsurfaceflinger.so (handleMessage+64) #02 pc 0000000000012340 /system/lib64/libutils.so (Looper::pollInner+156)格式:#帧号 pc 偏移(相对基址) 库路径 (函数名+函数内偏移)
6.4 Memory Map
0000007f8c000000-0000007f8c3a5000 /system/lib64/libsurfaceflinger.so配合 backtrace 偏移可计算实际地址:基址 0x7f8c000000 + 偏移 0x4b50 = 实际地址 0x7f8c004b50
七、Tombstone 还原工具
addr2line —— 最基础
aarch64-linux-android-addr2line-elibsurfaceflinger.so-f-C0x4b50ndk-stack —— 最常用
adb logcat|ndk-stack-sym./symbols/ ndk-stack-sym./symbols/-dumptombstone_00AOSP 自带 stack 脚本
adb logcat-d|./development/scripts/stack ./development/scripts/stack<tombstone_00objdump 反汇编辅助
aarch64-linux-android-objdump-d-Clibsurfaceflinger.so|grep-A30"MyClass::process"八、常见 Native Crash 速查表
| 信号 | 编号 | 含义 | 常见原因 |
|---|---|---|---|
| SIGSEGV | 11 | 段错误 | 空指针、野指针、缓冲区溢出、栈溢出 |
| SIGABRT | 6 | 异常终止 | abort()、assert() 失败、__builtin_trap() |
| SIGBUS | 7 | 总线错误 | 未对齐访问、mmap 失败后访问 |
| SIGILL | 4 | 非法指令 | 执行数据段、CPU 不支持该指令 |
| SIGFPE | 8 | 浮点异常 | 除零、整数溢出 |
| SIGSYS | 31 | 非法系统调用 | seccomp 过滤 |
SIGSEGV 两种 si_code
| si_code | 含义 | 定位方向 |
|---|---|---|
| SEGV_MAPERR (1) | 地址未映射 | 空指针、已释放内存 |
| SEGV_ACCERR (2) | 权限错误 | 写只读内存、执行数据段 |
九、实战定位流程
1. adb pull /data/tombstones/tombstone_00 2. 查看 signal 和 fault addr:确定崩溃类型 3. 查看 backtrace #00:找到崩溃源头的函数名和偏移 4. 找到对应 .so 的带符号版本:out/.../symbols/system/lib64/xxx.so 5. ndk-stack -sym ./symbols/ -dump tombstone_00 还原完整堆栈 6. 定位到源码行,分析空指针/非法访问的根因 7. objdump 反汇编确认(必要时)十、总结
Tombstone 是 Native 崩溃的"法医报告":记录了进程死亡瞬间的所有关键状态。
debuggerd 的 ptrace 机制使其无需侵入目标进程:通过内核的 ptrace 能力"旁观式"采集数据。
PC 寄存器 + backtrace 是定位的黄金组合:PC 告诉我们"在哪条指令崩的",backtrace 告诉我们"怎么走到这里的"。
还原工具链:addr2line(精确)+ ndk-stack(便捷)+ stack 脚本(快速)。
信号类型决定排查方向:SIGSEGV 找空指针/野指针,SIGABRT 找主动 abort,SIGBUS 找对齐问题。
下一篇我们将进入 Framework 层,深入分析System Server Watchdog机制——当系统卡死时,Watchdog 如何检测并触发系统重启。
本文基于 AOSP 7(Android Nougat)源码编写。后续版本(8.0+)引入了 crash_dump 进程替代 debuggerd 子进程模式,机制有所不同但思路一致。
