0

I'm trying to make a simple OS and I'm currently working on interrupts and the IDT.
So I implemented the IDT and a default exception handler that currently doe's nothing.
And when I run my OS in bochs I see that the OS triple faults right when it is jumping to the kernel.

bochs logs:

fetch_raw_descriptor: GDT: index (bf) 17 > limit (17)
interrupt(): gate descriptor is not valid sys seg (vector=0x0d)
interrupt(): gate descriptor is not valid sys seg (vector=0x08)

Exception 0x0d is General Protection Fault so My guess is that it has something to do with the GDT so here is my code for the GDT and how I load it:

LoadGDT:
    lgdt [g_GDTDesc]
    ret

g_GDT:
    ; NULL descrytpor
    dq 0

    ; 32 code segment
    dw 0FFFFh                   ; limit
    dw 0                        ; base
    db 0                        ; base
    db 10011010b                ; access
    db 11001111b                ; granularity
    db 0                        ; base high

    ; 32 data segment
    dw 0FFFFh                   ; limit
    dw 0                        ; base
    db 0                        ; base
    db 10010010b                ; access
    db 11001111b                ; granularity
    db 0                        ; base high

g_GDTDesc:
    dw g_GDTDesc - g_GDT - 1    ; size of gdt
    dd g_GDT                    ; address of gdt

But if it is because the IDT then I followed this tutorial from osDev wiki.

idt.c:

#define INTERRUPT_GATE_FLAGS 0x8e
typedef struct {
    u16     isr_low;            // lower 16 bit of ISR's address
    u16     kernel_cs;          // gdt segment selector
    u8      reserved;           // set to zero
    u8      flags;              // attribute flags
    u16     isr_high;           // higher 16 bit of ISR's address
} PACKED idt_entry_t;

typedef struct {
    u16     limit;
    u32     ptr;
} PACKED idtr_t;

__attribute__((aligned(0x10))) static idt_entry_t idt[256];
static idtr_t idtr;


void exception_handler(){
    __asm__ volatile("cli; hlt"); // currently doe's nothing
}

void IDT_set_descriptor(u8 interrupt, void* isr, u8 flags){
    idt_entry_t* descriptor = &idt[interrupt];

    descriptor->isr_low     = (u32)isr & 0xFFFF;
    descriptor->kernel_cs   = 0x08;
    descriptor->reserved    = 0;
    descriptor->flags       = flags;
    descriptor->isr_high    = ((u32)isr >> 16) & 0xFFFF;
}

extern void* isr_stub_table[];
void IDT_init(){
    idtr.ptr = (u32)&idt[0];
    idtr.limit = (u16)sizeof(idt) - 1;

    for(u8 interrupt=0; interrupt<32; interrupt++){
        IDT_set_descriptor(interrupt, isr_stub_table[interrupt], INTERRUPT_GATE_FLAGS);
    }

    __asm__ volatile("lidt %0": :"m"(idtr));
    STI();
}

idt.asm:

; exceptions with error code
%macro isr_err_stub 1
isr_stub_%+%1:
    call exception_handler
    iret
%endmacro

; exceptions without error code
%macro isr_no_err_stub 1
isr_stub_%+%1:
    call exception_handler
    iret
%endmacro

extern exception_handler
isr_no_err_stub 0
isr_no_err_stub 1
isr_no_err_stub 2
isr_no_err_stub 3
......
isr_no_err_stub 29
isr_err_stub    30
isr_no_err_stub 31

; defining the isr_stub_table
global isr_stub_table
isr_stub_table:
%assign i 0
%rep 32
    dd isr_stub_%+i
