Android异常信号处理详解

发布于:2025-08-07 ⋅ 阅读:(19) ⋅ 点赞:(0)

目录

一、异常信号介绍

二、异常信号处理框架

三、异常信号处理流程及关键代码

3.1 异常信号注册

3.2 异常触发及信号生成

3.3 异常信号传递

3.4 异常信号处理

四、设置信号栈

五、Android SignalChain机制


平台:Android 14 & kernel 5.4

一、异常信号介绍

用户进程启动时内核会通过linker链接器为该进程注册8种异常信号,当异常信号被触发时返回用户空间信号处理阶段会去抓取tombstone文件,如下:

序号

信号类型

数值

说明

触发原因

1

SIGABRT

6

Abort Signal(终止信号),表示程序因内部错误主动终止运行。通常由 abort() 函数显式触发。

1)显式调用 abort():如断言失败(assert())、库函数检测到不可恢复错误(如内存分配失败)。

2)未捕获的异常:某些语言(如 C++)中未处理的异常可能转换为 SIGABRT。

2

SIGBUS

7

Bus Error(总线错误),表示内存访问违反硬件对齐规则或地址无效。

1)内存对齐错误(如访问未对齐的指针)

2)访问物理地址无效的内存区域(如设备映射错误)

3)读写未映射或只读的内存页

3

SIGFPE

8

Floating Point Exception(浮点异常),表示数学运算错误(浮点或整数运算)。

1)浮点运算错误(如除以零、溢出、无效操作数)

2)整数运算异常(如除以零或模零)。

4

SIGILL

4

Illegal Instruction(非法指令),表示执行了无效或 CPU 不支持的机器指令。

1)执行无效或不存在的机器指令(如代码损坏、解码错误)。

2)CPU 不支持的指令(如使用特定架构扩展的指令但在硬件不支持时执行)。

3)函数返回值地址被篡改。

5

SIGSEGV

11

段错误(Segmentation Fault),因非法内存访问触发,进程尝试访问未分配或受保护的内存区域。

空指针引用、数组越界、访问已释放的内存、堆栈溢出或内存对齐等。

6

SIGSTKFLT

16

Stack Fault(栈故障),表示栈相关异常。

1)硬件检测到栈相关异常(如栈溢出或栈指针无效)。

2)在自定义栈管理场景中显式触发。

7

SIGSYS

31

Bad System Call(非法系统调用),表示系统调用参数或编号错误。

1)调用无效的系统调用号或参数。

2)系统调用参数类型/值不合法。

8

SIGTRAP

5

Trap Signal(陷阱信号),用于调试器设置断点或软件陷阱。

1)调试器设置断点(通过 int3 指令或硬件断点)。

2)软件陷阱指令(如 trap 指令)。

二、异常信号处理框架

三、异常信号处理流程及关键代码

完整信号处理流程:

1)注册阶段 (用户空间)

应用程序调用 sigaction() 注册处理函数 ---> 内核更新当前进程的 sighand_struct->action[]

2)触发阶段 (内核)

硬件异常/软件信号产生 ---> 内核设置 TIF_SIGPENDING 标志 ---> 将信号加入 pending 队列

3)传递阶段 (内核→用户空间)

进程从内核态返回用户态前 ---> 检查到 TIF_SIGPENDING ---> 调用 handle_signal() ---> 设置用户态栈帧和执行上下文

4)处理阶段 (用户空间)

执行注册的信号处理函数 ---> 完成自定义处理逻辑 ---> sigreturn() 系统调用

5)恢复阶段 (内核)

sigreturn 系统调用 ---> 恢复原始执行上下文 ---> 清除信号状态 ---> 返回到被中断的代码

3.1 异常信号注册

向内核注册8种异常信号的回调处理函数(debuggerd_signal_handler),内核存储该函数指针,当触发异常信号时,内核侧通过该函数指针回调执行该函数。

  • 用户空间

(1) 链接器初始化阶段

内核启动linker连接器,在 __linker_init 函数中调用初始化函数:

