背景
在我们的内部 Monkey 测试中频繁发生以下特征的 watchdog、anr 等现象,某个进程所有线程都被 ptrace_stop 以及其中一线程状态处于 pipe_read。
1 | ini复制代码sysTid=xx1 ptrace_stop |
原问题
system_server 进程与 vold 之间的 binder 通讯上阻塞了。
1 | less复制代码"main" prio=5 tid=1 Native |
然而 vold 进程的状态却被其它进程 ptrace_stop 挂起,无法查看它堆栈。
1 | ini复制代码----- Waiting Channels: pid 524 at 2023-08-30 04:30:18.586759099+0800 ----- |
而 crash_dump64 进程,父进程等管道数据,子进程等某进程结束。
1 | yaml复制代码u:r:crash_dump:s0 root 7328 7328 524 2241480 2996 pipe_read 0 S 19 0 - 0 fg 5 crash_dump64 crash_dump64 |
诸如此类问题,没啥好的办法,依据特征保存机器现场。
化简为繁
对 debuggerd_client.cpp 的 get_wchan_data 添加特征检测触发内核 panic 来得到该特征现场的 Ramdump 文件,然后在恢复用户态进程数据,详情参考《浅谈安卓离线内存分析项目》。
1 | c复制代码static std::string get_wchan_data(int fd, pid_t pid) { |
特征复现
测试一段时间后,终于得到一例发生 echo c > /proc/sysrq-trigger 触发内核死机问题。
1 | css复制代码[<ffffffc008f8d22c>] panic+0x190/0x388 |
首先我们得确定本次 panic 是我们添加调试代码触发的,那么我们可以先将 panic 进程转储一份 Core 文件出来进行栈回溯。crash 转储 Core 可参考《Crash 插件开发指南》。开源版本 lp 插件比 ram2core 性能以及其它均更优,大家可尝试用这个替代公版 crash-gcore。
1 | shell复制代码crash> ps -G 27252 |
并且对 system_server 的 Core 文件 Java 部分进行修复后。
1 | less复制代码art-parser> bt 27252 |
1 | ini复制代码art-parser> bt 27252 -v |
1 | php复制代码art-parser> disassemble 0x9b012068 -i 0x7d1a2cd01c |
1 | ini复制代码art-parser> p 0x1a631e38 |
1 | less复制代码(gdb) bt |
还原数据可知 system_server 正在 dump com.android.browser/.BrowserActivity 的 anr_trace.txt 过程中,并且 get_wchan_data 此时捕捉到特征发生在进程 453 身上。回到 crash 环境下,查看进程 453 的所有线程都在处于 ptrace_stop,以及一个线程处于 pipe_read 中。
1 | yaml复制代码crash> bt -g 453 |
离线分析
从堆栈以及寄存器上下文,可以知道线程 27712 正在读取管道 FD(0x3),接下来将进程 lmkd 453 转储一份 Core 文件,以便我们分析父子进程的关系。
1 | less复制代码// 进程 453 堆栈 |
1 | less复制代码(gdb) frame 2 |
这里我们可以知道 lmkd 线程 27712 在 debuggerd_handler.cpp:486(对应 Google 原生代码如图)处等待,而这个消息需要子进程 crash_dump 发送过来,因此我们在转储进程 27713 Core 文件。
1 | shell复制代码// 进程 27713 堆栈 |
同样的 crash_dump (27713) 在读取管道 FD(0x7),获得一个字符后退出,我们需进行分析子进程 forkpid 的场景。
1 | yaml复制代码crash> ps | grep 27713 |
找到子进程 27732 后,进一步转储 Core 下来到 gdb 上解析。
1 | shell复制代码(gdb) thread |
1 | ini复制代码(gdb) frame 3 |
从此处我们可以知道 27732 需要等待线程 27712 退出,然而 27712 却阻塞在管道中。
管道阻塞分析
1 | javascript复制代码crash> files 27712 |
1 | bash复制代码crash> files 27713 |
1 | bash复制代码crash> files 27732 |
从代码上看正常情况下,目前 27732 处于函数 wait_for_vm_process,此前最后一次往管道写入的数据为 ’0x1‘ ,并且进程 27712 不应该会发生管道阻塞,而会正常退出的。
接下来分析管道里的数据状态。
1 | ini复制代码crash> struct file.private_data ffffff81d4b6c000 -x |
从管道里的数据,可以确定最后一次写入数据 0x1 已经被读过了,并且从数据上我们可以了解到写入 0x1 之前,存在一函数先往管道写入 ‘f’ 字符。
于是找到内部定制的某功能代码实现。大致如下
1 | c复制代码bool Xxxxx::Xxxxxxx(int input_read_fd) { |
最后
原因是: 管道双方读写时序不可控,时序上刚好写者进程先发生,往缓存写入两个字符 01 66,然后读者进程,首次读取管道数据 4 个字符,取出了所有数据,导致后面管道阻塞。
1 | c复制代码bool Xxxxx::Xxxxxxx(int input_read_fd) { |
本文转载自: 掘金