Android异常信号处理详解
目录
一、异常信号介绍
二、异常信号处理框架
三、异常信号处理流程及关键代码
3.1 异常信号注册
3.2 异常触发及信号生成
3.3 异常信号传递
3.4 异常信号处理
四、其它
4.1 线程设置信号栈
平台: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 EL1tkernel_ventry 1, irq_invalid // IRQ EL1tkernel_ventry 1, fiq_invalid // FIQ EL1tkernel_ventry 1, error_invalid // Error EL1tkernel_ventry 1, sync // Synchronous EL1h <-- 数据中止走这里kernel_ventry 1, irq // IRQ EL1hkernel_ventry 1, fiq_invalid // FIQ EL1hkernel_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, \regsizemov x0, spbl el\el\ht\()_\regsize\()_\label\()_handler // 跳转到 C 函数处理.if \el == 0b ret_to_user // 返回用户空间.elseb 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);elsetrace_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)配置用户寄存器和栈指针,使进程在返回用户空间时跳转到信号处理函数执行。
如果线程设置了信号栈,会切换到该信号栈上执行(信号栈设置见4.1).
// 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博客
四、其它
-
4.1 线程设置信号栈
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_ANDROIDLOG(FATAL) << "Invalid use of alternate signal stack on Android";
#endifstack_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_structsigaltstack_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;
}