我很想了解 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.S
asENTRY(divide_error)
和entry_64.S
as宏zeroentry
1303 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_trap
SIGTRAP 到调试器。如果没有调试器 - 信号 SIGFPE 应该在保存核心文件时终止我们的进程,因为这是 SIGFPE 的默认操作(检查“标准信号”部分中的man 7 信号,在表中搜索 SIGFPE)。
该进程无法将 SIGFPE 设置为忽略它(我在这里不确定:1),但它可以定义自己的信号处理程序来处理信号(另一个处理 SIGFPE 的示例 )。这个处理程序可能只是从 siginfo 打印 %eip,运行然后死掉;甚至可能会尝试恢复情况并返回失败的指令。例如,这在某些 JIT 中可能很有用,例如,或; or 在高级语言中,例如or ,它可以将 SIGFPE 转换为语言异常,并且这些语言中的程序可以处理异常(例如,来自openjdk 的 spaghetti is in)。backtrace()
qemu
java
valgrind
java
ghc
hotspot/src/os/linux/vm/os_linux.cpp
在 debian 中有一个 SIGFPE 处理程序列表,通过代码搜索siagaction SIGFPE或信号 SIGFPE