Very lost on how to catch a divide by zero interrupt in x64

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
tmathmeyer
Posts: 4
Joined: Sun Jul 03, 2016 7:42 pm

Very lost on how to catch a divide by zero interrupt in x64

Post by tmathmeyer »

I just got back into starting kernel dev after moving but I seem to have hit a wall with catching a divide by 0 exception.
my strategy was to statically allocate enough space for the first 16 interrupt descriptors, set all the present flags to 0, then put a handler for DE exception in IDT[0].
I still fault however when dividing by zero. I've attached the interrupt handling code, which is entered into by calling load_IDT();

interrupts.h:

Code: Select all

#ifndef interrupts_h
#define interrupts_h

#include "ktype.h"

struct opts {
    uint8_t ist_index   : 3;
    uint8_t reserved    : 5;
    uint8_t int_or_trap : 1;
    uint8_t must_be_one : 3;
    uint8_t must_be_zro : 1;
    uint8_t privelage_l : 2;
    uint8_t present     : 1;
}__attribute__((packed));

typedef struct idt_entry {
	uint16_t ptr_low;
	uint16_t selector;
    struct opts opts;
    uint16_t ptr_mid;
    uint32_t ptr_high;
    uint32_t _zero;
}__attribute__((packed)) idt_entry_t;

typedef struct {
    uint16_t max_byte;
    uint64_t virt_start;
}__attribute__((packed)) descriptor_table_t;



idt_entry_t create_empty();
idt_entry_t create(uint16_t, size_t);
uint16_t cs();
struct opts *set_handler(uint8_t loc, size_t fn_ptr);
void load_IDT();

#endif
interrupts.c:

Code: Select all

#include "ktype.h"
#include "interrupts.h"
#include "libk.h"

idt_entry_t IDT[16];

static inline void _load_IDT(void* base, uint16_t size) {
    // This function works in 32 and 64bit mode
    struct {
        uint16_t length;
        void*    base;
    } __attribute__((packed)) IDTR = { size, base };

    asm ( "lidt %0" : : "m"(IDTR) );  // let the compiler choose an addressing mode
}

void divide_by_zero_handler() {
    for(;;);
}

void IDT_init() {
    for(int i=0;i<16;i++) {
        IDT[i] = create_empty();
    }
}

void load_IDT() {
    IDT_init();
    
    struct opts *options = set_handler(0, (uint64_t)&divide_by_zero_handler);
    
    _load_IDT(IDT, sizeof(IDT)-1);
}

struct opts *set_handler(uint8_t loc, size_t fn_ptr) {
    IDT[loc] = create(cs(), fn_ptr);
    return (struct opts *)&(IDT[loc].opts);
}

idt_entry_t create(uint16_t gdt_selector, size_t fn_ptr) {
    idt_entry_t result;
    result.selector = gdt_selector;
    result.ptr_low = (uint8_t)fn_ptr;
    result.ptr_mid = (uint8_t)(fn_ptr >> 16);
    result.ptr_high = (uint16_t)(fn_ptr >> 32);
    result.opts.ist_index   = 0;
    result.opts.reserved    = 0;
    result.opts.int_or_trap = 0;
    result.opts.must_be_one = 0x07;
    result.opts.must_be_zro = 0;
    result.opts.privelage_l = 0;
    result.opts.present     = 1;
    return result;
}

idt_entry_t create_empty() {
    idt_entry_t result;
    memset(&result, 0, sizeof(idt_entry_t));
    result.opts.must_be_one = 0x07;
    return result;
}

inline uint16_t cs(void) {
    uint16_t val;
    asm volatile ( "mov %%cs, %0" : "=r"(val) );
    return val;
}

//intel 8259 support later!

Like i have in the comment at the bottom of that last file, I know that for keyboard support I will have to interface with the 8259 (which I think is the id for the dual PIC on x86). I'm not trying to go that far yet, just get these exceptions caught.
pongozolin
Posts: 6
Joined: Sun May 10, 2015 7:04 pm

Re: Very lost on how to catch a divide by zero interrupt in

Post by pongozolin »

You'll always fault when you divide by zero. The exception handlers just prevent you from triple faulting and resetting your CPU.

A few things:
1. Your exception handlers should start in assembly, then call you C function. The compiler will do things you don't want, unless you use specific directives.
2. Your opts structure isn't quite right. It takes 3 bits to determine if the entry is for a trap/interrupt, and 1 bit to make it for 80386 32-bit code.
3. I would use pointers instead of returning structs.
alexfru
Member
Member
Posts: 1112
Joined: Tue Mar 04, 2014 5:27 am

Re: Very lost on how to catch a divide by zero interrupt in

Post by alexfru »

tmathmeyer wrote:

Code: Select all

    result.ptr_low = (uint8_t)fn_ptr;
    result.ptr_mid = (uint8_t)(fn_ptr >> 16);
    result.ptr_high = (uint16_t)(fn_ptr >> 32);
