Page 1 of 4

IDT Help

Posted: Tue Mar 09, 2021 9:45 pm
by isaiah0311
Sorry if this really obvious, I'm rather new to OS development.

Basically, I'm trying to set up my IDT so I can get keyboard input. I've tried numerous methods, but can't seem to get it working. Whenever I run my OS in qemu it just restarts. From what I've gathered online it's probably a triple fault because my IDT isn't set up right. This only occurs if I've used the STI directive so that's led me to believe my IRQs are incorrect.

This is the code I've been using to initialize the IDT.
My OS is already in long mode. I've set up paging and the GDT at this point in the code. Also, the PICs are remapped.

Code: Select all

long_mode_start:
    ; load null into all data segment registers
    mov ax, 0x0000
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    lidt [idt.pointer]
    sti

    call kernel_main
    hlt

idt:
.irq0:
    dq isr_wrapper.handler
    dw 0x0008
    db 0x00
    db 10101110b
    dw 0x0000
.irq1:
    dq isr_wrapper.handler
    dw 0x0008
    db 0x00
    db 10101110b
    dw 0x0000
; ---------------------------
; irq2-29
; ---------------------------
.irq30:
    dq isr_wrapper.error_code
    dw 0x0008
    db 0x00
    db 0x00
    db 10101110b
    dw 0x0000
.irq31:
    dq isr_wrapper.handler
    dw 0x0008
    db 0x00
    db 10101110b
    dw 0x0000
.pointer:
    dw $ - idt - 1
    dq idt

isr_wrapper:
.error_code:
    add rsp, 8

.handler:
    cld
    call interrupt_handler

    iretq

Re: IDT Help

