Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linux asm("int $0x0") vs division by zero

can someone explain the difference between the assembly instruction int $0x00 and performing an actual division by zero. I have a breakpoint set on divide_error() handler in the kernel associated with the 0th entry in the IDT (division error).

When I do this within my C program:

int i = 5/0;

then I hit the breakpoint (as expected). However,

asm volatile ("int $0x00")

does not fire the handler. Why?

like image 479
vnik Avatar asked May 29 '14 05:05

vnik


2 Answers

int 0h is not the same thing as the CPU generating trap 0 due to a divide by zero.

This article of Phrack does a good job of explaining the IDT and how Linux sets it up. The key parts being:

DPL=Descriptor Privilege Level

    The DPL is equal to 0 or 3. Zero is the most privileged level (kernel
mode).  The current execution level is saved in the CPL register (Current
Privilege Level). The UC (Unit Of Control) compares the value of the CPL
register against the DPL field of the interrupt in the IDT. The interrupt
handler is executed if the DPL field is greater (less privileged) or equal
to the value in the CPL register. Userland applications are executed in
ring3 (CPL==3). Certain interrupt handlers can thus not be invoked by
userland applications.

...

linux/arch/i386/kernel/traps.c::set_system_gate(n, addr)
        insert a trap gate.
    The DPL field is set to 3.

These interrupts can be invoked from the userland (ring3).

                set_system_gate(3,&int3)
                set_system_gate(4,&overflow)
                set_system_gate(5,&bounds)
                set_system_gate(0x80,&system_call);

linux/arch/i386/kernel/traps.c::set_trap_gate(n, addr)
        insert a trap gate with the DPL field set to 0.
        The Others exception are initialized with set_trap_gate : 

                set_trap_gate(0,&divide_error)
                set_trap_gate(1,&debug)
                set_trap_gate(2,&nmi)
                set_trap_gate(6,&invalid_op)
                set_trap_gate(7,&device_not_available)
                set_trap_gate(8,&double_fault)
                set_trap_gate(9,&coprocessor_segment_overrun)
                set_trap_gate(10,&invalid_TSS)
                set_trap_gate(11,&segment_not_present)
                set_trap_gate(12,&stack_segment)
                set_trap_gate(13,&general_protection)
                set_trap_gate(14,&page_fault)
                set_trap_gate(15,&spurious_interrupt_bug)
                set_trap_gate(16,&coprocessor_error)
                set_trap_gate(17,&alignement_check)
                set_trap_gate(18,&machine_check)

The descriptions there explain it perfectly. Only int 3, 4, 5 and 0x80 can be called from userspace, because the kernel sets their trap gates up with (Descriptor Prvilege Level) DPL=3.

The other processor exception vectors have DPL=0 (can only be called from ring 0).

When you divide by zero, the CPU first transitions to Ring 0, and the kernel handles the exception with divide_error. When you invoke it explicitly with int 0x00 however, you are still at (Current Privilege Level) CPL=3.

For the very low-level nitty-gritty details, you should consult the Intel Software Developer's Manual. Volume 2 describes the int instruction, and outlines all of the decision-making steps the CPU takes to decide how to handle the trap/interrupt. Volume 3 describes the intimate details of the IDT, Trap Gates, etc.

Specifically, Table 3-61 Decision Table explains exactly what happens in each possible way an interrupt happens. In your example, calling int 0x00 puts you in column 2, which essentially says:

if PE=1                 # protected mode enabled
and DPL < CPL           # DPL=0 - kernel set up trap gate like this
                        # CPL=3 - b/c you're in user-mode
and int type == S/W     # you executed int instruction (s/w interrupt)
then issue #GP          # General Protection fault
                        # -- kernel delivers this to usermode as SIGSEGV

Additional Reference:

  • Interrupt Descriptor Table (osdev.org)
  • Intel® 64 and IA-32 Architectures Software Developer Manuals (intel.com)
like image 134
Jonathon Reinhart Avatar answered Oct 18 '22 11:10

Jonathon Reinhart


If you want the short answer, it's that the divide by zero interrupt can only be called by the kernel. If you want the longer answer, check out the answer from ZarathustrA here.

like image 31
David Wohlferd Avatar answered Oct 18 '22 13:10

David Wohlferd