// bionic/linker/linker_main.cpp
void __linker_init(...) {
      ...
      return __linker_init_post_relocation(args, tmp_linker_so);
}
__linker_init_post_relocation(KernelArgumentBlock& args, soinfo& tmp_linker_so) {
    ...
    linker_main(args, exe_to_load);
    ...
}
// bionic/linker/linker_main.cpp 
linker_main(KernelArgumentBlock& args, const char* exe_to_load) {
    ...
    linker_debuggerd_init();
    ...
}
linker_main(KernelArgumentBlock& args, const char* exe_to_load) {
    ...
    linker_debuggerd_init();
    ...
}
// bionic/linker/linker_debuggerd_android.cpp 
void linker_debuggerd_init() {
 ...
  debuggerd_init(&callbacks);
}

(2) 核心注册函数:debuggerd_init()

// system/core/debuggerd/handler/debuggerd_handler.cpp
void debuggerd_init(debuggerd_callbacks_t* callbacks) {
  ...
  struct sigaction action;
  memset(&action, 0, sizeof(action));
  sigfillset(&action.sa_mask);
  action.sa_sigaction = debuggerd_signal_handler;  // 设置处理函数
  action.sa_flags = SA_RESTART | SA_SIGINFO;    // 关键标志
  action.sa_flags |= SA_ONSTACK;    // 关键标志,这确保信号处理与主栈操作完全隔离,避免因主栈溢出或冲突导致程序崩溃。
  debuggerd_register_handlers(&action);
}
// system/core/debuggerd/include/debuggerd/handler.h
static void __attribute__((__unused__)) debuggerd_register_handlers(struct sigaction* action) {
    // 注册8种异常信号
    sigaction(SIGABRT, action, nullptr);
    sigaction(SIGBUS, action, nullptr);
    sigaction(SIGFPE, action, nullptr);
    sigaction(SIGILL, action, nullptr);
    sigaction(SIGSEGV, action, nullptr);
    sigaction(SIGSTKFLT, action, nullptr);
    sigaction(SIGSYS, action, nullptr);
    sigaction(SIGTRAP, action, nullptr);

    sigaction(BIONIC_SIGNAL_DEBUGGER, action, nullptr);
}
  • 内核空间

用户执行sigaction系统调用接口,向内核注册信号(保存信号及信号处理函数指针):

// kernel/kernel/signal.c 
// 信号注册系统调用
SYSCALL_DEFINE3(sigaction, int, sig,
                const struct old_sigaction __user *, act,
                struct old_sigaction __user *, oact)
{
    struct k_sigaction new_ka, old_ka;
    int ret;
     // 1. 从用户空间复制处理函数配置
    if (act) {
            old_sigset_t mask;
            if (!access_ok(act, sizeof(*act)) ||
                __get_user(new_ka.sa.sa_handler, &act->sa_handler) ||
                __get_user(new_ka.sa.sa_restorer, &act->sa_restorer) ||
                __get_user(new_ka.sa.sa_flags, &act->sa_flags) ||
                __get_user(mask, &act->sa_mask))
                    return -EFAULT;
    //2. 设置信号的处理操作
    ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL);
    // 3. 复制旧的处理函数配置
    if (!ret && oact) {
        if (!access_ok(oact, sizeof(*oact)) ||
            __put_user(old_ka.sa.sa_handler, &oact->sa_handler) ||
            __put_user(old_ka.sa.sa_restorer, &oact->sa_restorer) ||
            __put_user(old_ka.sa.sa_flags, &oact->sa_flags) ||
            __put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask))
                return -EFAULT;
    }
}

3.2 异常触发及信号生成

以用户进程尝试访问无效内存地址(SIGSEGV)为例。

  • 硬件异常触发

触发条件:

当用户进程尝试访问无效内存地址时(如空指针或未映射地址),ARM64硬件MMU触发 Data Abort异常。

CPU行为:

1)发生数据中止异常(Data Abort)时,CPU 自动切换到 EL1 异常级别

2)保存 PSTATE 到 SPSR_EL1,PC 到 ELR_EL1

3)跳转到异常向量表对应条目

// // arch/arm64/kernel/entry.S
ENTRY(vectors)
    kernel_ventry   1, sync_invalid         // Synchronous EL1t
    kernel_ventry   1, irq_invalid          // IRQ EL1t
    kernel_ventry   1, fiq_invalid          // FIQ EL1t
    kernel_ventry   1, error_invalid        // Error EL1t

    kernel_ventry   1, sync                 // Synchronous EL1h  <-- 数据中止走这里
    kernel_ventry   1, irq                  // IRQ EL1h
    kernel_ventry   1, fiq_invalid          // FIQ EL1h
    kernel_ventry   1, error_invalid        // Error EL1h
