diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 61aebb6f..4c871884 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,3 +38,5 @@ jobs: run: zig*/zig build rt-test -Ddisable-display -Dtest-mode=Panic ${{ matrix.build_mode }} - name: Run runtime test - Scheduler run: zig*/zig build rt-test -Ddisable-display -Dtest-mode=Scheduler ${{ matrix.build_mode }} + - name: Run runtime test - Memory + run: zig*/zig build rt-test -Ddisable-display -Dtest-mode=Memory ${{ matrix.build_mode }} diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index fa7c75b1..f30eb4fa 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -669,6 +669,21 @@ pub fn runtimeTestCheckUserTaskState(ctx: *const CpuState) bool { return ctx.eax == 0xCAFE and ctx.ebx == 0xBEEF; } +/// +/// Trigger a page fault to test paging and its diagnostics +/// +/// Arguments: +/// IN the_vmm: The VMM to get an unallocated test address from +/// +pub fn runtimeTestChecksMem(the_vmm: *const vmm.VirtualMemoryManager(VmmPayload)) void { + var addr = the_vmm.start; + while (addr < the_vmm.end and (the_vmm.isSet(addr) catch unreachable)) { + addr += vmm.BLOCK_SIZE; + } + const should_fault = @intToPtr(*usize, addr).*; + _ = should_fault; +} + test "" { std.testing.refAllDecls(@This()); } diff --git a/src/kernel/arch/x86/paging.zig b/src/kernel/arch/x86/paging.zig index f87abf0a..325c3663 100644 --- a/src/kernel/arch/x86/paging.zig +++ b/src/kernel/arch/x86/paging.zig @@ -416,13 +416,20 @@ pub fn unmap(virtual_start: usize, virtual_end: usize, allocator: Allocator, dir } /// -/// Called when a page fault occurs. This will log the CPU state and control registers. +/// Called when a page fault occurs. +/// This will log the CPU state and control registers as well as some human-readable information. /// /// Arguments: /// IN state: *arch.CpuState - The CPU's state when the fault occurred. /// fn pageFault(state: *arch.CpuState) u32 { - log.info("State: {X}\n", .{state}); + const err = state.error_code; + const diag_present = if (err & 0b1 != 0) "present" else "non-present"; + const diag_rw = if (err & 0b10 != 0) "writing to" else "reading from"; + const diag_ring = if (err & 0b100 != 0) "user" else "kernel"; + const diag_reserved = if (err & 0b1000 != 0) " with reserved bit set" else ""; + const diag_fetch = if (err & 0b10000 != 0) "instruction" else "data"; + log.info("Page fault: {s} process {s} a {s} page during {s} fetch{s}\n", .{ diag_ring, diag_rw, diag_present, diag_fetch, diag_reserved }); var cr0 = asm volatile ("mov %%cr0, %[cr0]" : [cr0] "=r" (-> u32), ); @@ -435,7 +442,8 @@ fn pageFault(state: *arch.CpuState) u32 { var cr4 = asm volatile ("mov %%cr4, %[cr4]" : [cr4] "=r" (-> u32), ); - log.info("CR0: 0x{X}, CR2: 0x{X}, CR3: 0x{X}, CR4: 0x{X}\n", .{ cr0, cr2, cr3, cr4 }); + log.info("CR0: 0x{X}, CR2/address: 0x{X}, CR3: 0x{X}, CR4: 0x{X}, EIP: 0x{X}\n", .{ cr0, cr2, cr3, cr4, state.eip }); + log.info("State: {X}\n", .{state}); @panic("Page fault"); } diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig index d6cd6189..41071197 100644 --- a/src/kernel/kmain.zig +++ b/src/kernel/kmain.zig @@ -81,9 +81,10 @@ export fn kmain(boot_payload: arch.BootPayload) void { panic_root.panic(@errorReturnTrace(), "Failed to initialise panic symbols: {}\n", .{e}); }; - // The VMM runtime tests can't happen until the architecture has initialised itself + // The VMM and mem runtime tests can't happen until the architecture has initialised itself switch (build_options.test_mode) { .Initialisation => vmm.runtimeTests(arch.VmmPayload, kernel_vmm, &mem_profile), + .Memory => arch.runtimeTestChecksMem(kernel_vmm), else => {}, } diff --git a/test/runtime_test.zig b/test/runtime_test.zig index 6795daf5..700ffb5b 100644 --- a/test/runtime_test.zig +++ b/test/runtime_test.zig @@ -30,6 +30,9 @@ pub const TestMode = enum { /// Run the scheduler runtime test. Scheduler, + /// Run the memory runtime test. + Memory, + /// /// Return a string description for the test mode provided. /// @@ -45,6 +48,7 @@ pub const TestMode = enum { .Initialisation => "Initialisation runtime tests", .Panic => "Panic runtime tests", .Scheduler => "Scheduler runtime tests", + .Memory => "Memory runtime tests", }; } }; @@ -178,6 +182,28 @@ pub const RuntimeStep = struct { } } + /// + /// This tests the kernel's memory system and makes sure the expected diagnostics are printed. + /// + /// Arguments: + /// IN/OUT self: *RuntimeStep - Self. + /// + /// Return: bool + /// Whether the test has passed or failed. + /// + fn test_mem(self: *RuntimeStep) bool { + while (true) { + const msg = self.get_msg() catch return false; + defer self.builder.allocator.free(msg); + + std.debug.print("{s}\n", .{msg}); + + if (std.mem.eql(u8, msg, "[info] (x86_paging): Page fault: kernel process reading from a non-present page during data fetch")) { + return true; + } + } + } + /// /// The make function that is called by the builder. This will create a qemu process with the /// stdout as a Pipe. Then create the read thread to read the logs from the qemu stdout. Then @@ -309,6 +335,7 @@ pub const RuntimeStep = struct { .Initialisation => test_init, .Panic => test_panic, .Scheduler => test_scheduler, + .Memory => test_mem, }, }; return runtime_step;