3

I'm new to all this, so I apologize in advance if I missed something really obvious

So, I'm trying to make a simple kernel in x86 assembly and C. I was trying to get interrupts working. I am defining the GDT, IDT in assembly.

I'm not even sure what's wrong, the GDT or the IDT. The thing is, everything seems fine until I actually trigger an interrupt.

I checked OSDev, the Intel Manuals, James Molloy's guide, and random blog posts, and I just can't figure this out.

Here's the code:

; interrupts.asm
%macro ISRNOERR 1
isr%1:
    cli
    push byte 0
    push byte %1
    jmp isr_common_stub
isr%1_end:      
%endmacro

%macro ISRERR 1
isr%1:
    cli
    push byte %1
    jmp isr_common_stub
isr%1_end:      
%endmacro

    ISRNOERR 0
    ISRNOERR 1
    ISRNOERR 2
    ISRNOERR 3
    ISRNOERR 4
    ISRNOERR 5
    ISRNOERR 6
    ISRNOERR 7
    ISRERR 8
    ISRNOERR 9
    ISRERR 10
    ISRERR 11
    ISRERR 12
    ISRERR 13
    ISRERR 14
    ISRNOERR 15
    ISRNOERR 16
    ISRNOERR 17
    ISRNOERR 18
    ISRNOERR 19
    ISRNOERR 20
    ISRNOERR 21
    ISRNOERR 22
    ISRNOERR 23
    ISRNOERR 24
    ISRNOERR 25
    ISRNOERR 26
    ISRNOERR 27
    ISRNOERR 28
    ISRNOERR 29
    ISRNOERR 30
    ISRNOERR 31
    ISRNOERR 32

isr_common_stub:
    pusha
    mov ax, ds
    push eax

    mov ax, 0x10 ; Data segment descriptor (gdt.asm)
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    extern handler
    call handler

    pop eax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    popa
    add esp, 8
    sti
    iret
; idt.asm
section .text
global _load_idt
_load_idt:
    lidt [idt_info]
    ret

%macro IRQ 1
irq%1:
    dd isr%1
    dw 0x0008
    db 0x00
    db 10101110b
    dd isr%1_end
%endmacro

    %include "interrupts.asm"
    
section .rodata
idt:
    IRQ 0
    IRQ 1
    IRQ 2
    IRQ 3
    IRQ 4
    IRQ 5
    IRQ 6
    IRQ 7
    IRQ 8
    IRQ 9
    IRQ 10
    IRQ 11
    IRQ 12
    IRQ 13
    IRQ 14
    IRQ 15
    IRQ 16
    IRQ 17
    IRQ 18
    IRQ 19
    IRQ 20
    IRQ 21
    IRQ 22
    IRQ 23
    IRQ 24
    IRQ 25
    IRQ 26
    IRQ 27
    IRQ 28
    IRQ 29
    IRQ 30
    IRQ 31
    IRQ 32

idt_info:
    dw idt_info - idt - 1
    dd idt
// lime_main.c
#include <kernel/lime_tty.h>

extern void _load_gdt();  // From assembly
extern void _load_idt();

void lime_main()
{
    lime_tty_init(TtyTextMode);
    lime_tty_put_string("[ LIME ] Welcome to the Lime kernel!\n");
    
    _load_gdt();
    lime_tty_put_string("[ LIME ] Loaded GDT successfully!\n");

    _load_idt();
    lime_tty_put_string("[ LIME ] Loaded IDT successfully!\n");

    asm ("int $0x03");  // It's not crashing if I remove this 
}
; gdt.asm
section .data
    ALIGN 4

section .text
global _load_gdt
_load_gdt:
    cli
    lgdt [gdt_desc]
    jmp 0x08:gdt_flush

gdt_flush:
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    ret


section .rodata
gdt:
gdt_null:
    dd 0h
    dd 0h
    
gdt_code:
    dw 0FFFFh
    dw 00000h
    db 00h
    db 10011010b
    db 11001111b
    db 0
gdt_data:
    dw 0FFFFh
    dw 00000h
    db 00h
    db 10010010b
    db 11001111b
    db 0

