在Linux中除以零异常处理

时间:2012-11-26 11:14:24

标签: exception linux-kernel signals

我很想知道linux中的零异常处理。当执行除零运算时,会生成陷阱,即INT0被发送到处理器,最终SIGFPE信号被发送到执行该操作的进程。

如我所见,除以零的异常在trap_init()函数中注册为

set_trap_gate(0, &divide_error);

我想详细了解在INT0生成之间和SIGFPE发送到流程之前发生的事情?

1 个答案:

答案 0 :(得分:4)

陷阱处理程序已在arch/x86/kernel/traps.c

trap_init函数中注册
void __init trap_init(void)
..
    set_intr_gate(X86_TRAP_DE, &divide_error);

set_intr_gate将处理函数的地址写入idt_table x86/include/asm/desc.h

如何定义divide_error函数?作为macro in traps.c

DO_ERROR_INFO(X86_TRAP_DE, SIGFPE, "divide error", divide_error, FPE_INTDIV,
         regs->ip)

DO_ERROR_INFO定义为a bit above in the same traps.c

193 #define DO_ERROR_INFO(trapnr, signr, str, name, sicode, siaddr)         \
194 dotraplinkage void do_##name(struct pt_regs *regs, long error_code)     \
195 {                                                                       \
196         siginfo_t info;                                                 \
197         enum ctx_state prev_state;                                      \
198                                                                         \
199         info.si_signo = signr;                                          \
200         info.si_errno = 0;                                              \
201         info.si_code = sicode;                                          \
202         info.si_addr = (void __user *)siaddr;                           \
203         prev_state = exception_enter();                                 \
204         if (notify_die(DIE_TRAP, str, regs, error_code,                 \
205                         trapnr, signr) == NOTIFY_STOP) {                \
206                 exception_exit(prev_state);                             \
207                 return;                                                 \
208         }                                                               \
209         conditional_sti(regs);                                          \
210         do_trap(trapnr, signr, str, regs, error_code, &info);           \
211         exception_exit(prev_state);                                     \
212 }

(实际上它定义了do_divide_error函数,该函数由小的asm编码存根"入口点"用准备好的参数调用。宏在entry_32.S中定义为{{ 3}}和entry_64.SENTRY(divide_error)macro zeroentry

因此,当用户除以零(并且此操作到达OoO中的报废缓冲区)时,硬件会生成陷阱,将%eip设置为divide_error存根,它会设置帧并调用C函数{ {1}}。函数do_divide_error将创建描述错误的do_divide_error结构(signo = siginfo_t,addr =失败指令的地址等),然后它将尝试通知所有通知者,注册{ {3}}(实际上它是一个钩子,有时由1303 zeroentry divide_error do_divide_error使用; kprobe register_die_notifier - 仅适用于int3或gpf; uprobe' s the in-kernel debugger "kgdb" - 仅限于int3等。)

由于DIE_TRAP通常不会被通知程序阻止,因此将调用kprobe_exceptions_notify。它的代码为SIGFPE

do_trap

139 static void __kprobes 140 do_trap(int trapnr, int signr, char *str, struct pt_regs *regs, 141 long error_code, siginfo_t *info) 142 { 143 struct task_struct *tsk = current; ... 157 tsk->thread.error_code = error_code; 158 tsk->thread.trap_nr = trapnr; 170 171 if (info) 172 force_sig_info(signr, info, tsk); ... 175 } 会使用arch_uprobe_exception_notifydo_trap进程发送一个信号,该信号将强制显示该进程无法忽略的信号" ..如果进程有一个活动的调试器(我们当前的进程是current - 由gdb或strace编写),那么ptrace会将信号SIGFPE从send_signal转换为当前进程到SIGTRAP调试器。如果没有调试器 - 信号SIGFPE应该在保存核心文件时终止我们的进程,因为这是SIGFPE的默认操作(在#34;标准信号"部分中检查do_trap function,在中搜索SIGFPE表)。

该过程无法设置SIGFPE忽略它(我不确定:force_sig_info),但它可以定义自己的信号处理程序来处理信号(man 7 signal 1)。这个处理程序可能只是从siginfo打印%eip,运行do_trap并死掉;或者甚至可以尝试恢复情况并返回失败的指令。这可能在某些JIT中很有用,例如backtrace()qemujava;或者在valgrindjava等高级语言中,可以将SIGFPE转换为语言异常,这些语言的程序可以处理异常(例如,来自example of handing SIGFPE的spaghetti)。 / p>

通过anotheropenjdk is in hotspot/src/os/linux/vm/os_linux.cpp的代码搜索,debian中有一个SIGFPE处理程序列表