1

I am trying to setup GDT/IDT/Paging. Below code is what i do,

global start
global CODE_SEG
global isr_asm
global DATA_SEG
global loadGDT
global isr_wrapper
extern main
extern isr

section .text
bits 32
start:
    mov esp, stack_top

    call main

    hlt

align 4
isr_wrapper:
        cli
    pushad
    call isr
        popad
    iret

loadGDT:
    ; Set our own GDT, can't rely GDT register being valid after bootloader
    ; transfers control to our entry point
    lgdt [gdt_descriptor]         ; Load GDT Register with GDT record
    mov eax, DATA_SEG
    mov ds, eax         ; Reload all the data descriptors with Data selector (2nd argument)
    mov es, eax
    mov gs, eax
    mov fs, eax
    mov ss, eax

    jmp CODE_SEG:.setcs
                        ; Do the FAR JMP to next instruction to set CS with Code selector, and
                        ;    set the EIP (instruction pointer) to offset of setcs
.setcs:


section .bss

align 4096

stack_bottom:
    resb 4096 * 4 ; Reserve this many bytes
stack_top:
    
    
section .rodata
gdt_start:
    dq 0x0000000000000000
gdt_code:
    dq 0x00CF9A000000FFFF
gdt_data:
    dq 0x00CF92000000FFFF
gdt_end:

; GDT descriptor
gdt_descriptor:
    dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
    dd gdt_start ; address (32 bit)

; define some constants for later use
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

And my main is,


typedef unsigned short uint16_t;
typedef unsigned char uint8_t;
typedef unsigned int uint32_t;
typedef unsigned int uintptr_t;

extern int CODE_SEG;
extern int gdt_descriptor;
extern int DATA_SEG;
extern void loadGDT(void);

typedef struct {
    uint16_t    isr_low;      // The lower 16 bits of the ISR's address
    uint16_t   kernel_cs;    // The GDT segment selector that the CPU will load into CS before calling the ISR
    uint8_t     reserved;     // Set to zero
    uint8_t     attributes;   // Type and attributes; see the IDT page
    uint16_t    isr_high;     // The higher 16 bits of the ISR's address
} __attribute__((packed)) idt_entry_t;

__attribute__((aligned(0x10)))
static idt_entry_t idt[256]; // Create an array of IDT entries; aligned for performance
        
typedef struct {
    uint16_t    limit;
    uint32_t    base;
} __attribute__((packed)) idtr_t;

static idtr_t idtr;

static inline void lcr3(unsigned int val)
{
  asm volatile("movl %0,%%cr3" : : "r" (val));
}


// 8MB mapping
int pagetable[2][1024] __attribute__((aligned(4096)));
int pagedirectory[2] __attribute__((aligned(4096)));

void idt_set_descriptor(uint8_t vector, void* isr, uint8_t flags) {
    idt_entry_t* descriptor = &idt[vector];

    descriptor->isr_low        = (uint32_t)isr & 0xFFFF;
    descriptor->kernel_cs      = CODE_SEG; // this value can be whatever offset your kernel code selector is in your GDT
    descriptor->attributes     = flags;
    descriptor->isr_high       = (uint32_t)isr >> 16;
    descriptor->reserved       = 0;
}

static inline void halt(void)
{
    asm volatile("hlt" : : );
}

extern void isr_wrapper(void);

void isr(void)
{
}

void idt_init() {
    idtr.base = (uintptr_t)&idt[0];
    idtr.limit = (uint16_t)sizeof(idt_entry_t) * 256 - 1;

    for (uint16_t vector = 0; vector < 256; vector++) {
        idt_set_descriptor(vector, isr_wrapper, 0x8E);
    }
    
    idt_set_descriptor(0x00, isr, 0x8F);
    idt_set_descriptor(0x08, isr, 0x8F);

    __asm__ volatile ("lidt %0" : : "m"(idtr)); // load the new IDT
}

int main(void)
{
    int i;
        int j;
        int addr=0;

    // Create your page table here
        for(i=0;i<2;i++){
                for(j=0;j<1024;j++){
                        pagetable[i][j]=addr|0b11;
                        addr +=4096;
                }
                pagedirectory[i]=(unsigned int )(&pagetable[i])|0b11;
        }

    lcr3((unsigned int)(&pagedirectory));

       loadGDT();
    idt_init();

    __asm__ volatile ("sti"); // set the interrupt flag
    while(1) {
     halt();
    }
}

