IDT Help

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
isaiah0311
Member
Member
Posts: 25
Joined: Tue Mar 09, 2021 9:31 pm
Libera.chat IRC: isaiah0311

Re: IDT Help

Post by isaiah0311 »

Octocontrabass wrote: 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!
How do I get QEMU to give me a prompt? Currently I run qemu and it immediately boots into my OS and then crashes. This is the command I use.

Code: Select all

qemu-system-x86_64 -cdrom dist/x86_64/kernel.iso -no-reboot -d int
I think with the new code I have 48 IRQs, but I could be wrong. How do I fix my exception handlers? I thought that's what the IRQs did, exception and interrupt handling.
SKC
Member
Member
Posts: 28
Joined: Thu Feb 18, 2021 3:07 am

Re: IDT Help

Post by SKC »

As Octocontrabass said, you can't access CS directly. Use the following code to change it (it's in AT&T syntax because I don't know how the operand sizes work in Intel's syntax for these instructions):

Code: Select all

pushq 0x8 # Push the code selector. Pushes 64 bits (although the selector is 16 bits)
pushq cs_reload_lbl # Push the address of the label
retfq # Return Far - This will set RIP to cs_reload_lbl and CS to 0x8
cs_reload_lbl:
# The rest of the code here
isaiah0311 wrote:How do I fix my exception handlers? I thought that's what the IRQs did, exception and interrupt handling.
You can't handle exceptions and IRQs with the exact same code. Each interrupt serves a different purpose.
In my OS, I have one IRQ handler and one exception handler, both written in C (actually C++ but I don't use any C++ features except classes). These two handlers are called by small assembly handlers, one for each interrupt. The assembly handlers push all the registers and call the handlers, giving them the interrupt's number and an error code (if the exception has one). The C handlers call a thread that handles the exception/IRQ. After the C handler returns, the assembly handler pops all the registers and returns (iret).
For now, I think your exception handler could just print some exception info and then hang ('jmp $').

Also, about your 'long_mode_start' function, I don't understand it. Where and when do you load the GDT? And why do you still set the segment registers to 0?
Besides that, you don't need to push rsp in your handler. The interrupt does it by itself. And since you'll probably push all the registers a few more times in your code, I'd recommend you create a 'pusha' macro.
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: IDT Help

Post by Octocontrabass »

isaiah0311 wrote:How do I get QEMU to give me a prompt?
The Windows builds of QEMU include a GUI with a convenient menu to switch between them, but the QEMU documentation says you can use keyboard shortcuts to switch to the monitor too.
isaiah0311 wrote:I think with the new code I have 48 IRQs,
You have 16 IRQs. IRQs are interrupt requests that come from hardware external to the CPU core. The CPU core itself can also generate 32 exceptions. All together, that's 48 interrupts, which requires at least 48 entries in your IDT, and 48 ISRs to handle all of them.

If you get the terminology wrong, you'll definitely be confused.
isaiah0311
Member
Member
Posts: 25
Joined: Tue Mar 09, 2021 9:31 pm
Libera.chat IRC: isaiah0311

Re: IDT Help

Post by isaiah0311 »

SKC wrote: As Octocontrabass said, you can't access CS directly. Use the following code to change it (it's in AT&T syntax because I don't know how the operand sizes work in Intel's syntax for these instructions):

Code: Select all

