Post

Android中信号SIGQUIT的特殊作用

Android中信号SIGQUIT的特殊作用

正常情况下,进程收到SIGQUIT信号会生成coredump然后退出。但在Android系统中,SIGQUIT有特殊用途:用来dump线程状态/调用栈。

注:本文内容基于android-14.0.0_r74

1. 背景

近期发现一个问题,执行bugreportz收集信息的时候,系统中有几个应用会退出,退出原因是收到了SIGQUIT信号。

查看Android源码发现,bugreportz会调用dumpstate收集系统中各进程的调用栈等信息,dumpstate是通过向目标进程发送SIGQUIT信号触发信息收集流程的: bugreportz工作流程

默认情况下,进程收到信号QIGQUIT后的预期行为是生成coredump并退出。那些不退出的进程,一定是做了特殊处理,比如:

  1. 注册信号处理函数(注意:信号处理函数在哪个线程执行是不确定的)
  2. 所有的线程都屏蔽信号SIGQUIT,只留一个专用线程处理该信号

Android中采用的是第2种方式,它创建了一个专门的信号处理线程Signal Catcher,收到SIGQUIT后dump其他线程的调用栈等信息。

出问题的几个进程,都有一个线程调用pthread_sigmask修改了blocked signals,导致未阻塞SIGQUIT,而dumpstate发送的SIGQUIT又恰好分到了该线程执行,结果就是生成coredump然后退出。

Android中SIGQUIT相关处理流程如下图所示:

  • zygote进程初始化的时候就屏蔽了信号SIGQUIT,fork出来的app进程自然也屏蔽了这个信号
  • 由于创建新线程的时候会继承当前线程的sigmask,所以app进程后续创建的线程也都自动屏蔽该信号
  • app进程在执行用户代码前,创建了专门的信号处理线程Signal Catcher,用于处理SIGQUIT等信号

SIGQUIT处理流程简图

如果你只关心基本原理,本文读到这里就可以了。
如果你想看下Android是怎么实现的(最好是参照源码阅读),请继续。

2. SIGQUIT屏蔽流程

前面提到zygote中就屏蔽了信号SIGQUIT,所以这块只涉及zygote的初始化过程。zygote的初始化主要分为下面几部分(下图头部横向部分所示):

  • init.rc脚本启动zygote进程主程序
  • 初始化Android运行环境,这里的AndroidRuntime只是个抽象层,或者可以理解成更上层的运行环境(相对于下面的ART来讲)
  • JniInvocation加载真正的运行环境实现库libart.so,这里可以配置加载哪种运行环境,Dalvik切换ART就是在这里实现的;如果你自己实现了一套运行环境,只需要在这里适配就行,其他地方不需要修改
  • 初始化ART运行环境,创建Java虚拟机

具体调用链图中标注的比较清晰,这里就不再赘述了。点击查看高清大图

SIGQUIT屏蔽流程图

上图最后一步Runtime::BlockSignals的实现如下(看到SIGQUIT了吧):

1
2
3
4
5
6
7
8
9
10
11
// art/runtime/runtime.cc

void Runtime::BlockSignals() {
  SignalSet signals;
  signals.Add(SIGPIPE);
  // SIGQUIT is used to dump the runtime's state (including stack traces).
  signals.Add(SIGQUIT);
  // SIGUSR1 is used to initiate a GC.
  signals.Add(SIGUSR1);
  signals.Block();
}

SignalSet::Block最终调用pthread_sigmask64对信号进行屏蔽:

1
2
3
4
5
void Block() {
  if (pthread_sigmask64(SIG_BLOCK, &set_, nullptr) != 0) {
    PLOG(FATAL) << "pthread_sigmask failed";
  }
}

3. SIGQUIT捕获流程

SIGQUIT的捕获流程如下图所示:

  • zygote在第3步fork出应用进程后,应用进程也是屏蔽了信号SIGQUIT
  • 在执行用户代码前,框架层进过一番调用,最终在第10步创建了Signal Catcher线程,专门用来处理SIGQUIT等信号

具体调用链图中标注的比较清晰,这里就不再赘述了。点击查看高清大图

SIGQUIT捕获流程

Signal Catcher线程主函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// art/runtime/signal_catcher.cc

void* SignalCatcher::Run(void* arg) {
  SignalCatcher* signal_catcher = reinterpret_cast<SignalCatcher*>(arg);

  // ... 删减部分非相关代码

  // Set up mask with signals we want to handle.
  SignalSet signals;
  signals.Add(SIGQUIT);
  signals.Add(SIGUSR1);

  while (true) {
    int signal_number = signal_catcher->WaitForSignal(self, signals);
    switch (signal_number) {
    case SIGQUIT:
      signal_catcher->HandleSigQuit();
      break;
    case SIGUSR1:
      signal_catcher->HandleSigUsr1();
      break;
    default:
      LOG(ERROR) << "Unexpected signal %d" << signal_number;
      break;
    }
  }
}

其中SignalCatcher::WaitForSignal又调用SignalSet::Wait,最终阻塞在函数sigwait64上等待信号:

1
2
3
4
5
6
7
8
9
int Wait() {
  // Sleep in sigwait() until a signal arrives. gdb causes EINTR failures.
  int signal_number;
  int rc = TEMP_FAILURE_RETRY(sigwait64(&set_, &signal_number));
  if (rc != 0) {
    PLOG(FATAL) << "sigwait failed";
  }
  return signal_number;
}

进程收到SIGQUIT信号后,因为其他线程都屏蔽了该信号,只能发给Signal Catcher线程处理,在该线程上调用函数SignalCatcher::HandleSigQuit输出调用栈等信息。

4. 其他

不同于SIGQUIT,Android为SIGSEGV注册了信号处理函数,这个操作也是在zygote中就完成的,这样fork出来的app进程就自动继承了这个信号处理函数。

具体流程和本文第2部分[SIGQUIT的屏蔽流程]类似,Runtime::Init在调用Runtime::BlockSignals屏蔽了SIGQUIT信号之后,紧接着就为SIGSEGV等信号注册了处理函数,具体调用链如下:

  1. art/runtime/runtime.cc中的Runtime::Init
  2. art/runtime/runtime_android.cc中的InitPlatformSignalHandlers
  3. art/runtime/runtime_common.cc中的InitPlatformSignalHandlersCommon注册了下面几个信号的处理函数:
    • 信号处理函数是art/runtime/runtime_android.cc中的HandleUnexpectedSignalAndroid
    • 它又调用art/runtime/runtime_common.cc中的HandleUnexpectedSignalCommondump 调用栈等crash信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void InitPlatformSignalHandlersCommon(void (*newact)(int, siginfo_t*, void*),
                                      struct sigaction* oldact,
                                      bool handle_timeout_signal) {
  // ...

  int rc = 0;
  rc += sigaction(SIGABRT, &action, oldact);
  rc += sigaction(SIGBUS, &action, oldact);
  rc += sigaction(SIGFPE, &action, oldact);
  rc += sigaction(SIGILL, &action, oldact);
  rc += sigaction(SIGPIPE, &action, oldact);
  rc += sigaction(SIGSEGV, &action, oldact);
#if defined(SIGSTKFLT)
  rc += sigaction(SIGSTKFLT, &action, oldact);
#endif
  rc += sigaction(SIGTRAP, &action, oldact);
}
This post is licensed under CC BY 4.0 by the author.