C++ Bare Bones

From OSDev.wiki
Revision as of 07:28, 13 October 2011 by Solar (talk | contribs) (Cleaned up some inconsistency on main() / kmain() / Entry.cpp / kernel.cpp. Removed C/C++ loader code - with no stack set up, calling C code isn't that smart. See discussion.)
Jump to navigation Jump to search
Difficulty level

Medium
This tutorial sets up the most basic bootable C++ kernel. The theory can be found in C++.

If you have a GRUB-booting C kernel (please read and follow through that document first!), extending it to C++ isn't that difficult. All that is needed are a couple of lines to cater for C++ specifics.

Linker Script

Using C++ requires you to take a few extra sections into account:

Section Name Description
ctor C++ static/global constructors Constructors for static objects used in C++ (must be explicitely called, see below)
dtor C++ static/global destructors Destructors for static objects used in C++ (ditto, though the use is open for discussion)
gnu.linkonce GCC vague linkages Sections dedicated for GCC's vague linking (see the documentation for more information)

Because there is no environment executing your kernel (you can't expect the bootloader to do this), you have to execute your own constructors (and possibly destructors). Both are described below. There is however discussion about whether executing the global destructors in your kernel makes any sense when you're shutting down (seeing as you're shutting the computer or OS down). It would probably be best to call these destructors only if you have specific object destructors that need to clean something up before the kernel is shut down (and you can't or don't want to explicitly call these). Note also that you should call the destructors in reverse order! This to make sure objects depending on other objects to exist may result in problems if not done correctly.

The vague linking sections should be split across multiple sections: you should put them in text, rodata, data and bss. Here is an example linker script:

SECTIONS
{
	. = 0x100000;

	.text ALIGN(4096) :
	{
		*(.text*)
		*(.gnu.linkonce.t*)
	}

	.rodata ALIGN(4096) :
	{
		start_ctors = .;
		*(.ctor*)
		end_ctors = .;

		start_dtors = .;
		*(.dtor*)
		end_dtors = .;

		*(.rodata*)
		*(.gnu.linkonce.r*)
	}
	
	.data ALIGN(4096) :
	{
		*(.data*)
		*(.gnu.linkonce.d*)
	}

	.bss ALIGN(4096) :
	{
		*(.COMMON*)
		*(.bss*)
		*(.gnu.linkonce.b*)
	}
	
	 /DISCARD/ :
	 {
		*(.comment)
		*(.eh_frame) /* You should discard this unless you're implementing runtime support for C++ exceptions. */
	 }
}

The script might require modification to suit your kernel's needs. Note that forgetting to add the vague linking sections might result in GRUB randomly not being able to load your kernel anymore (e.g. after modifying the most trivial code). It might also explain sudden rises in executable size by 50 kB and other issues.

Using C++ adds two more sections of interest to your kernel binaries: The constructors and destructors of static objects. You have to modify the linker script to include them. Note that GCC aligns them on a 4 byte (or 8 byte if your kernel will run on a 64 bit architecture) boundary, so make sure that the labels that mark the beginning and end of the constructor / destructor lists are aligned as well. In the example script above, this is done by placing them at the beginning of the .rodata section, which is aligned at a page boundary.

Calling Constructors & Destructors

The constructors have to be executed as quickly as possible after booting your kernel (just make sure you execute them before you actually use any of the objects that require them). Destructors should preferably be called somewhere before your kernel shuts the computer down or simply returns from the kmain function.

Below you'll find a couple of examples on how to do this.

NASM

extern start_ctors, end_ctors, start_dtors, end_dtors

loader:
   ; Possibly set up a stack here: mov esp, stack + STACKSIZE
   push eax                        ; Multiboot magic number
   push ebx                        ; Multiboot info structure

static_ctors_loop:
   mov ebx, start_ctors
   jmp .test

.body:
   call [ebx]
   add ebx,4

.test:
   cmp ebx, end_ctors
   jb .body

   call kmain                      ; call kernel proper

static_dtors_loop:
   mov ebx, start_dtors
   jmp .test

.body:
   call [ebx]
   add ebx,4

.test:
   cmp ebx, end_dtors
   jb .body

   hlt                             ; halt machine should kernel return

GAS

loader:
   # Possibly set up a stack here: mov  $(stack + STACKSIZE), %esp
   push %eax                       # Multiboot magic number
   push %ebx                       # Multiboot data structure

   # calling static constructors
   mov  $start_ctors, %ebx
   jmp  2f

1:
   call *(%ebx)
   add  $4, %ebx

2:
   cmp  $end_ctors, %ebx
   jb   1b

   call kmain                     # call kernel proper

   # calling static destructors
   mov  $start_dtors, %ebx
   jmp  4f

3:
   call *(%ebx)
   add  $4, %ebx

4:
   cmp  $end_dtors, %ebx
   jb   3b

   hlt                             # halt machine should kernel return

kmain.cpp

Now, all that is needed is to declare C style linkage for the kernel entry function, so that its name will not get mangled to C++ linkage style and you can call it from your multiboot header Assembly file:

extern "C" void kmain(struct mb_header *header, unsigned magic)
{
   // write your kernel here
}

Notes

In this code loader is the entry point to the binary. Make sure that your linker script has something like

ENTRY(loader)

loader should in its turn call "kmain".

Compiler Options

The options for g++ are slightly different than when building a C kernel.

g++ -o kmain.o -c kmain.cpp -Wall -Wextra -Werror -nostdlib -fno-builtin -nostartfiles -nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector

The reasons why most of these are disabled is because they usually require runtime support (which you in a basic kernel don't have). For a more thorough explanation, see the C++ article. There is also an article about the stack smashing protector if you want to enable --stack-protector.

Questions

Does anyone know if those .ctors and .dtors are specific to some compiler or if there's an ABI stating them?
It's defined in the ELF ABI for System V platforms, but it's used by most Unices. The concept of using a constructor/destructor list for bootup is not so much specified by any C++ ABI, but it is used in most implementations.
Does anyone know how to control the order of the static ctors?
It is assumed that the compiler puts them into the correct order. However, it is probably best to keep inter-dependencies at an absolute minimum.

See Also

Articles