Skip to content

Various bug fixes as well as a change to provide our own GDT. #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

mkilgore
Copy link

@mkilgore mkilgore commented May 8, 2015

I would take a look at the commit list for better information on what all this pull request entails, each commit has a description describing it (Most of them are fairly small/simple fixes).

The bigger changes are the addition of a Makefile and the GDT. The Makefile just executes the same instructions as in the README.md - Though, I set CFLAGS to provide -ffreestanding, since this is kernel code.

For the GDT, it's extremely simple and just does a flat 4GB for the kernel code and data. The reason it's necessary to provide one is because GRUB doesn't guarentee that the GDT is actually usable when our kernel starts. Since interrupts cause the segment registers to be reloaded, the code would fault on an interrupt if we don't have a valid GDT. It's simple enough to provide one. It's worth noting, I modeled the GDT code off of my own kernel's GDT code, to make it easier. You can see it here, the file ./arch/x86/include/arch/gdt.h. I mostly just pulled the GDT_ENTRY macro, which makes creating GDT entries much easier.

The other small changes are removing the stdio.h include (I suspect that was just left in accidentally), removing a redundent cli instruction from start, fixing the end of start so that we guarentee execution will never continue beyond it, saving and restoring the registers in the keyboard interrupt handler, and make-sure the IDT and now GDT structures are packed by the compiler to guarentee we don't have any padding inside them.

I'd be happy to separate out some of the commits if you'd prefer.

mkilgore added 11 commits May 8, 2015 06:24
We can't include the host machines stdio.h header in our kernel code.
It's not actually being used, so removing this doesn't break anything.
Create a simple Makefile to compile the kernel. 'kernel.asm' was changed
to 'head.asm' to avoid naming conflict with 'kernel.c'. The Makefile
generates .o files based on the filename, so 'kernel.asm' and 'kernel.c'
would both compile to 'kernel.o'. A simple rename fixes the issue.
The bootloader will never call 'start' with interrupts enabled, so
executing a cli here is redundent.
The 'hlt' instruction only halts the CPU until the next interrupt
occurs. Beacuse of this, it's necessary to both use 'cli' before-hand to
make-sure interrupts are off before we execute 'hlt', but also put the
'hlt' in a loop anyway to protect against nmi's, guarenteeing we don't
continue.
C compilers are allowed to pad struct entries out, meaning that entry 2
may not come directly after entry 1, but instead empty space may be
inbetween the two, for various reasons. gcc provides
__attribute__((packed)) to guard against this, which when applied to a
struct guarentees that the struct will not be compiled with any empty
space. This is necessary for structs like IDT_entry, where they map to
structure the hardware is going to expect in a very specefic setup with
no padding.
GRUB documentation says that we can't rely on the GDT that GRUB supplies
us, and that it may not be valid to use. Since our IDT requires
specifying a selector, we need to have our own GDT so we can supply a
valid selector for the interrupt handler.
Like the 'gdt_ptr' struct, the 'idt_ptr' struct is a structure defining
the specefic layout x86 wants for the IDT description. Using the
structure like this lets us avoid all the bitshifting that used to be
required.
Right now, kmain does a busy loop while we wait for interrupts. It's
much more effecient to put a hlt instruction inside of the loop, which
pauses the CPU until the next interrupt is recieved. This still requires
a loop because execution will continue after the hlt instruction once we
return from an interrupt.
Currently, we don't back up any of the registers before calling the
actual interrupt routine. This will become a problem because the
interrupt routine is going to trash these values, and they won't be
restored by 'iretd'. The simple solution is to push all the segments and
then all the registers onto the stack, and then pop them right before we
return from the interrupt.
Since building now uses 'make', reflect that in the README.md
@arjun024
Copy link
Owner

arjun024 commented May 9, 2015

Thanks for your contribution. The article and code is reduced to the bare minimum intentionally so that it's accessible for beginners, and is brief. I have resisted the temptation to add a Makefile to let the users do each of the steps themselves.

Oh stdio.h .. oh I can't believe how I botched it up and how that crept in. As for the GDT, the article is predicated on the assumption that GRUB provides a GDT that could be put to use. If that isn't true, I will need to rewrite the article before I provide one independently.

@mkilgore
Copy link
Author

mkilgore commented May 9, 2015

That's fair enough, a little later today I'll remove the Makefile, and change the names back. You will want to add -ffreestanding to the gcc line.

See the multiboot spec here. Basically, while GRUB provides segments in a specefic configuration, it may also clear the GDTR register. When that's cleared, you can't reload any of the segment registers or else it will try to read the invalid GDTR - The solution is of course to propvide your own GDT as a replacement. You can also see the note in EFLAGS about IF always being cleared, so interrupts are always off.

I think I may have a bug though - load_gdt should reload the segment registers as well I think. I'll look into that.

@arjun024
Copy link
Owner

arjun024 commented May 9, 2015

Yes, it's anyway better to have a GDT to ourselves. I will try and incorporate the changes once I get the time to add the GDT part to the article.

@arjun024
Copy link
Owner

I have gone ahead and removed the stdio header. It's very misleading to let it stay there. thanks

call keyboard_handler_main

popad
pop ds

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be in reverse order?

Copy link
Author

@mkilgore mkilgore Dec 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say you would be right. I'm guessing I never noticed this bug because all of those segments probably contain the same value everywhere in the program. but obviously it's still a bug and it would easily blow things up if/when features are added that make use of the segment registers (Such as using them for CPU-local data).

On that note it also appears the segments are also never assigned, so it's quite likely this code got lucky in other areas. A while back I had an issue stemming from something similar in my kernel, it's fixed here. The code in this PR doesn't reload the segments after the lgdt, but that's necessary to ensure they actually contain the correct values and also to ensure they start utilizing the newly defined segments.

@FrankRay78
Copy link

FWIW, I think this is a really good PR @mkilgore as it adds several realistic features even the most barebones interrupt example requires.

idt_address = (unsigned long)IDT ;
idt_ptr[0] = (sizeof (struct IDT_entry) * IDT_SIZE) + ((idt_address & 0xffff) << 16);
idt_ptr[1] = idt_address >> 16 ;
idt_ptr.limit = sizeof(IDT);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be sizeof(IDT) - 1 ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think you're right, but FWIW it shouldn't matter that much since nothing should read past the end of the IDT or GDT (and if they did, are likely to read more than one byte).

struct gdt_ptr gdt_ptr;

gdt_ptr.base = (unsigned long)GDT;
gdt_ptr.limit = sizeof(GDT);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be sizeof(GDT) - 1 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants