From f60b6af52f4137eea241c8aa537d8ba9fcf3356b Mon Sep 17 00:00:00 2001 From: Microindole Date: Fri, 8 May 2026 19:44:32 +0800 Subject: [PATCH] feat(boot): load separate x86 kernel image from UEFI bootloader --- Makefile | 42 +++++- arch/x86/boot/main.c | 249 ++++++++++++++++++++++++++++++++++-- arch/x86/include/efi.h | 145 ++++++++++++++++++++- arch/x86/kernel/entry.S | 17 +++ arch/x86/kernel/linker.ld | 23 ++++ docs/roadmap.md | 6 + include/tianole/boot_info.h | 1 + include/tianole/elf.h | 44 +++++++ kernel/main.c | 37 +++++- 9 files changed, 539 insertions(+), 25 deletions(-) create mode 100644 arch/x86/kernel/entry.S create mode 100644 arch/x86/kernel/linker.ld create mode 100644 include/tianole/elf.h diff --git a/Makefile b/Makefile index dbf316b..e47b9d5 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,14 @@ SHELL := /bin/bash CC := clang +ELF_LD := ld.lld LD := lld-link ARCH ?= x86_64 ifeq ($(ARCH),x86_64) ARCH_DIR := arch/x86 EFI_ARCH_TARGET := x86_64-unknown-windows +KERNEL_ARCH_TARGET := x86_64-unknown-none-elf BOOT_EFI_NAME := BOOTX64.EFI else $(error Unsupported ARCH '$(ARCH)') @@ -16,6 +18,8 @@ BUILD_DIR := build EFI_DIR := $(BUILD_DIR)/image/EFI/BOOT BOOT_EFI := $(EFI_DIR)/$(BOOT_EFI_NAME) BOOT_OBJ := $(BUILD_DIR)/arch/boot/main.obj +KERNEL_ELF := $(BUILD_DIR)/image/kernel.elf +KERNEL_OBJS := $(BUILD_DIR)/arch/kernel/entry.o $(BUILD_DIR)/kernel/main.o DEBUG_LOG := $(BUILD_DIR)/debug.log OVMF_CODE ?= /usr/share/OVMF/OVMF_CODE_4M.fd OVMF_VARS_TEMPLATE ?= /usr/share/OVMF/OVMF_VARS_4M.fd @@ -34,6 +38,23 @@ CFLAGS := \ -Iinclude \ -I$(ARCH_DIR)/include +KERNEL_CFLAGS := \ + -target $(KERNEL_ARCH_TARGET) \ + -ffreestanding \ + -fno-stack-protector \ + -fno-builtin \ + -fno-pic \ + -mno-red-zone \ + -Wall \ + -Wextra \ + -Werror \ + -Iinclude \ + -I$(ARCH_DIR)/include + +KERNEL_ASFLAGS := \ + -target $(KERNEL_ARCH_TARGET) \ + -ffreestanding + LDFLAGS := \ /subsystem:efi_application \ /entry:efi_main \ @@ -42,19 +63,34 @@ LDFLAGS := \ /machine:x64 \ /out:$(BOOT_EFI) +KERNEL_LDFLAGS := \ + -m elf_x86_64 \ + -nostdlib \ + -T $(ARCH_DIR)/kernel/linker.ld \ + -o $(KERNEL_ELF) + .PHONY: all clean run run-headless dirs -all: $(BOOT_EFI) +all: $(BOOT_EFI) $(KERNEL_ELF) dirs: - mkdir -p $(BUILD_DIR)/arch/boot $(EFI_DIR) + mkdir -p $(BUILD_DIR)/arch/boot $(BUILD_DIR)/arch/kernel $(BUILD_DIR)/kernel $(EFI_DIR) -$(BOOT_OBJ): $(ARCH_DIR)/boot/main.c $(ARCH_DIR)/include/efi.h include/tianole/boot_info.h | dirs +$(BOOT_OBJ): $(ARCH_DIR)/boot/main.c $(ARCH_DIR)/include/efi.h include/tianole/boot_info.h include/tianole/elf.h | dirs $(CC) $(CFLAGS) -c $< -o $@ $(BOOT_EFI): $(BOOT_OBJ) | dirs $(LD) $(LDFLAGS) $< +$(BUILD_DIR)/arch/kernel/entry.o: $(ARCH_DIR)/kernel/entry.S | dirs + $(CC) $(KERNEL_ASFLAGS) -c $< -o $@ + +$(BUILD_DIR)/kernel/main.o: kernel/main.c include/tianole/boot_info.h | dirs + $(CC) $(KERNEL_CFLAGS) -c $< -o $@ + +$(KERNEL_ELF): $(KERNEL_OBJS) $(ARCH_DIR)/kernel/linker.ld | dirs + $(ELF_LD) $(KERNEL_LDFLAGS) $(KERNEL_OBJS) + $(OVMF_VARS): | dirs cp $(OVMF_VARS_TEMPLATE) $@ diff --git a/arch/x86/boot/main.c b/arch/x86/boot/main.c index 02a5774..bbe5ab1 100644 --- a/arch/x86/boot/main.c +++ b/arch/x86/boot/main.c @@ -1,16 +1,18 @@ #include "efi.h" +#include "tianole/elf.h" #include "tianole/boot_info.h" -static efi_char16_t banner_text[] = { +typedef void __attribute__((sysv_abi)) (*kernel_entry_fn_t)(const tianole_boot_info_t *); + +static efi_char16_t boot_banner_text[] = { 'T', 'i', 'a', 'n', 'o', 'l', 'e', ' ', 'x', '8', '6', ' ', 'b', 'o', 'o', 't', - ' ', 's', 't', 'u', 'b', ' ', 'l', 'o', - 'a', 'd', 'e', 'd', '.', '\r', '\n', 0 + 'l', 'o', 'a', 'd', 'e', 'r', '.', '\r', '\n', 0 }; -static const tianole_boot_info_t boot_info = { - .version = TIANOLE_BOOT_INFO_VERSION, - .boot_flags = 0, +static efi_char16_t kernel_path_text[] = { + '\\', 'k', 'e', 'r', 'n', 'e', 'l', '.', + 'e', 'l', 'f', 0 }; static inline void debug_putc(char ch) { @@ -26,19 +28,240 @@ static void debug_puts(const char *text) { } } -static void debug_boot_info(const tianole_boot_info_t *info) { - (void)info; - debug_puts("boot_info.version=1\n"); +static void *mem_copy(void *dst, const void *src, uint64_t size) { + uint8_t *out = (uint8_t *)dst; + const uint8_t *in = (const uint8_t *)src; + uint64_t i; + + for (i = 0; i < size; ++i) { + out[i] = in[i]; + } + + return dst; +} + +static void mem_zero(void *dst, uint64_t size) { + uint8_t *out = (uint8_t *)dst; + uint64_t i; + + for (i = 0; i < size; ++i) { + out[i] = 0; + } +} + +static efi_status open_root( + efi_handle image_handle, + efi_system_table_t *system_table, + efi_file_protocol_t **root +) { + efi_guid_t loaded_image_guid = efi_loaded_image_protocol_guid(); + efi_guid_t simple_fs_guid = efi_simple_file_system_protocol_guid(); + efi_loaded_image_protocol_t *loaded_image; + efi_simple_file_system_protocol_t *fs; + efi_status status; + + status = system_table->boot_services->handle_protocol( + image_handle, + &loaded_image_guid, + (void **)&loaded_image + ); + if (status != EFI_SUCCESS) { + return status; + } + + status = system_table->boot_services->handle_protocol( + loaded_image->device_handle, + &simple_fs_guid, + (void **)&fs + ); + if (status != EFI_SUCCESS) { + return status; + } + + return fs->open_volume(fs, root); +} + +static efi_status read_entire_file( + efi_system_table_t *system_table, + efi_file_protocol_t *root, + efi_char16_t *path, + void **buffer, + uint64_t *size +) { + efi_guid_t file_info_guid = efi_file_info_guid(); + efi_file_protocol_t *file; + efi_file_info_t *file_info; + efi_uintn_t info_size; + efi_uintn_t read_size; + efi_status status; + + status = root->open(root, &file, path, EFI_OPEN_MODE_READ, 0); + if (status != EFI_SUCCESS) { + return status; + } + + info_size = 0; + status = file->get_info(file, &file_info_guid, &info_size, 0); + if (status != EFI_BUFFER_TOO_SMALL) { + file->close(file); + return status; + } + + status = system_table->boot_services->allocate_pool(EFI_LOADER_DATA, info_size, (void **)&file_info); + if (status != EFI_SUCCESS) { + file->close(file); + return status; + } + + status = file->get_info(file, &file_info_guid, &info_size, file_info); + if (status != EFI_SUCCESS) { + file->close(file); + return status; + } + + *size = file_info->file_size; + status = system_table->boot_services->allocate_pool(EFI_LOADER_DATA, *size, buffer); + if (status != EFI_SUCCESS) { + file->close(file); + return status; + } + + read_size = *size; + status = file->read(file, &read_size, *buffer); + file->close(file); + if (status != EFI_SUCCESS) { + return status; + } + + if (read_size != *size) { + return EFI_LOAD_ERROR; + } + + return EFI_SUCCESS; +} + +static efi_status validate_kernel_elf(const elf64_ehdr_t *ehdr) { + if (*(const uint32_t *)ehdr->ident != ELF_MAGIC) { + return EFI_LOAD_ERROR; + } + if (ehdr->ident[4] != ELFCLASS64 || ehdr->ident[5] != ELFDATA2LSB) { + return EFI_LOAD_ERROR; + } + if (ehdr->ident[6] != EV_CURRENT || ehdr->version != EV_CURRENT) { + return EFI_LOAD_ERROR; + } + if (ehdr->type != ET_EXEC || ehdr->machine != EM_X86_64) { + return EFI_LOAD_ERROR; + } + if (ehdr->phentsize != sizeof(elf64_phdr_t)) { + return EFI_LOAD_ERROR; + } + + return EFI_SUCCESS; +} + +static efi_status load_kernel_image( + efi_system_table_t *system_table, + const void *kernel_image, + uint64_t kernel_size, + kernel_entry_fn_t *entry_out +) { + const elf64_ehdr_t *ehdr = (const elf64_ehdr_t *)kernel_image; + const elf64_phdr_t *phdrs; + uint16_t index; + efi_status status; + + if (kernel_size < sizeof(*ehdr)) { + return EFI_LOAD_ERROR; + } + + status = validate_kernel_elf(ehdr); + if (status != EFI_SUCCESS) { + return status; + } + + phdrs = (const elf64_phdr_t *)((const uint8_t *)kernel_image + ehdr->phoff); + + for (index = 0; index < ehdr->phnum; ++index) { + const elf64_phdr_t *phdr = &phdrs[index]; + efi_physical_address_t segment_base; + uint64_t page_count; + + if (phdr->type != PT_LOAD) { + continue; + } + + if (phdr->memsz < phdr->filesz) { + return EFI_LOAD_ERROR; + } + if (phdr->offset + phdr->filesz > kernel_size) { + return EFI_LOAD_ERROR; + } + + segment_base = phdr->paddr; + page_count = (phdr->memsz + 0xfffULL) >> 12; + status = system_table->boot_services->allocate_pages( + EFI_ALLOCATE_ADDRESS, + EFI_LOADER_DATA, + page_count, + &segment_base + ); + if (status != EFI_SUCCESS) { + return status; + } + + mem_zero((void *)(uintptr_t)phdr->paddr, phdr->memsz); + mem_copy( + (void *)(uintptr_t)phdr->paddr, + (const uint8_t *)kernel_image + phdr->offset, + phdr->filesz + ); + } + + *entry_out = (kernel_entry_fn_t)(uintptr_t)ehdr->entry; + return EFI_SUCCESS; } efi_status EFIAPI efi_main(efi_handle image_handle, efi_system_table_t *system_table) { - (void)image_handle; + efi_file_protocol_t *root; + void *kernel_image; + uint64_t kernel_size; + kernel_entry_fn_t kernel_entry; + tianole_boot_info_t boot_info = { + .version = TIANOLE_BOOT_INFO_VERSION, + .boot_flags = TIANOLE_BOOT_FLAG_BOOT_SERVICES_ACTIVE, + }; + efi_status status; - system_table->con_out->output_string(system_table->con_out, banner_text); - debug_puts("Tianole x86 boot stub loaded.\n"); - debug_boot_info(&boot_info); + system_table->con_out->output_string(system_table->con_out, boot_banner_text); + debug_puts("Tianole x86 bootloader loaded.\n"); + status = open_root(image_handle, system_table, &root); + if (status != EFI_SUCCESS) { + debug_puts("failed: open_root\n"); + return status; + } + + status = read_entire_file(system_table, root, kernel_path_text, &kernel_image, &kernel_size); + root->close(root); + if (status != EFI_SUCCESS) { + debug_puts("failed: read kernel.elf\n"); + return status; + } + + status = load_kernel_image(system_table, kernel_image, kernel_size, &kernel_entry); + if (status != EFI_SUCCESS) { + debug_puts("failed: load kernel image\n"); + return status; + } + + debug_puts("jumping to kernel entry\n"); + kernel_entry(&boot_info); + + debug_puts("kernel returned unexpectedly\n"); for (;;) { system_table->boot_services->stall(1000 * 1000); } + + return EFI_SUCCESS; } diff --git a/arch/x86/include/efi.h b/arch/x86/include/efi.h index dc0d63b..5bf97ca 100644 --- a/arch/x86/include/efi.h +++ b/arch/x86/include/efi.h @@ -7,8 +7,28 @@ typedef uint64_t efi_status; typedef void *efi_handle; typedef uint16_t efi_char16_t; typedef uint64_t efi_uintn_t; +typedef uint64_t efi_physical_address_t; #define EFI_SUCCESS 0 +#define EFI_LOAD_ERROR 0x8000000000000001ULL +#define EFI_INVALID_PARAMETER 0x8000000000000002ULL +#define EFI_BUFFER_TOO_SMALL 0x8000000000000005ULL +#define EFI_OUT_OF_RESOURCES 0x8000000000000009ULL +#define EFI_NOT_FOUND 0x800000000000000eULL + +#define EFI_ALLOCATE_ADDRESS 2 +#define EFI_LOADER_DATA 4 +#define EFI_OPEN_MODE_READ 0x0000000000000001ULL + +#define EFI_FILE_INFO_ID 0x09576e92 +#define EFI_FILE_INFO_ID_A 0x6d3f +#define EFI_FILE_INFO_ID_B 0x11d2 +#define EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID_A 0x964e5b22 +#define EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID_B 0x6459 +#define EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID_C 0x11d2 +#define EFI_LOADED_IMAGE_PROTOCOL_GUID_A 0x5b1b31a1 +#define EFI_LOADED_IMAGE_PROTOCOL_GUID_B 0x9562 +#define EFI_LOADED_IMAGE_PROTOCOL_GUID_C 0x11d2 #if defined(__x86_64__) #define EFIAPI __attribute__((ms_abi)) @@ -16,6 +36,13 @@ typedef uint64_t efi_uintn_t; #define EFIAPI #endif +typedef struct { + uint32_t data1; + uint16_t data2; + uint16_t data3; + uint8_t data4[8]; +} efi_guid_t; + typedef struct { uint64_t signature; uint32_t revision; @@ -26,6 +53,10 @@ typedef struct { typedef struct efi_simple_text_output_protocol efi_simple_text_output_protocol_t; typedef struct efi_boot_services efi_boot_services_t; +typedef struct efi_simple_file_system_protocol efi_simple_file_system_protocol_t; +typedef struct efi_file_protocol efi_file_protocol_t; +typedef struct efi_loaded_image_protocol efi_loaded_image_protocol_t; +typedef struct efi_system_table efi_system_table_t; struct efi_simple_text_output_protocol { void *reset; @@ -35,14 +66,87 @@ struct efi_simple_text_output_protocol { ); }; +typedef struct { + uint64_t size; + uint64_t file_size; + uint64_t physical_size; + uint64_t create_time[2]; + uint64_t last_access_time[2]; + uint64_t modification_time[2]; + uint64_t attribute; + efi_char16_t file_name[1]; +} efi_file_info_t; + +struct efi_file_protocol { + uint64_t revision; + efi_status(EFIAPI *open)( + efi_file_protocol_t *self, + efi_file_protocol_t **new_handle, + efi_char16_t *file_name, + uint64_t open_mode, + uint64_t attributes + ); + efi_status(EFIAPI *close)(efi_file_protocol_t *self); + void *delete_; + efi_status(EFIAPI *read)( + efi_file_protocol_t *self, + efi_uintn_t *buffer_size, + void *buffer + ); + void *write; + void *get_position; + void *set_position; + efi_status(EFIAPI *get_info)( + efi_file_protocol_t *self, + efi_guid_t *information_type, + efi_uintn_t *buffer_size, + void *buffer + ); + void *set_info; + void *flush; +}; + +struct efi_simple_file_system_protocol { + uint64_t revision; + efi_status(EFIAPI *open_volume)( + efi_simple_file_system_protocol_t *self, + efi_file_protocol_t **root + ); +}; + +struct efi_loaded_image_protocol { + uint32_t revision; + efi_handle parent_handle; + efi_system_table_t *system_table; + efi_handle device_handle; + void *file_path; + void *reserved; + uint32_t load_options_size; + void *load_options; + void *image_base; + uint64_t image_size; + uint32_t image_code_type; + uint32_t image_data_type; + void *unload; +}; + struct efi_boot_services { efi_table_header_t hdr; void *raise_tpl; void *restore_tpl; - void *allocate_pages; + efi_status(EFIAPI *allocate_pages)( + int type, + int memory_type, + efi_uintn_t pages, + efi_physical_address_t *memory + ); void *free_pages; void *get_memory_map; - void *allocate_pool; + efi_status(EFIAPI *allocate_pool)( + int pool_type, + efi_uintn_t size, + void **buffer + ); void *free_pool; void *create_event; void *set_timer; @@ -53,7 +157,11 @@ struct efi_boot_services { void *install_protocol_interface; void *reinstall_protocol_interface; void *uninstall_protocol_interface; - void *handle_protocol; + efi_status(EFIAPI *handle_protocol)( + efi_handle handle, + efi_guid_t *protocol, + void **interface + ); void *reserved; void *register_protocol_notify; void *locate_handle; @@ -69,7 +177,7 @@ struct efi_boot_services { void *set_watchdog_timer; }; -typedef struct { +struct efi_system_table { efi_table_header_t hdr; efi_char16_t *firmware_vendor; uint32_t firmware_revision; @@ -83,6 +191,33 @@ typedef struct { efi_boot_services_t *boot_services; uint64_t number_of_table_entries; void *configuration_table; -} efi_system_table_t; +}; + +static inline efi_guid_t efi_file_info_guid(void) { + return (efi_guid_t){ + .data1 = EFI_FILE_INFO_ID, + .data2 = EFI_FILE_INFO_ID_A, + .data3 = EFI_FILE_INFO_ID_B, + .data4 = {0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}, + }; +} + +static inline efi_guid_t efi_simple_file_system_protocol_guid(void) { + return (efi_guid_t){ + .data1 = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID_A, + .data2 = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID_B, + .data3 = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID_C, + .data4 = {0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}, + }; +} + +static inline efi_guid_t efi_loaded_image_protocol_guid(void) { + return (efi_guid_t){ + .data1 = EFI_LOADED_IMAGE_PROTOCOL_GUID_A, + .data2 = EFI_LOADED_IMAGE_PROTOCOL_GUID_B, + .data3 = EFI_LOADED_IMAGE_PROTOCOL_GUID_C, + .data4 = {0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}, + }; +} #endif diff --git a/arch/x86/kernel/entry.S b/arch/x86/kernel/entry.S new file mode 100644 index 0000000..07e87ad --- /dev/null +++ b/arch/x86/kernel/entry.S @@ -0,0 +1,17 @@ +.global _start +.type _start, @function + +.section .text +_start: + lea kernel_stack_top(%rip), %rsp + call kernel_main + +1: + hlt + jmp 1b + +.section .bss +.balign 16 +kernel_stack: + .skip 16384 +kernel_stack_top: diff --git a/arch/x86/kernel/linker.ld b/arch/x86/kernel/linker.ld new file mode 100644 index 0000000..0e462c6 --- /dev/null +++ b/arch/x86/kernel/linker.ld @@ -0,0 +1,23 @@ +ENTRY(_start) + +SECTIONS +{ + . = 0x200000; + + .text : ALIGN(4K) { + *(.text .text.*) + } + + .rodata : ALIGN(4K) { + *(.rodata .rodata.*) + } + + .data : ALIGN(4K) { + *(.data .data.*) + } + + .bss : ALIGN(4K) { + *(COMMON) + *(.bss .bss.*) + } +} diff --git a/docs/roadmap.md b/docs/roadmap.md index 79275e8..ba4179c 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -149,6 +149,12 @@ - 屏幕和 `debug.log` 有可见输出 - 程序可常驻,便于调试 +当前已经开始进入第 1 阶段: + +- 代码结构已经为 `bootloader -> kernel` 交接预留边界 +- `boot_info` 已经作为项目自有交接结构存在 +- `arch/x86/` 已经承载当前架构专用启动代码 + 当前还没有完成: - 独立 `kernel` 接管 diff --git a/include/tianole/boot_info.h b/include/tianole/boot_info.h index b9b7560..aa5e892 100644 --- a/include/tianole/boot_info.h +++ b/include/tianole/boot_info.h @@ -14,5 +14,6 @@ typedef struct { } tianole_boot_info_t; #define TIANOLE_BOOT_INFO_VERSION 1u +#define TIANOLE_BOOT_FLAG_BOOT_SERVICES_ACTIVE (1u << 0) #endif diff --git a/include/tianole/elf.h b/include/tianole/elf.h new file mode 100644 index 0000000..8d95557 --- /dev/null +++ b/include/tianole/elf.h @@ -0,0 +1,44 @@ +#ifndef TIANOLE_ELF_H +#define TIANOLE_ELF_H + +#include + +#define ELF_MAGIC 0x464c457fu +#define ELFCLASS64 2 +#define ELFDATA2LSB 1 +#define EV_CURRENT 1 + +#define ET_EXEC 2 +#define EM_X86_64 62 + +#define PT_LOAD 1 + +typedef struct { + unsigned char ident[16]; + uint16_t type; + uint16_t machine; + uint32_t version; + uint64_t entry; + uint64_t phoff; + uint64_t shoff; + uint32_t flags; + uint16_t ehsize; + uint16_t phentsize; + uint16_t phnum; + uint16_t shentsize; + uint16_t shnum; + uint16_t shstrndx; +} elf64_ehdr_t; + +typedef struct { + uint32_t type; + uint32_t flags; + uint64_t offset; + uint64_t vaddr; + uint64_t paddr; + uint64_t filesz; + uint64_t memsz; + uint64_t align; +} elf64_phdr_t; + +#endif diff --git a/kernel/main.c b/kernel/main.c index 49c6059..32a6c4e 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -1,4 +1,33 @@ -/* - * Kernel code starts in the next stage. - * The initial milestone is a verified UEFI boot path. - */ +#include + +#include "tianole/boot_info.h" + +static inline void debug_putc(char ch) { + __asm__ volatile("outb %0, $0xe9" : : "a"(ch)); +} + +static void debug_puts(const char *text) { + while (*text != '\0') { + if (*text == '\n') { + debug_putc('\r'); + } + debug_putc(*text++); + } +} + +void kernel_main(const tianole_boot_info_t *boot_info) { + debug_puts("kernel_main entered\n"); + if (boot_info != 0 && boot_info->version == TIANOLE_BOOT_INFO_VERSION) { + debug_puts("boot_info.version ok\n"); + } else { + debug_puts("boot_info.version invalid\n"); + } + + if (boot_info != 0 && (boot_info->boot_flags & TIANOLE_BOOT_FLAG_BOOT_SERVICES_ACTIVE) != 0) { + debug_puts("boot services still active\n"); + } + + for (;;) { + __asm__ volatile("hlt"); + } +}