目录
平台: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接口的同时,增强信号处理的灵活性和可靠性,适应移动设备环境需求。