END(vectors)
  • 内核异常向量处理

同步异常处理入口:

// arch/arm64/kernel/entry.S
SYM_CODE_START_LOCAL(el\el\ht\()_\regsize\()_\label)
        kernel_entry \el, \regsize
        mov        x0, sp
        bl        el\el\ht\()_\regsize\()_\label\()_handler  // 跳转到 C 函数处理
        .if \el == 0
        b        ret_to_user  // 返回用户空间
        .else
        b        ret_to_kernel
        .endif

数据中止处理(el1h_64_sync_handler为硬件中断处理函数):

// arch/arm64/kernel/entry-common.c 

asmlinkage void noinstr el1h_64_sync_handler(struct pt_regs *regs)
{
    unsigned long esr = read_sysreg(esr_el1);
    switch (ESR_ELx_EC(esr)) {
    case ESR_ELx_EC_DABT_CUR:
    case ESR_ELx_EC_IABT_CUR:
            el1_abort(regs, esr);
            break;
            ...
}
static void noinstr el1_abort(struct pt_regs *regs, unsigned long esr)
{
    ...
    do_mem_abort(far, esr, regs);
    local_daif_mask();
    exit_to_kernel_mode(regs);
}

// arch/arm64/mm/fault.c
void do_mem_abort(unsigned long far, unsigned long esr, struct pt_regs *regs)
{
    ...
    trace_mem_abort_entries(addr, esr, regs);
    ...
    arm64_notify_die(inf->name, regs, inf->sig, inf->code, addr, esr);
}

trace_mem_abort_entries(unsigned long far, unsigned long esr, struct pt_regs *regs)
{
        if (!trace_mem_abort_enabled())
                return;

        if (user_mode(regs))
                trace_mem_abort_user(far, esr, regs);
        else
                trace_mem_abort_kernel(far, esr, regs);
}
  • 信号生成

内核在处理异常时,会根据异常类型生成对应的异常信号(如SIGSEGV):

// arch/arm64/mm/fault.c
static const struct fault_info fault_info[] = {
    ...
    // do_translation_fault:处理页面未映射的错误(如访问未分配的虚拟地址)
    // do_page_fault:处理页面存在但权限不足的情况(如访问只读页面)
    { do_translation_fault,        SIGSEGV, SEGV_MAPERR,        "level 0 translation fault"        },
    { do_translation_fault,        SIGSEGV, SEGV_MAPERR,        "level 1 translation fault"        },
    { do_translation_fault,        SIGSEGV, SEGV_MAPERR,        "level 2 translation fault"        },
    { do_translation_fault,        SIGSEGV, SEGV_MAPERR,        "level 3 translation fault"        },
    { do_page_fault,        SIGSEGV, SEGV_ACCERR,        "level 1 access flag fault"        },
    { do_page_fault,        SIGSEGV, SEGV_ACCERR,        "level 2 access flag fault"        },
    { do_page_fault,        SIGSEGV, SEGV_ACCERR,        "level 3 access flag fault"        },
    { do_page_fault,        SIGSEGV, SEGV_ACCERR,        "level 1 permission fault"        },
    { do_page_fault,        SIGSEGV, SEGV_ACCERR,        "level 2 permission fault"        },
    { do_page_fault,        SIGSEGV, SEGV_ACCERR,        "level 3 permission fault"        },
    ...
};
static int __kprobes do_page_fault(unsigned long far, unsigned long esr,
                                   struct pt_regs *regs)
{
    ...
    __do_page_fault(mm, vma, addr, mm_flags, vm_flags, regs);
    ...
    // 生成SIGSEGV信号,并发送信号给问题进程
    arm64_force_sig_fault(SIGSEGV,
                                  fault == VM_FAULT_BADACCESS ? SEGV_ACCERR : SEGV_MAPERR,
                                  far, inf->name);
}
// arch/arm64/kernel/traps.c
void arm64_force_sig_fault(int signo, int code, unsigned long far,
                           const char *str)
{
    ...
    force_sig_fault(signo, code, (void __user *)far);
}
// kernel/signal.c 
int force_sig_fault(int sig, int code, void __user *addr
        ___ARCH_SI_IA64(int imm, unsigned int flags, unsigned long isr))
{
    return force_sig_fault_to_task(sig, code, addr
                                   ___ARCH_SI_IA64(imm, flags, isr), current);
}

int force_sig_fault_to_task(int sig, int code, void __user *addr
        ___ARCH_SI_IA64(int imm, unsigned int flags, unsigned long isr)
        , struct task_struct *t)
{
        // 构建signal结构体
        struct kernel_siginfo info;
        clear_siginfo(&info);
        info.si_signo = sig;
        info.si_errno = 0;
        info.si_code  = code;
        info.si_addr  = addr;
        // 发送信号给问题进程
        return force_sig_info_to_task(&info, t, HANDLER_CURRENT);
}

3.3 异常信号传递

// kernel/kernel/signal.c
// 信号传递核心逻辑
force_sig_info_to_task(struct kernel_siginfo *info, struct task_struct *t,
        enum sig_handler handler)
{
    ...
     // 1. 设置信号挂起标志、唤醒进程(如果处于睡眠状态)
    recalc_sigpending_and_wake(t);
    // 2. 给问题进程发送信号
    send_signal_locked(sig, info, t, PIDTYPE_PID);
    ...
}

3.4 异常信号处理

用户进程从内核返回用户空间前,会去处理该进程的信号。跳转到Handler(信号处理函数指针),返回用户空间执行该进程注册的信号回调处理函数(debuggerd_signal_handler)。

  • 返回用户空间前的信号处理

主要做了以下事情:

1)保存当前进程的寄存器状态到用户栈上的信号帧(rt_sigframe)。

