[笔记分享] [Exception] 用户空间异常之catch流程小结

平台: MSM8260
OS: Android 2.3.4

概述

一般的Linux中,当user space ap执行发生exception的时候,可能我们只能看到segment fault这样的错误,在pc上的话可以用gdb、backtrace来跟踪产生exception的地方, 但是如果在手机上的话就不好跟踪了,不过在android平台上,google提供了一个catch exception的守护进程,叫debuggerd。它能将user space发生的exception捕捉到然后通过logcat打印出来。

通过Log发现,它的call stack只有一些地址,而不是想kernel对应的function name.因此,得将这些offset转成function name才能是huma-readable嘛。

所以本文先分析debuggerd的catch原理,然后再讲述如何转换offset为function name。


原理

介绍

看下Debuggerd的大概执行流程如下:

[笔记分享] [Exception] 用户空间异常之catch流程小结

不管它运用了什么工具或者功能,大概的流程是逃不掉的:
获取exception process pid -> 跟踪 -> 获取想要的info –> 保存到文件或者打印到shell中,下面分析code.

client

Android启动之后,在init.rc中,debuggerd会被作为一个service而启动。
看它main函数:

[笔记分享] [Exception] 用户空间异常之catch流程小结

先建立一个作为logd的client,因为logd server需要从debuggerd service中获取信息,所以它被作为一个logd client存在了,这里我们可以不必理会。

然后将自己作为android:debuggerd的server,既然它作为server,肯定有client存在咯。以android:debuggerd为关键字搜索中…

有一个叫[email protected]\linker,看它做啥了。

[笔记分享] [Exception] 用户空间异常之catch流程小结

初始化一些信号捕捉相应处理函数,当发生这些信号时,debugger_signal_handler就会被至执行。
BTW,当user space 发生exception时,kernel经过一番处理之后最终会发信号给user space。
So, debugger_signal_handler干啥了?

[笔记分享] [Exception] 用户空间异常之catch流程小结

tid = gettid();获取exception的tid号。
s = socket_abstract_client(“android:debuggerd”, SOCK_STREAM);建立名为android:debugger的socket client。一个C/S的概念产生了有没有?

RETRY_ON_EINTR(ret, write(s, &tid, sizeof(unsigned))); 这句就将tid号写了进去,供server读取了。
Ok, client主要是获取exception tid号,然后供server读取。

server

Server的main中最后作了个死循环,用accept来监听是否有client连上来。当exception发生时,accept就返回,返回值就是client id。然后执行 handle_crashing_process(fd);

[笔记分享] [Exception] 用户空间异常之catch流程小结
[笔记分享] [Exception] 用户空间异常之catch流程小结
[笔记分享] [Exception] 用户空间异常之catch流程小结
[笔记分享] [Exception] 用户空间异常之catch流程小结

  • getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &len);:获取client的pid/uid/gid;
  • read(fd, &tid, sizeof(unsigned)): 记得在client有个write动作不?所以这里能得到exception的tid了!
  • ptrace(PTRACE_ATTACH, tid, 0, 0);: 将ptrace与tid进行连接。这里跟踪exception用到了ptrace,也就是告诉exception process说我要对你调试了,到时我要一些debug信息你得要给我哦! 所以后面的以ptrace打头的函数我们只要看它传的是什么类型,获取的是什么参数就够了。
  • waitpid(tid, &status, __WALL);: 等待tid释放。
  • WSTOPSIG(status);: 获取signal number.

一般exception时都会跑到SIGSTKFLT这条case中,好了,我们看核心处理函数engrave_tombstone。

[笔记分享] [Exception] 用户空间异常之catch流程小结

  • mkdir(TOMBSTONE_DIR, 0755);: 创建TOMBSTONE_DIR目录,TOMBSTONE_DIR为”/data/tombstones”,看来有将log保存的意图哟~, 看find_and_open_tombstone.

[笔记分享] [Exception] 用户空间异常之catch流程小结

这个函数表示我们最大可以建立10个tombsontes文件,文件名字按照00~09排列,每次保存的文件是按照时间顺序来建立的。当10个全部用完时,它又回到第一个也就是00文件开始保存log。保存log其实是用到了_LOG函数:

[笔记分享] [Exception] 用户空间异常之catch流程小结

其实也就是call了write函数,另外,当in_tombstone_only为false时,Log也会被送往logcat显示。继续看engrave_tombstone函数中的dump_crash_banner:

[笔记分享] [Exception] 用户空间异常之catch流程小结

从/proc/pid/cmdline中获取到了进程执行的命令,也就可以知道是哪个进程(指的是字符串名字不是pid)发生了exception。接着通过dump_build_info获取了编译信息:然后通过dump_fualt_addr获取了产生exception的信号类型已经错误地址。

上述信息对我们来说用处不是很大,关键是engrave_tombstone后面call到了dump_crash_report这个函数,里面才我们需要的pc、lr、stack信息。

[笔记分享] [Exception] 用户空间异常之catch流程小结
[笔记分享] [Exception] 用户空间异常之catch流程小结

  • dump_registers: 将进程的每个register dump出来。
  • parse_maps_line: 从/proc/pid/maps中读取map表。BTW,maps中存着整个进程空间内存的分布,包括data/text/stack/so等。如/bin/charge的maps如下这样子:

[笔记分享] [Exception] 用户空间异常之catch流程小结

如libc.so的代码段被映射到debuggerd的0xafd00000到0xafd40000这段空间。
映射出来的map表存在了链表mi变量中,供unwind_backtrace_with_ptrace使用。对于unwind_backtrace_with_ptrace,一开始没注意到它的功能,以为只是打一下pc的地址之类的功能,后来才发现它才最关键,将我们需要的call stack address给打印出来了,而且它根据map表计算得到的已经是相对偏移了,这样就不用我们自己再计算。(Android 2.2似乎没这个功能)如下所示:

[笔记分享] [Exception] 用户空间异常之catch流程小结

其实最终我们就是通过它来得到call stack function name的。对于下面要讲述到的stack里面的东西,经过我的分析,和实际需要的call stack却对不上号。个人觉得用户空间的sp应该会保存call stack的呀!不清楚中间原理是怎么样的,不过既然从unwind_backtrace_with_ptrace得到了call stack,权且不管过程如何。

继续看dump_stack_and_code: 它将exception时的pc, lr, sp都简单地打印出来了。如下:

[笔记分享] [Exception] 用户空间异常之catch流程小结

这些信息是否游泳根据个人情况来分析。其中在call _LOG()里面有个参数map_to_name,它就是利用了我们的map表然后找到相应offset的所在的地址空间名字。如000099b5落在了0x8000~0xa000这段地址空间之内。

关键几步已经分析完了, 接着dump_sibling_thread_report会看是否是多线程的,如果是的话又会多打几遍同样的exception log,你也可以将函数中的_LOG给关了,免得在tomestones的文件中下看到重复的log。

后面的一些code主要就是关闭exception process了。

到这里,catch exception flow已经分析完了~~~