debuggerd阻塞问题导致冻屏

时间:2021-03-13 03:36:00
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          }