Is this right? Look at your structure definition.
tmathmeyer
Posts: 4
Joined: Sun Jul 03, 2016 7:42 pm

Re: Very lost on how to catch a divide by zero interrupt in

Post by tmathmeyer »

pongozolin wrote:You'll always fault when you divide by zero. The exception handlers just prevent you from triple faulting and resetting your CPU.

A few things:
1. Your exception handlers should start in assembly, then call you C function. The compiler will do things you don't want, unless you use specific directives.
2. Your opts structure isn't quite right. It takes 3 bits to determine if the entry is for a trap/interrupt, and 1 bit to make it for 80386 32-bit code.
3. I would use pointers instead of returning structs.
addressing each of your comments:
0. I had meant to write double or triple fault, as obviously I am trying to catch the DE fault. When I dump the registers from QEMU when it fails, it is showing a double fault, which is why I assume that I've botched the IDT.
1. I'll give that a shot and report back soon.
2. I got the opts structure layout from: http://os.phil-opp.com/catching-exceptions.html, which shows only one bit (bit 8) for toggling interrupt / trap. This page: http://wiki.osdev.org/Interrupt_Descriptor_Table shows an IDT structure that is very different, but also seems to be for 32 bits? These two structs are not even closely similar, and I'm not sure which is right.
3. I might switch to using pointers if I am still stuck, but since there are no parts of the idt_entry_t that point to other places on the stack it should work fine.


alexfru wrote:
tmathmeyer wrote:

Code: Select all

    result.ptr_low = (uint8_t)fn_ptr;
    result.ptr_mid = (uint8_t)(fn_ptr >> 16);
    result.ptr_high = (uint16_t)(fn_ptr >> 32);
Is this right? Look at your structure definition.
WOW I can't believe I missed that! But even after I fixed it, it still doesn't work.
User avatar
JAAman
Member
Member
Posts: 879
Joined: Wed Oct 27, 2004 11:00 pm
Location: WA

Re: Very lost on how to catch a divide by zero interrupt in

Post by JAAman »

tmathmeyer wrote: 2. I got the opts structure layout from: http://os.phil-opp.com/catching-exceptions.html, which shows only one bit (bit 8) for toggling interrupt / trap. This page: http://wiki.osdev.org/Interrupt_Descriptor_Table shows an IDT structure that is very different, but also seems to be for 32 bits? These two structs are not even closely similar, and I'm not sure which is right.
the proper reference should not be either one, but rather the Intel manuals... sometimes I think we are harming people by putting too much into the wiki, since it discourages people from using the proper sources

never rely on any wiki or tutorial for things that can be easily looked up in the Intel manuals -- always verify it (there are a lot of wrong, broken, or buggy tutorials)
tmathmeyer
Posts: 4
Joined: Sun Jul 03, 2016 7:42 pm

Re: Very lost on how to catch a divide by zero interrupt in

Post by tmathmeyer »

so taking from section 5.8.3.1 in the intel manual this image:
Image
i have this code:
interrupts.h:

Code: Select all

#ifndef interrupts_h
#define interrupts_h

#include "ktype.h"

struct opts {
    uint8_t ZEROS     : 8;
    uint8_t gate_type : 4;
    uint8_t ZERO      : 1;
    uint8_t DPL       : 3;
    uint8_t present   : 1;
}__attribute__((packed));

typedef struct idt_entry {
    uint16_t ptr_low;
    uint16_t selector;
    struct opts opts;
    uint16_t ptr_mid;
    uint32_t ptr_high;

    uint8_t  _1_reserved : 8;
    uint8_t  _type       : 5;
    uint32_t _2_reserved : 19;
}__attribute__((packed)) idt_entry_t;

idt_entry_t create_empty();
idt_entry_t create(uint16_t, uint64_t);
uint16_t cs();
struct opts *set_handler(uint8_t loc, uint64_t fn_ptr);
void load_IDT();

#endif
interrupts.c:

Code: Select all

#include "ktype.h"
#include "interrupts.h"
#include "libk.h"

idt_entry_t IDT[16];

static inline void _load_IDT(void* base, uint16_t size) {
    // This function works in 32 and 64bit mode
    struct {
        uint16_t length;
        void*    base;
    } __attribute__((packed)) IDTR = { size, base };
    kprintf("%4fs%4fx\n", "LIDT = ", &IDT);
    asm ( "lidt %0" : : "m"(IDTR) );  // let the compiler choose an addressing mode
}

void _divide_by_zero_handler() {
    for(;;);
}

void IDT_init() {
    for(int i=0;i<16;i++) {
        IDT[i] = create_empty();
    }
}

void load_IDT() {
    IDT_init();
    set_handler(0x00, (uint64_t)&_divide_by_zero_handler);
    set_handler(0x08, (uint64_t)&_divide_by_zero_handler);
    _load_IDT(IDT, sizeof(IDT)-1);
}