%assign i i+1
%endrep
neta cohen
  • 137
  • 11
  • Your IDT apparently includes task gates, which requires a "system" segment that has bit 4 of the access mask clear. Both of your segments are set to 1. – Tim Roberts Jul 20 '23 at 20:08
  • Where my IDT include a task gate and what doe's it mean? and to fix it I just add another segment that has this bit set to 0? – neta cohen Jul 20 '23 at 20:25
  • What is INTERRUPT_GATE_FLAGS? That certainly implies it's a task gate. Are you in real mode when you do this? – Tim Roberts Jul 20 '23 at 21:15
  • INTERRUPT_GATE_FLAGS is a macro defined in idt.h that I forgot to paste here... it's equal to 0x8e. I'm in 32 bit protected mode – neta cohen Jul 20 '23 at 21:21
  • 1
    While I see problems with the IRQ stubs (the IDT tutorial on OSDEV you are using has bugs) I don't see the one that specifically would cause what you are seeing. Since you are in BOCHS have you tried stepping through the kernel to see where it is failing? Often the register dump BOCHS gives can be useful. Without a [mcve] this could be difficult to find. I hope you are building the code as 32-bit and not 64-bit. If you put the code and build commands on Github or similar service I could look. – Michael Petch Jul 20 '23 at 22:17
  • what are the bugs in that code? when I run it with bochs it jumps to the exception handler as soon as it start to run the kernel. I've uploaded my code to github – neta cohen Jul 21 '23 at 09:42
  • I just got onto Stackoverflow for the first time today. I'll look at your Github but the stub for error codes doesn't remove the error code before the IRET nor do the stubs seem to save and restore the registers that are clobbered before returning with the IRET. I doiubt that those were the cause since your exception handlers never return (you have a CLI and HLT that stop the handlers before the IRET) – Michael Petch Jul 21 '23 at 17:29
  • 1
    By looking at your Github repository there are a number of things that stand out. In `kernel_entry.asm` you have `section .text` where it should be `section .entry` to ensure that the linker script `link.ld` places that code first. As it is it appears the code in `idt.asm` by chance gets loaded first. And then in `main.asm` you do `mov dh, 1` in `load_kernel`. That tells `disk_load` to read 1 sector into memory (512 bytes). Your `kernel.bin` file appears larger than that. You will have to specify a number of sectors (in `dh`) that is high enough to read the entire kernel into memory. – Michael Petch Jul 21 '23 at 19:10
  • 1
    The reason calling to your kernel executed an exception handler immediately was because the code in `idt.obj` happened to appear first (at memory address 0x1000) when the linker built `kernel.bin`. Your `link.ld` file says to load the `.entry` section before everything else and the `kernel_entry.asm` was using `.text`. You want the code in `kernel_entry.asm` to be put in `kernel.bin` first before any other code. That code happens to call `main`. – Michael Petch Jul 21 '23 at 20:44
  • thanks! I changed both the `.text` to `.entry` in the `kernel_entry.asm` and the amount of sectors I read in the bootloader and I'll change the way I handle the interrupts with error codes as well. One question though.... I remember reading somewhere that it is a bad idea to read multiple sectors in one int 13 and it's better to do it in multiple int 13s, is it true? – neta cohen Jul 22 '23 at 18:38
  • You can read more than 1 sector at a time but on real hardware made in the past 30 years you may find not being able to read sectors across a cylinder boundary. On ancient hardware in the 80s there was no guarantee a BIOS would read past a track boundary. If you read one at a time you avoid all the issues (but slower on real floppy hardware). Reading one at a time you can avoid DMA issues (crossing the 64KiB boundaries under 1 megabyte like 0x10000, 0x20000, 0x30000 etc ). Emulators are often more forgiving. BOCHS has an limit of reading 72 sectors at a time and QEMU is 128 as the maximum. – Michael Petch Jul 22 '23 at 18:54
  • I have this [Stackoverflow answer](https://stackoverflow.com/a/54894586/3857942) that shows how you can read 1 sector at a time. It uses LBA addressing but converts the LBA address into Cylinder/Head/Sectors to be used by Int 13h/ah=2. – Michael Petch Jul 22 '23 at 18:57

1 Answers1

2

As Michael Petch pointed out, turned out my code had two main issues.

The first is that in my boot loader I loaded only one sector even though my kernel is bigger than that.
The second is that in link.ldt, my linking script, I had:

 .entry              : { __entry_start = .;      *(.entry)   }
 .text               : { __text_start = .;       *(.text)    }

that means that the section .entry will be linked before the .text sectoin.
But in kernel_entry.asm which is a file that calls the main kernel function I started with .text, so my kernel wasn't linked properly.

neta cohen
  • 137
  • 11