在Linux中除以零的异常处理

Nik*_*ike 5 signals exception linux-kernel

我很好奇理解linux中的零除异常处理。当执行零除运算时,将生成陷阱,即将陷阱INT0发送到处理器,并最终将SIGFPE信号发送到执行该操作的进程。

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

set_trap_gate(0, &divide_error);
Run Code Online (Sandbox Code Playgroud)

我想详细了解一下,在INT0生成之前和SIGFPE发送到流程之前发生了什么?

osg*_*sgx 5

陷阱处理程序trap_init已从arch / x86 / kernel / traps.c注册在函数中

void __init trap_init(void)
..
    set_intr_gate(X86_TRAP_DE, &divide_error);
Run Code Online (Sandbox Code Playgroud)

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

如何定义Divor_Error函数?作为traps.c

DO_ERROR_INFO(X86_TRAP_DE, SIGFPE, "divide error", divide_error, FPE_INTDIV,
         regs->ip)
Run Code Online (Sandbox Code Playgroud)

和宏DO_ERROR_INFO定义比特上述相同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 }
Run Code Online (Sandbox Code Playgroud)

(实际上它定义了do_divide_error这是由小ASM编码存根“入口点”与准备参数调用功能宏中定义。entry_32.SENTRY(divide_error)entry_64.S作为zeroentry1303 zeroentry divide_error do_divide_error

因此,当用户除以零(并且此操作到达OoO中的退出缓冲区)时,硬件会生成陷阱,将%eip设置为divide_errorstub,然后设置框架并调用C函数do_divide_error。该函数do_divide_error将创建siginfo_t描述错误的结构(signo = SIGFPE,addr =失败指令的地址等),然后它将尝试通知所有已注册的通知程序register_die_notifier(实际上是一个钩子,有时被内核调试器使用” kgdb“; kprobe的kprobe_exceptions_notify-仅用于int3或gpf; uprobe的arch_uprobe_exception_notify-再次仅int3等)。

由于DIE_TRAP通常不会被通知程序阻止,因此将调用该do_trap函数。它的简短代码为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 }
Run Code Online (Sandbox Code Playgroud)

do_trap会使用发送信号到current进程force_sig_info,该信号将“强制发出该进程不能忽略的信号”。如果该进程有一个活动的调试器(我们的当前进程ptrace由gdb或strace -ed),send_signal则将翻译从当前信号SIGFPE do_trap到SIGTRAP到调试器的信号。如果没有调试器,则信号SIGFPE应该在保存核心文件的同时终止我们的进程,因为这是SIGFPE的默认操作(请在“标准信号”部分中检查man 7信号,在表中搜索SIGFPE)。

该过程无法将SIGFPE设置为忽略它(我不确定此处:1),但是它可以定义自己的信号处理程序来处理信号(将SIGFPE交给 另一个示例)。该处理程序可能只是从siginfo打印%eip,然后运行backtrace()并死亡;甚至可能尝试恢复情况并返回失败的指令。在一些即时编译器喜欢,这可能是有用的,例如qemujava,或valgrind; 或高级语言(如java或)中ghc,这些语言可以将SIGFPE转换为语言异常,而使用这些语言的程序可以处理该异常(例如,来自openjdk的hotspot/src/os/linux/vm/os_linux.cpp意大利面条在中)。

通过代码搜索在debian中有一个SIGFPE处理程序列表,用于关联SIGFPE信号SIGFPE