我很想了解 linux 中除以零异常处理。当执行除以零操作时,会生成一个陷阱,即INT0发送到处理器并最终将SIGFPE信号发送到执行该操作的进程。
如我所见,除以零异常在trap_init()函数中注册为
set_trap_gate(0, ÷_error);
我想详细了解,在INT0生成和SIGFPE发送到进程之前发生了什么?
我很想了解 linux 中除以零异常处理。当执行除以零操作时,会生成一个陷阱,即INT0发送到处理器并最终将SIGFPE信号发送到执行该操作的进程。
如我所见,除以零异常在trap_init()函数中注册为
set_trap_gate(0, ÷_error);
我想详细了解,在INT0生成和SIGFPE发送到进程之前发生了什么?
陷阱处理程序在arch/x86/kernel/traps.ctrap_init的函数中注册
void __init trap_init(void)
..
set_intr_gate(X86_TRAP_DE, ÷_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.Sas宏zeroentry1303 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