aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2025-07-21 12:13:10 +0000
committerFelix Morgner <felix.morgner@ost.ch>2025-07-22 20:18:04 +0000
commiteb22cdcad4c27527a63a6e457e80c752f76821c6 (patch)
treece85cbfb81372d725f3f51bd056b2ce9eb320508
parent2e58f85807427d121fe1de80089e602f1f35944b (diff)
downloadteachos-eb22cdcad4c27527a63a6e457e80c752f76821c6.tar.xz
teachos-eb22cdcad4c27527a63a6e457e80c752f76821c6.zip
x86_64: clean up bootstrap code
-rw-r--r--.vscode/settings.json4
-rw-r--r--arch/x86_64/src/boot/boot.s512
2 files changed, 269 insertions, 247 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index e8e872e..0d62c95 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -14,5 +14,9 @@
"[cpp]": {
"editor.formatOnSave": true,
"editor.tabSize": 2,
+ },
+
+ "[gas]": {
+ "editor.rulers": [80]
}
} \ No newline at end of file
diff --git a/arch/x86_64/src/boot/boot.s b/arch/x86_64/src/boot/boot.s
index f3d9585..7a46795 100644
--- a/arch/x86_64/src/boot/boot.s
+++ b/arch/x86_64/src/boot/boot.s
@@ -1,50 +1,22 @@
-.extern _end_physical
-.extern _init
-.extern kernel_main
-
-
/**
- * Uninitialized data for the bootstrapping process.
+ * @brief Uninitialized data for the bootstrapping process.
*/
.section .boot_bss, "aw", @nobits
/**
- * Reserve some space for the Multiboot 2 information pointer.
+ * @brief Storage for the multiboot2 information pointer.
*/
.global multiboot_information_pointer
multiboot_information_pointer: .skip 8
-/**
- * Align page maps to 4 KiB or the assembler code, will cause crashes when attempting to enable paging.
- */
.align 4096
-/**
- * Reserve space for the page maps we are going to use during startup.
- *
- * Note: We are going to use large pages to make the initial mapping code
- * simpler.
- *
- * We need:
- * - A single PML 4 (since we will only use 4-level paging)
- * - 1 PML 3
- * - 1 PML 2
- */
-
-.global page_map_level_4
page_map_level_4: .skip 512 * 8
-
-.global page_map_level_3
page_map_level_3: .skip 512 * 8
-
-.global page_map_level_2
page_map_level_2: .skip 512 * 8
/**
- * Stack space for the bootstrapping process.
- *
- * Note: We are going to reserve 1 MiB for now. If/when the kernel requires
- * more space to run, it will have to relocate the stack.
+ * @brief Storage for the bootstrap stack.
*/
.section .boot_stack, "aw", @nobits
.align 16
@@ -53,210 +25,240 @@ stack_bottom: .skip 1 << 20
stack_top:
/**
- * Constants for the bootstrapping process.
+ * @brief Constants for the bootstrapping process.
*/
.section .boot_rodata, "a", @progbits
/**
- * A valid Global Descriptor Table is still required in long mode.
- *
- * Bit 41: "A" in the access byte => mark the segment as accessed.
- * Bit 41: "RW" in the access byte => mark the segment as readable (code) or writable (data).
- * Bit 43: "E" in the access byte => mark the segment as executable.
- * Bit 44: "S" in the access byte => mark the segment as code or data.
- * Bit 47: "P" in the access byte => mark the segment as being present.
- * Bit 53: "L" in the flags byte => mark the segment as being for long mode
+ * @brief A basic GDT for long mode.
*/
-
global_descriptor_table:
+.set GDT_ACCESSED, 40
+.set GDT_READ_WRITE, 41
+.set GDT_EXECUTABLE, 43
+.set GDT_DESCRIPTOR_TYPE, 44
+.set GDT_PRESENT, 47
+.set GDT_LONG_MODE, 53
global_descriptor_table_null = . - global_descriptor_table
.quad 0
global_descriptor_table_code = . - global_descriptor_table
-.quad (1<<40) | (1<<41) | (1<<43) | (1<<44) | (1<<47) | (1<<53)
+.quad (1 << GDT_ACCESSED) | (1 << GDT_READ_WRITE) | (1 << GDT_EXECUTABLE) | (1 << GDT_DESCRIPTOR_TYPE) | (1 << GDT_PRESENT) | (1 << GDT_LONG_MODE)
global_descriptor_table_data = . - global_descriptor_table
-.quad (1<<40) | (1<<41) | (1<<44) | (1<<47)
+.quad (1 << GDT_ACCESSED) | (1 << GDT_READ_WRITE) | (1 << GDT_DESCRIPTOR_TYPE) | (1 << GDT_PRESENT)
global_descriptor_table_end:
-/**
- * We are going to print some messages in case we panic during boot, so we are
- * going to store them here as well
- */
-.global message_prefix_panic
-message_prefix_panic:
-.string "TeachOS Panic: "
-message_not_loaded_by_multiboot2:
-.string "The operating system was not loaded by a Multiboot 2 loader."
-message_cpuid_instruction_no_supported:
-.string "The 'cpuid' instruction is not supported on this platform."
-mesage_long_mode_not_supported:
-.string "Long mode is not supported by this platform."
+message_prefix_panic: .string "Panic: "
+message_not_loaded_by_multiboot2: .string "The operating system was not loaded by a Multiboot 2 loader."
+message_cpuid_instruction_no_supported: .string "The 'cpuid' instruction is not supported on this platform."
+message_long_mode_not_supported: .string "Long mode is not supported by this platform."
+message_return_from_kernel_main: .string "Execution returned from kernel main."
/**
- * Mutable data for the bootstrapping process.
+ * @brief Initialized data for the bootstrapping process.
*/
.section .boot_data, "aw", @progbits
/**
- * We need a pointer to our current position in the VGA text buffer.
+ * @brief A pointer to the current position within the VGA text buffer.
*/
.global vga_buffer_pointer
vga_buffer_pointer: .quad 0xb8000
/**
- * Code for the bootstrapping process.
+ * @brief Code for the bootstrapping process.
*/
.section .boot_text, "ax", @progbits
.align 16
.code32
-.global halt
-halt:
-1:
- hlt
- jmp 1b
+.macro pie_base
+ push %esi
+ call 0f
+ 0:
+ pop %esi
+.endm
+
+.macro function_start
+ push %ebp
+ mov %esp, %ebp
+.endm
+
+.macro function_end
+ leave
+ ret
+.endm
+
+.macro pie_function_start
+ function_start
+ pie_base
+.endm
+
+.macro pie_function_end
+ pop %esi
+ function_end
+.endm
/**
- * Print a given panic message and then halt the machine.
+ * @brief Prepare the environment and start the kernel.
*
- * Parameters:
- * - [stack - 0] message: the message to print
+ * This function performs all necessary checks to ensure the system was loaded
+ * by the expected loader and supports all features required to run the kernel.
+ * If successful, it prepares the system by setting up memory virtualization
+ * and then start the kernel proper.
+ *
+ * @param %eax The Multiboot 2 magic marker.
+ * @param %ebx The Multiboot 2 information pointer.
+ * @return void This function does not return.
*/
-_panic:
- push %ebp
+.global _start
+_start:
+ call 0f
+0:
+ pop %esi
+
+ lea (stack_top - 0b)(%esi), %ecx
+
+ mov %ecx, %esp
mov %esp, %ebp
- call .Lpanic_get_ip
-.Lpanic_get_ip:
- pop %eax
- lea (message_prefix_panic - .Lpanic_get_ip)(%eax), %eax
+ call _assert_loaded_by_multiboot2_loader
+ call _save_multiboot_information_pointer
- push %eax
- push $0x4f
- call _print
- add $8, %esp
+ call _assert_cpuid_instruction_is_supported
+ call _assert_cpu_supports_long_mode
- push 8(%ebp)
- push 0x4f
- call _print
- add $8, %esp
+ call _prepare_page_maps
+ call _enable_paging
+ call _enable_sse
+ call _reload_gdt
+
+ lea (_transition_to_long_mode - 0b)(%esi), %eax
+ pushl $global_descriptor_table_code
+ pushl %eax
+ lret
+
+/**
+ * @brief Halt the system.
+ *
+ * This function will instruct the CPU to halt. It will try to keep the CPU
+ * halted, even if interrupts occur.
+ *
+ * @return This function never returns.
+ */
+.global halt
+_halt:
+ function_start
- call halt
+1:
+ hlt
+ jmp 1b
+
+ function_end
/**
- * Print a message via the VGA buffer.
+ * @brief Print a message via the VGA text buffer.
*
- * Parameters:
- * - [stack - 4] message: the message to print
- * - [stack - 0] color: the color of the message
+ * @param ebp+12 The message to print.
+ * @param ebp+8 The color to print the message in.
*/
_print:
- push %ebp
- mov %esp, %ebp
+ pie_function_start
- push %ebx
- push %esi
push %edi
+ push %ebx
- call .Lprint_get_ip
-.Lprint_get_ip:
- pop %edi
- lea (vga_buffer_pointer - .Lprint_get_ip)(%edi), %edi
-
- mov 8(%ebp), %eax
- mov 12(%ebp), %ebx
+ mov 8(%ebp), %al
+ mov 12(%ebp), %edx
mov $0, %ecx
- mov (%edi), %esi
-
-.Lprint_loop:
- mov (%ebx, %ecx), %dl
- test %dl, %dl
- je .Lupdate_vga_buffer_address
- mov %dl, (%esi, %ecx, 2)
- mov %al, 1(%esi, %ecx, 2)
+ lea (vga_buffer_pointer - 0b)(%esi), %edi
+ mov (%edi), %edi
+
+1:
+ mov (%edx, %ecx), %bl
+ test %bl, %bl
+ je 2f
+ mov %bl, (%edi, %ecx, 2)
+ mov %al, 1(%edi, %ecx, 2)
inc %ecx
- jmp .Lprint_loop
+ jmp 1b
-.Lupdate_vga_buffer_address:
+2:
shl $1, %ecx
- add %ecx, %esi
- mov %esi, (%edi)
+ add %ecx, %edi
+ lea (vga_buffer_pointer - 0b)(%esi), %ecx
+ mov %edi, (%ecx)
-.Lprint_end:
- pop %edi
- pop %esi
pop %ebx
- mov %ebp, %esp
- pop %ebp
- ret
+ pop %edi
+
+ pie_function_end
/**
- * This is our entry point after being loaded by the bootloader.
+ * @brief Print a given panic message and then halt the machine as if by calling ::halt()
*
- * Having this in assembly makes it easier for us to keep things together.
+ * @param ebp+4 A message to print.
+ * @return This function does not return.
*/
-.global _start
-_start:
- call .Lstart_get_ip
-.Lstart_get_ip:
- pop %esi
- lea (stack_top - .Lstart_get_ip)(%esi), %ecx
-
- mov %ecx, %esp
- mov %esp, %ebp
+_panic:
+ pie_function_start
- call assert_loaded_by_multiboot2_loader
- call assert_cpuid_instruction_is_supported
- call assert_cpu_supports_long_mode
- call prepare_page_maps
- call enable_paging
- call enable_sse
+ lea (message_prefix_panic - 0b)(%esi), %eax
- sub $10, %esp
- lea (global_descriptor_table - .Lstart_get_ip)(%esi), %eax
- movw $(global_descriptor_table_end - global_descriptor_table -1), (%esp)
- mov %eax, 2(%esp)
- movl $0, 6(%esp)
+ push %eax
+ push $0x4f
+ call _print
- lgdt (%esp)
- add $10, %esp
+ mov 16(%ebp), %eax
+ mov %eax, 8(%ebp)
+ call _print
+ add $8, %esp
- lea (_transition_to_long_mode - .Lstart_get_ip)(%esi), %eax
- pushl $global_descriptor_table_code
- pushl %eax
- lret
+ call _halt
- call halt
+ pie_function_end
/**
- * Assert that the CPU supports going into long mode.
+ * Assert that we were loaded by a Multiboot 2 compliant bootloader.
+ *
+ * This assertion will panic the system if the magic signature was not found.
+ * If we were loaded my an appropriate bootloader, this function also saves
+ * the provided MBI pointer to `multiboot_information_pointer`.
*/
-assert_cpu_supports_long_mode:
- mov $0x80000000, %eax
- cpuid
- cmp $0x80000001, %eax
- jb .Llong_mode_assertion_failed
+_assert_loaded_by_multiboot2_loader:
+ pie_function_start
- mov $0x80000001, %eax
- cpuid
- test $(1 << 29), %edx
- jz .Llong_mode_assertion_failed
- ret
-.Llong_mode_assertion_failed:
- call .Lassert_cpu_supports_long_mode_fail_get_ip
-.Lassert_cpu_supports_long_mode_fail_get_ip:
- pop %eax
- lea (mesage_long_mode_not_supported - .Lassert_cpu_supports_long_mode_fail_get_ip)(%eax), %eax
+ .set MULTIBOOT2_MAGIC, 0x36d76289
+ cmp $MULTIBOOT2_MAGIC, %eax
+ je 1f
+ lea (message_not_loaded_by_multiboot2 - 0b)(%esi), %eax
push %eax
call _panic
+1:
+ pie_function_end
/**
- * Assert that the CPU supports the CPUID instruction.
+ * @brief Store the multiboot 2 information pointer in the global memory.
+ *
+ * @return void
+ */
+_save_multiboot_information_pointer:
+ pie_function_start
+
+ lea (multiboot_information_pointer - 0b)(%esi), %eax
+ mov %ebx, (%eax)
+
+ pie_function_end
+
+/**
+ * @brief Assert that the CPU supports the CPUID instruction.
*
* The primary way to check for support of the instruction is to flip the ID
* bin in EFLAGS and then check if this changed was accepted. If so, the CPU
* supports the CPUID instruction, otherwise it most-likely doesn't.
*/
-assert_cpuid_instruction_is_supported:
+_assert_cpuid_instruction_is_supported:
+ pie_function_start
+
pushfl
pop %eax
mov %eax, %ecx
@@ -271,52 +273,100 @@ assert_cpuid_instruction_is_supported:
popfl
cmp %ecx, %eax
- je .Lcpuid_assertion_fail
- ret
-.Lcpuid_assertion_fail:
- call .Lassert_cpuid_instruction_is_supported_fail_get_ip
-.Lassert_cpuid_instruction_is_supported_fail_get_ip:
- pop %eax
- lea (message_cpuid_instruction_no_supported - .Lassert_cpuid_instruction_is_supported_fail_get_ip)(%eax), %eax
+ jne 1f
+ lea (message_cpuid_instruction_no_supported - 0b)(%esi), %eax
push %eax
call _panic
+1:
+ pie_function_end
+
/**
- * Assert that we were loaded by a Multiboot 2 compliant bootloader.
- *
- * This assertion will panic the system if the magic signature was not found.
- * If we were loaded my an appropriate bootloader, this function also saves
- * the provided MBI pointer to `multiboot_information_pointer`.
+ * @brief Assert that the CPU supports going into long mode.
*/
-assert_loaded_by_multiboot2_loader:
- cmp $0x36d76289, %eax /* Check if we received the
- expected magic */
- jne .Lmultiboot2_assertion_fail /* Panic otherwise */
- call .Lassert_loaded_by_multiboot2_loader_get_ip
-.Lassert_loaded_by_multiboot2_loader_get_ip:
- pop %eax
- lea (multiboot_information_pointer - .Lassert_loaded_by_multiboot2_loader_get_ip)(%eax), %eax
- mov %ebx, (%eax) /* Store the MBI pointer */
- ret
-.Lmultiboot2_assertion_fail:
- call .Lassert_loaded_by_multiboot2_loader_fail_get_ip
-.Lassert_loaded_by_multiboot2_loader_fail_get_ip:
- pop %eax
- lea (message_not_loaded_by_multiboot2 - .Lassert_loaded_by_multiboot2_loader_fail_get_ip)(%eax), %eax
+_assert_cpu_supports_long_mode:
+ pie_function_start
+
+ mov $0x80000000, %eax
+ cpuid
+ cmp $0x80000001, %eax
+ jb 1f
+
+ mov $0x80000001, %eax
+ cpuid
+ test $(1 << 29), %edx
+ jnz 2f
+1:
+ lea (message_long_mode_not_supported - 0b)(%esi), %eax
push %eax
call _panic
+2:
+ pie_function_end
/**
- * Enable paging.
+ * @brief Prepare a recursive page map hierarchy
+ *
+ * We map all physical memory we were loaded in plus one additional page. The
+ * mapping is done in terms of huge pages (2 MiB per page) to save on required
+ * page map entries.
+ *
+ * @return void
+ */
+_prepare_page_maps:
+ pie_function_start
+ push %ebx
+
+ .set HUGE_PAGES_TO_MAP, 16
+
+ /* Map the P4 table recursively */
+ lea (page_map_level_4 - 0b)(%esi), %eax
+ mov %eax, %ebx
+ or $0b11, %ebx
+ mov %ebx, (511 * 8)(%eax)
+
+ /* Add an entry to the PML4, pointing to the PML3 */
+ lea (page_map_level_3 - 0b)(%esi), %ebx
+ or $0b11, %ebx
+ mov %ebx, (((0x0000000000100000 >> 39) & 0x1ff) * 8)(%eax)
+
+ /* Add an entry to the PML3, pointing to the PML2 */
+ lea (page_map_level_3 - 0b)(%esi), %eax
+ lea (page_map_level_2 - 0b)(%esi), %ebx
+ or $0b11, %ebx
+ mov %ebx, (((0x0000000000100000 >> 30) & 0x1ff) * 8)(%eax)
+
+ /* Add entries for huge pages to the PML2 */
+ push %edi
+ lea (page_map_level_2 - 0b)(%esi), %ebx
+ mov $HUGE_PAGES_TO_MAP, %edi
+ xor %ecx, %ecx
+
+1:
+ mov $(1 << 21), %eax
+ mul %ecx
+ or $0b10000011, %eax
+ mov %eax, (%ebx, %ecx, 8)
+
+ inc %ecx
+ cmp %edi, %ecx
+ jne 1b
+
+ pop %edi
+ pop %ebx
+ pie_function_end
+
+/**
+ * @p Enable memory virtualization via paging.
*
* Note: This routine expects for there to be a valid set of page maps already
* set up for use.
- */
-enable_paging:
- call .Lenable_paging_get_ip
-.Lenable_paging_get_ip:
- pop %eax
-lea (page_map_level_4 - .Lenable_paging_get_ip)(%eax), %eax
+ *
+ * @return void
+ */For
+_enable_paging:
+ pie_function_start
+
+ lea (page_map_level_4 - 0b)(%esi), %eax
mov %eax, %cr3
/* Enable Physical Address Extension */
@@ -335,12 +385,14 @@ lea (page_map_level_4 - .Lenable_paging_get_ip)(%eax), %eax
or $(1 << 31), %eax
mov %eax, %cr0
- ret
+ pie_function_end
/**
- * Enable use of SSE instructions.
+ * @brief Enable use of SSE instructions.
*/
-enable_sse:
+_enable_sse:
+ function_start
+
mov %cr0, %eax
and $0xfffffffb, %eax
or $0x00000002, %eax
@@ -350,57 +402,26 @@ enable_sse:
or $(3 << 9), %eax
mov %eax, %cr4
- ret
+ function_end
/**
- * Prepare the page maps.
+ * @brief Prepare a new GTD and load make it active.
*
- * We map all physical memory we were loaded in plus one additional page. The
- * mapping is done in terms of huge pages (2 MiB per page) to save on required
- * page map entries.
+ * @return void
*/
-prepare_page_maps:
- call .Lprepare_page_maps_get_ip
-.Lprepare_page_maps_get_ip:
- pop %edi
- /* Map the P4 table recursively */
- lea (page_map_level_4 - .Lprepare_page_maps_get_ip)(%edi), %eax
- mov %eax, %ebx
- or $0b11, %eax /* Write present + writable flags into eax register */
- mov %eax, (511 * 8)(%ebx)
-
- /* Add an entry to the PML4, pointing to the PML3 */
- lea (page_map_level_3 - .Lprepare_page_maps_get_ip)(%edi), %eax
- lea (page_map_level_4 - .Lprepare_page_maps_get_ip)(%edi), %ebx
- or $0x3, %eax
- mov %eax, (((0x0000000000100000 >> 39) & 0x1ff) * 8)(%ebx)
-
- /* Add an entry to the PML3, pointing to the PML2 */
- lea (page_map_level_2 - .Lprepare_page_maps_get_ip)(%edi), %eax
- lea (page_map_level_3 - .Lprepare_page_maps_get_ip)(%edi), %ebx
- or $0x3, %eax
- mov %eax, (((0x0000000000100000 >> 30) & 0x1ff) * 8)(%ebx)
-
- xor %ecx, %ecx
-
- push %esi
- lea (_end_linear - .Lprepare_page_maps_get_ip)(%edi), %esi
- shr $21, %esi
- add $2, %esi
+_reload_gdt:
+ pie_function_start
-.Lmap_pages:
- lea (page_map_level_2 - .Lprepare_page_maps_get_ip)(%edi), %ebx
- mov $(1 << 21), %eax
- mul %ecx
- or $((1 << 0) | (1 << 1) | (1 << 7)), %eax
- mov %eax, (%ebx,%ecx,8)
+ sub $10, %esp
+ lea (global_descriptor_table - 0b)(%esi), %eax
+ movw $(global_descriptor_table_end - global_descriptor_table -1), (%esp)
+ mov %eax, 2(%esp)
+ movl $0, 6(%esp)
- inc %ecx
- cmp %esi, %ecx
- jne .Lmap_pages
+ lgdt (%esp)
+ add $10, %esp
- pop %esi
- ret
+ pie_function_end
.section .boot_text, "ax", @progbits
.code64
@@ -413,12 +434,9 @@ _transition_to_long_mode:
mov %rax, %fs
mov %rax, %gs
- leaq vga_buffer_pointer(%rip), %rax
- movl $0xb8000, (%rax)
-
xor %rax, %rax
call _init
call main
- call halt
+ call _halt