gdt_desc:
    dw gdt_desc - gdt - 1
    dd gdt
  • Get Bochs and single-step it in Bochs's debugger. – user253751 Nov 17 '20 at 16:55
  • Just glancing at the code I noticed in your `%macro IRQ 1` macro that you use `dd isr%1` and `dd isr%1_end` . I believe those should be `dw` (16-bit words). Each of your Interrupt descriptors is 12 bytes in size rather than 8 which will munge the IDT. – Michael Petch Nov 17 '20 at 17:13
  • @MichaelPetch If I change that to `dw`, it starts complaining about some `relocation truncated to fit R_386_16 againsr .text` – poisson_myfish Nov 17 '20 at 17:15
  • 2
    The second problem is that you are trying to put 32-bit addresses in 16-bit fields which cause the relocation problems. You really need to get the lower 16-bits of the ISR entry routine and store it in the first 2 bytes of the descriptor and take the upper 16-bits of the ISR routine entry point and put them in the last 2 bytes of the descriptor. Unfortunately there is no relocation entry supported by the LD linker for x86 to support such math on the addresses. The easiest thing to do is update the ISR entry parts of the descriptor programmatically when your kernel starts running. – Michael Petch Nov 17 '20 at 17:21
  • 2
    Some of the problems you will encounter are rather similar to this Q&A I did on Stackoverflow: https://stackoverflow.com/questions/58192042/solution-needed-for-building-a-static-idt-and-gdt-at-assemble-compile-link-time . YOu can do this at link time but you'd need to use the power of the Linker to build the IDT. If you are new to linker scripts, writing the code to update those fields in C when your kernel first starts might be easiest. – Michael Petch Nov 17 '20 at 17:23
  • @MichaelPetch At the bottom of https://wiki.osdev.org/Interrupt_Descriptor_Table , you can see the assembly example they give. How do they do it, without using linker scripts? – poisson_myfish Nov 17 '20 at 17:26
  • The IDT is just an array of structures (8 bytes in size). You can build the interrupt table in memory when your kernel starts just as easily as filling in data in an array. If you read this answer of mine on Stackoverflow you will see how I do that in `main.c` : https://stackoverflow.com/a/37635449/3857942 . I have a couple of IDT related structures for the IDTR (IDT record) and structure for the IDT descriptor and I just make the IDT at runtime. That answer provides a [mcve] that handles one IRQ (The keyboard) but you just create entries for all the exceptions and interrupts you want. – Michael Petch Nov 17 '20 at 17:28
  • 2
    The reason the OSDev Wiki example works is because they assume the ISR/IRQ entry points are loaded in the first 64KiB of memory. Since it is in the first 64KiB of memory then the top 16 bits of the offset to each entry point is always 0 and the ISR/IRQ address always fits in a 16-bit `dw` so there is no relocation error. Is it right to assume your kernel is `Multiboot` and that your kernel started at 0x100000? If that is the case 0x100000 and higher addresses are outside the first 64KiB of memory and can't fit in a 16-bit value and thus the OSDev example wouldn't work. – Michael Petch Nov 17 '20 at 17:39
  • @MichaelPetch My kernel is indeed multiboot. I am, as I said, a beginner on all this, so I don't understand how to know where my kernel starts – poisson_myfish Nov 17 '20 at 17:42
  • @MichaelPetch I did a bit of research, and now I understand that grub loads the kernel at 0x100000, that means that my kernel gets loaded there – poisson_myfish Nov 17 '20 at 18:12
  • 1
    @MichaelPetch Alright, thank you so much! – poisson_myfish Nov 17 '20 at 18:15

1 Answers1

1

I fixed it in the meantime, with the help of @MichaelPetch. Basically, the problem was, as he was trying to tell me, that I was defining wrong sizes for the values in the IDT. I was defining double words (dd) for the higher and lower bits, but I had to define 16-bit words (dw).

Because of relocation problems, I can't really statically define this without linker scripts and stuff. I tried to avoid this, so I ended up going for a dynamic approach, I did it in C.

There's not really a lot to explain, there are tons of resources out there that I missed (or misunderstanded).

If you are like me, a beginner, and don't really understand anything, my advice is to just take a break, and come back with a fresh mind. The OSDev Wiki is going to help you a lot (the forums too).