From e94956882fbc0dfa3ebd4cffbfda31b4b04e51d0 Mon Sep 17 00:00:00 2001
From: Amelia Coutard <eliottulio.coutard@gmail.com>
Date: Mon, 27 Feb 2023 12:47:20 +0100
Subject: [PATCH] Implemented an elf64 loader for test_module

---
 grub.cfg                 |  2 +-
 kernel/src/elf64.hpp     | 44 ++++++++++++++++++++++
 kernel/src/kernel.cpp    | 79 ++++++++++++++++++++++++++++++++--------
 kernel/src/paging.hpp    | 15 ++++++++
 kernel/src/ring3.S       |  2 +-
 kernel/src/ring3.hpp     |  2 +-
 test_module/module.mk    | 11 ++----
 test_module/src/test.cpp |  8 +++-
 8 files changed, 136 insertions(+), 27 deletions(-)

diff --git a/grub.cfg b/grub.cfg
index 11f6906..f1fc76e 100644
--- a/grub.cfg
+++ b/grub.cfg
@@ -1,4 +1,4 @@
 menuentry "Amycros" {
 	multiboot2 /boot/kernel.elf64
-	module2 /boot/test-module test-module
+	module2 /boot/test-module.elf64 test-module
 }
diff --git a/kernel/src/elf64.hpp b/kernel/src/elf64.hpp
index 6f70f09..17bbfa3 100644
--- a/kernel/src/elf64.hpp
+++ b/kernel/src/elf64.hpp
@@ -1 +1,45 @@
 #pragma once