Posted: Wed Mar 10, 2021 3:24 am
by SKC
Hi.
It looks like you copied the protected mode code from the wiki (correct me if I'm wrong).
In long mode, the entries should look like this:

Code: Select all

struct IDT_Entry
{
    uint16_t offset1; // The low 16 bits of the address
    uint16_t selector;
    uint8_t IST:3; // You can ignore this for now
    uint8_t zero0:5; // Must be zero
    uint8_t type:4;
    uint8_t StorageSegment:1;
    uint8_t privl:2;
    uint8_t present:1;
    uint16_t offset2; // The next 16 bits of the address
    uint32_t offset3; // The rest of the address
    uint32_t zero1; // Must be zero as well
};
So your code should look like:

Code: Select all

dw isr_wrapper.handler
dw 0x8
db 10101110b
dw isr_wrapper.handler>>16
dd isr_wrapper.handler>>32
dd 0
(I'm not really fluent in Intel's syntax, but I hope you get the idea)

Btw, you shouldn't load 0 into the segment registers. You should create a proper GDT.

Re: IDT Help

Posted: Wed Mar 10, 2021 5:46 am
by Octocontrabass
isaiah0311 wrote:Also, the PICs are remapped.
The first 32 entries in the IDT are for exceptions. If you've remapped the PICs to use any of those entries, you're going to have a lot of problems. (And if you remapped them outside the first 32 entries, any IRQ will cause an exception because your IDT only has 32 entries.)
isaiah0311 wrote:

Code: Select all

    call kernel_main
    hlt
The HLT instruction only stops the CPU until the next interrupt. After that, the CPU will try to execute whatever comes next and probably crash your OS.
isaiah0311 wrote:

Code: Select all

.handler:
    cld
    call interrupt_handler

    iretq
Your interrupt handler doesn't preserve the registers being used by the interrupted code. That's bad for whatever gets interrupted.

Re: IDT Help

Posted: Wed Mar 10, 2021 9:23 am
by isaiah0311
SKC wrote:

Code: Select all

dw isr_wrapper.handler
dw 0x8
db 10101110b
dw isr_wrapper.handler>>16
dd isr_wrapper.handler>>32
dd 0
When I try to compile this, I get an error saying "shift operator may only be applied to scalar values".
SKC wrote: Btw, you shouldn't load 0 into the segment registers. You should create a proper GDT.
I was following a tutorial and that's what it told me to do.
Here is my GDT. Is this right? Can I remove putting null into the segment registers?

Code: Select all

section .rodata
gdt:
    dq 0 ; null descriptor
.code: equ $ - gdt
    dq 0x00209a0000000000 ; 64-bit code descriptor (exec/read)
    dq 0x0009200000000000 ; 64-bit data descriptor (read/write)
align 4
    dw 0
.pointer:
    dw $ - gdt - 1
    dq gdt
Octocontrabass wrote: The first 32 entries in the IDT are for exceptions. If you've remapped the PICs to use any of those entries, you're going to have a lot of problems. (And if you remapped them outside the first 32 entries, any IRQ will cause an exception because your IDT only has 32 entries.)
Here is how I remap the PICs. This is what I gathered from reading the wiki and looking at other's code. I thought this just allowed me to access the keyboard, I didn't know it messed with IDT exceptions.

Code: Select all

setup_pics:
    mov al, 0x11
    out 0x20, al ; restart PIC1
    out 0x10, al ; restart PIC2

    mov al, 0x20
    out 0x21, al ; PIC1 now starts at 32
    mov al, 0x28
    out 0x11, al ; PIC2 now starts at 40

    mov al, 0x04
    out 0x21, al ; setup cascading
    mov al, 0x02
    out 0xa1, al

    mov al, 0x01
    out 0x21, al
    out 0xa1, al
    ret

Re: IDT Help

Posted: Wed Mar 10, 2021 9:55 am
by SKC
isaiah0311 wrote:When I try to compile this, I get an error saying "shift operator may only be applied to scalar values".
In my OS, I set up the IDT in C, so I foolishly wrote 'isr_wrapper.handler>>'. Since the labels get their values during linking, they can't be used as constants...
You can use this template and set up the offset in runtime:

Code: Select all

irq_n: ; IRQ number N
    dw 0 ; offset1
    dw 0x8
    db 0 ; I forgot to add this byte in the previous reply...
    db 10101110b
    dw 0 ; offset2
    dd 0 ; offset3
    dd 0
; This code will put isr_wrapper.handler in offset1/2/3
mov rax,isr_wrapper.handler
mov rbx,irq_n
mov word [rbx],ax ; offset1
shr rax,16
mov word [rbx+6],ax ; offset2
shr rax,16
mov dword [rbx+8],eax ; offset3
As to the GDT, I think that's fine. Just make sure to load the GDT before you touch any segment registers.

Again, I'm used to work with AT&T's syntax, so my code might be a bit inaccurate, but I hope you get the general idea.

Re: IDT Help

Posted: Wed Mar 10, 2021 9:57 am
by bzt
isaiah0311 wrote:Whenever I run my OS in qemu it just restarts. From what I've gathered online it's probably a triple fault because my IDT isn't set up right.
Try bochs. It won't restart in case of a triple-fault, instead it will give you a debugger prompt. It also has commands to dump the GDT and IDT parsed, so you can spot what's wrong with them (see "info gdt" and "info idt", and also "info sreg"). It helps a lot when you can see how the CPU parsed your IDT, you'll see what bits are wrong.

Cheers,
bzt

Re: IDT Help

Posted: Wed Mar 10, 2021 10:46 am
by isaiah0311
SKC wrote:

Code: Select all

irq_n: ; IRQ number N
    dw 0 ; offset1
    dw 0x8
    db 0 ; I forgot to add this byte in the previous reply...
    db 10101110b
    dw 0 ; offset2
    dd 0 ; offset3
    dd 0
; This code will put isr_wrapper.handler in offset1/2/3
mov rax,isr_wrapper.handler
mov rbx,irq_n
mov word [rbx],ax ; offset1
shr rax,16
mov word [rbx+6],ax ; offset2
shr rax,16
mov dword [rbx+8],eax ; offset3
Thank you for the help!
Right now I have this. Is this ok, or should I have a separate function outside of the irq_n code move the offsets in?

Code: Select all

wrap_irq:
    mov word [rbx], ax
    shr rax, 16
    mov word [rbx + 6], ax
    shr rax, 16
    mov dword [rbx + 8], eax
    ret

idt:
.irq0:
    dw 0
    dw 0
    db 0
    db 10101110b
    dw 0
    dd 0
    dd 0
    mov rax, isr_wrapper.handler
    mov rbx, idt.irq0
; -----------------------------------
; repeat for irq1-31
; -----------------------------------
.pointer
    dw $ - idt - 1
    dq idt
bzt wrote: Try bochs. It won't restart in case of a triple-fault, instead it will give you a debugger prompt. It also has commands to dump the GDT and IDT parsed, so you can spot what's wrong with them (see "info gdt" and "info idt", and also "info sreg"). It helps a lot when you can see how the CPU parsed your IDT, you'll see what bits are wrong.
I couldn't get bochs set up. It always failed at the display_library in my bochsrc. I looked up all the different options I could use and none of them worked. I'm on Ubuntu 20.04.

Re: IDT Help

Posted: Wed Mar 10, 2021 11:25 am
by SKC
isaiah0311 wrote:Right now I have this. Is this ok, or should I have a separate function outside of the irq_n code move the offsets in?
You set the selector to 0 while it should be 0x8.
And yes, you should seperate the code from the IDT. In your 'long_mode_start' function, you can add something like this right after loading the GDT:

Code: Select all

mov rbx,idt
mov rax,isr_wrapper.handler ; Assuming you use the same handler for all the interrupts. It's fine for now, but you'll need to change it.
mov rcx,48 ; rcx should be the number of entries in your IDT
.idt_loop: ; This loop sets the offset for each IDT entry
    mov word [rbx],ax ; offset1
    shr rax,16
    mov word [rbx+6],ax ; offset2
    shr rax,16
    mov dword [rbx+8],eax ; offset3
    add rbx,16 ; Go to the next entry
    dec rcx
    jne .idt_loop ; 'dec' will set ZF to 1 if the result is 0
Also, I've just noticed that in your code, you load the GDT after setting the segment registers. This should be the opposite (load first and then set). Besides that, you need to load CS as well.

Re: IDT Help

Posted: Wed Mar 10, 2021 12:06 pm
by bzt
isaiah0311 wrote:I couldn't get bochs set up. It always failed at the display_library in my bochsrc. I looked up all the different options I could use and none of them worked. I'm on Ubuntu 20.04.
Don't give up, bochs has debug capabilities that no other. Here's a default rc you could use. You only need to change a few paths in it:
1. in line 6, add your own BIOS image's path (could be SeaBIOS, whatever. You can use the same as qemu's: /usr/share/qemu/bios.bin)
2. in line 7, add your own video card ROM image's path (could be Cirrus, Ati or BochsVGA, whatever, you can use the same as qemu's: /usr/share/qemu/vgabios.bin)
3. in line 14, add your disk image's path (yeah, you have to generate this yourself)
That's all. Should work after fixing these paths:

