Creating a 64-bit kernel: Difference between revisions

m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
(→‎Compiling: Do not use SSE ops, or several #UD and #NM exceptions will get triggered)
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(27 intermediate revisions by 18 users not shown)
Line 1:
{{Rating|32}}{{Template:Kernel designs}}
{{In_Progress}}
{{Disputed|Talk:Creating_a_64-bit_kernel#Disputed}}
 
== Prerequisites ==
Line 5 ⟶ 7:
Make sure that you have the following done before proceeding:
 
* Have [[GCC_Cross-Compiler|built a cross-compiler]] for the x86_64-elf target.
* Do you have an [[GCC_Cross-Compiler_for_x86_64|x86-64 cross-compiler?]]. Preferably use GCC 4.3 or newer (Only in case of building your kernel with the AMD64 ABI 'large' code model. If you're building with the recommended -mcmodel=kernel instead, any gcc that supports 64-bit will do, including GCC versions shipped with the amd64 Linux and BSD distributions)
* 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 ==
The kernel should run in a uniform environment. Let's make this simple for now...
 
=== kmainkernel.c ===
<sourcesyntaxhighlight lang="c">
void kmainkernel_main(void)
{
/* What goes here is up to you */
}
</syntaxhighlight>
</source>
 
== Compiling ==
Line 25 ⟶ 26:
Linking will be done later...
 
<sourcesyntaxhighlight lang="bash">gcc \
x86_64-elf-gcc -ffreestanding -mcmodel=large -mno-red-zone -mno-mmx -mno-sse -mno-sse2 -c foo.c -o foo.o
-m64 \
</syntaxhighlight>
-ffreestanding \
-nostdlib \
-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+), and -nostdlib makes sure that GCC doesn't add any userspace-dependent code to our kernel. 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 46 ⟶ 37:
 
== Linking ==
The kernel will be linked as an elf64-x86-64x86_64 executable, to run at a virtual higher-half address. Let'sWe use a linker script...:
 
=== link.ld ===
ENTRY(kmain_start)
OUTPUT_FORMAT(elf64-x86-64)
ENTRY(kmain)
SECTIONS
{
Line 70 ⟶ 60:
}
.ehframeeh_frame : AT(ADDR(.ehframeeh_frame) - KERNEL_VMA)
{
_ehframe = .;
*(.ehframeeh_frame)
. = ALIGN(4096);
}
Line 101 ⟶ 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-pc-elf-ldgcc -nostdlib -nodefaultlibsffreestanding <other options> -T <linker script> <all object files> -o <kernel executable> <all-nostdlib object files>-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 121 ⟶ 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 131 ⟶ 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 144 ⟶ 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 is still in beta and unreliably implements the [http://grub.enbug.org/MultibootDraft multiboot draft] or the original multiboot specification. Highly recommended that you use the latest CVS version.'''
Note that GRUB2, which implements [http://download.savannah.gnu.org/releases-noredirect/grub/phcoder/multiboot.pdf Multiboot 2], does not support switching into long mode.
 
First, create an assembly file like the following, which will set up virtual addressing and long mode:
Line 194 ⟶ 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 -nodefaultlibs 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 212 ⟶ 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
* [http://forum.osdev.org/viewtopic.php?f=1&p=136701 Leaving long mode] to protected mode
* [http://forum.osdev.org/viewtopic.php?p=170634 Linker-script writers beware]: COMMON Symbols
* [http://forum.osdev.org/viewtopic.php?f=1&t=17213 Switching from long mode to compatibility mode]
 
[[Category:Tutorials|Creating a 64-bit kernel]]
[[Category:X86-64|Creating a 64-bit kernel]]
[[Category:Kernel]]