在Linux中除以零异常处理 [英] divide by zero exception handling in Linux

查看:1818
本文介绍了在Linux中除以零异常处理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很想知道在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, &divide_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, &divide_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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