Code: Select all

$ bochs -f bochs.rc
Cheers,
bzt

Re: IDT Help

Posted: Wed Mar 10, 2021 1:23 pm
by isaiah0311
SKC wrote:

Code: Select all

mov rbx,idt
mov rax,isr_wrapper.handler ; Assuming you use the same handler for all the interrupts. It's fine for now, but you'll need to change it.
mov rcx,48 ; rcx should be the number of entries in your IDT
.idt_loop: ; This loop sets the offset for each IDT entry
    mov word [rbx],ax ; offset1
    shr rax,16
    mov word [rbx+6],ax ; offset2
    shr rax,16
    mov dword [rbx+8],eax ; offset3
    add rbx,16 ; Go to the next entry
    dec rcx
    jne .idt_loop ; 'dec' will set ZF to 1 if the result is 0
I tried implementing this and now Qemu reboots even without STI. I'm guessing I messed up implementing it.

Code: Select all

long_mode_start:
    ; gdt is set before entering long mode
    ; load null into all data segment registers
    mov ax, 0
    mov cs, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    call load_idt
    lidt [idt.pointer]
    sti

    call kernel_main ; infinite loop in kernel_main()
    mov rax, 60
    mov rdi, 2
    syscall

load_idt:
    mov rbx, idt.irq ; tried with both idt.irq and idt
    mov rax, isr_wrapper.handler
    mov rcx, 48
.loop:
    mov word [rbx], ax
    shr rax, 16
    mov word [rbx + 6], ax
    shr rax, 16
    mov dword [rbx + 8], eax
    add rbx, 16
    dec rcx
    jne .loop
    ret

idt:
.irq:
    dw 0
    dw 8
    db 0
    db 10101110b
    dw 0
    dd 0
    dd 0
.pointer:
    dw $ - idt - 1
    dq idt

isr_wrapper:
.error_code: ; unused currently
    add rsp, 8
