Creating a 64-bit kernel: Difference between revisions

[unchecked revision][unchecked revision]
Content deleted Content added
m Remove redundant option
m Bot: Replace deprecated source tag with syntaxhighlight
 
(21 intermediate revisions by 15 users not shown)
Line 1:
{{Rating|32}}{{Template:Kernel designs}}
{{In_Progress}}
{{Disputed|Talk:Creating_a_64-bit_kernel#Disputed}}
 
== Prerequisites ==
Line 6 ⟶ 8:
 
* Have [[GCC_Cross-Compiler|built a cross-compiler]] for the x86_64-elf target.
* Read up on long mode and how to [[X86-64|initialize/use it]] (if you intend on not using a 64-bit aware bootloader or rolling your own).
* Decide now on how to load your kernel - your own bootloader, [[GRUB]] (with separate loader executable), or GRUB2 (elf64 + 32-bit bootstrap code), or a 64-bit capable bootloader such as [[Limine]] or [[BOOTBOOT]].
* Know what a [[Higher Half Kernel|higher-half kernel]] is and how it works.
* Decide now on how to load your kernel - your own bootloader, [[GRUB]] (with separate loader executable), or GRUB2 (elf64 + 32-bit bootstrap code).
 
== The Main Kernel ==
Line 14 ⟶ 15:
 
=== kernel.c ===
<sourcesyntaxhighlight lang="c">
void kernel_main(void)
{
/* What goes here is up to you */
}
</syntaxhighlight>
</source>
 
== Compiling ==
Line 25 ⟶ 26:
Linking will be done later...
 
<sourcesyntaxhighlight lang="bash">x86_64-elf-gcc \
x86_64-elf-gcc -ffreestanding -mcmodel=large -mno-red-zone -mno-mmx -mno-sse -mno-sse2 -c foo.c -o foo.o
-ffreestanding \
</syntaxhighlight>
-mcmodel=large \
-mno-red-zone \
-mno-mmx \
-mno-sse \
-mno-sse2 \
-mno-sse3 \
-mno-3dnow \
<other options> \
-c -o <object file> <source file></source>
 
The -mcmodel=large argument enables us to run the kernel at any 64-bit virtual memory address we want (only in GCC 4.3+). In fact, using the 'large' code model is discouraged due to its inefficiency, but it can be fine as a start. Check the [[System V ABI|SysV AMD64 ABI]] document for extra details.
 
You will need to instruct GCC not to use the the AMD64 ABI 128-byte 'red zone', which resides below the stack pointer, or your kernel will be ''interrupt unsafe''. Check this [http://forum.osdev.org/viewtopic.php?t=21720 thread] on the forums for extra context.
Line 44 ⟶ 37:
 
== Linking ==
The kernel will be linked as an 86_64x86_64 executable, to run at a virtual higher-half address. Let'sWe use a linker script...:
 
=== link.ld ===
Line 98 ⟶ 91:
Feel free to edit this linker script to suit your needs. Set ENTRY(...) to your entry function, and KERNEL_VMA to your base virtual address.
 
NowYou can link with the followingkernel like this:
 
<sourcesyntaxhighlight lang="bash">x86_64-elf-gcc -ffreestanding <other options> -T <linker script> <all object files> -o <kernel executable> <all object files> -nostdlib -lgcc</sourcesyntaxhighlight>
 
'''Note''': Obviously there is no bootstrap assembly yet, which is the hard part of starting out, and you can't link without it.
Congratulations! Your kernel has been compiled!
 
== Loading ==
Before you can actually use your kernel, you need to deal with the hard job of loading it. Here are your threefour options:
 
=== With your own boot loader ===
Line 118 ⟶ 111:
* Enter Long Mode by far jump to the kernel entry point in (virtual) memory
 
=== With a separate64 bit aware loader ===
Open Source boot loaders written for long mode kernels already exist, you don't have to reinvent the wheel. Unlike GRUB (which does not support switching to long mode), bootloaders such as [[Limine]] or [[BOOTBOOT]] can load your 64-bit kernel directly by doing all the things listed in the previous section (and more). It saves you the struggle to write and properly link bootstrap code or to implement your own boot loader entirely from scratch. Therefore using a 64-bit aware bootloader is a nice and easy, reasonably bullet-proof, choice for beginners.
This requires the use of [[GRUB]] or another multiboot1-compliant loader. This may be the most error free of the three.
 
=== With legacy GRUB ===
'''Note''': The advise in this section is bit questionable in its current form.
See [[Creating_a_64-bit_kernel_using_a_separate_loader|Creating a 64-bit kernel using a separate loader]]
 
This requires the use of [[GRUB]] or another multiboot1-compliant loader. This may be the most error free of the threefour, but creating a multiboot-compatible kernel properly has its own set of pitfalls.
 
A quick rundown:
Line 128 ⟶ 127:
* Enter Long Mode by far jump to the kernel entry point
 
Note that this code has to be compiledstored asin a elf32 format and must contain the multiboot1-header. Either compile with
i*86-elf-gcc
or
x86_64-pc-elf-gcc -m32
 
Also remember to set the text section to start at 0x100000 (-Ttext 0x100000) when linking your loader.
Line 141 ⟶ 137:
 
=== With a 32-bit bootstrap in your kernel ===
This requires the use of any ELF64-compatible loader that loads into protected-mode (GRUB2, or patched GRUB Legacy). This may be the simplest in the long run, but is hell to set up (well, it was for me - but I saved you some work ;).
 
This requires the use of any ELF64-compatible loader that loads into protected-mode (GRUB2, or patched GRUB Legacy). This may be the simplest in the long run, but is hellmore difficult to set up (well, it was for me - but I saved you some work ;).
'''Note that GRUB2, which implements [http://download.savannah.gnu.org/releases-noredirect/grub/phcoder/multiboot.pdf Multiboot 2], does not yet support switching into long mode. Check the specification every now and then.'''
 
First, create an assembly file like the following, which will set up virtual addressing and long mode:
Line 191 ⟶ 187:
multiboot <kernel executable>
}
 
==== With Visual C++ ====
The technique for creating a 64 bit kernel with a 32 bit bootstrap is similar to GCC. You need to create an assembly bootstrap with nasm (masm may work, but the author uses nasm).
Note that this stub '''must''' be assembled to a ''64'' bit object file (-f win64). Your stub then has a BITS 32 directive.
Note that, although nasm will not complain about this, Microsoft link will. It complains about address relocations, due to the memory model settings (/LARGEADDRESSAWARE, which is required for /DRIVER).
As such, you need a method of generating the correct 32 bit code, while fooling link into generating a 64 bit relocation. Here is a macro for you:
<syntaxhighlight lang='asm'>
;Encode 32 bit moves without the ADDR32 issue
%macro mov_abs32 2
%if %1==eax
db 0xB8
%elif %1==ebx
db 0xBB
%elif %1==ecx
db 0xB9
%elif %1==edx
db 0xBA
%elif %1==edi
db 0xBF
%elif %1==esi
db 0xBE
%elif %1==ebp
db 0xBD
%elif %1==esp
db 0xBC
%else
%error "Unknown register"
%endif
dq %2+(0x90909090 << 32)
%endmacro
;This translates as mov %1, %2 NOP NOP NOP NOP
;Example usage:
;mov_abs32 eax, (gdtr-KADDR_OFFSET)
;lgdt [eax]
</syntaxhighlight>
 
== Possible Problems ==
You may experience some problems. Fix them '''immediately''' or risk spending a lot of time debugging later...
 
=== My kernel is way too big!!! ===
Try each of the following, in order:
* Make sure you're compiling with the -nostdlib options
* You can try changing the OUTPUT_FORMAT to elf64-little
* Try cross-compiling the '''latest''' version of binutils and gcc
* Try linking your kernel with the option "-z max-page-size=0x1000" to force the linker to use 4kb pages.
* Make sure you're compiling with the -nostdlib option (equivalent to passing the both -nodefaultlibs and -nostartfiles options).
* You can try changing the OUTPUT_FORMAT to elf64-little.
* Try cross-compiling the '''latest''' version of binutils and gcc.
 
== Kernel Virtual Memory ==
Line 209 ⟶ 240:
Another approach is to treat the kernel address space as any other address space and dynamically map its regions. This provides the advantage of simplifying the page allocator by avoiding the need of physical memory 'zones': all physical RAM is available for any part of the kernel. An example of this approach is mapping the kernel to 0xfffffff800000000 as usual. Below that virtual address you put a large mapping for the entire physical address space, and use the virtual 0xfffffff800000000 -> 0xffffffffffffffff region above kernel memory area as a temporary mappings space.
 
== See Also ==
=== Articles ===
* [[X86-64]]
===Forum Threads ===
* [http://forum.osdev.org/viewtopic.php?f=8&t=16779 Creating a 64-bit Kernel Tutorial] about this article
* [http://forum.osdev.org/viewtopic.php?p=170634 Linker-script writers beware: COMMON Symbols] on the obscure 'COMMON' symbols and their effect on BSS
* [http://forum.osdev.org/viewtopic.php?t=21720 Long-mode Kernels and the AMD64 ABI 'Red Zone'] on the 'red zone' and its major effect on interrupt handling
 
====Switching to earlier modes====
* [http://forum.osdev.org/viewtopic.php?f=1&p=136701 Leaving long mode] to protected mode
* [http://forum.osdev.org/viewtopic.php?f=1&t=17213 Switching from long mode to compatibility mode]
Line 223 ⟶ 252:
[[Category:Tutorials|Creating a 64-bit kernel]]
[[Category:X86-64|Creating a 64-bit kernel]]
[[Category:Kernel]]