Page 1 of 2
IDT troubles
Posted: Sat Nov 02, 2024 3:24 pm
by restingwitchface
Good day! I'm an avid OSdever (was very interested in the theory and recently acquired the sufficient hardware to experiment and make my own). In the current OS I'm building, I'm having difficulty with setting up my IDT. I'm booting using Multiboot - not Multiboot2 - using the skeleton provided in the first "Bare Bones". It's still 32-bit. Most other issues were easy to resolve, but this guy is bugging me; sorry if it's trivial.
My kernel, after setting up the stack, sets up the GDT via (AT&T syntax)
Code: Select all
pushl %eax
pushl %ebx
call load_default_gdt
popl %ebx
popl %eax
where the C function to actually do the GDT-loading is
Code: Select all
#include <stddef.h>
#include <stdint.h>
typedef struct {
uint16_t lim1;
uint16_t base1;
uint8_t base2;
uint8_t access;
uint8_t lim2_flags;
uint8_t base3;
} gdt_entry_t;
typedef struct {
uint16_t size;
uint32_t addr;
} __attribute__ ((packed)) gdtr_t; /* Also used for the IDTR */
gdt_entry_t fill_gdt_entry (uint32_t limit, uint32_t base, uint8_t access) {
gdt_entry_t entry;
uint8_t granularity = 0;
if (limit > 0x100000) {
granularity = 1;
limit >>= 12;
}
entry.lim1 = limit & 0xFFFF;
entry.lim2_flags = (limit >> 16) | (granularity << 7) | 0x40;
entry.base1 = base & 0xFFFF;
entry.base2 = (base >> 16) & 0xFF;
entry.base3 = base >> 24;
entry.access = access;
return entry;
}
void lgdt (gdt_entry_t *entries, size_t count) {
gdtr_t gdtr;
if (count*sizeof(gdt_entry_t) > 0x10000) {
return;
}
gdtr.size = count*sizeof(gdt_entry_t)-1;
gdtr.addr = (uint32_t) entries;
__asm__ __volatile__ ( "lgdt %0" : : "m" (gdtr) : );
}
void load_default_gdt () {
gdt_entry_t entries[3];
entries[0] = fill_gdt_entry(0, 0, 0);
entries[1] = fill_gdt_entry(0xFFFFFFFF, 0, 0x99);
entries[2] = fill_gdt_entry(0xFFFFFFFF, 0, 0x93);
lgdt((gdt_entry_t*) entries, 3);
}
This produces a 4G kernel code segment After we do some other (presumably irrelevant) stuff, we setup the IDT with
Code: Select all
extern uint32_t _isr222;
typedef struct {
uint16_t offset1;
uint16_t segment;
uint8_t resv;
uint8_t flags;
uint16_t offset2;
} idt_entry_t;
idt_entry_t fill_idt_entry (uint32_t offset, uint8_t flags, uint16_t segment) {
idt_entry_t entry;
entry.offset1 = offset & 0xFFFF;
entry.segment = 8*segment;
entry.resv = 0;
entry.flags = flags;
entry.offset2 = offset >> 16;
return entry;
}
void load_default_idt (uint32_t isr1) {
idt_entry_t entries[256];
size_t idx;
for (idx = 0; idx < 256; idx++) {
entries[idx] = fill_idt_entry(0,0,0);
}
entries[0xDE] = fill_idt_entry((uint32_t) isr1, 0xCE, 1);
lidt((idt_entry_t*) entries);
}
load_default_idt(_isr222);
where the code for our inline assembly function LIDT is exactly the same as for LGDT, minus "count" (always 256) and, well, using the LIDT instruction instead of LGDT. In an also-linked-in assembly file I have an assembly wrapper (which is referenced in the above file as an external integer):
Code: Select all
.section .text
.global _isr222
.type _isr222, @function
_isr222:
pushal
cld
call isr_222
popal
iret
When I set the IF and interrupt at vector 0xDE, QEMU crashes, but in a weird way: it goes to "SeaBIOS... booting from PXE", then writes like half a line, then the logs from the kernel show again, then it crashes again, etc. It's very weird. When I don't interrupt but still set up the interrupt vector, it doesn't crash, but I obviously don't have a GPF handler.
Re: IDT troubles
Posted: Sat Nov 02, 2024 9:39 pm
by davmac314
When I set the IF and interrupt at vector 0xDE
"set the IF" is ringing alarm bells. You don't need to do that in order to issue a software interrupt, which is what I assume you are doing.
And, if you do enable interrupts by setting IF, it's possible that there's a pending hardware interrupt which immediately causes a fault (leading to a double and then triple fault, which causes a reset). Leave them disabled. If that doesn't work, use a debugger to step through and find out exactly where it's going wrong.
QEMU crashes, but in a weird way: it goes to "SeaBIOS... booting from PXE", then writes like half a line, then the logs from the kernel show again, then it crashes again, etc. It's very weird.
That's not really weird at all, it's a classic "triple-fault reset" boot loop. Beginners get those a lot.
(I suggest setting up handlers for the exceptions before you try to get general interrupts (or even anything else) working. It will be very useful for debugging.)
Re: IDT troubles
Posted: Sat Nov 02, 2024 10:29 pm
by nullplan
Yes, I also think there is an issue with your exception handlers. For now, at the start, you should make each exception handler just say its name, and the CS:EIP it was called from (part of the interrupt frame) before halting the system. That will tell you exactly which instruction caused which exception.
Later, when you have userspace processes, you may want to notify them of the crash in some way, but that is then.
Re: IDT troubles
Posted: Sun Nov 03, 2024 6:00 am
by restingwitchface
Yes, immediately after posting this I guessed it might be a triple fault, but wasn't sure how it would be triggered. Since paging is disabled and my segments all started at 0, I didn't take the possibility of not being able to find the interrupt handler into account.
This error also occurred without an explicit sti instruction before the int 0xDE. The only thing that the interrupt handler was doing is printing "I was interrupted at vector 0xDE!" and halting, as you suggested.
Anyways, by changing the access byte on segment 1 to 0x9A (rather than 0x99, which has read permissions, stupidly, disabled), it doesn't triple fault, but the message isn't printed either. I'll set up some exception handlers to see if it's a GPF or something.
Re: IDT troubles
Posted: Sun Nov 03, 2024 6:48 am
by restingwitchface
I set up the following exception handlers:
Code: Select all
void isr_0 () {
panic("Division by zero!");
}
void isr_6 () {
panic("Undefined opcode!");
}
void isr_8 () {
panic("Double fault!");
}
void isr_11 () {
panic("Segment not present!");
}
void isr_13 () {
panic("General protection fault!");
}
void load_default_idt () {
idt_entry_t entries[256];
size_t idx;
for (idx = 0; idx < 256; idx++) {
entries[idx] = fill_idt_entry(0,0,0);
}
entries[0] = fill_idt_entry(_isr0, 0x8E, 1);
entries[6] = fill_idt_entry(_isr6, 0x8E, 1);
entries[8] = fill_idt_entry(_isr8, 0x8E, 1);
entries[11] = fill_idt_entry(_isr11, 0x8E, 1);
entries[13] = fill_idt_entry(_isr13, 0x8E, 1);
lidt((idt_entry_t*) entries);
}
but a division by zero or explicit int 0 now does nothing...
Re: IDT troubles
Posted: Sun Nov 03, 2024 12:58 pm
by MichaelPetch
restingwitchface wrote: ↑Sun Nov 03, 2024 6:48 am
but a division by zero or explicit int 0 now does nothing...
This could be any number of things without seeing all your code and how you build things. One thing that comes to mind is if you are creating a custom bootloader (are you?) - are you reading enough sectors to read the entire kernel? Adding string literals in C can bloat the kernel size (depending on how you build things) and often it results in strings not being displayed. If you are using GRUB/multiboot/Limine as a bootloader then this likely isn't the problem.
I see you have a Github account. If you were to commit your code to a repository we could better assist you.
Re: IDT troubles
Posted: Sun Nov 03, 2024 1:34 pm
by restingwitchface
No, I'm just booting the Multiboot image directly from QEMU, via
Code: Select all
qemu-system-x86_64 -kernel [...] -initrd [...]
I haven't tested it on real hardware yet, but QEMU should be pretty accurate. I don't generally like to put unfinished stuff on Git. If I still can't figure it out later - since I haven't devoted much time after my post earlier - I'll put it on there.
Re: IDT troubles
Posted: Sun Nov 03, 2024 1:48 pm
by MichaelPetch
If you are using something supporting Mulitboot (the -kernel option does) then what I suggested as one possible problem (the kernel not being read in memory completely) is not likely the issue at all.
Since you are using mulitboot do you create and load your own GDT and then reload CS (with a FAR JMP or equivalent and then reload the segment registers? Run QEMU with the options `-d int -no-shutdown -no-reboot` to see what exceptions were thrown if any. You could do that and then post the last few exceptions (the last 50 lines should suffice) here in a comment.
Re: IDT troubles
Posted: Sun Nov 03, 2024 2:20 pm
by restingwitchface
Thank you! Most of the logs seem to be SMIs, but here are the last 57 lines.
Code: Select all
SMM: enter
EAX=000000b5 EBX=00008a80 ECX=00005678 EDX=00000005
ESI=00000000 EDI=06fff5ce EBP=0000696e ESP=0000696e
EIP=000f8a7d EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA]
CS =0008 00000000 ffffffff 00c09b00 DPL=0 CS32 [-RA]
SS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA]
DS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA]
FS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA]
GS =0010 00000000 ffffffff 00c09300 DPL=0 DS [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT= 000f7460 00000037
IDT= 000f749e 00000000
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=00000008 CCD=00006954 CCO=ADDL
EFER=0000000000000000
SMM: after RSM
EAX=000000b5 EBX=00008a80 ECX=00005678 EDX=00000003
ESI=06f33300 EDI=06fff5ce EBP=00006968 ESP=00006968
EIP=00008a80 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =dd00 000dd000 ffffffff 00809300
CS =f000 000f0000 ffffffff 00809b00
SS =0000 00000000 ffffffff 00809300
DS =0000 00000000 ffffffff 00809300
FS =0000 00000000 ffffffff 00809300
GS =c900 000c9000 ffffffff 00809300
LDT=0000 00000000 0000ffff 00008200
TR =0000 00000000 0000ffff 00008b00
GDT= 00000000 00000000
IDT= 00000000 000003ff
CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=00000000 CCD=00000001 CCO=EFLAGS
EFER=0000000000000000
0: v=00 e=0000 i=1 cpl=0 IP=0008:0000000000200acd pc=0000000000200acd SP=0010:0000000000206fd0 env->regs[R_EAX]=00000000000007ff
EAX=000007ff EBX=00209060 ECX=002067c8 EDX=00000000
ESI=00000000 EDI=00002000 EBP=00206ff8 ESP=00206fd0
EIP=00200acd EFL=00000007 [-----PC] 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= 00206fd8 00000017
IDT= 002067c8 000007ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=000065d1 CCD=000032e8 CCO=SARL
EFER=0000000000000000
Re: IDT troubles
Posted: Sun Nov 03, 2024 2:35 pm
by MichaelPetch
If those are the last lines and there was nothing after then it appears that software interrupt 0 or a div by zero was raised. v=00 is the exception 0 interrupt/exception. Since there was nothing faulting afterwards and the CS and data segment registers look okay I can only say that it appears an exception was successfully raised and that the processor did something until it sat and did nothing else. So something in the exception handling prevented you from giving a message about the fault raised. Maybe something with your _isr0 stub handler for interrupt 0 is wrong or if there is a problem with the `panic` code? What does `panic` do? Write to the display? serial port? If a serial port did you tell QEMU to send it to a file or stdio?
Re: IDT troubles
Posted: Sun Nov 03, 2024 3:07 pm
by restingwitchface
Alright. My panic function prints the desired message to stdout (VGA), as well as EAX, EBX, ECX, EDX, EIP (before the call), ESP, EBP, ESI, EDI & EFLAGS, and I already verified that it works normally.
Re: IDT troubles
Posted: Sun Nov 03, 2024 3:11 pm
by MichaelPetch
Okay so your panic code does what is expected? So how does it not work afterwards. What were you expecting to happen vs what you are observing? For instance if you used `int $0x0` to raise interrupt 0 then it should continue with the code after the software interrupt and run whatever that was. If you caused a div by 0 then when IRET returns it will attempt to execute the same instruction that cause the div by zero and repeatedly do that forever (it will appear to hang). You'd also see a flood of v=00 in the log while in that loop which we don't see in the log. The exception being if your interrupt handler modified the return address (or registers on the stack) before the IRET but that likely doesn't seem like something you would have done? Or did you?
If panic goes into an infinite loop or does a `hlt` of course it will never return.
Re: IDT troubles
Posted: Sun Nov 03, 2024 4:55 pm
by restingwitchface
> What were you expecting to happen vs what you are observing?
I expected that int0 calls _isr0 calls isr_0 calls panic, which prints "Division by zero!", the registers in hex format (this subroutine I have tested), and hangs. I am observing that it *just* hangs, without executing the panic. The QEMU logs still show nothing after the seemingly successful int 0. It is frustrating that it does not describe where it thinks the interrupt is, just EIP when interrupting, since else I could check against objdump for where it's going.
Re: IDT troubles
Posted: Sun Nov 03, 2024 5:02 pm
by sebihepp
As int 0 is raised without triple fault and only printing Text doesnt Work, please Check if you set your access to video memory as volatile.
Re: IDT troubles
Posted: Sun Nov 03, 2024 5:04 pm
by restingwitchface
No change.