我很想了解linux中被零除的异常处理。当执行除零操作时,会生成一个陷阱,即向处理器发送INT0
,并最终向执行该操作的进程发送SIGFPE
信号。
如我所见,除以零异常在trap_init()
函数中注册为
set_trap_gate(0, ÷_error);
我想详细了解,在生成INT0
和将SIGFPE
发送到进程之前,这段时间内发生了什么?
发布于 2014-03-16 04:55:24
陷阱处理程序在arch/x86/kernel/traps.c的trap_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函数是如何定义的?作为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
中定义为ENTRY(divide_error)
,在entry_64.S
中定义为macro 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
注册的通知器(实际上它是一个钩子,有时由the in-kernel debugger "kgdb"使用;kprobe的kprobe_exceptions_notify -仅适用于int3或gpf;uprobe的arch_uprobe_exception_notify
-同样仅限int3,等等)。
因为通知程序通常不会阻止DIE_TRAP,所以将调用do_trap
function。它有一个简短的代码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
将使用force_sig_info
向current
进程发送一个信号,这将“强制该进程不能忽略的信号”。如果该进程有活动的调试器(我们当前的进程是gdb或strace的ptrace
-ed ),那么send_signal
会将当前进程的信号SIGFPE从do_trap
转换为调试器的SIGTRAP。如果没有调试器-信号SIGFPE应该在保存核心文件时杀死我们的进程,因为这是SIGFPE的默认操作(检查“标准信号”部分中的man 7 signal,在表格中搜索SIGFPE )。
进程不能将SIGFPE设置为忽略它(这里我不确定:1),但它可以定义自己的信号处理程序来处理信号(example of handing SIGFPE another)。此处理程序可能只是打印来自siginfo的%eip,运行backtrace()
并退出;或者它甚至可能尝试恢复情况并返回到失败的指令。例如,在qemu
、java
或valgrind
等JIT中,或者在java
或ghc
等高级语言中,这可能很有用,这些语言可以将SIGFPE转换为语言异常,并且这些语言中的程序可以处理该异常(例如,来自openjdk is in hotspot/src/os/linux/vm/os_linux.cpp
的意大利面)。
在debian中有一个用于siagaction SIGFPE或signal SIGFPE的SIGFPE处理程序列表
https://stackoverflow.com/questions/13563688
复制相似问题