pushq 0x8 # Push the code selector. Pushes 64 bits (although the selector is 16 bits)
pushq cs_reload_lbl # Push the address of the label
retfq # Return Far - This will set RIP to cs_reload_lbl and CS to 0x8
cs_reload_lbl:
# The rest of the code here
Alright I changed the code and now load CS properly, thank you.
SKC wrote: You can't handle exceptions and IRQs with the exact same code. Each interrupt serves a different purpose.
In my OS, I have one IRQ handler and one exception handler, both written in C (actually C++ but I don't use any C++ features except classes). These two handlers are called by small assembly handlers, one for each interrupt. The assembly handlers push all the registers and call the handlers, giving them the interrupt's number and an error code (if the exception has one). The C handlers call a thread that handles the exception/IRQ. After the C handler returns, the assembly handler pops all the registers and returns (iret).
For now, I think your exception handler could just print some exception info and then hang ('jmp $').
So I understand what you're saying, but I just don't know how to implement it. Right now I use the loop that you recommended in a previous answer.
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
So this should make all 48 entries in my IDT? If this is correct, what am I missing? I apologize ahead of time for my lack of knowledge.
Here is the whole main64.asm file.

Code: Select all

global long_mode_start
extern kernel_main
extern interrupt_handler

section .text
bits 64
long_mode_start:
	; push 0x8 into cs
	push 0x8
	push cs_reload
	retfq

cs_reload:
	call load_idt
	lidt [idt.pointer]
	sti
	
	call kernel_main
	mov rax, 60
	mov rdi, 2
	syscall

load_idt:
	mov rbx, 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 0x0000
	dw 0x8000
	db 0x00
	db 10101110b
	dw 0x0000
	dd 0x00000000
	dd 0x00000000
.pointer:
        dw $ - idt - 1
        dq idt

isr_wrapper:
.handler:
	push rax
	push rcx
	push rdx
	push rbx
	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
	
	pop r15
	pop r14
	pop r13
	pop r12
	pop r11
	pop r10
	pop r9
	pop r8
	pop rdi
	pop rsi
	pop rbp
	pop rbx
	pop rdx
	pop rcx
	pop rax
        iretq
SKC wrote: Also, about your 'long_mode_start' function, I don't understand it. Where and when do you load the GDT? And why do you still set the segment registers to 0?
Besides that, you don't need to push rsp in your handler. The interrupt does it by itself. And since you'll probably push all the registers a few more times in your code, I'd recommend you create a 'pusha' macro.
My GDT is loaded in main.asm when the OS is still in protected mode. I then make a long jump in order to reach long_mode_start after switching to long mode. The segment registers were set to 0 because that is how it was explained to me in a tutorial, I have no other reason. I've taken out that part as of now. Here is the GDT code from the other file.

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
And the GDT is loaded like this in the start function:

Code: Select all

lgdt [gtd.pointer]
jmp gdt.code:long_mode_start
Octocontrabass wrote: The Windows builds of QEMU include a GUI with a convenient menu to switch between them, but the QEMU documentation says you can use keyboard shortcuts to switch to the monitor too.
Thank you, I finally got the prompt.
SKC
Member
Member
Posts: 28
Joined: Thu Feb 18, 2021 3:07 am

Re: IDT Help

Post by SKC »

isaiah0311 wrote:So I understand what you're saying, but I just don't know how to implement it. Right now I use the loop that you recommended in a previous answer.
...
So this should make all 48 entries in my IDT? If this is correct, what am I missing?
The code will set all entries to a specific handler. You can use it now for your exceptions, but you'll need to give each interrupt its own handler at some point. And here the disadvantage of my method reveals itself - writing a handler for each interrupt and then configuring each interrupt separately requires lots of code. I use a simple python script to generate the code, but still, it's very messy and can be confusing. Maybe someone else has a better idea.

Now to your code. Since you create and load the GDT in protected mode, you don't need to change CS (CS is changed when you switch to long mode). However, you do need to change the data segment registers. It looks like you don't fully understand the segment selectors, so I'll explain them:
Each selector is made out of 3 parts: the privilege, the table index, and the descriptor index. In a C struct, the selector would look like this:

Code: Select all

struct Selector
{
    uint16_t privilege:2;
    uint16_t table:1; // 0 for GDT, 1 for LDT
    uint16_t index:13;
};
So, for your data segment descriptors, use 0x10 (index 2, table 0, privilege 0). You need to set them right after you enter long mode.
And in your IDT, you need to use 0x8 (index 1, table 0, privilege 0) and not 0x8000 (index 4096, table 0, privilege 0) for the code selector.
isaiah0311
Member
Member
Posts: 25
Joined: Tue Mar 09, 2021 9:31 pm
Libera.chat IRC: isaiah0311

Re: IDT Help

Post by isaiah0311 »

SKC wrote: The code will set all entries to a specific handler. You can use it now for your exceptions, but you'll need to give each interrupt its own handler at some point. And here the disadvantage of my method reveals itself - writing a handler for each interrupt and then configuring each interrupt separately requires lots of code. I use a simple python script to generate the code, but still, it's very messy and can be confusing. Maybe someone else has a better idea.
I think I'll try adding a handler for each entry. The code still breaks whenever I turn on interrupts. Unless there is something I need the handler to do in order to make it work? Right now it calls an function in C that does nothing and then hangs with jmp $.
SKC wrote: It looks like you don't fully understand the segment selectors, so I'll explain them:
Each selector is made out of 3 parts: the privilege, the table index, and the descriptor index. In a C struct, the selector would look like this:

Code: Select all

struct Selector
{
    uint16_t privilege:2;
    uint16_t table:1; // 0 for GDT, 1 for LDT
    uint16_t index:13;
};
So, for your data segment descriptors, use 0x10 (index 2, table 0, privilege 0). You need to set them right after you enter long mode.
And in your IDT, you need to use 0x8 (index 1, table 0, privilege 0) and not 0x8000 (index 4096, table 0, privilege 0) for the code selector.
This makes a lot more sense now, thank you!
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

Re: IDT Help

Post by bzt »

isaiah0311 wrote: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.
That's a progress :-) Well, yes, I meant in general, however that's where it actually is on my distro.

Try bochsbios, that also contains the qemu BIOS at /usr/share/bochs/BIOS-qemu-latest. And about vgabios, that should be installed at /usr/share/vgabios/vgabios.bin (both packages should be already installed on your computer as bochs package depends on them. If not, just install them). Man, Ubuntu packages are insane :-D

Cheers,
bzt
isaiah0311
Member
Member
Posts: 25
Joined: Tue Mar 09, 2021 9:31 pm
Libera.chat IRC: isaiah0311

Re: IDT Help

Post by isaiah0311 »

bzt wrote:Try bochsbios, that also contains the qemu BIOS at /usr/share/bochs/BIOS-qemu-latest. And about vgabios, that should be installed at /usr/share/vgabios/vgabios.bin (both packages should be already installed on your computer as bochs package depends on them. If not, just install them). Man, Ubuntu packages are insane :-D
I got Bochs working, thank you so much! So it boots up, but just gives me a black screen and a terminal. Is there a command I need to use to boot into the OS?
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

Re: IDT Help

Post by bzt »

isaiah0311 wrote:I got Bochs working, thank you so much! So it boots up, but just gives me a black screen and a terminal. Is there a command I need to use to boot into the OS?
Yes, same as with gdb and qemu -Ss: the machine is stopped right before any code is executed. This is to allow you to set up break points. Just continue execution with a "c" command.
Read this and this to get you started. Here's a list of available commands (not all of them, use "help" and "help info" commands at the debugger prompt to list them).

Some of the features that will make your life easier:
1. use xchg bx,bx instruction in your code if you want to invoke the debugger (exchanging a value with itself does nothing in other VMs) In contrast to int 3, this won't change the stack nor the VM's state in any way and works without a correct IDT too ;-)
2. use out 0e9h, al to print a character to the terminal (very handy and a single instruction is a lot simpler than sending a character to the serial port)
3. learn its debugger commands, for example "page" command will debug the page table walk for you, I've found that feature extremely useful (and no other debugger can do that)
4. using "info idt" will display the decoded IDT, also very useful
5. you can dump peripheral's registers too, like "info pic" or "info pit" (you'll see later how useful those are when you're debugging IRQs)

