ART 虚拟机信号处理机制解析:从 Hook sigaction 到信号链构建


引言

在 Android 系统中,ART(Android Runtime)虚拟机负责执行应用程序的字节码,并管理内存、垃圾回收等核心功能。为了处理运行时的异常(如空指针、栈溢出)和系统信号(如 SIGSEGVSIGBUS),ART 实现了一套复杂的信号处理机制。本文将通过源码分析,深入探讨 ART 如何通过 Hook sigaction 构建信号链,并管理信号处理的全流程。


一、为什么需要 Hook sigaction?

Linux 系统的信号处理机制允许用户注册自定义处理函数来响应特定信号(如 SIGSEGV 表示段错误)。默认情况下,开发者可以通过 sigaction 函数注册处理逻辑。然而,在 ART 虚拟机中,需要更精细地控制信号处理流程,原因包括:

  1. 优先级管理:ART 需要在用户注册的处理函数之前拦截信号,优先处理虚拟机内部逻辑(如垃圾回收、栈溢出检测)。
  2. 信号链扩展:允许多个处理函数按顺序执行(如先由 ART 处理,再转发给用户)。
  3. 信号屏蔽控制:动态调整信号掩码,避免信号处理过程中的竞态条件。

为了实现这些目标,ART 必须 Hook 系统的 sigaction 函数,构建自己的信号处理框架。


二、Hook sigaction 的实现机制

1. 保存原始函数指针

ART 通过动态链接库操作(dlopendlsym)获取 libc 中原始的 sigactionsigprocmask 函数地址,并将其保存到全局变量中:

// 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 对象,其中包含两类处理函数:

  1. 特殊处理函数special_handlers_):由 ART 内部注册,如垃圾回收、空指针检查。
  2. 用户处理函数:用户通过 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 实现了高度灵活的信号链管理,其核心优势包括:

  1. 优先级控制:确保虚拟机内部逻辑优先处理。
  2. 可扩展性:支持多级处理函数的链式调用。
  3. 安全性:通过信号掩码管理避免竞态条件。

这种设计不仅提升了 Android 应用的稳定性,还为开发者提供了强大的调试工具(如堆栈打印)。理解这一机制,有助于深入掌握 ART 的运行时行为,并为性能优化和问题排查提供基础。


流程图总结

                    +---------------------+
                    |  用户调用 sigaction   |
                    +---------------------+
                              ↓
                    +---------------------+
                    | ART 的 __sigaction   |
                    | - 检查信号是否 claimed |
                    +---------------------+
                              ↓
                    +---------------------+      否
                    | 信号是否被 claimed? | -------→ 调用原始 sigaction
                    +---------------------+
                              ↓ 是
                    +---------------------+
                    | 记录用户处理函数       |
                    | 注册 ART Handler     |
                    +---------------------+
                              ↓
                    +---------------------+
                    | 信号触发             |
                    +---------------------+
                              ↓
                    +---------------------+
                    | ART Handler 处理     |
                    | - 执行特殊处理逻辑     |
                    +---------------------+
                              ↓
                    +---------------------+
                    | 调用用户处理函数       |
                    +---------------------+

通过这一机制,ART 在保证系统稳定性的同时,为开发者提供了灵活的信号处理能力。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容