2)设置用户空间信号处理函数的参数(如 siginfo_t 和 ucontext_t)。

3)配置用户寄存器和栈指针,使进程在返回用户空间时跳转到信号处理函数执行。

如果线程设置了信号栈,会切换到该信号栈上执行(信号栈设置见第四章节).

// kernel/kernel/signal.c
void do_signal(struct pt_regs *regs) {
    struct ksignal ksig;
   ...
    if (test_thread_flag(TIF_SIGPENDING) && get_signal(&ksig)) {  // 没有待处理信号,直接返回
            // 处理信号:保存现场、设置信号栈、跳转到用户空间信号处理函数
            handle_signal(&ksig, regs);
            return;
    }
    ...
}

handle_signal(struct ksignal *ksig, struct pt_regs *regs)
{
        sigset_t *oldset = sigmask_to_save();
        int failed;

        /* Set up the stack frame */
        failed = setup_rt_frame(ksig, oldset, regs);

        signal_setup_done(failed, ksig, 0);
}

setup_rt_frame(struct ksignal *ksig, sigset_t *set, struct pt_regs *regs)
{
        struct rt_sigframe __user *sf;
        /*
        获取用户栈空间:
        如果进程设置了 sigaltstack(独立信号栈),则使用该栈;否则使用当前栈。
        返回一个指向用户空间 rt_sigframe 结构的指针 sf。
        */
        sf = get_sigframe(ksig, regs, sizeof(struct rt_sigframe));
        /*
         保存用户态的寄存器状态:
        将内核保存的寄存器上下文(regs)复制到用户栈中的 rt_sigframe->uc_mcontext。
        确保在信号处理完成后可以恢复进程的原始执行状态。
         */
        err |= stash_usr_regs(sf, regs, set);
       ...
        /*
        配置寄存器以跳转到信号处理函数:
        r0: 传递信号编号(如 SIGSEGV)。
        ret: 修改 PC 寄存器为信号处理函数地址,强制跳转。
        blink: 指向 sa_restorer(通常为 _rt_sigreturn),用于信号处理完成后恢复现场。
        sp: 将栈指针指向新构造的信号帧顶部。
         */
        regs->r0 = ksig->sig;
        // 修改PC指针为信号处理函数地址
        regs->ret = (unsigned long)ksig->ka.sa.sa_handler;

        /* 设置返回地址(restorer) */
        if(!(ksig->ka.sa.sa_flags & SA_RESTORER))
                return 1;
        regs->blink = (unsigned long)ksig->ka.sa.sa_restorer;
        /* 设置用户栈指针指向信号帧 */
        regs->sp = (unsigned long)sf;
        ...
        return err;
}
  • 用户空间执行debuggerd_signal_handler信号回调函数

