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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With