1 问题背景:
产线和测试组低概率出现一些冻屏,当时拿到测试组的手机,经过定位发现手机冻屏的原因是debuggerd64一直处于阻塞状态,发现重启一下debuggerd64进程手机就恢复了。当时定位只看到debuggerd64位进程一直处于unix_stream_connect连接状态,并没有想到更多的线索。
1.1 弯路和想当然:
当时第一眼认为是google如下问题的引入的:当时我们跟google沟通,让他们帮忙解决了system_server在dump mediaserver模式错误的问题,google的改法是从debuggerd64进程里面redirect 32bit debuggerd,中间用了socket。
https://android-review.googlesource.com/#/c/123592/
https://android-review.googlesource.com/#/c/123572/
https://android-review.googlesource.com/#/c/124120/
这块代码进行了重新reveiw和压力测试,并没有发现异常,奇怪?
1.2 问题定位:
后来由于时间的原因,一直没有在定位。产线和测试组仍旧低概率发现由于此原因导致冻屏;
曾经发现当手机在emmc写测试大io状态下,更容易复现。写了一些压力apk去模拟,但是也没有复现出来。
2015/6/24号,测试组又发生了一次冻屏,这次有时间好好思考一下为什么:
首先看了看手机debuggerd64进程的状态,仍旧是阻塞状态,
cat /proc/393/stack
[<0000000000000000>] __switch_to+0x70/0x7c
[<0000000000000000>] unix_wait_for_peer+0x94/0xc0
[<0000000000000000>] unix_stream_connect+0x148/0x424
[<0000000000000000>] SyS_connect+0x78/0xc0
[<0000000000000000>] cpu_switch_to+0x48/0x4c
[<0000000000000000>] 0xffffffffffffffff
unix_stream_connect这个东西永远不返回,如果发生tombstone,watchdog,anr在dump stack的时候都会通过debuggerd64进行stack,如果这个一直阻塞,会阻塞发生问题的进程:
root 392 1 2156 996 00a7156c f76fa9cc S /system/bin/debuggerd
root 393 1 4176 1552 00b3b3c4 855eacd8 S /system/bin/debuggerd64
昨天通过strace查看了一下,看看到底阻塞在哪个socket上:
strace -p 393
Process 393 attached
connect(10, {sa_family=AF_LOCAL, sun_path="/data/system/ndebugsocket"}, 110
真是意外,跟我想象的不一样,当时自己认为是跟32bit的debuggerd通信阻塞了(还看了半天代码,一度怀疑是kernel的bug)
________________________________________
什么场景会导致connect一直连接不上?
导致这种情况有两种可能性:
1. 一是server端并没有调用accept函数
2. 另外一种可能是server由于某种原因阻塞或者退出了,socket也没有关闭,并且永远不在调用accept函数
________________________________________
ndebugsocket是system_server进程创建的,第一种的情况不存在,第二种从代码上看确实存在。
mActivityManagerService.systemReady(new Runnable() {
ActivityManagerService.startObservingNativeCrashes
-->NativeCrashListener.run (NativeCrashListener.java)
--->create/bind/listen/accept调用
从这个过程中我们推断了一下问题发生的条件:
1. 手机开机和android重启中发生了tombstone,debuggerd64检测到这个tombstone,通过ndebugsocket告诉ams;发现出问题的手机system_server确实重启过。
2. ams的NativeCrashListener run函数阻塞了或者退出了。
从现在的逻辑看,
1. run退出肯定会导致问题,这个异常处理google没有做,我们可以补上
2. 如果是run函数阻塞,这个到需要再详细分析一下代码:
两个可疑的是:
函数: consumeNativeCrashData,本函数就是从peerFd中读取所有的数据
函数: Os.write(peerFd, ackSignal, 0, 1);回写socket数据
后来由于手机没电重启了,真不知道是那种情况了,等下次发生的时候再看看。
________________________________________
后来测试组,又给了一部手机,由于crash lister没有指定进程名字,因此看了三个Thread-XX stack的:
发现一个可疑的thread(跟正常的手机比较,分别应该是在recvmsg,futex,accpet)这个可疑thread正在mutex上,是在一个锁上consumeNativeCrashData;
synchronized (mAm.mPidsSelfLocked) {
pr = mAm.mPidsSelfLocked.get(pid);
}
或者
synchronized (mAm) {
pr.crashing = true;
pr.forceCrashReport = true;
}
如果system_server在拿着这个lock的时候发生tombstone非常危险,很可能形成死锁。
17 修改:
1. NativeCrashListener.run函数退出的时候,将socket删除,这个时候socket不在存在,debuggerd自然不会阻塞
-java代码
01 diff --git a/services/core/java/com/android/server/am/NativeCrashListener.java b/services/core/java/com/android/server/am/NativeCrashListener.java
02 old mode 100644
03 new mode 100755
04 index d42d415..f29b1f1
05 --- a/services/core/java/com/android/server/am/NativeCrashListener.java
06 +++ b/services/core/java/com/android/server/am/NativeCrashListener.java
07 @@ -96,6 +96,7 @@ final class NativeCrashListener extends Thread {
08 * and processes the crash dump that is passed through.
09 */
10 NativeCrashListener(ActivityManagerService am) {
11 + super("NativeCrashListener");
12 mAm = am;
13 }
14
15 @@ -164,6 +165,11 @@ final class NativeCrashListener extends Thread {
16 }
17 } catch (Exception e) {
18 Slog.e(TAG, "Unable to init native debug socket!", e);
19 + } finally {
20 + File socketFile = new File(DEBUGGERD_SOCKET_PATH);
21 + if (socketFile.exists()) {
22 + socketFile.delete();
23 + }
24 }
25 }
2. 在debuggerd连接的时候,增加超时机制避免问题
-cpp代码
01 diff --git a/debuggerd/tombstone.cpp b/debuggerd/tombstone.cpp
02 old mode 100644
03 new mode 100755
04 index 4234fae..7f4a14f
05 --- a/debuggerd/tombstone.cpp
06 +++ b/debuggerd/tombstone.cpp
07 @@ -708,6 +708,12 @@ static char* find_and_open_tombstone(int* fd) {
08 static int activity_manager_connect() {
09 int amfd = socket(PF_UNIX, SOCK_STREAM, 0);
10 if (amfd >= 0) {
11 + //set connect timout 2s
12 + struct timeval tv;
13 + memset(&tv, 0, sizeof(tv));
14 + tv.tv_sec = 2; // tight leash
15 + setsockopt(amfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
16 +
17 struct sockaddr_un address;
18 int err;
19
20 @@ -717,8 +723,6 @@ static int activity_manager_connect() {
21 err = TEMP_FAILURE_RETRY(connect(
22 amfd, reinterpret_cast<struct sockaddr*>(&address), sizeof(address)));
23 if (!err) {
24 - struct timeval tv;
25 - memset(&tv, 0, sizeof(tv));
26 tv.tv_sec = 1; // tight leash
27 err = setsockopt(amfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
28 if (!err) {
29 @@ -727,6 +731,7 @@ static int activity_manager_connect() {
30 }
31 }
32 if (err) {
33 + ALOGE("failed to connect to %s error:(%d,%s)\n",address.sun_path, errno, strerror(errno));
34 close(amfd);
35 amfd = -1;
36 }