Created
February 21, 2016 10:37
-
-
Save zesterer/f46a2a4121bdcc443959 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// We're writing this code in 32-bit protected mode for now | |
.code32 | |
// Declare multiboot header creation constants | |
.set ALIGN, 1 << 0 // Align loaded modules on page boundaries | |
.set MEMINFO, 1 << 1 // Provide memory map | |
.set FLAGS, ALIGN | MEMINFO // Multiboot 'flag' field | |
.set MAGIC, 0x1BADB002 // Magic number helps bootloader find the header | |
.set CHECKSUM, -(MAGIC + FLAGS) // Checksum of the above to prove it's multiboot | |
// Declare some things related to memory page tables (long (64-bit) mode setup) | |
.set PAGE_TABLE_BASE, 0x8000 | |
// Declare a header as in the Multiboot Standard. We put this into a special | |
// section so we can force the header to be in the start of the final program. | |
// You don't need to understand all these details as it is just magic values that | |
// is documented in the multiboot standard. The bootloader will search for this | |
// magic sequence and recognize us as a multiboot kernel. | |
.section .multiboot | |
.align 4 | |
.long MAGIC | |
.long FLAGS | |
.long CHECKSUM | |
// Currently the stack pointer register (esp) points at anything and using it may | |
// cause massive harm. Instead, we'll provide our own stack. We will allocate | |
// room for a small temporary stack by creating a symbol at the bottom of it, | |
// then allocating 16384 bytes for it, and finally creating a symbol at the top. | |
// This stack is being allocated a small region of 16 kilobytes. Once we have a | |
// C environment running and we've entered long (64-bit) mode, we can define a | |
// proper stack for the kernel. | |
//NOTE: On the x86 architecture, the stack grows DOWNWARD. | |
// The @nobits directive specifies that this region should contain empty space only | |
.section .bootstrap_stack, "aw", @nobits | |
stack_bottom: //Add a label for the bottom of the stack | |
.skip 16384 //Define 16 KiB of memory | |
stack_top: //Add a label for the top of the stack | |
// The linker script specifies _bootstrap as the entry point to the kernel and the | |
// bootloader will jump to this position once the kernel has been loaded. It | |
// doesn't make sense to return from this function as the bootloader is gone. | |
.section .text | |
.global _bootstrap | |
.type _bootstrap, @function | |
// The 32-bit bootstrap section | |
_bootstrap: | |
// Set up long (64-bit) mode | |
jmp _enter_long_mode | |
// Set the size of the _ symbol to the current location '.' minus its start. | |
// This is useful when debugging or when you implement call tracing. | |
.size _bootstrap, . - _bootstrap | |
// Code to enter long (64-bit) mode. We want to do this ASAP within the kernel. No | |
// point in hanging around in 32-bit protected mode. | |
_enter_long_mode: | |
// Zero out 3 pages of RAM ready to create 3 page tables | |
xor %eax, %eax // Set the accumulator to zero | |
mov $PAGE_TABLE_BASE, %edi // Start at the page table base | |
mov $0x3000, %ecx // The size of the 3 page table (i.e: 3 pages) | |
rep stosb // Repeatedly clear areas of memory until we've cleared those 3 pages | |
// Link everything to the top-level page table | |
mov $((PAGE_TABLE_BASE + 0x1000) | 0x3), %eax // Place a constant corresponding with the location of the second page table into the accumulator | |
mov %eax, (PAGE_TABLE_BASE + 0x1000) // Move this constant into the top-level page table so it knows where the next one is | |
mov $((PAGE_TABLE_BASE + 0x2000) | 0x3), %eax // Ditto, but for the second page table | |
mov %eax, (PAGE_TABLE_BASE + 0x1000) // Ditto, but for the third page table | |
// Define a set of 2MB pages to identify how the first 16 MB of RAM is mapped | |
mov $(0 | (1 << 7) | 0x3), %eax // Constant evaluates to 131. Start of paged memory perhaps? | |
mov %eax, (PAGE_TABLE_BASE + 0x2000) // Map the start of the paged memory into the lowest-level page table | |
add $0x200000, %eax // Increment the page location according to the one we just mapped in the line above | |
mov %eax, (PAGE_TABLE_BASE + 0x2008) // Same as above, but for the next page | |
add $0x200000, %eax // Same as above, but for the next page | |
mov %eax, (PAGE_TABLE_BASE + 0x2010) // Same as above, but for the next page | |
add $0x200000, %eax // Same as above, but for the next page | |
mov %eax, (PAGE_TABLE_BASE + 0x2018) // Same as above, but for the next page | |
add $0x200000, %eax // Same as above, but for the next page | |
mov %eax, (PAGE_TABLE_BASE + 0x2020) // Same as above, but for the next page | |
add $0x200000, %eax // Same as above, but for the next page | |
mov %eax, (PAGE_TABLE_BASE + 0x2028) // Same as above, but for the next page | |
add $0x200000, %eax // Same as above, but for the next page | |
mov %eax, (PAGE_TABLE_BASE + 0x2030) // Same as above, but for the next page | |
add $0x200000, %eax // Same as above, but for the next page | |
mov %eax, (PAGE_TABLE_BASE + 0x2038) // Same as above, but for the next page | |
add $0x200000, %eax // Same as above, but for the next page - technically this line isn't needed | |
// Tell the CPU about the page table by placing the address into the control register | |
mov $PAGE_TABLE_BASE, %eax | |
mov %eax, %cr3 | |
// Actually load the long (64-bit) mode GDT (Global Descriptor Table) | |
lgdt gdt64 | |
// Set CR4.PAE (Tell the CPU that we're going int PAE memory mode with only 3 page table levels) | |
mov %cr4, %eax // Move the value of the control register into the accumulator | |
bts $5, %eax // Set the fifth bit in the accumulator register to 1 | |
mov %eax, %cr4 // Move the value of the accumulator back into the control register | |
// Enable long mode finally! | |
// EFER.LME=1 | |
mov $0xc0000080, %ecx | |
rdmsr | |
bts $8, %eax | |
wrmsr | |
// Enable paging (But really enable long mode (CR0.PG)) | |
mov %cr0, %eax | |
bts $31, %eax | |
mov %eax, %cr0 | |
// Jump to the long (64-bit) mode code | |
ljmp $0x08, $(_long_mode_start) | |
// Long (64-bit) mode code from now on! | |
.code64 | |
// The long (64-bit) mode bootstrap | |
_long_mode_start: | |
// Welcome to kernel mode! We now have sufficient code for the bootloader to | |
// load and run the operating system. It doesn't do anything interesting yet. | |
// Perhaps we would like to call printf("Hello, World\n"). You should now | |
// realize one of the profound truths about kernel mode: There is nothing | |
// there unless you provide it yourself. There is no printf function. There | |
// is no <stdio.h> header. If you want a function, you will have to code it | |
// yourself. And that is one of the best things about kernel development: | |
// you get to make the entire system yourself. You have absolute and complete | |
// power over the machine, there are no security restrictions, no safe | |
// guards, no debugging mechanisms, there is nothing but what you build. | |
// By now, you are perhaps tired of assembly language. You realize some | |
// things simply cannot be done in C, such as making the multiboot header in | |
// the right section and setting up the stack. However, you would like to | |
// write the operating system in a higher level language, such as C or C++. | |
// To that end, the next task is preparing the processor for execution of | |
// such code. C doesn't expect much at this point and we only need to set up | |
// a stack. Note that the processor is not fully initialized yet and stuff | |
// such as floating point instructions are not available yet. | |
// To set up a stack, we simply set the esp register to point to the top of | |
// our stack (as it grows downwards). | |
movl $stack_top, %esp // Set the stack pointer to the top of the temporary stack | |
// We are now ready to actually execute C code. We cannot embed that in an | |
// assembly file, so we'll create a kernel.c file in a moment. In that file, | |
// we'll create a C entry point called kernelMain and call it here. | |
call kernelStart // Enter the kernelStart function (C) | |
// In case the function returns, we'll want to put the computer into an | |
// infinite loop. To do that, we use the clear interrupt ('cli') instruction | |
// to disable interrupts, the halt instruction ('hlt') to stop the CPU until | |
// the next interrupt arrives, and jumping to the halt instruction if it ever | |
// continues execution, just to be safe. We will create a local label rather | |
// than real symbol and jump to there endlessly. | |
cli // Disable interrupts | |
hlt // Halt the CPU | |
.Lhang: //A hang procedure just in case the CPU ever wakes up for some reason | |
jmp .Lhang //Jump back into the procedure (i.e: an infinite loop) | |
// Set the size of the _start symbol to the current location '.' minus its start. | |
// This is useful when debugging or when you implement call tracing. | |
.size _bootstrap, . - _bootstrap | |
// The GDT (Global Descriptor Table) | |
.align 4 | |
gdt64: | |
// First entry, also GDT descriptor | |
.word 0xffff | |
.long gdt64 | |
.word 0 | |
// Entry 64-bit code segment | |
.long 0x00000000 // Base & limit are ignored | |
.long 0x00af9a00 // Type: 64-bit, code, <present>, priviledge 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment