From c68a93b302a7c7b32a8591aff0409df2e6331f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Gr=C3=A9goire?= Date: Fri, 17 Apr 2026 20:35:44 -0400 Subject: [PATCH] feat: better panic handler --- build.zig | 59 +++++++++++++++--------- build/ksyms.py | 86 ++++++++++++++++++++++------------- build/linker-scripts/uml.lds | 5 ++- src/kern/amd64/int.zig | 34 +++++++++++--- src/kern/panic.zig | 87 +++++++++++++++++++++++++++++++++--- src/ksyms.zig | 13 ++++++ 6 files changed, 217 insertions(+), 67 deletions(-) create mode 100644 src/ksyms.zig diff --git a/build.zig b/build.zig index 0297fc3..9fc4ab1 100644 --- a/build.zig +++ b/build.zig @@ -45,14 +45,15 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); + const ksyms_module = b.createModule(.{ + .root_source_file = b.path("src/ksyms.zig"), // Use the static wrapper + .optimize = optimize, + }); + // first pass const empty_cmd = b.addSystemCommand(&.{ "python3", "build/ksyms.py", "--empty" }); empty_cmd.addArg("--output"); - const empty_zig = empty_cmd.addOutputFileArg("ksyms_empty.gen.zig"); - const empty_ksyms_module = b.createModule(.{ - .root_source_file = empty_zig, - .optimize = optimize, - }); + const empty_ksyms_s = empty_cmd.addOutputFileArg("ksyms_empty.s"); const rtl_module = b.createModule(.{ .root_source_file = b.path("src/rtl/root.zig"), @@ -61,17 +62,6 @@ pub fn build(b: *std.Build) void { rtl_module.addImport("rtl", rtl_module); - const base_module = b.createModule(.{ - .root_source_file = b.path("src/root.zig"), - .imports = &.{ - .{ .name = "config", .module = config_module }, - .{ .name = "rtl", .module = rtl_module }, - }, - .optimize = optimize, - }); - - base_module.addImport("base", base_module); - const arch = if (plat.os == .freestanding) switch (plat.arch) { .x86_64 => "amd64", else => @tagName(plat.arch), @@ -86,7 +76,10 @@ pub fn build(b: *std.Build) void { }, }); - const kernel_nosym = addKernel(b, plat, optimize, config_module, empty_ksyms_module, rtl_module, base_module, arch_module); + const base_nosym = makeBaseModule(b, optimize, config_module, rtl_module, ksyms_module); + const kernel_nosym = addKernel(b, plat, optimize, config_module, ksyms_module, rtl_module, base_nosym, arch_module); + + kernel_nosym.root_module.addAssemblyFile(empty_ksyms_s); b.step("kernel-nosym", "Build nosym kernel").dependOn(&kernel_nosym.step); @@ -96,14 +89,13 @@ pub fn build(b: *std.Build) void { ksyms.step.dependOn(&kernel_nosym.step); ksyms.addFileArg(kernel_nosym.getEmittedBin()); ksyms.addArg("--output"); - const ksyms_zig = ksyms.addOutputFileArg("ksyms.gen.zig"); - const ksyms_module = b.createModule(.{ - .root_source_file = ksyms_zig, - .optimize = optimize, - }); + const ksyms_s = ksyms.addOutputFileArg("ksyms.s"); + + const base_module = makeBaseModule(b, optimize, config_module, rtl_module, ksyms_module); // second pass const kernel = addKernel(b, plat, optimize, config_module, ksyms_module, rtl_module, base_module, arch_module); + kernel.root_module.addAssemblyFile(ksyms_s); b.default_step.dependOn(&b.addInstallArtifact(kernel, .{}).step); @@ -181,6 +173,26 @@ fn targetQueryForPlatform(plat: config.Platform) std.Target.Query { return q; } +fn makeBaseModule( + b: *std.Build, + optimize: std.builtin.OptimizeMode, + config_module: *std.Build.Module, + rtl_module: *std.Build.Module, + ksyms_module: *std.Build.Module, +) *std.Build.Module { + const base = b.createModule(.{ + .root_source_file = b.path("src/root.zig"), + .imports = &.{ + .{ .name = "config", .module = config_module }, + .{ .name = "rtl", .module = rtl_module }, + .{ .name = "ksyms", .module = ksyms_module }, + }, + .optimize = optimize, + }); + base.addImport("base", base); + return base; +} + fn addKernel(b: *std.Build, plat: config.Platform, optimize: std.builtin.OptimizeMode, config_module: *std.Build.Module, ksyms_module: *std.Build.Module, rtl: *std.Build.Module, base: *std.Build.Module, arch: *std.Build.Module) *std.Build.Step.Compile { const target = b.resolveTargetQuery(targetQueryForPlatform(plat)); @@ -205,6 +217,7 @@ fn addKernel(b: *std.Build, plat: config.Platform, optimize: std.builtin.Optimiz }), }); + base.addImport("ksyms", ksyms_module); kernel.root_module.addImport("config", config_module); kernel.root_module.addImport("ksyms", ksyms_module); kernel.root_module.addImport("base", base); @@ -213,6 +226,8 @@ fn addKernel(b: *std.Build, plat: config.Platform, optimize: std.builtin.Optimiz kernel.use_llvm = true; kernel.use_lld = true; + kernel.root_module.omit_frame_pointer = false; + if (plat.os == .freestanding) { kernel.linkage = .static; kernel.pie = false; diff --git a/build/ksyms.py b/build/ksyms.py index 70b39ac..ffd266e 100755 --- a/build/ksyms.py +++ b/build/ksyms.py @@ -2,66 +2,88 @@ # Simple kernel symbol dumper # Usage: ksyms.py --input --output -import sys -import subprocess import argparse +import subprocess +import sys -excluded_symbols = ['text_start_addr', 'text_end_addr', 'rodata_start_addr', 'rodata_end_addr', 'data_start_addr', 'data_end_addr'] +excluded_symbols = [ + "text_start_addr", + "text_end_addr", + "rodata_start_addr", + "rodata_end_addr", + "data_start_addr", + "data_end_addr", +] -def parse_nm_line(line: str) -> tuple[int, str]: + +def parse_nm_line(line: str): parts = line.split() if len(parts) < 3: return None addr_str, type, name = parts[0], parts[1], parts[2] - # Only get symbols in text - if type.lower() != 't': + # Only get symbols in text + if type.lower() != "t": return None - + if name in excluded_symbols: return None addr = int(addr_str, 16) return addr, name -def run_nm(binary: str) -> list[str]: +def run_nm(binary: str): result = subprocess.run(["nm", "-n", binary], capture_output=True, text=True) if result.returncode != 0: raise RuntimeError(f"nm failed: {result.stderr}") return result.stdout.splitlines() + parser = argparse.ArgumentParser(description="Generate kernel symbols from a binary") parser.add_argument("--input", help="Input kernel binary") -parser.add_argument("--output", help="Output C file") +parser.add_argument("--output", help="Output Assembly file (.s)") parser.add_argument("--empty", action="store_true", help="Generate empty ksyms") args = parser.parse_args() -if args.empty: - with open(args.output, 'w') as f: - f.write("// Auto-generated by build/ksyms.py - do not edit\n\n") - f.write("pub const KSym = struct {\n addr: u64,\n name: []const u8,\n};\n\n") - f.write("pub const ksyms: []const KSym = &.{};\n") - sys.exit(0) +with open(args.output, "w") as f: + f.write("/* Auto-generated by build/ksyms.py - do not edit */\n") + f.write(".section .rodata\n") -lines = run_nm(args.input) + if args.empty: + f.write(".global ksyms_array\n") + f.write(".global ksyms_count\n") + f.write(".align 8\n") + f.write("ksyms_array:\n") + f.write("ksyms_count:\n") + f.write(" .quad 0\n") + sys.exit(0) -symbols = [] -for line in lines: - parsed = parse_nm_line(line) - if parsed: - symbols.append(parsed) - + lines = run_nm(args.input) + symbols = [] + for line in lines: + parsed = parse_nm_line(line) + if parsed: + symbols.append(parsed) -# Sort by address -symbols.sort(key=lambda x: x[0]) + # Sort by address + symbols.sort(key=lambda x: x[0]) -with open(args.output, 'w') as f: - f.write("// Auto-generated by build/ksyms.py - do not edit\n\n") - f.write("pub const KSym = struct {\n addr: u64,\n name: []const u8,\n};\n\n") - f.write("pub const ksyms: []const KSym = &.{\n") - for addr, name in symbols: - escaped = name.replace('\\', '\\\\').replace('"', '\\"') - f.write(f" .{{ .addr = 0x{addr:016x}, .name = \"{escaped}\" }},\n") - f.write("};\n") + for i, (addr, name) in enumerate(symbols): + escaped = name.replace("\\", "\\\\").replace('"', '\\"') + f.write(f'.Lstr{i}: .asciz "{escaped}"\n') + + f.write("\n.global ksyms_array\n") + f.write(".global ksyms_count\n") + f.write(".align 8\n") + f.write("ksyms_array:\n") + + for i, (addr, name) in enumerate(symbols): + name_len = len(name.encode("utf-8")) + f.write(f" .quad 0x{addr:016x}\n") + f.write(f" .quad .Lstr{i}\n") + f.write(f" .quad {name_len}\n") + + f.write("\nksyms_count:\n") + f.write(f" .quad {len(symbols)}\n") diff --git a/build/linker-scripts/uml.lds b/build/linker-scripts/uml.lds index 49418fa..65b3778 100644 --- a/build/linker-scripts/uml.lds +++ b/build/linker-scripts/uml.lds @@ -10,5 +10,8 @@ SECTIONS { KEEP(*(.percpu_init_array)) __init_array_percpu_end = .; } - + } INSERT AFTER .data; + +PROVIDE(text_start_addr = ADDR(.text)); +PROVIDE(text_end_addr = ADDR(.text) + SIZEOF(.text)); diff --git a/src/kern/amd64/int.zig b/src/kern/amd64/int.zig index 1c6571d..e03189f 100644 --- a/src/kern/amd64/int.zig +++ b/src/kern/amd64/int.zig @@ -1,6 +1,8 @@ const amd64 = @import("arch"); const std = @import("std"); const b = @import("base"); +const ke = b.ke; +const ki = ke.private; extern const __interrupt_vectors: [256]usize; @@ -43,17 +45,35 @@ const exception_msg = [32][]const u8{ export fn isr_handler_main(frame: *const amd64.IrqFrame) callconv(.{ .x86_64_sysv = .{} }) void { if (frame.intno < 32) { - std.log.info("Unhandled exception: 0x{x} ({s}), err=0x{x}, pc=0x{x}", .{ frame.intno, exception_msg[frame.intno], frame.errcode, frame.rip }); - std.log.info("RAX=0x{x:0>16} RBX=0x{x:0>16} RCX=0x{x:0>16} RDX=0x{x:0>16}", .{ frame.rax, frame.rbx, frame.rcx, frame.rdx }); - std.log.info("RSI=0x{x:0>16} RDI=0x{x:0>16} RBP=0x{x:0>16} RSP=0x{x:0>16}", .{ frame.rsi, frame.rdi, frame.rbp, frame.rsp }); - std.log.info("R8= 0x{x:0>16} R9=0x{x:0>16} R10=0x{x:0>16} R11=0x{x:0>16}", .{ frame.r8, frame.r9, frame.r10, frame.r11 }); - std.log.info("R12=0x{x:0>16} R13=0x{x:0>16} R14=0x{x:0>16} R15=0x{x:0>16}", .{ frame.r12, frame.r13, frame.r14, frame.r15 }); + std.log.err("Unhandled exception: 0x{x} ({s}), err=0x{x}, pc=0x{x}", .{ frame.intno, exception_msg[frame.intno], frame.errcode, frame.rip }); + std.log.err("RAX=0x{x:0>16} RBX=0x{x:0>16} RCX=0x{x:0>16} RDX=0x{x:0>16}", .{ frame.rax, frame.rbx, frame.rcx, frame.rdx }); + std.log.err("RSI=0x{x:0>16} RDI=0x{x:0>16} RBP=0x{x:0>16} RSP=0x{x:0>16}", .{ frame.rsi, frame.rdi, frame.rbp, frame.rsp }); + std.log.err("R8= 0x{x:0>16} R9=0x{x:0>16} R10=0x{x:0>16} R11=0x{x:0>16}", .{ frame.r8, frame.r9, frame.r10, frame.r11 }); + std.log.err("R12=0x{x:0>16} R13=0x{x:0>16} R14=0x{x:0>16} R15=0x{x:0>16}", .{ frame.r12, frame.r13, frame.r14, frame.r15 }); const cr2 = amd64.read_cr(2); const cr3 = amd64.read_cr(3); const rflags = amd64.rflags(); - std.log.info("CR2=0x{x:0>16} CR3=0x{x:0>16} RFLAGS={x}", .{ cr2, cr3, @as(u64, @bitCast(rflags)) }); - @panic("Unhandled exception"); + var buf: [128]u8 = undefined; + var writer = std.Io.Writer.fixed(&buf); + + writer.print("0x{x:0>8} [", .{@as(u64, @bitCast(rflags))}) catch {}; + if (rflags.carry) writer.writeAll(" CF") catch {}; + if (rflags.parity) writer.writeAll(" PF") catch {}; + if (rflags.auxiliary) writer.writeAll(" AF") catch {}; + if (rflags.zero) writer.writeAll(" ZF") catch {}; + if (rflags.sign) writer.writeAll(" SF") catch {}; + if (rflags.trap) writer.writeAll(" TF") catch {}; + if (rflags.interrupt_enable) writer.writeAll(" IF") catch {}; + if (rflags.direction) writer.writeAll(" DF") catch {}; + if (rflags.overflow) writer.writeAll(" OF") catch {}; + if (rflags.resume_) writer.writeAll(" RF") catch {}; + if (rflags.virtual_8086_mode) writer.writeAll(" VM") catch {}; + writer.print(" IOPL={d}]", .{rflags.iopl}) catch {}; + + std.log.err("CR2=0x{x:0>16} CR3=0x{x:0>16} RFLAGS={s}", .{ cr2, cr3, buf[0..writer.end] }); + + ki.panic.panic_with_frame("Unhandled exception", frame.rbp); } } diff --git a/src/kern/panic.zig b/src/kern/panic.zig index 4e06fb3..07844b9 100644 --- a/src/kern/panic.zig +++ b/src/kern/panic.zig @@ -1,15 +1,92 @@ const std = @import("std"); +const config = @import("config"); const b = @import("base"); +const ksyms = @import("ksyms"); const ke = b.ke; const ki = ke.private; +extern var text_start_addr: u8; +extern var text_end_addr: u8; + +const StackFrame = struct { + prev: ?*StackFrame, + return_addr: usize, +}; + +const Symbol = struct { + name: []const u8, + offset: usize, +}; + +fn get_symbol_name(addr: usize) ?Symbol { + const symbols = ksyms.get_symbols(); + + // Do a binary search on the ksyms array. + // The address may not be exact, so we look for the closest + // symbol that is less than or equal to the address. + var left: usize = 0; + var right: usize = symbols.len; + while (left < right) { + const mid = left + (right - left) / 2; + const sym = symbols[mid]; + if (sym.addr == addr) { + return .{ .name = sym.name_ptr[0..sym.name_len], .offset = 0 }; + } else if (sym.addr < addr) { + left = mid + 1; + } else { + right = mid; + } + } + + if (left > 0) { + const sym = symbols[left - 1]; + + if (sym.addr <= addr) { + return .{ .name = sym.name_ptr[0..sym.name_len], .offset = addr - sym.addr }; + } + } + + return null; +} + +fn is_kernel_text(addr: usize) bool { + const s = @intFromPtr(&text_start_addr); + const e = @intFromPtr(&text_end_addr); + return addr >= s and addr < e; +} + +fn walk_stack_frame(base: usize) void { + var frame: ?*StackFrame = @ptrFromInt(base); + var depth: usize = 0; + while (frame) |f| : (depth += 1) { + if (depth > 64) break; + const ret_addr = f.return_addr; + if (!is_kernel_text(ret_addr)) { + break; + } + const sym = get_symbol_name(ret_addr) orelse Symbol{ .name = "???", .offset = 0 }; + std.log.err(" #{d} {s}+0x{x} - 0x{x}", .{ depth, sym.name, sym.offset, ret_addr }); + frame = f.prev; + } +} + +pub fn panic_with_frame( + msg: []const u8, + frame: usize, +) noreturn { + std.log.err("KERNEL PANIC: {s}", .{msg}); + std.log.err("Stack trace:", .{}); + walk_stack_frame(frame); + + while (true) {} +} + pub fn panic( msg: []const u8, first_trace_addr: ?usize, ) noreturn { - _ = first_trace_addr; - - std.log.err("KERNEL PANIC: {s}", .{msg}); - - while (true) {} + if (@hasDecl(config, "CONFIG_ARCH_UM")) { + std.debug.defaultPanic(msg, first_trace_addr); + } + panic_with_frame(msg, @frameAddress()); } diff --git a/src/ksyms.zig b/src/ksyms.zig new file mode 100644 index 0000000..84e6235 --- /dev/null +++ b/src/ksyms.zig @@ -0,0 +1,13 @@ +pub const KSym = extern struct { + addr: u64, + name_ptr: [*]const u8, + name_len: usize, +}; + +extern const ksyms_array: [0]KSym; +extern const ksyms_count: usize; + +pub fn get_symbols() []const KSym { + const ptr = @as([*]const KSym, @ptrCast(&ksyms_array)); + return ptr[0..ksyms_count]; +}