struct opts *set_handler(uint8_t loc, uint64_t fn_ptr) {
    IDT[loc] = create(cs(), fn_ptr);
    return (struct opts *)&(IDT[loc].opts);
}

idt_entry_t create(uint16_t gdt_selector, uint64_t fn_ptr) {
    idt_entry_t result;
    result.selector = gdt_selector;
    result.ptr_low = (uint16_t)fn_ptr;
    result.ptr_mid = (uint16_t)(fn_ptr >> 16);
    result.ptr_high = (uint32_t)(fn_ptr >> 32);
    
    result.opts.present     = 1;
    result.opts.DPL         = 0;
    result.opts.gate_type   = 0x0C;
    result.opts.ZERO        = 0;
    result.opts.ZEROS       = 0;

    result._1_reserved = 0;
    result._2_reserved = 0;
    result._type = 0;
    return result;
}

idt_entry_t create_empty() {
    idt_entry_t result;
    memset(&result, 0, sizeof(idt_entry_t));
    return result;
}

inline uint16_t cs(void) {
    uint16_t val;
    asm volatile ( "mov %%cs, %0" : "=r"(val) );
    return val;
}

//intel 8259 support later!
However it still doesn't work. I can verify that my IDT is loaded correctly by printing the address of IDT in the _load_IDT() function, and sleeping before I do the invalid division, then comparing that to the output of QEMU when it fails. for reference, here is what I get from qemu:

Code: Select all

check_exception old: 0xffffffff new 0x0
     0: v=00 e=0000 i=0 cpl=0 IP=0008:0000000000103c0e pc=0000000000103c0e SP=0010:0000000000109fd0 env->regs[R_EAX]=0000000000000004
RAX=0000000000000004 RBX=0000000000000800 RCX=0000000000000000 RDX=0000000000000000
RSI=00000000000b80a0 RDI=00000000000b8000 RBP=0000000000109ff0 RSP=0000000000109fd0
R8 =0000000000000000 R9 =0000000000000000 R10=0000000000109e30 R11=0000000000102fcf
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=0000000000103c0e RFL=00200012 [----A--] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 0000000000000000 00000000 00009300 DPL=0 DS   [-WA]
CS =0008 0000000000000000 00000000 00209a00 DPL=0 CS64 [-R-]
SS =0010 0000000000000000 00000000 00009300 DPL=0 DS   [-WA]
DS =0010 0000000000000000 00000000 00009300 DPL=0 DS   [-WA]
FS =0018 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0018 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT=     0000000000100018 00000017
IDT=     000000000010a000 0000010f
CR0=80000013 CR2=0000000000000000 CR3=0000000000105000 CR4=00000620
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000005f5e0ff CCD=0000000000000001 CCO=SUBL    
EFER=0000000000000500
check_exception old: 0x0 new 0xd
     1: v=08 e=0000 i=0 cpl=0 IP=0008:0000000000103c0e pc=0000000000103c0e SP=0010:0000000000109fd0 env->regs[R_EAX]=0000000000000004
RAX=0000000000000004 RBX=0000000000000800 RCX=0000000000000000 RDX=0000000000000000
RSI=00000000000b80a0 RDI=00000000000b8000 RBP=0000000000109ff0 RSP=0000000000109fd0
R8 =0000000000000000 R9 =0000000000000000 R10=0000000000109e30 R11=0000000000102fcf
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=0000000000103c0e RFL=00200012 [----A--] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 0000000000000000 00000000 00009300 DPL=0 DS   [-WA]
CS =0008 0000000000000000 00000000 00209a00 DPL=0 CS64 [-R-]
SS =0010 0000000000000000 00000000 00009300 DPL=0 DS   [-WA]
DS =0010 0000000000000000 00000000 00009300 DPL=0 DS   [-WA]
FS =0018 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0018 0000000000000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT=     0000000000100018 00000017
IDT=     000000000010a000 0000010f
CR0=80000013 CR2=0000000000000000 CR3=0000000000105000 CR4=00000620
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000005f5e0ff CCD=0000000000000001 CCO=SUBL    
EFER=0000000000000500
check_exception old: 0x8 new 0xd
mikeee
Posts: 7
Joined: Wed Mar 18, 2009 8:04 am

Re: Very lost on how to catch a divide by zero interrupt in

Post by mikeee »

Double check the definition of the DPL field.
tmathmeyer
Posts: 4
Joined: Sun Jul 03, 2016 7:42 pm

Re: Very lost on how to catch a divide by zero interrupt in

Post by tmathmeyer »

mikeee wrote:Double check the definition of the DPL field.
sigh. I need to stop making typos. thanks a ton everyone who helped me out :) I had the old 3 bit field in there for a total of 17 bits. oops.
Post Reply