7

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

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

set_trap_gate(0, &divide_error);

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

4

1 回答 1

9

陷阱处理程序在arch/x86/kernel/traps.ctrap_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 函数是如何定义的?作为一个traps.c

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

在同一个 traps.c 中DO_ERROR_INFO定义了一点:

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.SasENTRY(divide_error)entry_64.Saszeroentry1303 zeroentry divide_error do_divide_error中定义:)

因此,当用户除以零(并且此操作到达 OoO 中的退休缓冲区)时,硬件会生成一个陷阱,将 %eip 设置为divide_error存根,它会设置帧并调用 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 }

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

该进程无法将 SIGFPE 设置为忽略它(我在这里不确定:1),但它可以定义自己的信号处理程序来处理信号(另一个处理 SIGFPE 的示例 )。这个处理程序可能只是从 siginfo 打印 %eip,运行然后死掉;甚至可能会尝试恢复情况并返回失败的指令。例如,这在某些 JIT 中可能很有用,例如,或; or 在高级语言中,例如or ,它可以将 SIGFPE 转换为语言异常,并且这些语言中的程序可以处理异常(例如,来自openjdk 的 spaghetti is in)。backtrace()qemujavavalgrindjavaghchotspot/src/os/linux/vm/os_linux.cpp

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

于 2014-03-15T20:55:24.033 回答