.handler:
    push rax
    push rcx
    push rdx
    push rbx
    push rsp
    push rbp
    push rsi
    push rdi
    push r8
    push r9
    push r10
    push r11
    push r12
    push r13
    push r14
    push r15
    
    cld ; C code following the system V ABI requires df to be clear on function entry
    call interrupt_handler ; does nothing currently

    pop r15
    pop r14
    pop r13
    pop r12
    pop r11
    pop r10
    pop r9
    pop r8
    pop rdi
    pop rsi
    pop rbp
    pop rsp
    pop rbx
    pop rdx
    pop rcx
    pop rax
    iretq

Re: IDT Help

Posted: Wed Mar 10, 2021 1:25 pm
by isaiah0311
bzt wrote: 1. in line 6, add your own BIOS image's path (could be SeaBIOS, whatever. You can use the same as qemu's: /usr/share/qemu/bios.bin)
2. in line 7, add your own video card ROM image's path (could be Cirrus, Ati or BochsVGA, whatever, you can use the same as qemu's: /usr/share/qemu/vgabios.bin)
3. in line 14, add your disk image's path (yeah, you have to generate this yourself)
I'm still getting an error: "dlopen failed for module 'x' (libbx_x.so): file not found".

Re: IDT Help

Posted: Wed Mar 10, 2021 3:13 pm
by Octocontrabass
isaiah0311 wrote:I tried implementing this and now Qemu reboots even without STI.
Try adding "-no-reboot" to your QEMU command line. (And maybe also "-d int" to see which exceptions are causing the CPU to reboot. You may need to disable hardware acceleration for this to work.) You can use the QEMU console to dump the GDT and IDT using commands like "info gdt" and "info idt" as well.
isaiah0311 wrote:

Code: Select all

    mov cs, ax
The MOV instruction cannot be used to load the CS register. You'll have to use a far JMP, far CALL, or far RET. Note that Intel allows far JMP and far CALL to have a 64-bit offset but AMD does not!

Re: IDT Help

Posted: Wed Mar 10, 2021 5:47 pm
by bzt
isaiah0311 wrote:I'm still getting an error: "dlopen failed for module 'x' (libbx_x.so): file not found".
Then you haven't installed bochs properly. There should be a directory with bochs plugins, the exact directory differs on distros. Something line /usr/lib/bochs/plugins or /usr/share/bochs/plugins etc. If you've compiled bochs from source, it's going to be /usr/local/lib/bochs/plugins. That directory should contain lots and lots of libbx_*.so files.

If you have installed bochs with your distros package manager, then check out the package's file list. For example Ubuntu (uses /usr/lib/i386-linux-gnu/bochs/plugins/).

Worst case try:

Code: Select all

find /usr -name libbx_x.so
If you can't find that file,then try

Code: Select all

find /usr -name libbx_sdl2.so
If you can't find x, but you've found sdl2, then in the bochs.rs file replace

Code: Select all

display_library: sdl2
. That should work (note: installing bochs-sdl works with Wayland too, I can imagine Ubuntu installs this X11-less version by default. I don't use Ubuntu, so I'm not sure). What I can make out of the deps, "bochs" requires "bochs-gui", which can be provided by either the "bochs-x" or the "bochs-sdl" package, so maybe try to check which one was actually installed (use "dpkg -l | grep bochs") and use that in the bochs.rc file. Or just install "bochs-x" if it's not installed already.

Ps.: I wanted to link the 20.04LTS package but for some reason it throws 500 Internal Server Error. It could be that the bochs package is corrupted under Ubuntu LTS? That's strange.

Cheers,
bzt

Re: IDT Help

Posted: Wed Mar 10, 2021 6:40 pm
by isaiah0311
Octocontrabass wrote: The MOV instruction cannot be used to load the CS register. You'll have to use a far JMP, far CALL, or far RET. Note that Intel allows far JMP and far CALL to have a 64-bit offset but AMD does not!
It now works if I don't use STI again. Thank you, it seems that was the issue.
Octocontrabass wrote: Try adding "-no-reboot" to your QEMU command line. (And maybe also "-d int" to see which exceptions are causing the CPU to reboot. You may need to disable hardware acceleration for this to work.)
Alright so I tried this and a lot of text came out. Here is a snippet. I'm not quite sure what is going on.

Code: Select all

