调试系列4:debuggerd源码篇

一、概述

Android系统有监控程序异常退出的机制,这便是本文要讲述得debuggerd守护进程。当发生native crash或者主动调用debuggerd时,会输出进程相关的状态信息到文件或者控制台。输出的debuggerd数据 保存在文件/data/tombstones/tombstone_XX,该类型文件个数上限位10个,当超过时则每次覆盖时间最老的文件。针对进程出现的不同的状态,Linux kernel会发送相应的signal给异常进程,捕获signal并对其做相应的处理(通常动作是退出异常进程)。而Android在这机制的前提下,通过拦截这些信号来dump进程信息,方便开发人员调试分析。

debuggerd守护进程会打开socket服务端,当需要调用debuggerd服务时,先通过客户端进程向debuggerd服务端建立socket连接,然后发送不同的请求给debuggerd服务端,当服务端收到不同的请求,则会采取相应的dump操作。接下来从源码角度来探索debuggerd客户端和服务端的工作原理。

二、debuggerd客户端

通过adb执行上面的命令都能触发debuggerd进行相应的dump操作,其中参数-b`表示在控制台中输出backtrace,参数tid表示的是需要dump的进程或者线程id。这两个命令的输出结果相差较大,下面来一步步分析看看这两个命令分别能触发哪些操作,执行上述任一命令都会调用debuggerd的main方法()。

2.1 main

[-> /debuggerd/debuggerd.cpp]

对于debuggerd命令,必须指定线程tid,否则不做任何操作,直接返回。

2.2 do_explicit_dump

[-> /debuggerd/debuggerd.cpp]

dump_backtrace等于true代表的是输出backtrace到控制台,否则意味着输出到tombstone文件。

2.3 dump_backtrace_to_file

[-> libcutils/debugger.c]

该方法的功能:

  • 首先,向debuggerd的socket服务端发出DEBUGGER_ACTION_DUMP_BACKTRACE请求,然后阻塞等待;
  • 循环遍历读取debuggerd服务端发送过来的数据,并写入到buffer;
  • 再将buffer数据输出到fd,此处是stdout文件描述符(屏幕终端)。

2.4 dump_tombstone

[-> libcutils/debugger.c]

该方法的功能:

  • 首先,向debuggerd的socket服务端发出DEBUGGER_ACTION_DUMP_TOMBSTONE请求,然后阻塞等待;
  • 循环遍历读取debuggerd服务端发送过来的tombstone文件名,并写入到buffer;
  • 将buffer数据拷贝到pathbuf,即拷贝tombstone文件名。

2.5 make_dump_request

[-> libcutils/debugger.c]

该函数的功能是与debuggerd服务端建立socket通信,并发送action请求,以执行相应操作。

2.6 send_request

2.7 小节

通过调用debuggerd <tid>命令调用流程图:

debuggerd_client

执行debuggerd命令最终都是调用send_request()方法,向debuggerd服务端发出DEBUGGER_ACTION_DUMP_TOMBSTONE或者DEBUGGER_ACTION_DUMP_BACKTRACE请求,那对于debuggerd服务端收到相应命令做了哪些操作呢,要想明白这个过程,接下来看看debuggerd服务端的工作。

三、debuggerd服务端

在执行debuggerd命令之前,debuggerd服务端早早就以准备就绪,时刻等待着client请求的到来。

3.1 debuggerd.rc

由init进程fork子进程来以daemon方式启动,定义在debuggerd.rc文件(旧版本位于init.rc)

init进程会解析上述rc文件,调用/system/bin/debuggerd文件,进入main方法,此时不带有任何参数。 接下来进入main()方法。

3.2 main

[-> /debuggerd/debuggerd.cpp]

3.3 do_server

[-> /debuggerd/debuggerd.cpp]

主要功能:

  • 忽略debuggerd进程自身crash的处理过程;
  • 重置所有crash handlers;
  • 建立socket通信中的服务端;
  • 循环等待客户端的连接,并调用handle_request处理客户端请求。

3.4 handle_request

[-> /debuggerd/debuggerd.cpp]

3.5 read_request

[-> /debuggerd/debuggerd.cpp]

该方法的功能是首先从socket获取client进程的pid,uid,gid用于权限控制,能处理以下三种情况:

  • C/C++进程crash时发送过来的请求;
  • root权限既可以可收集backtraces,又可以dump tombstones;
  • system权限只允许收集backtraces。

针对这些情况若相应的tid不存在或selinux权限不满足,则都忽略该显式dump请求。read_request执行完成后,则从socket通道中读取到request信息。

3.6 worker_process

处于client发送过来的请求,server端通过子进程来处理

[-> /debuggerd/debuggerd.cpp]

这个流程比较长,这里介绍attach_gdb=false的执行流程

3.6.1 open_tombstone

[-> tombstone.cpp]

其中TOMBSTONE_TEMPLATE为data/tombstones/tombstone_%02d,文件个数上限MAX_TOMBSTONES=10

打开tombstone文件规则:

  1. 当已使用的tombstone文件个数小于10时,则创建新的tombstone文件;否则执行2;
  2. 获取修改时间最老的tombstone文件,并覆写该文件;
  3. 如果最老文件不存在,则默认使用文件data/tombstones/tombstone_00

3.6.2 BacktraceMap::Create

[-> BacktraceMap.cpp]

3.6.3 activity_manager_connect

[-> debuggerd.cpp]

该方法的功能是建立与ActivityManager的socket连接。

3.6.4 perform_dump

根据接收到不同的signal采取相应的操作

[-> debuggerd.cpp]

致命信号有SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGSEGV,SIGSTKFLT,SIGTRAP共7个信息,能造成native crash。

3.6.5 activity_manager_write

[-> debuggerd.cpp]

3.7 monitor_worker_process

父进程处理

[-> debuggerd.cpp]

3.8 小节

调用流程:

整个过程的核心方法为worker_process(),其流程如下:

  1. 根据请worker_process()求中的不同action采取相应操作,除此之外则立即结束进程
    • DEBUGGER_ACTION_DUMP_TOMBSTONE,则调用open_tombstone并继续执行;
    • DEBUGGER_ACTION_CRASH ,则调用open_tombstone并继续执行;
    • DEBUGGER_ACTION_DUMP_BACKTRACE,则直接继续执行;
  2. 调用ptrace方法attach到目标进程;
  3. 调用BacktraceMap::Create来生成backtrace;
  4. 当Action=DEBUGGER_ACTION_CRASH,则执行activity_manager_connect;
  5. 调用drop_privileges来取消特权模式;
  6. 通过perform_dump执行dump操作;
    • SIGSTOP && DEBUGGER_ACTION_DUMP_TOMBSTONE,则engrave_tombstone()
    • SIGSTOP && DEBUGGER_ACTION_DUMP_BACKTRACE,则dump_backtrace()
    • SIGBUS等致命信号,则engrave_tombstone()
  7. 当Action=DEBUGGER_ACTION_DUMP_TOMBSTONE,则将向client端写入tombstone数据;
  8. 调用activity_manager_write,将进程crash情况告知AMS;
  9. 调用ptrace方法detach到目标进程;
  10. 当Action=DEBUGGER_ACTION_CRASH,发送信号SIGKILL给目标进程tid
  11. 调用exit来结束进程。

可知:

debuggerd -b <tid>:
发送请求的action为DEBUGGER_ACTION_DUMP_BACKTRACE,则调用dump_backtrace();

debuggerd <tid>: 发送请求的action为DEBUGGER_ACTION_DUMP_TOMBSTONE,则调用engrave_tombstone();

native crash: 发送请求的action为DEBUGGER_ACTION_CRASH,且发送信号为SIGBUS等致命信号,则调用engrave_tombstone()

再接下来,需要重点看看engrave_tombstonedump_backtrace这两个方法。

四、tombstone

4.1 engrave_tombstone

[-> debuggerd/tombstone.cpp]

4.2 dump_crash

[-> debuggerd/tombstone.cpp]

主要输出信息:

  • dump_header_info
  • 主线程dump_thread
  • dump_logs (ro.debuggable=1 才输出此项)
  • 兄弟线程dump_thread
  • dump_logs (ro.debuggable=1 才输出此项)

4.3 dump_header_info

[-> debuggerd/tombstone.cpp]

例如:

4.4 dump_thread(主)

调用方法dump_thread(log, pid, tid, map, signal, si_code, abort_msg_address, true);

[-> debuggerd/tombstone.cpp]

4.4.1 dump_thread_info

  • 获取进程名:/proc/<pid>/cmdline
  • 获取线程名:/proc/<tid>/comm

例如:

4.4.2 dump_signal_info

  • 对于SIGBUSSIGFPESIGILLSIGSEGVSIGTRAP时触发的dump,则会输出fault addr的具体地址,
  • 对于SIGSTOP时,则输出fault addr为”——–”

例如:

此处get_sigcode函数功能负责根据signal以及si_code来获取相应信息,下面来列举每种signal所包含的信息种类:

signal get_sigcode
SIGILL ILL_ILLOPC
SIGILL ILL_ILLOPN
SIGILL ILL_ILLADR
SIGILL ILL_ILLTRP
SIGILL ILL_PRVOPC
SIGILL ILL_PRVREG
SIGILL ILL_COPROC
SIGILL ILL_BADSTK
signal get_sigcode
SIGBUS BUS_ADRALN
SIGBUS BUS_ADRERR
SIGBUS BUS_OBJERR
SIGBUS BUS_MCEERR_AR
SIGBUS BUS_MCEERR_AO
signal get_sigcode
SIGFPE FPE_INTDIV
SIGFPE FPE_INTOVF
SIGFPE FPE_FLTDIV
SIGFPE FPE_FLTOVF
SIGFPE FPE_FLTUND
SIGFPE FPE_FLTRES
SIGFPE FPE_FLTINV
SIGFPE FPE_FLTSUB
signal get_sigcode
SIGSEGV SEGV_MAPERR
SIGSEGV SEGV_ACCERR
SIGSEGV SEGV_BNDERR
SIGSEGV SEGV_MAPERR
signal get_sigcode
SIGTRAP TRAP_BRKPT
SIGTRAP TRAP_TRACE
SIGTRAP TRAP_BRANCH
SIGTRAP TRAP_HWBKPT

4.4.3 dump_abort_message

4.4.4 dump_registers

输出系统寄存器信息,这里以arm为例来说明

[-> debuggerd/arm/Machine.cpp]

通过ptrace获取寄存器状态信息,这里输出r0-r9,sl,fp,ip,sp,lr,pc,cpsr 以及32个fpregs和一个fpscr.

4.4.5 dump_backtrace_and_stack

[-> debuggerd/tombstone.cpp]

4.4.5.1 输出backtrace信息

[-> debuggerd/Backtrace.cpp]

4.4.5.2 输出stack信息

[-> debuggerd/tombstone.cpp]

4.4.6 dump_memory_and_code

[-> debuggerd/arm/Machine.cpp]

4.4.7 dump_all_maps

[-> debuggerd/tombstone.cpp]

当内存出现故障时,可搜索关键词:

4.5 dump_logs

[-> debuggerd/tombstone.cpp]

4.6 dump_thread(兄弟)

dump_thread(log, pid, sibling, map, 0, 0, 0, false);

[-> debuggerd/tombstone.cpp]

兄弟线程dump_thread的输出内容:

  1. 线程相关信息,包含pid/tid以及相应name
  2. 寄存器状态
  3. backtrace以及stack

4.7 小节

engrave_tombstone主要输出信息:

  • dump_header_info
  • 主线程dump_thread
  • dump_logs (ro.debuggable=1 才输出此项)
  • 兄弟线程dump_thread
  • dump_logs (ro.debuggable=1 才输出此项)

主线程dump_thread

  1. build相关头信息;
  2. 线程相关信息,包含pid/tid以及相应name
  3. signal相关信息,包含fault address
  4. 寄存器状态
  5. backtrace
  6. stack
  7. memory near
  8. code around
  9. memory map

其中加粗项2,4,5,6只是兄弟线程调用dump_thread也会输出的内容,其他项便只有主线程才会输出。

五、 backtrace

5.1 dump_backtrace

[-> debuggerd/backtrace.cpp]

5.2 dump_process_header

[-> debuggerd/backtrace.cpp]

例如:

5.3 dump_thread

[-> debuggerd/backtrace.cpp]

输出backtrace信息

5.3.1 dump_backtrace_to_log

[-> debuggerd/Backtrace.cpp]

backtrace->NumFrames()是指该backtrace中栈帧数,通过循环遍历输出每一栈帧FormatFrameData的信息.

//栈帧数 pc指针 map_name (函数名+offset)
#01 pc 000000000001cca4 /system/lib64/libc.so (epoll_pwait+32)

这些map信息是由/proc/%d/maps解析出来的

5.3.2 实例

debuggerd -b命令,参数虽然指定tid,但输出结果会把整个线程组的backtrace都打印出来(上面只是省略).

5.4 dump_process_footer

[-> debuggerd/backtrace.cpp]

例如:----- end 1789 -----

5.5 小结

backtrace输出信息:

  • dump_process_header
  • 主线程backtrace
  • 兄弟线程backtrace
  • dump_process_footer

可见dump_backtrace主要输出主线程与兄弟线程的backtrace,而dump_tombstone的信息量远比其丰富。

六、实例

这里是dump_tombstone文件内容的组成:

6.1 文件头信息

6.2 主线程dump_thread

6.3 兄弟线程dump_thread

所有兄弟线程是以一系列---作为开头的分割符。

总结

这里主要以源码角度来分析debuggerd的原理,整个过程中最重要的产物便是tombstone文件,先留坑,后续再进一步讲述如何分析tombstone文件。

  • 对于debuggerd -b,则只输出backtrace,核心方法是debuggerd/tombstone.cpp 中的dump_thread();
  • 对于debuggerd或者native crash, 输出信息量比较大,核心方法是debuggerd/backtrace.cpp中的 dump_thread();

相关源码

作者微博:@Gityuan

原文出处:Gityuan.com

1 收藏 评论

关于作者:gityuan

Android系统工程师,www.gityuan.com博主,个人新浪微博:http://weibo.com/gityuan 个人主页 · 我的文章 · 3 ·     

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部