static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* context) {
    ...
    // 伪线程创建crash_dump子进程,抓取debug信息,传给tombstone保存
     pid_t child_pid = clone(debuggerd_dispatch_pseudothread, pseudothread_stack,
      CLONE_THREAD | CLONE_SIGHAND | CLONE_VM | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID,
      &thread_info, nullptr, nullptr, &thread_info.pseudothread_tid);
    ...
    // 内核收到signal信号,若coredump功能开启,抓取coredump文件
    resend_signal(info);
}

 coredump实现原理可参考该文档:Linux CoreDump机制详解-CSDN博客

四、设置信号栈

app线程被创建初始化时,会为线程设置独立的信号栈。当信号处理函数被触发时,系统会切换到这个栈上执行,避免主栈被破坏或者溢出。

// art/runtime/thread.cc
bool Thread::Init(ThreadList* thread_list, JavaVMExt* java_vm, JNIEnvExt* jni_env_ext) {
  ...
  SetUpAlternateSignalStack();
  ...
}

// art/runtime/thread_linux.cc 
void Thread::SetUpAlternateSignalStack() {
  // Create and set an alternate signal stack.
#ifdef ART_TARGET_ANDROID
  LOG(FATAL) << "Invalid use of alternate signal stack on Android";
#endif
  stack_t ss;
  ss.ss_sp = new uint8_t[kHostAltSigStackSize];
  ss.ss_size = kHostAltSigStackSize;
  ss.ss_flags = 0;
  CHECK(ss.ss_sp != nullptr);
  SigAltStack(&ss, nullptr);
  ...
}

static void SigAltStack(stack_t* new_stack, stack_t* old_stack) {
  // 系统调用,执行内核sigaltstack函数
  sigaltstack(new_stack, old_stack);
}
// kernel/signal.c
SYSCALL_DEFINE2(sigaltstack,const stack_t __user *,uss, stack_t __user *,uoss)
{
        stack_t new, old;
        int err;
        if (uss && copy_from_user(&new, uss, sizeof(stack_t)))
                return -EFAULT;
        err = do_sigaltstack(uss ? &new : NULL, uoss ? &old : NULL,
                              current_user_stack_pointer(),
                              MINSIGSTKSZ);
        if (!err && uoss && copy_to_user(uoss, &old, sizeof(stack_t)))
                err = -EFAULT;
        return err;
}

/*
    ss: 用户提供的新备用栈配置(类型为stack_t*)。
    oss: 返回当前备用栈的配置(类型为stack_t*)。
    sp: 当前线程的用户栈指针(用户空间的栈顶地址),用于判断是否在信号栈上执行。
    min_ss_size: 内核定义的最小备用栈大小(通常为8KB)。
*/
static int
do_sigaltstack (const stack_t *ss, stack_t *oss, unsigned long sp,
                size_t min_ss_size)
{
        struct task_struct *t = current;
        int ret = 0;

        if (oss) {
                memset(oss, 0, sizeof(stack_t));
                oss->ss_sp = (void __user *) t->sas_ss_sp;
                oss->ss_size = t->sas_ss_size;
                oss->ss_flags = sas_ss_flags(sp) |
                        (current->sas_ss_flags & SS_FLAG_BITS);
        }
        // 设置新的信号栈
        if (ss) {
                void __user *ss_sp = ss->ss_sp;
                size_t ss_size = ss->ss_size;
                unsigned ss_flags = ss->ss_flags;
                int ss_mode;

                if (unlikely(on_sig_stack(sp)))
                        return -EPERM;

                ss_mode = ss_flags & ~SS_FLAG_BITS;
                if (unlikely(ss_mode != SS_DISABLE && ss_mode != SS_ONSTACK &&
                                ss_mode != 0))
                        return -EINVAL;

                if (t->sas_ss_sp == (unsigned long)ss_sp &&
                    t->sas_ss_size == ss_size &&
                    t->sas_ss_flags == ss_flags)
                        return 0;
                // 更新到该线程对应的task_struct
                sigaltstack_lock();
                if (ss_mode == SS_DISABLE) {
                        ss_size = 0;
                        ss_sp = NULL;
                } else {
                        if (unlikely(ss_size < min_ss_size))
                                ret = -ENOMEM;
                        if (!sigaltstack_size_valid(ss_size))
                                ret = -ENOMEM;
                }
                if (!ret) {
                        t->sas_ss_sp = (unsigned long) ss_sp;
                        t->sas_ss_size = ss_size;
                        t->sas_ss_flags = ss_flags;
                }
                sigaltstack_unlock();
        }
        return ret;
}

