7

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?

Shahbaz
  • 46,337
  • 19
  • 116
  • 182
Nike
  • 455
  • 1
  • 5
  • 16
  • possible duplicate of [Why linux kernel use trap gate to handle divide\_error exception?](http://stackoverflow.com/questions/8530794/why-linux-kernel-use-trap-gate-to-handle-divide-error-exception) Check out this **[answer](http://stackoverflow.com/a/15501983/319204)**. – TheCodeArtist Jul 21 '13 at 03:34

1 Answers1

11

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

Community
  • 1
  • 1
osgx
  • 90,338
  • 53
  • 357
  • 513