C++ Bare Bones: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
(Synced with Bare Bones.)
 
(29 intermediate revisions by 11 users not shown)
Line 1: Line 1:
#REDIRECT [[Bare Bones]]
{{Rating|2}}
:''In this tutorial we will compile a simple C++ kernel and boot it. The theory can be found in [[C++]].''


{{Redirect page}}
<big><b>WAIT! Have you read [[Getting Started]], [[Beginner Mistakes]], and some of the related [[:Category:OS theory|OS theory]]?</b></big>

==Preface==

This tutorial assumes you have a compiler / assembler / linker toolchain capable of handling [[ELF]] files. On a Windows machine, you are ''strongly'' encouraged to set up a [[GCC Cross-Compiler]], as it removes all the various toolchain-specific issues you might have ("PE operation on a non-PE file", "unsupported file format", and a number of others). While technically a Linux machine already has an ELF-capable toolchain, you are ''still'' encouraged to build a cross-compiler, as it is the first step to do, and it keeps you from relying on things you shouldn't (header files, for example).

To make starting an OS easy, we will be using a lot of existing parts, GRUB will be the bootloader, and the kernel will be in ELF format. GRUB (a Multiboot compliant boot loader) puts the system in to the correct state for your kernel to start executing. This includes enabling the A20 line (to give you access to all available memory addresses) and putting the system in to 32-bit Protected Mode, giving you access to a theoretical 4GiB of memory. We will not use a flat binary but a kernel in ELF format, so that we have a lot of control to tell GRUB where to load which part in memory.

==Overview==

Even when using [[GRUB]], some setup is required before entering a <tt>main()</tt> type function. The most basic setup to get an [[ELF]] format kernel to be booted by GRUB consists of three files:
* loader.s - assembler "glue" between bootloader and kernel
* kernel.c - your actual kernel routines
* linker.ld - for linking the above files

==C++ specifics==

loader.s takes over over control from the Multiboot bootloader, and jumps into the kernel proper.

Using C++ requires you to take a few extra sections into account when compared to the [[Bare Bones | C variant]] of this tutorial:

{| {{wikitable}}
|-
! 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 usefulness is open for discussion)
|-
! gnu.linkonce
| GCC vague linkages
| Sections dedicated for GCC's vague linking (see [http://gcc.gnu.org/onlinedocs/gcc/Vague-Linkage.html#Vague-Linkage 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. You will need a linker script only later on, but to understand the other source, have a look at it now:

<pre>
ENTRY (loader)

SECTIONS
{
. = 0x00100000;

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

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

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

*(.rodata*)
*(.gnu.linkonce.r*)
}

.data ALIGN (0x1000) :
{
*(.data)
*(.gnu.linkonce.d*)
}

.bss : {
sbss = .;
*(COMMON)
*(.bss)
*(.gnu.linkonce.b*)
ebss = .;
}

/DISCARD/ :
{
*(.comment)
*(.eh_frame) /* discard this, unless you are implementing runtime support for C++ exceptions. */
}
}
</pre>

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.

Note that the ctor and dtor sections need to be properly aligned. The linker script shown here does that by placing them at the beginning of the .rodata section, which is aligned at a page boundary.

==loader.s==

loader.s takes over control from the Multiboot bootloader, calls the constructors, and jumps into the kernel proper.

===NASM===
<source lang="asm">
global loader ; making entry point visible to linker

extern kmain ; kmain is defined in kmain.cpp

extern start_ctors ; beginning and end
extern end_ctors ; of the respective
extern start_dtors ; ctors and dtors section,
extern end_dtors ; declared by the linker script

; setting up the Multiboot header - see GRUB docs for details
MODULEALIGN equ 1<<0 ; align loaded modules on page boundaries
MEMINFO equ 1<<1 ; provide memory map
FLAGS equ MODULEALIGN | MEMINFO ; this is the Multiboot 'flag' field
MAGIC equ 0x1BADB002 ; 'magic number' lets bootloader find the header
CHECKSUM equ -(MAGIC + FLAGS) ; checksum required

section .text

align 4
dd MAGIC
dd FLAGS
dd CHECKSUM

; reserve initial kernel stack space
STACKSIZE equ 0x4000 ; that's 16k.

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

mov ebx, start_ctors ; call the constructors
jmp .ctors_until_end
.call_constructor:
call [ebx]
add ebx,4
.ctors_until_end:
cmp ebx, end_ctors
jb .call_constructor

call kmain ; call kernel proper

