三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Android 7系统异常问题排查(三)Native层—Tombstone机制深度解析

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_00tombstone_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.cppdebuggerd 客户端信号处理器
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.exidxARM 专用异常索引表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-C0x4b50

ndk-stack —— 最常用

adb logcat|ndk-stack-sym./symbols/ ndk-stack-sym./symbols/-dumptombstone_00

AOSP 自带 stack 脚本

adb logcat-d|./development/scripts/stack ./development/scripts/stack<tombstone_00

objdump 反汇编辅助

aarch64-linux-android-objdump-d-Clibsurfaceflinger.so|grep-A30"MyClass::process"

八、常见 Native Crash 速查表

信号编号含义常见原因
SIGSEGV11段错误空指针、野指针、缓冲区溢出、栈溢出
SIGABRT6异常终止abort()、assert() 失败、__builtin_trap()
SIGBUS7总线错误未对齐访问、mmap 失败后访问
SIGILL4非法指令执行数据段、CPU 不支持该指令
SIGFPE8浮点异常除零、整数溢出
SIGSYS31非法系统调用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 反汇编确认(必要时)

十、总结

  1. Tombstone 是 Native 崩溃的"法医报告":记录了进程死亡瞬间的所有关键状态。

  2. debuggerd 的 ptrace 机制使其无需侵入目标进程:通过内核的 ptrace 能力"旁观式"采集数据。

  3. PC 寄存器 + backtrace 是定位的黄金组合:PC 告诉我们"在哪条指令崩的",backtrace 告诉我们"怎么走到这里的"。

  4. 还原工具链:addr2line(精确)+ ndk-stack(便捷)+ stack 脚本(快速)。

  5. 信号类型决定排查方向:SIGSEGV 找空指针/野指针,SIGABRT 找主动 abort,SIGBUS 找对齐问题。

下一篇我们将进入 Framework 层,深入分析System Server Watchdog机制——当系统卡死时,Watchdog 如何检测并触发系统重启。


本文基于 AOSP 7(Android Nougat)源码编写。后续版本(8.0+)引入了 crash_dump 进程替代 debuggerd 子进程模式,机制有所不同但思路一致。

← 返回列表