引言
在 Android 系统中,ART(Android Runtime)虚拟机负责执行应用程序的字节码,并管理内存、垃圾回收等核心功能。为了处理运行时的异常(如空指针、栈溢出)和系统信号(如 SIGSEGV
、SIGBUS
),ART 实现了一套复杂的信号处理机制。本文将通过源码分析,深入探讨 ART 如何通过 Hook sigaction
构建信号链,并管理信号处理的全流程。
一、为什么需要 Hook sigaction?
Linux 系统的信号处理机制允许用户注册自定义处理函数来响应特定信号(如 SIGSEGV
表示段错误)。默认情况下,开发者可以通过 sigaction
函数注册处理逻辑。然而,在 ART 虚拟机中,需要更精细地控制信号处理流程,原因包括:
- 优先级管理:ART 需要在用户注册的处理函数之前拦截信号,优先处理虚拟机内部逻辑(如垃圾回收、栈溢出检测)。
- 信号链扩展:允许多个处理函数按顺序执行(如先由 ART 处理,再转发给用户)。
- 信号屏蔽控制:动态调整信号掩码,避免信号处理过程中的竞态条件。
为了实现这些目标,ART 必须 Hook 系统的 sigaction
函数,构建自己的信号处理框架。
二、Hook sigaction 的实现机制
1. 保存原始函数指针
ART 通过动态链接库操作(dlopen
和 dlsym
)获取 libc 中原始的 sigaction
和 sigprocmask
函数地址,并将其保存到全局变量中:
// sigchain.cc
static decltype(&sigaction) linked_sigaction;
static decltype(&sigprocmask) linked_sigprocmask;
void InitializeSignalChain() {
lookup_libc_symbol(&linked_sigaction, sigaction, "sigaction");
lookup_libc_symbol(&linked_sigprocmask, sigprocmask, "sigprocmask");
}
这一过程在虚拟机的初始化阶段完成。
2. 替换系统函数
ART 重新实现了 sigaction
,使其指向自定义逻辑:
extern "C" int sigaction(int signal, const struct sigaction* new_action,
struct sigaction* old_action) {
InitializeSignalChain();
return __sigaction(signal, new_action, old_action, linked_sigaction);
}
当用户调用 sigaction
时,实际执行的是 ART 的 __sigaction
函数。该函数的核心逻辑是:
- 若信号已被 ART “声明”:记录用户的处理函数,但不传递给内核。
-
若未被声明:调用原始的
sigaction
函数,由内核处理。
3. 信号的声明(Claim)
对于需要由 ART 优先处理的信号(如 SIGSEGV
),ART 会调用 Claim
方法,将内核的 sigaction
替换为自身的 SignalChain::Handler
:
void SignalChain::Register(int signo) {
struct sigaction handler_action = {};
handler_action.sa_sigaction = SignalChain::Handler;
// 注册到内核
linked_sigaction(signo, &handler_action, &action_);
}
此时,所有该信号的处理请求都会首先进入 ART 的 Handler
函数。
三、信号链的构建与执行流程
1. 信号链的结构
ART 为每个信号维护一个 SignalChain
对象,其中包含两类处理函数:
-
特殊处理函数(
special_handlers_
):由 ART 内部注册,如垃圾回收、空指针检查。 -
用户处理函数:用户通过
sigaction
注册的函数。
以 SIGSEGV
为例,其处理流程如下:
+---------------------+
| ART 特殊处理函数 | → 检查是否在编译代码中,处理 GC 或栈溢出
+---------------------+
↓
+---------------------+
| 用户处理函数 1 | → 用户注册的第一个处理函数
+---------------------+
↓
+---------------------+
| 用户处理函数 2 | → 用户注册的第二个处理函数
+---------------------+
2. 信号处理的核心逻辑
当信号触发时,SignalChain::Handler
被调用:
void SignalChain::Handler(int signo, siginfo_t* siginfo, void* ucontext_raw) {
// 1. 优先执行 ART 的特殊处理函数
for (const auto& handler : chains[signo].special_handlers_) {
if (handler.sc_sigaction(signo, siginfo, ucontext_raw)) {
return; // 处理成功则直接返回
}
}
// 2. 转发到用户注册的处理函数
chains[signo].action_.sa_sigaction(signo, siginfo, ucontext_raw);
}
-
特殊处理函数示例:
ART 的NullPointerHandler
会检查空指针异常,若命中则直接处理;否则交给用户逻辑。
3. 信号掩码的动态调整
在信号处理过程中,ART 需要控制信号掩码以确保正确处理嵌套信号:
sigset_t previous_mask;
// 解除对当前信号的屏蔽(若用户设置了 SA_NODEFER)
linked_sigprocmask(SIG_SETMASK, &handler.sc_mask, &previous_mask);
// 执行用户处理函数
ScopedHandlingSignal restorer(signo, !handler_noreturn);
// 恢复原始信号掩码
linked_sigprocmask(SIG_SETMASK, &previous_mask, nullptr);
这一机制允许用户处理函数按需屏蔽其他信号,避免竞态条件。
四、调试支持:LogStack 的实现
当信号未被处理时,ART 通过 LogStack
打印调用栈,辅助开发者定位问题:
void LogStack() {
#if defined(__BIONIC__)
unwindstack::AndroidLocalUnwinder unwinder;
unwindstack::AndroidUnwinderData data;
if (unwinder.Unwind(data)) {
data.DemangleFunctionNames();
// 打印符号化的堆栈信息
for (const auto& frame : data.frames) {
LogError(" #%02zu pc %08" PRIx64 " %s",
frame.num, frame.rel_pc, frame.function_name.c_str());
}
}
#endif
}
通过 AndroidUnwinder
解析堆栈帧,ART 可以输出类似以下的调试信息:
#00 pc 0000134a art::NullPointerHandler::Action
#01 pc 00001b2c art::SignalChain::Handler
五、实际应用场景
1. 空指针检查(NullPointerHandler)
当发生 SIGSEGV
时,NullPointerHandler
会检查当前代码是否在 ART 生成的编译代码中:
bool NullPointerHandler::Action(int sig, siginfo_t* info, void* context) {
if (IsInGeneratedCode(info, context)) {
// 解析栈帧并处理空指针
return true;
}
return false;
}
2. 栈溢出处理(StackOverflowHandler)
ART 通过 SIGSEGV
检测栈溢出,并调整线程栈空间:
bool StackOverflowHandler::Action(int sig, siginfo_t* info, void* context) {
Thread* self = Thread::Current();
if (self->IsStackOverflow()) {
ExpandStack(self); // 扩展栈空间
return true;
}
return false;
}
六、总结
ART 的信号处理机制通过 Hook sigaction
实现了高度灵活的信号链管理,其核心优势包括:
- 优先级控制:确保虚拟机内部逻辑优先处理。
- 可扩展性:支持多级处理函数的链式调用。
- 安全性:通过信号掩码管理避免竞态条件。
这种设计不仅提升了 Android 应用的稳定性,还为开发者提供了强大的调试工具(如堆栈打印)。理解这一机制,有助于深入掌握 ART 的运行时行为,并为性能优化和问题排查提供基础。
流程图总结
+---------------------+
| 用户调用 sigaction |
+---------------------+
↓
+---------------------+
| ART 的 __sigaction |
| - 检查信号是否 claimed |
+---------------------+
↓
+---------------------+ 否
| 信号是否被 claimed? | -------→ 调用原始 sigaction
+---------------------+
↓ 是
+---------------------+
| 记录用户处理函数 |
| 注册 ART Handler |
+---------------------+
↓
+---------------------+
| 信号触发 |
+---------------------+
↓
+---------------------+
| ART Handler 处理 |
| - 执行特殊处理逻辑 |
+---------------------+
↓
+---------------------+
| 调用用户处理函数 |
+---------------------+
通过这一机制,ART 在保证系统稳定性的同时,为开发者提供了灵活的信号处理能力。