mov ebx, end_dtors ; call the destructors
jmp .test
.call_destructor:
sub ebx, 4
call [ebx]
.dtors_until_end:
cmp ebx, start_dtors
jb .call_destructor

cli
.hang:
hlt ; halt machine should kernel return
jmp .hang

section .bss

align 4
stack:
resb STACKSIZE ; reserve 16k stack on a doubleword boundary
</source>

Assemble using:
<source lang="bash">nasm -f elf -o loader.o loader.s</source>

===GAS===
<source lang="asm">
.global loader # making entry point visible to linker

# setting up the Multiboot header - see GRUB docs for details
.set ALIGN, 1<<0 # align loaded modules on page boundaries
.set MEMINFO, 1<<1 # provide memory map
.set FLAGS, ALIGN | MEMINFO # this is the Multiboot 'flag' field
.set MAGIC, 0x1BADB002 # 'magic number' lets bootloader find the header
.set CHECKSUM, -(MAGIC + FLAGS) # checksum required

.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM

# reserve initial kernel stack space
.set STACKSIZE, 0x4000 # that is, 16k.
.comm stack, STACKSIZE, 32 # reserve 16k stack on a quadword boundary

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

mov $start_ctors, %ebx # call the constructors
jmp 2f
1:
call *(%ebx)
add $4, %ebx
2:
cmp $end_ctors, %ebx
jb 1b

call kmain # call kernel proper

mov $end_dtors, %ebx # call the destructors
jmp 4f
3:
sub $4, %ebx
call *(%ebx)
4:
cmp $start_dtors, %ebx
jb 3b

cli
hang:
hlt # halt machine should kernel return
jmp hang
</source>

Assemble using:
<source lang="bash">as -o loader.o loader.s</source>

==kmain.cpp==

This is not exactly your average <tt>int main()</tt> (which is one of the reasons why you should not call it like that). Most notably, you do not have any library stuff available. As soon as you write so much as <tt>#include &lt;</tt>, you have probably made the first mistake. Welcome to kernel land.

You also need 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.

<source lang="c">
extern "C" void kmain( void* mbd, unsigned int magic )
{
if ( magic != 0x2BADB002 )
{
/* Something went not according to specs. Print an error */
/* message and halt, but do *not* rely on the multiboot */
/* data structure. */
}

/* You could either use multiboot.h */
/* (http://www.gnu.org/software/grub/manual/multiboot/multiboot.html#multiboot_002eh) */
/* or do your offsets yourself. The following is merely an example. */
//char * boot_loader_name =(char*) ((long*)mbd)[16];

/* Print a letter to screen to see everything is working: */
unsigned char *videoram = (unsigned char *) 0xb8000;
videoram[0] = 65; /* character 'A' */
videoram[1] = 0x07; /* light grey (7) on black (0). */
}
</source>

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

<source lang="bash">i586-elf-g++ -o kmain.o -c kmain.cpp -Wall -Wextra -Werror \
-nostdlib -fno-builtin -nostartfiles -nodefaultlibs \
-fno-exceptions -fno-rtti -fno-stack-protector
</source>

Note: the flags -Wall -Wextra -Werror are not exactly required, but using them will certainly help you later on. They might seem to be a pest, but remember: The compiler is your friend!

Note #2: You may be able to use gcc instead of the i586-elf-gcc from the cross-compiler, but this does not work out of the box on at least Windows and 64-bit linux.

Note #3: The reasons for the disabling of exceptions, RTTI and the stack protector 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 [[GCC_Stack_Smashing_Protector|stack smashing protector]] if you want to enable --stack-protector.

==linker.ld==

The contents of this file were already presented above.

Link using:
<source lang="bash">i586-elf-ld -T linker.ld -o kernel.bin loader.o kmain.o</source>
Note: Again, using ld instead of i585-elf-ld may occasionally work, but in most configurations you get an error or an unbootable image.

The file kernel.bin is now your kernel (all other files are no longer needed).

Refer to [[Bare Bones#Booting the kernel]] for further instructions.

==Questions==
;Does anyone know if those '''.ctor'''s and '''.dtor'''s 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 '''ctor'''s?
: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===
*[[C++]]
*[[Bare Bones]]

[[Category:Bare bones tutorials]]
[[Category:C++]]
[[de:C++-Kernel_mit_GRUB]]

Latest revision as of 09:33, 22 June 2024

Redirect to:

This page is a redirect.
This redirect is categorized in following manner: