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.)
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:
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.
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:
If you can't find that file,then try
If you can't find x, but you've found sdl2, then in the bochs.rs file replace
. 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
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
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!