Skip to content

Instantly share code, notes, and snippets.

@zesterer
Created February 21, 2016 10:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zesterer/f46a2a4121bdcc443959 to your computer and use it in GitHub Desktop.
Save zesterer/f46a2a4121bdcc443959 to your computer and use it in GitHub Desktop.
// 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