My make:qemu-system-x86_64 -cdrom hello.iso -vga std -s -serial file:serial.log -d int,cpu_reset -no-shutdown -no-reboot

As soon as i enable irq i am getting double fault, general protection fault, which is not served and results in triple fault and cpu rebooting..

Below is the dump from qemu,

     0: v=08 e=0000 i=0 cpl=0 IP=0008:00000000001014b2 pc=00000000001014b2 SP=0010:0000000000106ff0 env->regs[R_EAX]=0000000000000008
EAX=00000008 EBX=0010da60 ECX=0000008f EDX=00000010
ESI=00000000 EDI=00000000 EBP=00106ff8 ESP=00106ff0
EIP=001014b2 EFL=00000202 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00100018 00000017
IDT=     0010b010 000007ff
CR0=00000011 CR2=00000000 CR3=00108000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=00000000 CCD=00106fe0 CCO=EFLAGS
EFER=0000000000000000
check_exception old: 0xffffffff new 0xd
     1: v=0d e=e2c0 i=0 cpl=0 IP=0008:00000000001014b2 pc=00000000001014b2 SP=0010:0000000000106ff0 env->regs[R_EAX]=0000000000000008
EAX=00000008 EBX=0010da60 ECX=0000008f EDX=00000010
ESI=00000000 EDI=00000000 EBP=00106ff8 ESP=00106ff0
EIP=001014b2 EFL=00000202 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00100018 00000017
IDT=     0010b010 000007ff
CR0=00000011 CR2=00000000 CR3=00108000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=00000000 CCD=00106fe0 CCO=EFLAGS
EFER=0000000000000000
check_exception old: 0xd new 0xd
     2: v=08 e=0000 i=0 cpl=0 IP=0008:00000000001014b2 pc=00000000001014b2 SP=0010:0000000000106ff0 env->regs[R_EAX]=0000000000000008
EAX=00000008 EBX=0010da60 ECX=0000008f EDX=00000010
ESI=00000000 EDI=00000000 EBP=00106ff8 ESP=00106ff0
EIP=001014b2 EFL=00000202 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00100018 00000017
IDT=     0010b010 000007ff
CR0=00000011 CR2=00000000 CR3=00108000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000 
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=00000000 CCD=00106fe0 CCO=EFLAGS
EFER=0000000000000000
check_exception old: 0x8 new 0xd
Triple fault

How is the segment selector getting value 0xe2c0? What am i doing wrong here, if someone can comment i am thankful to you, i am trying to debug this for days now..

1: v=0d e=e2c0 i=0 cpl=0 IP=0008:00000000001014b2 pc=00000000001014b2 

I tried remote debugging with gdb and as soon as sti is executed, it gets SIGRESET as soon as triple fault, i dont understand why. tried breakpoint in isr_wrapper and its not hit..

If i dont add "sti" in other words if interrupts are not enabled then there is no fault from qemu. I have setup IDT/GDT/paging right to my knowledge; what am I missing here?

I have tried wiki os dev, intel software developers manual, everywhere in web, but with no hope..

8
  • 2
    Bochs' built-in debugger can pretty-print GDT entries and things like that to help you make sure you have the machine set up the way you intended. Try it instead of GDB connected to QEMU. Commented Apr 11 at 15:27
  • First thing that stands out besides the e=e2c0 is the fact that you have 0: v=08 e=0000 . May I ask if you remapped the PICs? My guess is that they aren't remapped and the timer interrupt came in on ISR 8. ISR 8/double fault happens to overlap the timer interrupt by default until the PIC(s) are remapped. I am guessing a timer interrupt may have occurred and it came in appearing as a double fault. Commented Apr 11 at 20:47
  • 2
    As written, you've got no code after the .setcs label, so the processor will just start executing random instructions instead of returning from loadGDT. Commented Apr 11 at 20:48
  • 1
    @hari: no, it won't. Commented Apr 11 at 21:10
  • 1
    That code after is whatever shows up in the .text segment later on. 'popa' makes no sense since 'loadGDT' doesn't even have a 'pusha'. After '.setcs:' add a 'ret'' Commented Apr 11 at 21:21

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.