在Linux中除以零异常处理 [英] divide by zero exception handling in Linux
问题描述
INT0
发送到处理器,最终 SIGFPE
信号是发送到执行操作的进程。 正如我所看到的,零异常除以 trap_init()
函数作为
set_trap_gate(0,& divide_error);
我想详细了解一下,在$ code> INT0 正在生成,并且 SIGFPE
被发送到进程之前
陷阱处理程序已从 trap_init 函数中注册/kernel/traps.c?v=3.12#L728rel =nofollow noreferrer> arch / x86 / kernel / traps.c
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,FPE_INTDIV,
regs-> ip)
宏 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枚举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回报; \
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
as 宏 zeroentry
: 1303 zeroentry divide_error do_divide_error
/ a>)
所以当用户除以零(这个操作到达OoO中的退休缓冲区)时,硬件会产生陷阱,将%eip设置为 divide_error
stub,它设置框架并调用C函数 do_divide_error
。函数 do_divide_error
将创建描述错误的 siginfo_t
struct(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
将使用当前进程发送一个信号.c?v = 3.12#L1222rel =nofollow noreferrer> force_sig_info
,这将强制进程不能忽略的信号。如果进程有一个主动调试器(我们当前的进程是gdb或strace的 ptrace
),那么 send_signal
将信号SIGFPE从 do_trap
转换为SIGTRAP到调试器的当前进程。如果没有调试器 - 信号SIGFPE应该在保存核心文件时杀死我们的进程,因为这是SIGFPE的默认动作(检查 man 7信号在标准信号一节中,在表中搜索SIGFPE)。
进程无法设置SIGFPE来忽略它(我不确定这里: 1 ),但它可以定义自己的信号处理程序来处理信号(处理SIGFPE的示例 另一个)。这个处理程序可能只是从siginfo打印%eip,运行 backtrace()
或者甚至可以尝试恢复这种情况并返回失败的指令。这可能是有用的,例如在一些JIT,如 qemu
, java
或 valgrind
;或者使用高级语言,如 java
或 ghc
,可以将SIGFPE转换为语言异常,并以这些语言编程可以处理异常(例如,从 openjdk在 hotspot / src / os / linux / vm / os_linux.cpp
)。
通过 siagaction SIGFPE 的代码搜索,在debian中列出了一些SIGFPE处理程序/ a>或信号SIGFPE
I am curious to understand the divide by zero exception handling in linux. When divide by zero operation is performed, a trap is generated i.e. INT0
is sent to the processor and ultimately SIGFPE
signal is sent to the process that performed the operation.
As I see, the divide by zero exception is registered in trap_init()
function as
set_trap_gate(0, ÷_error);
I want to know in detail, what all happens in between the INT0
being generated and before the SIGFPE
being sent to the process?
Trap handler is registered in the trap_init
function from arch/x86/kernel/traps.c
void __init trap_init(void)
..
set_intr_gate(X86_TRAP_DE, ÷_error);
set_intr_gate
writes the address of the handler function into idt_table
x86/include/asm/desc.h.
How is the divide_error function defined? As a macro in traps.c
DO_ERROR_INFO(X86_TRAP_DE, SIGFPE, "divide error", divide_error, FPE_INTDIV,
regs->ip)
And the macro DO_ERROR_INFO
is defined 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 }
(Actually it defines the do_divide_error
function which is called by the small asm-coded stub "entry point" with prepared arguments. The macro is defined in entry_32.S
as ENTRY(divide_error)
and entry_64.S
as macro zeroentry
: 1303 zeroentry divide_error do_divide_error
)
So, when a user divides by zero (and this operation reaches the retirement buffer in OoO), hardware generates a trap, sets %eip to divide_error
stub, it sets up the frame and calls the C function do_divide_error
. The function do_divide_error
will create the siginfo_t
struct describing the error (signo=SIGFPE
, addr= address of failed instruction,etc), then it will try to inform all notifiers, registered with register_die_notifier
(actually it is a hook, sometimes used by the in-kernel debugger "kgdb"; kprobe's kprobe_exceptions_notify - only for int3 or gpf; uprobe's arch_uprobe_exception_notify
- again only int3, etc).
Because DIE_TRAP is usually not blocked by the notifier, the do_trap
function will be called. It has a short code of 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
will send a signal to the current
process with force_sig_info
, which will "Force a signal that the process can't ignore".. If there is an active debugger for the process (our current process is ptrace
-ed by gdb or strace), then send_signal
will translate the signal SIGFPE to the current process from do_trap
into SIGTRAP to debugger. If no debugger - the signal SIGFPE should kill our process while saving the core file, because that is the default action for SIGFPE (check man 7 signal in the section "Standard signals", search for SIGFPE in the table).
The process can't set SIGFPE to ignore it (I'm not sure here: 1), but it can define its own signal handler to handle the signal (example of handing SIGFPE another). This handler may just print %eip from siginfo, run backtrace()
and die; or it even may try to recover the situation and return to the failed instruction. This may be useful for example in some JITs like qemu
, java
, or valgrind
; or in high-level languages like java
or ghc
, which can turn SIGFPE into a language exception and programs in these languages can handle the exception (for example, spaghetti from openjdk is in hotspot/src/os/linux/vm/os_linux.cpp
).
There is a list of SIGFPE handlers in debian via codesearch for siagaction SIGFPE or for signal SIGFPE
这篇关于在Linux中除以零异常处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!