diff --git a/core/src/elf.zig b/core/src/elf.zig new file mode 100644 index 0000000..be6bea7 --- /dev/null +++ b/core/src/elf.zig @@ -0,0 +1,152 @@ +const std = @import("std"); +const target = @import("builtin").target; +const vmm = @import("arch/vmm.zig").arch; +const platform = @import("arch/platform.zig").arch; +const pmm = @import("pmm.zig"); +const debug = @import("arch/debug.zig"); + +const ELFMAG = "\x7fELF"; +const SELFMAG = 4; +const EI_CLASS = 4; // File class byte index +const ELFCLASS64 = 2; // 64-bit objects +const EI_DATA = 5; // Data encoding byte index +const ELFDATA2LSB = 1; // 2's complement, little endian +const ET_EXEC = 2; // Executable file +const PT_LOAD = 1; // Loadable program segment +const EM_MACH = switch (target.cpu.arch) { + .x86_64 => 62, + else => @compileError("unsupported architecture"), +}; + +const Elf64_Ehdr align(8) = packed struct { + e_ident: u128, // Magic number and other info + e_type: u16, // Object file type + e_machine: u16, // Architecture + e_version: u32, // Object file version + e_entry: u64, // Entry point virtual address + e_phoff: u64, // Program header table file offset + e_shoff: u64, // Section header table file offset + e_flags: u32, // Processor-specific flags + e_ehsize: u16, // ELF header size in bytes + e_phentsize: u16, // Program header table entry size + e_phnum: u16, // Program header table entry count + e_shentsize: u16, // Section header table entry size + e_shnum: u16, // Section header table entry count + e_shstrndx: u16, // Section header string table index +}; + +const Elf64_Phdr align(8) = packed struct { + p_type: u32, // Segment type + p_flags: u32, // Segment flags + p_offset: u64, // Segment file offset + p_vaddr: u64, // Segment virtual address + p_paddr: u64, // Segment physical address + p_filesz: u64, // Segment size in file + p_memsz: u64, // Segment size in memory + p_align: u64, // Segment alignment +}; + +const ElfError = error{ + InvalidExecutable, +}; + +fn canExecuteSegment(flags: u32) bool { + return (flags & 1) > 0; +} + +fn canWriteSegment(flags: u32) bool { + return (flags & 2) > 0; +} + +pub fn loadElf(allocator: *pmm.FrameAllocator, directory: *vmm.PageDirectory, base_address: pmm.PhysFrame) !usize { + const address = base_address.virtualAddress(vmm.PHYSICAL_MAPPING_BASE); + + debug.print("Address: {}\n", .{address}); + + const elf_header: *Elf64_Ehdr = @ptrFromInt(address); + + debug.print("ELF header: {}\n", .{elf_header}); + + const e_ident: [*]u8 = @ptrFromInt(address); + + if (!std.mem.eql(u8, e_ident[0..SELFMAG], ELFMAG[0..SELFMAG])) { + debug.print("Error while loading ELF: ELF header has no valid magic\n", .{}); + return error.InvalidExecutable; + } + + if (e_ident[EI_CLASS] != ELFCLASS64) { + debug.print("Error while loading ELF: ELF object is not 64-bit\n", .{}); + return error.InvalidExecutable; + } + + if (e_ident[EI_DATA] != ELFDATA2LSB) { + debug.print("Error while loading ELF: ELF object is not 2's complement little-endian\n", .{}); + return error.InvalidExecutable; + } + + if (elf_header.e_type != ET_EXEC) { + debug.print("Error while loading ELF: ELF object is not an executable\n", .{}); + return error.InvalidExecutable; + } + + if (elf_header.e_machine != EM_MACH) { + debug.print("Error while loading ELF: ELF object's target architecture does not match the current one\n", .{}); + return error.InvalidExecutable; + } + + if (elf_header.e_phnum == 0) { + debug.print("Error while loading ELF: ELF object has no program headers\n", .{}); + return error.InvalidExecutable; + } + + var i: usize = 0; + var program_header: *align(1) Elf64_Phdr = @ptrFromInt(address + elf_header.e_phoff); + + debug.print("Program header address: {x}\n", .{address + elf_header.e_phoff}); + + while (i < elf_header.e_phnum) { + debug.print("Program header: {}\n", .{program_header.*}); + if (program_header.p_type == PT_LOAD) { + debug.print("ELF: Loading segment (offset={d}, base={x}, filesize={d}, memsize={d})\n", .{ program_header.p_offset, program_header.p_vaddr, program_header.p_filesz, program_header.p_memsz }); + + const vaddr_diff: u64 = @rem(program_header.p_vaddr, platform.PAGE_SIZE); + const base_vaddr: u64 = program_header.p_vaddr - vaddr_diff; + + var flags: u32 = @intFromEnum(vmm.Flags.User) | @intFromEnum(vmm.Flags.NoExecute); + if (canWriteSegment(program_header.p_flags)) flags |= @intFromEnum(vmm.Flags.ReadWrite); + if (canExecuteSegment(program_header.p_flags)) flags &= ~@as(u32, @intFromEnum(vmm.Flags.NoExecute)); + + // Allocate physical memory for the segment + try vmm.allocAndMap(allocator, directory, base_vaddr, try std.math.divCeil(usize, program_header.p_memsz + vaddr_diff, platform.PAGE_SIZE), flags); + + try vmm.memsetUser(directory, vmm.PHYSICAL_MAPPING_BASE, base_vaddr, 0, vaddr_diff); + + try vmm.copyToUser(directory, vmm.PHYSICAL_MAPPING_BASE, program_header.p_vaddr, @ptrFromInt(address + program_header.p_offset), program_header.p_filesz); + + const bss_size = program_header.p_memsz - program_header.p_filesz; + + try vmm.memsetUser(directory, vmm.PHYSICAL_MAPPING_BASE, program_header.p_vaddr + program_header.p_filesz, 0, bss_size); + } else { + debug.print("ELF: Encountered non-loadable program header, skipping\n", .{}); + } + + i += 1; + + const new_address = address + elf_header.e_phoff + (i * elf_header.e_phentsize); + + debug.print("Program header address: {x}\n", .{new_address}); + + program_header = @ptrFromInt(new_address); + } + + return elf_header.e_entry; +} + +pub fn allocateStack(allocator: *pmm.FrameAllocator, directory: *vmm.PageDirectory, stack_top: usize, stack_size: usize) !usize { + const pages = try std.math.divCeil(usize, stack_size, platform.PAGE_SIZE); + const stack_bottom = stack_top - (pages * platform.PAGE_SIZE); + + try vmm.allocAndMap(allocator, directory, stack_bottom, pages, @intFromEnum(vmm.Flags.ReadWrite) | @intFromEnum(vmm.Flags.User) | @intFromEnum(vmm.Flags.NoExecute)); + + return stack_top - 16; +} diff --git a/core/src/main.zig b/core/src/main.zig index b1354c6..d3a30ac 100644 --- a/core/src/main.zig +++ b/core/src/main.zig @@ -1,14 +1,23 @@ const std = @import("std"); const easyboot = @cImport(@cInclude("easyboot.h")); const debug = @import("arch/debug.zig"); +const cpu = @import("arch/cpu.zig"); const platform = @import("arch/platform.zig").arch; const interrupts = @import("arch/interrupts.zig").arch; const vmm = @import("arch/vmm.zig").arch; const multiboot = @import("multiboot.zig"); const pmm = @import("pmm.zig"); +const thread = @import("thread.zig"); +const elf = @import("elf.zig"); const MultibootInfo = [*c]u8; +const Context = struct { + allocator: *pmm.FrameAllocator, + directory: *vmm.PageDirectory, + regs: *interrupts.InterruptStackFrame, +}; + export fn _start(magic: u32, info: MultibootInfo) callconv(.C) noreturn { interrupts.disableInterrupts(); @@ -17,40 +26,87 @@ export fn _start(magic: u32, info: MultibootInfo) callconv(.C) noreturn { while (true) {} } - debug.print("Hello world from the kernel!\n", .{}); - multiboot.parseMultibootTags(@ptrCast(info)); platform.platformInit(); - debug.print("GDT initialized\n", .{}); + const tag = multiboot.findMultibootTag(easyboot.multiboot_tag_mmap_t, @ptrCast(info)) orelse { + debug.print("error: No memory map multiboot tag found!\n", .{}); + while (true) {} + unreachable; + }; - if (multiboot.findMultibootTag(easyboot.multiboot_tag_mmap_t, @ptrCast(info))) |tag| { - var allocator = pmm.initializeFrameAllocator(tag) catch |err| { - debug.print("Error while initializing frame allocator: {}\n", .{err}); - while (true) {} - }; + var allocator = pmm.initializeFrameAllocator(tag) catch |err| { + debug.print("Error while initializing frame allocator: {}\n", .{err}); + while (true) {} + }; - var init_directory = std.mem.zeroes(vmm.PageDirectory); - const base: usize = vmm.createInitialMappings(&allocator, tag, &init_directory) catch |err| { - debug.print("Error while creating initial mappings: {}\n", .{err}); - while (true) {} - }; + var dir: vmm.PageDirectory = std.mem.zeroes(vmm.PageDirectory); + const base: usize = vmm.createInitialMappings(&allocator, tag, &dir) catch |err| { + debug.print("Error while creating initial mappings: {}\n", .{err}); + while (true) {} + }; - debug.print("Physical memory base mapping for init: {x}\n", .{base}); - } else { - debug.print("No memory map multiboot tag found!\n", .{}); - } + debug.print("Physical memory base mapping for init: {x}\n", .{base}); + + const frame = pmm.allocFrame(&allocator) catch |err| { + debug.print("Error while creating frame for user page directory: {}\n", .{err}); + while (true) {} + }; + + // At this point the physical address space is already mapped into kernel virtual memory. + const init_directory: *vmm.PageDirectory = @ptrFromInt(frame.virtualAddress(vmm.PHYSICAL_MAPPING_BASE)); + init_directory.* = dir; + + cpu.setupCore(&allocator) catch |err| { + debug.print("Error while setting up core-specific scheduler structures: {}\n", .{err}); + while (true) {} + }; + + const init = thread.createThreadControlBlock(&allocator) catch |err| { + debug.print("Error while creating thread control block for init: {}\n", .{err}); + while (true) {} + }; + + init.directory = init_directory; + thread.arch.initUserRegisters(&init.regs); + thread.arch.setArgument(&init.regs, base); + + thread.addThreadToScheduler(cpu.thisCore(), init); + + const ctx = Context{ .allocator = &allocator, .directory = init_directory, .regs = &init.regs }; + + multiboot.findMultibootTags(easyboot.multiboot_tag_module_t, @ptrCast(info), struct { + fn handler(mod: *easyboot.multiboot_tag_module_t, c: *const anyopaque) void { + const context: *const Context = @alignCast(@ptrCast(c)); + const name = "init"; + if (std.mem.eql(u8, mod.string()[0..name.len], name[0..name.len])) { + const phys_frame = pmm.PhysFrame{ .address = mod.mod_start }; + debug.print("Loading init from module at address {x}, virtual {x}\n", .{ mod.mod_start, phys_frame.virtualAddress(vmm.PHYSICAL_MAPPING_BASE) }); + const entry = elf.loadElf(context.allocator, context.directory, pmm.PhysFrame{ .address = mod.mod_start }) catch |err| { + debug.print("Error while loading ELF file for init: {}\n", .{err}); + while (true) {} + }; + thread.arch.setAddress(context.regs, entry); + } + } + }.handler, &ctx); + + const default_stack_size = 0x80000; // 512 KiB. + const stack = elf.allocateStack(&allocator, init_directory, base - platform.PAGE_SIZE, default_stack_size) catch |err| { + debug.print("Error while creating stack for init: {}\n", .{err}); + while (true) {} + }; + thread.arch.setStack(&init.regs, stack); platform.platformEndInit(); - asm volatile ("int3"); - - while (true) {} + thread.enterTask(init); } pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { debug.print("--- KERNEL PANIC! ---\n", .{}); debug.print("{s}\n", .{message}); + debug.print("return address: {x}\n", .{@returnAddress()}); while (true) {} }