I started writing my OS kernel a few weeks ago using a combination of assembler and C. This seemed to be the easiest way to get started, and it sure was. C is very simplistic and easy to compile and link. However, just recently I realized that I really miss the object oriented way of structuring the code. This gets apparent once you reach a certain size of the project where it it’s just not a few files anymore. You start thinking in terms of classes and namespaces and what not, and suddenly C is no longer that convenient. Sure, you can accomplish most object oriented stuff in C as well but it requires lots of discipline and manual bookkeeping. These things you get naturally in C++…
So today I set out to upgrade my project to use C++ instead. I will of course still use assembler for the low level functionality, but most of the kernel will be fully object oriented from now on.
Compiling C++
The problem with using C++ is that you need to take care of some additional dependencies that GCC requires. Normally this is satisfied with the standard libraries for the system, but as we effectively ARE the system we can’t rely on that. We need to implement everything on our own. No problems though, it’s not that hard.
After some fiddling and reading of guides, mainly the C++ guide in the OSDev wiki, I managed to get my new C++ kernel up and running. These are the GCC options I use for compilation:
gcc -Wall -O -fstrength-reduce -finline-functions -nostdinc -fno-builtin -fno-rtti -fno-exceptions -I./include -c main.cpp
These options tell GCC to do some optimizations (not too much though because we need the code to be safe and robust). I also disable some standard libs, turn off the run time type information and exceptions. This makes my code lean and mean, just like a kernel should be.
I also had to define a couple of functions that GCC needs for special situations:
/* Required to support pure virtual functions */
extern "C" void __cxa_pure_virtual()
{
// This is called if a function could not be found
}
extern "C" void __gxx_personality_v0()
{
// Called after an exception occured
}
The first one is called if you try to call a pure virtual function and it has not been implemented. In practice this should never happen unless there is something wrong with the compiler. In other words, you will get a compile time error, not a runtime one. But I guess GCC must be safe and not trust code blindly.
The second function is a bit of a mystery. I have specified that I don’t want any exception handling, but GCC still wants a function for unwinding the stack when an exception is thrown. I won’t be using try/catch so this should never be called either.
start.asm
My kernel is booted from a small piece of assembler code that takes care of setting up some basic structures and enable paging. I will go through it later to keep this post from getting too long.
However, what I wanted to point out is that I updated the startup code so that it initializes all static constructors and destructors as explained in the C++ bare bones article.
Linking
The last part that was a bit tricky was the linking. I’m not an expert in LD linker scripts, but I managed to put it all together and it works now. For those of you interested in how it looks, here it is in full:
OUTPUT_FORMAT("binary")
virt = 0xC0100000;
phys = 0x00100000;
SECTIONS
{
.text virt : AT(phys) {
codevirt = .;
code = LOADADDR(.text);
*(.text)
*(.gnu.linkonce.t.*) /* C++ classes */
*(.rodata*)
*(.gnu.linkonce.r.*) /* Read only (not used?) */
. = ALIGN(4096);
}
.data : AT(phys + (datavirt - codevirt))
{
datavirt = .;
*(.data)
start_ctors = .;
*(.ctor*)
end_ctors = .;
start_dtors = .;
*(.dtor*)
end_dtors = .;
*(.eh_frame)
. = ALIGN(4096);
}
.bss : AT(phys + (bssvirt - codevirt))
{
bssvirt = .;
bss = LOADADDR(.bss);
*(.bss)
*(COMMON)
*(.gnu.linkonce.b.*)
/*. = ALIGN(4096);*/
}
endvirt = .;
end = LOADADDR(.bss) + (endvirt - bssvirt);
}
I will go through all this in a later post, when I explain my kernel memory layout.
Leave a Reply