check_exception old: 0xffffffff new 0xd
     1: v=0d e=0202 i=0 cpl=0 IP=0008:0000000000100200 pc=0000000000100200 SP=0000:0000000000108fe0 env->regs[R_EAX]=0000000000000000
RAX=0000000000000000 RBX=000000000010099a RCX=0000000000000140 RDX=0000000000100018
RSI=0000000000000000 RDI=000000000000003e RBP=0000000000108ff0 RSP=0000000000108fe0
R8 =0000000000000000 R9 =0000000000000000 R10=0000000000000000 R11=0000000000000000
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=0000000000100200 RFL=00000202 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0000 0000000000000000 00000000 00000000
CS =0008 0000000000000000 00000000 00209a00 DPL=0 CS64 [-R-]
SS =0000 0000000000000000 00000000 00000000
DS =0000 0000000000000000 00000000 00000000
FS =0000 0000000000000000 00000000 00000000
GS =0000 0000000000000000 00000000 00000000
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT=     0000000000100050 00000019
IDT=     000000000010069a 00000000
CR0=80000011 CR2=0000000000000000 CR3=0000000000102000 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000000 CCD=0000000000000001 CCO=EFLAGS  
EFER=0000000000000500
check_exception old: 0xd new 0xd
     2: v=08 e=0000 i=0 cpl=0 IP=0008:0000000000100200 pc=0000000000100200 SP=0000:0000000000108fe0 env->regs[R_EAX]=0000000000000000
RAX=0000000000000000 RBX=000000000010099a RCX=0000000000000140 RDX=0000000000100018
RSI=0000000000000000 RDI=000000000000003e RBP=0000000000108ff0 RSP=0000000000108fe0
R8 =0000000000000000 R9 =0000000000000000 R10=0000000000000000 R11=0000000000000000
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=0000000000100200 RFL=00000202 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0000 0000000000000000 00000000 00000000
CS =0008 0000000000000000 00000000 00209a00 DPL=0 CS64 [-R-]
SS =0000 0000000000000000 00000000 00000000
DS =0000 0000000000000000 00000000 00000000
FS =0000 0000000000000000 00000000 00000000
GS =0000 0000000000000000 00000000 00000000
LDT=0000 0000000000000000 0000ffff 00008200 DPL=0 LDT
TR =0000 0000000000000000 0000ffff 00008b00 DPL=0 TSS64-busy
GDT=     0000000000100050 00000019
IDT=     000000000010069a 00000000
CR0=80000011 CR2=0000000000000000 CR3=0000000000102000 CR4=00000020
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=0000000000000000 CCD=0000000000000001 CCO=EFLAGS  
EFER=0000000000000500
check_exception old: 0x8 new 0xd
bzt wrote: Then you haven't installed bochs properly. There should be a directory with bochs plugins, the exact directory differs on distros. Something line /usr/lib/bochs/plugins or /usr/share/bochs/plugins etc. If you've compiled bochs from source, it's going to be /usr/local/lib/bochs/plugins. That directory should contain lots and lots of libbx_*.so files.
I got it working, I installed the bochs-x library. Now I get an error saying /usr/share/qemu/bios.bin does not exist. I'm guessing you were being general and meant just the path to whatever bios.bin file qemu uses, that's my bad. So I investigated the /usr/share/gemu directory and found a bunch of different .bin files. I'm not sure which one is the bios and which one is the vga bios. Here are the .bin, .rom, and .img files in the directory: bios-microvm.bin, kvmvapic.bin, linuxboot_dma.bin, pvh.bin, pxe-ne2k_isa.rom, pxe-rtl8139.rom, s390-ccw.img, sgabios.bin, linuxboot.bin, multiboot.bin, pxe-e1000.rom, pxe-pcnet32.rom, pxe-virtio.rom, s3900-netboot.img

Re: IDT Help

Posted: Wed Mar 10, 2021 7:42 pm
by Octocontrabass
isaiah0311 wrote:

Code: Select all

     1: v=0d e=0202
It's #GP caused by the IDT entry for interrupt 0x20 (32), which should be IRQ0 according to how you've programmed the PICs. Your IDT only has entries for interrupt 0 through 31, so it's not surprising you'll get an exception.

It's immediately followed by #DF because your IDT entry for #GP is invalid. Perhaps use "info idt" in the QEMU monitor to see what QEMU thinks of your IDT. You should probably fix your exception handlers before you worry about IRQs!