For example, you could do

Code: Select all

xchg bx,bx
sti
and then at the bochs debugger prompt issue "s" command multiple times and you'll see step-by-step what's happening inside the CPU when you enable the interrupts flag, and what instructions lead to the triple fault.

Cheers,
bzt
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: IDT Help

Post by Octocontrabass »

isaiah0311 wrote:

Code: Select all

idt:
.irq:
	dw 0x0000
	dw 0x8000
	db 0x00
	db 10101110b
	dw 0x0000
	dd 0x00000000
	dd 0x00000000
.pointer:
        dw $ - idt - 1
        dq idt
Well, now your IDT has only one entry, so that's not going to work. You need at least 48 entries. You also still have a bad CS selector and the DPL is set to 1 instead of 0.
isaiah0311
Member
Member
Posts: 25
Joined: Tue Mar 09, 2021 9:31 pm
Libera.chat IRC: isaiah0311

Re: IDT Help

Post by isaiah0311 »

Octocontrabass wrote:Well, now your IDT has only one entry, so that's not going to work. You need at least 48 entries. You also still have a bad CS selector and the DPL is set to 1 instead of 0.
Thank you for pointing that out, I fixed the code so there is 48 IRQs now.
SKC said my CS selector should be good because it is set when I switch into long mode. Is there something else I am missing?
Also, I don't know what a DPL is, I apologize.
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: IDT Help

