C++ Bare Bones: Difference between revisions
[unchecked revision] | [unchecked revision] |
Added source tags |
mNo edit summary |
||
Line 26:
|}
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
The vague linking sections should be split
<pre>
|
Revision as of 16:42, 6 September 2010
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) : { *(.rodata*) *(.gnu.linkonce.r*) } .data ALIGN(4096) : { start_ctors = .; *(.ctor*) end_ctors = .; start_dtors = .; *(.dtor*) end_dtors = .; *(.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.
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 main 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 main ; 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 main # 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
C/C++
extern start_ctors, end_ctors, start_dtors, end_dtors;
void loader(void)
{
//- call all the static constructors in the list.
for(unsigned long *constructor(&start_ctors); constructor < &end_ctors; ++constructor)
((void (*) (void)) (*constructor)) ();
//- call kernel proper
main();
//- call all the static destructors in the list.
for(unsigned long *destructor(&start_dtors); destructor < &end_dtors; ++destructor)
((void (*) (void)) (*destructor)) ();
}
Entry.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 main(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 "main" (or something like "_main" if your linker requires you to add a leading underscore).
Compiler Options
The options for g++ are slightly different than when building a C kernel.
g++ -o main.o -c kernel.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.