五、Android SignalChain机制

  • SignalChain机制的由来?

上述讲的是基于Linux内核的sigaction()方法进行信号注册与处理的机制,我们称作为传统的信号处理机制。由于传统的信号处理机制存在一定的局限性,因此Android提出了SignalChain信号处理机制。

  • 什么是SignalChain机制?

Android 的 SignalChain 是对 Linux 信号处理机制的一次重要改进,通过引入链式管理、优先级控制和封装层,在保持兼容性的同时解决了传统机制的局限性。

  • 传统的信号处理机制与SignalChain机制差异性

特性

Linux 信号处理机制

Android SignalChain机制

处理上下文

被中断线程的上下文 (同步,不安全)

专门的SignalCatcher线程 (异步,安全)

处理函数管理

全局替换 (sigaction) ,通过 sigaction 注册信号处理器时,默认会覆盖之前注册的处理器。如,如果多个应用层通过 sigaction(SIGSEGV, ...) 注册自己的处理器,则最后一个注册的处理器会成为最终生效的唯一处理器。

链式调用 (协作处理),允许多组件(ART, debuggerd, libs)共存协作处理信号。维护了一个 链表结构的信号处理器列表(chains),而非单个注册点。系统关键处理器(如 ART 的 art_sigsegv_handler)通过 AddSpecialSignalHandlerFn 被添加到链表的最前端,应用层通过 sigaction 注册的处理器会被添加到链表的末尾。

面向对象

支持线程级和进程级信号处理。

进程级机制:所有信号处理链属于进程范围,不区分线程。

阻塞性

信号处理函数 (signal handler) 在信号到达时同步中断当前线程的执行流。处理函数本身运行在一个非常受限且不安全的上下文中(类似于中断上下文)。

sigchain 库拦截信号后,其处理函数执行极简操作(通常是设置一个标志或写入一个管道),然后将控制权立即交还给被中断的线程。真正的、可能复杂的信号处理工作(如收集堆栈跟踪、生成 tombstone、通知 ART 虚拟机)是由一个专门的、高优先级的用户态线程 SignalCatcher 在正常、安全的上下文中完成的。

栈溢出处理

信号处理函数在被中断线程的栈上执行。如果该线程的栈空间已经耗尽(本身就是栈溢出 SIGSEGV),处理函数将无法运行,导致进程直接终止而无任何诊断信息。

sigchain为某些关键信号(如 SIGSEGV)注册的极简调度函数,会尝试在拦截到信号后立即切换到备用信号栈。即使主线程栈已崩溃,备用栈通常仍有空间运行这个极简的调度函数,使其有机会通知 SignalCatcher 线程或执行最基本的崩溃记录(如写入 tombstone 的核心转储部分),极大地提高了在栈崩溃场景下获取诊断信息的成功率。

  • SignalChain机制作用?

1)防止信号处理器被覆盖:Android通过SignalChain机制管理信号处理链,确保系统信号处理器(如art_sigsegv_handler)优先处理信号,防止被应用层的信号处理器覆盖。而传统的Linux信号处理中,sigaction会覆盖之前的信号处理器,导致后续的处理器无法被正确调用。

2)统一管理:确保关键系统功能(如内存错误处理、ANR检测)优先执行,维护系统稳定性和用户体验。

3)兼容性与扩展性:在兼容Linux接口的同时,增强信号处理的灵活性和可靠性,适应移动设备环境需求。


网站公告

今日签到

点亮在社区的每一天
去签到