Post by Octocontrabass »

isaiah0311 wrote:Thank you for pointing that out, I fixed the code so there is 48 IRQs now.
IDT descriptors are not IRQs. You should use the correct name, otherwise you might get confused!
isaiah0311 wrote:SKC said my CS selector should be good because it is set when I switch into long mode. Is there something else I am missing?
Aren't you using 0x8 for your CS selector when you switch to long mode? You wrote 0x8000 in the IDT descriptor.
isaiah0311 wrote:Also, I don't know what a DPL is, I apologize.
Descriptor Privilege Level. It's one of the fields in the IDT descriptor. It determines which privilege level is allowed to call each ISR using the INT instruction. For now, you should set the DPL to 0. You can always change it later if you decide less-privileged code needs to use the INT instruction.
isaiah0311
Member
Member
Posts: 25
Joined: Tue Mar 09, 2021 9:31 pm
Libera.chat IRC: isaiah0311

Re: IDT Help

Post by isaiah0311 »

Octocontrabass wrote:
isaiah0311 wrote:Thank you for pointing that out, I fixed the code so there is 48 IRQs now.
IDT descriptors are not IRQs. You should use the correct name, otherwise you might get confused!
To my understanding, the loop SKC wrote for me in an earlier reply should put 48 entries into the IDT. I think those are the descriptors. I also have 48 IRQs (I'm not sure how many of these I'm supposed to have).
Octocontrabass wrote:
isaiah0311 wrote:SKC said my CS selector should be good because it is set when I switch into long mode. Is there something else I am missing?
Aren't you using 0x8 for your CS selector when you switch to long mode? You wrote 0x8000 in the IDT descriptor.
I updated this after SKC pointed this out earlier.
Octocontrabass wrote:
isaiah0311 wrote:Also, I don't know what a DPL is, I apologize.
Descriptor Privilege Level. It's one of the fields in the IDT descriptor. It determines which privilege level is allowed to call each ISR using the INT instruction. For now, you should set the DPL to 0. You can always change it later if you decide less-privileged code needs to use the INT instruction.
Do I set that in the IDT.pointer section? How do I do that?
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: IDT Help

Post by Octocontrabass »

isaiah0311 wrote:I also have 48 IRQs (I'm not sure how many of these I'm supposed to have).
You're using the PICs, so you have 16 IRQs. You have 48 of something else. ISRs? IDT descriptors?
isaiah0311 wrote:Do I set that in the IDT.pointer section? How do I do that?
The DPL is bits 5 and 6 of this byte in the descriptor:

Code: Select all

db 10101110b
Change it to 10001110b to set the DPL to 0.
SKC
Member
Member
Posts: 28
Joined: Thu Feb 18, 2021 3:07 am

Re: IDT Help

Post by SKC »

isaiah0311 wrote:To my understanding, the loop SKC wrote for me in an earlier reply should put 48 entries into the IDT.
Well yes, but actually no. The loop sets the entries to the same handler. If you use it for the entire IDT, every interrupt/exception/IRQ will call the same code. Since you're in a very early stage, you can set the exception entries (0-31) to the same handler. However, as I said before, you will need to give each interrupt its own handler at some point. But for now, setting all the exceptions (only exceptions, not IRQs) to a dummy handler that just hangs is fine.

Oh, about your confusion with the naming, read this. I hope it helps :)
Post Reply