+
+#include <cstdint>
+#include "lib/phys_ptr.hpp"
+
+namespace os { namespace elf {
+	struct header;
+	struct program_header;
+
+
+	struct __attribute__((packed)) header {
+		std::uint8_t magic[4];
+		std::uint8_t bitn;
+		std::uint8_t endianness;
+		std::uint8_t header_version;
+		std::uint8_t abi;
+		std::uint64_t : 64;
+		std::uint16_t type;
+		std::uint16_t arch;
+		std::uint32_t elf_version;
+		void* entry;
+		std::uint64_t program_header_table;
+		std::uint64_t section_header_table;
+		std::uint32_t flags;
+		std::uint16_t header_size;
+		std::uint16_t entry_size_program_header_table;
+		std::uint16_t entry_count_program_header_table;
+		std::uint16_t entry_size_section_header_table;
+		std::uint16_t entry_count_section_header_table;
+		std::uint16_t index_section_names_section_header_table;
+	};
+	static_assert(sizeof(header) == 64);
+
+	struct __attribute__((packed)) program_header {
+		std::uint32_t type;
+		std::uint32_t flags;
+		std::uint64_t p_offset;
+		std::byte* p_vaddr;
+		std::uint64_t : 64;
+		std::uint64_t p_filesz;
+		std::uint64_t p_memsz;
+		std::uint64_t align;
+	};
+	static_assert(sizeof(program_header) == 56);
+} } // namespace os::elf
diff --git a/kernel/src/kernel.cpp b/kernel/src/kernel.cpp
index d07261d..1485a17 100644
--- a/kernel/src/kernel.cpp
+++ b/kernel/src/kernel.cpp
@@ -168,19 +168,68 @@ extern "C" void kmain(unsigned long magic, os::phys_ptr<const multiboot2::info_s
 	PML4T.contents[0].P = false;
 	os::paging::page_allocator.deallocate({.ptr = os::phys_ptr<os::paging::page>(get_base_address(PML4T.contents[0]).get_phys_addr()), .size = 1});
 	os::paging::page_allocator.print_all();
-	// Map test_module to virtual address 0x0000'0000'0000'1000.
-	{
-		os::phys_ptr<os::paging::PT>   PT  {os::paging::page_allocator.allocate(1).ptr.get_phys_addr()};
-		os::phys_ptr<os::paging::PDT>  PDT {os::paging::page_allocator.allocate(1).ptr.get_phys_addr()};
-		os::phys_ptr<os::paging::PDPT> PDPT{os::paging::page_allocator.allocate(1).ptr.get_phys_addr()};
-		PML4T.contents[0] = {             .P = 1, .R_W = 0, .U_S = 1, .PWT = 0, .PCD = 0, .base_address = 0, .NX = 0};
-		PDPT->contents[0] = {.non_page = {.P = 1, .R_W = 0, .U_S = 1, .PWT = 0, .PCD = 0, .base_address = 0, .NX = 0}};
-		PDT->contents[0] = {.non_page =  {.P = 1, .R_W = 0, .U_S = 1, .PWT = 0, .PCD = 0, .base_address = 0, .NX = 0}};
-		PT->contents[1] = {               .P = 1, .R_W = 0, .U_S = 1, .PWT = 0, .PCD = 0, .PAT = 0, .G = 0, .base_address = 0, .NX = 0};
-		set_base_address(PML4T.contents[0], PDPT);
-		set_base_address(PDPT->contents[0], PDT);
-		set_base_address(PDT->contents[0],  PT);
-		set_page_base_address(PT->contents[1], test_module.start_address);
+	// Load test-module elf file:
+	const os::elf::header elf_header = *os::phys_ptr<os::elf::header>(test_module.start_address.get_phys_addr());
+
+	// Check if elf is valid:
+	os::assert(elf_header.magic[0] == '\x7f', "No elf header.");
+	os::assert(elf_header.magic[1] == 'E', "No elf header.");
+	os::assert(elf_header.magic[2] == 'L', "No elf header.");
+	os::assert(elf_header.magic[3] == 'F', "No elf header.");
+	os::assert(elf_header.bitn == 2, "Elf file not 64 bits.");
+	os::assert(elf_header.endianness == 1, "Elf file not little endian.");
+	os::assert(elf_header.type == 2, "Elf file not executable.");
+	os::assert(elf_header.arch == 0x3E, "Elf file not x86_64.");
+
+	for (std::size_t i = 0; i < elf_header.entry_count_program_header_table; i++) {
+		const os::elf::program_header program_header = *os::phys_ptr<os::elf::program_header>(
+			(os::phys_ptr<std::byte>(test_module.start_address.get_phys_addr()) + elf_header.program_header_table
+				+ i * elf_header.entry_size_program_header_table).get_phys_addr()
+		);
+		if (program_header.type != 1) { // Segment shouldn't be loaded.
+			os::print("Segment.\n");
+			continue;
+		}
+		os::print("Segment: loadable\n");
+		os::assert((std::uintptr_t(program_header.p_vaddr) & 0xFFF) == 0, "Program segment not 4KiB aligned.");
+
+		// Allocate memory for segment:
+		std::size_t nb_pages = (program_header.p_memsz + 1023) / 1024;
+		for (std::size_t i = 0; i < nb_pages; i++) {
+			const auto indices = os::paging::calc_page_table_indices(program_header.p_vaddr + i * 0x1000);
+			os::assert(indices.pml4e < 256, "Userspace program must be in the lower-half of virtual memory.");
+			if (PML4T.contents[indices.pml4e].P == 0) {
+				PML4T.contents[indices.pml4e] = {.P = 1, .R_W = 0, .U_S = 1, .PWT = 0, .PCD = 0, .base_address = 0, .NX = 0};
+				const auto PDPT_alloc = os::paging::page_allocator.allocate(1);
+				set_base_address(PML4T.contents[indices.pml4e], os::phys_ptr<os::paging::PDPT>(PDPT_alloc.ptr.get_phys_addr()));
+			}
+			os::paging::PDPT& PDPT = *get_base_address(PML4T.contents[indices.pml4e]);
+			if (PDPT.contents[indices.pdpe].non_page.P == 0) {
+				PDPT.contents[indices.pdpe] = {.non_page = {.P = 1, .R_W = 0, .U_S = 1, .PWT = 0, .PCD = 0, .base_address = 0, .NX = 0}};
+				const auto PDT_alloc = os::paging::page_allocator.allocate(1);
+				set_base_address(PDPT.contents[indices.pdpe], os::phys_ptr<os::paging::PDT>(PDT_alloc.ptr.get_phys_addr()));
+			}
+			os::paging::PDT& PDT = *get_base_address(PDPT.contents[indices.pdpe]);
+			if (PDT.contents[indices.pde].non_page.P == 0) {
+				PDT.contents[indices.pde] = {.non_page = {.P = 1, .R_W = 0, .U_S = 1, .PWT = 0, .PCD = 0, .base_address = 0, .NX = 0}};
+				const auto PT_alloc = os::paging::page_allocator.allocate(1);
+				set_base_address(PDT.contents[indices.pde], os::phys_ptr<os::paging::PT>(PT_alloc.ptr.get_phys_addr()));
+			}
+			os::paging::PT& PT = *get_base_address(PDT.contents[indices.pde]);
+			if (PT.contents[indices.pe].P == 0) {
+				PT.contents[indices.pe] = {.P = 1, .R_W = 0, .U_S = 1, .PWT = 0, .PCD = 0, .PAT = 0, .G = 0, .base_address = 0, .NX = 0};
+				const auto page_alloc = os::paging::page_allocator.allocate(1);
+				set_page_base_address(PT.contents[indices.pe], os::phys_ptr<os::paging::page>(page_alloc.ptr.get_phys_addr()));
+			}
+		}
+
+		// Initialise memory for segment:
+		for (std::size_t i = 0; i < program_header.p_filesz; i++) {
+			program_header.p_vaddr[i] = os::phys_ptr<std::byte>(test_module.start_address.get_phys_addr())[program_header.p_offset + i];
+		}
+		for (std::size_t i = program_header.p_filesz; i < program_header.p_memsz; i++) {
+			program_header.p_vaddr[i] = std::byte(0);
+		}
 	}
 	asm volatile("invlpg (%0)" ::"r" (0x1000) : "memory");
 
@@ -191,7 +240,7 @@ extern "C" void kmain(unsigned long magic, os::phys_ptr<const multiboot2::info_s
 		os::print("{}->{} ({})\n", std::uint64_t(virt), std::uint64_t(phys.get_phys_addr()), size);
 	});
 
-	// Allow userspaaaaaaaaace in ring 3. Otherwise, we just immediately page fault.
+	// Allow kernel in ring 3. Otherwise, we just immediately page fault.
 	// Will make it a module soon-ish.
 	get_base_address(PML4T.contents[511])->contents[510].page.U_S = true;
 	get_base_address(PML4T.contents[511])->contents[511].page.U_S = true;
@@ -204,5 +253,5 @@ extern "C" void kmain(unsigned long magic, os::phys_ptr<const multiboot2::info_s
 	os::print("Enabling syscalls.\n");
 	os::enable_syscalls();
 	os::print("Moving to ring 3.\n");
-	os::ftl_to_userspace();
+	os::ftl_to_userspace(elf_header.entry);
 }
diff --git a/kernel/src/paging.hpp b/kernel/src/paging.hpp
index 5a2c809..677caa2 100644
--- a/kernel/src/paging.hpp
+++ b/kernel/src/paging.hpp
@@ -207,6 +207,21 @@ inline void set_page_base_address(PE& PE, phys_ptr<page> ptr) {
 	PE.base_address = ptr.get_phys_addr() / 0x1000ull;
 }
 
+struct page_table_indices {
+	std::uint16_t pml4e;
+	std::uint16_t pdpe;
+	std::uint16_t pde;
+	std::uint16_t pe;
+};
+constexpr page_table_indices calc_page_table_indices(void* ptr) {
+	return {
+		.pml4e = std::uint16_t((std::uint64_t(ptr) >> 39) & 0x1FF),
+		.pdpe  = std::uint16_t((std::uint64_t(ptr) >> 30) & 0x1FF),
+		.pde   = std::uint16_t((std::uint64_t(ptr) >> 21) & 0x1FF),
+		.pe    = std::uint16_t((std::uint64_t(ptr) >> 12) & 0x1FF),
+	};
+}
+
 // For all present page mappings, calls f(virtual address, physical address, page size in bytes (4KiB, 2MiB or 1GiB)).
 void on_all_pages(const PML4T& PML4T, void f(page*, phys_ptr<page>, std::size_t));
 
diff --git a/kernel/src/ring3.S b/kernel/src/ring3.S
index 48bf822..f648cf1 100644
--- a/kernel/src/ring3.S
+++ b/kernel/src/ring3.S
@@ -2,7 +2,7 @@
 
 .globl ftl_to_userspace
 ftl_to_userspace:
-	mov $0x1000, %rcx
+	mov %rdi, %rcx
 	mov $0x202, %r11 # EFLAGS
 	sysretq
 
diff --git a/kernel/src/ring3.hpp b/kernel/src/ring3.hpp
index e19e354..6bb02b3 100644
--- a/kernel/src/ring3.hpp
+++ b/kernel/src/ring3.hpp
@@ -24,6 +24,6 @@ struct __attribute__((packed)) tss {
 void set_ring0_stack(tss& tss, std::uint64_t stack);
 extern "C" void load_tss();
 void enable_syscalls();
-extern "C" void ftl_to_userspace();
+extern "C" void ftl_to_userspace(void* program);
 
 }
diff --git a/test_module/module.mk b/test_module/module.mk
index 54e04aa..437b5bf 100644
--- a/test_module/module.mk
+++ b/test_module/module.mk
@@ -8,9 +8,9 @@
 SRC_DIR := test_module/src/
 OUT_DIR := test_module/out/
 DEP_DIR := test_module/dep/
-EXEC_NAME := test.elf64
+EXEC_NAME := test-module.elf64
 
-TO_ISO += isodir/boot/test-module
+TO_ISO += isodir/boot/$(EXEC_NAME)
 TO_CLEAN += $(OUT_DIR) $(DEP_DIR)
 
 LOCAL_CXXFLAGS := $(CXXFLAGS) -O0
@@ -20,12 +20,9 @@ CPPOBJS := $(patsubst $(SRC_DIR)%,$(OUT_DIR)%.o,$(shell find $(SRC_DIR) -name '*
 ASMOBJS := $(patsubst $(SRC_DIR)%,$(OUT_DIR)%.o,$(shell find $(SRC_DIR) -name '*.S'))
 OBJS := $(CPPOBJS) $(ASMOBJS)
 
-isodir/boot/test-module: $(OUT_DIR)$(EXEC_NAME)
+isodir/boot/$(EXEC_NAME): $(OUT_DIR)$(EXEC_NAME)
 	mkdir -p "$(@D)"
-	objdump -d "$<" | \
-	grep '  [0-9a-e]\{6\}' | sed 's/  [^ \t]*\t//' | sed 's/\t.*//' | sed 's/[ \t]\+/ /g' | tr '\n' ' ' | \
-	sed 's/  / /g' | xargs -n 1 printf '\\\\x%s\n' | xargs -n 1 printf \
-	> "$@"
+	install -m 644 "$<" "$@"
 
 $(OUT_DIR)$(EXEC_NAME): OBJS := $(OBJS)
 $(OUT_DIR)$(EXEC_NAME): LDLIBS := -nostdlib -lgcc
diff --git a/test_module/src/test.cpp b/test_module/src/test.cpp
index c694e32..b2d8219 100644
--- a/test_module/src/test.cpp
+++ b/test_module/src/test.cpp
@@ -1,9 +1,13 @@
 extern "C" void print(char c);
 
-extern "C" void _start() {
-	const char str[4] = "AH\n";
+void printstr(const char* str) {
 	for (int i = 0; str[i] != '\0'; i++) {
 		print(str[i]);
 	}
+}
+
+extern "C" void _start() {
+	const char* str = "ACAB cependant.\n";
+	printstr(str);
 	while (true) {}
 }
-- 
2.46.0