feat: better panic handler

This commit is contained in:
Loïc Grégoire
2026-04-17 20:35:44 -04:00
parent 927ea2e1b8
commit c68a93b302
6 changed files with 217 additions and 67 deletions
+37 -22
View File
@@ -45,14 +45,15 @@ pub fn build(b: *std.Build) void {
.optimize = optimize, .optimize = optimize,
}); });
const ksyms_module = b.createModule(.{
.root_source_file = b.path("src/ksyms.zig"), // Use the static wrapper
.optimize = optimize,
});
// first pass // first pass
const empty_cmd = b.addSystemCommand(&.{ "python3", "build/ksyms.py", "--empty" }); const empty_cmd = b.addSystemCommand(&.{ "python3", "build/ksyms.py", "--empty" });
empty_cmd.addArg("--output"); empty_cmd.addArg("--output");
const empty_zig = empty_cmd.addOutputFileArg("ksyms_empty.gen.zig"); const empty_ksyms_s = empty_cmd.addOutputFileArg("ksyms_empty.s");
const empty_ksyms_module = b.createModule(.{
.root_source_file = empty_zig,
.optimize = optimize,
});
const rtl_module = b.createModule(.{ const rtl_module = b.createModule(.{
.root_source_file = b.path("src/rtl/root.zig"), .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); 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) { const arch = if (plat.os == .freestanding) switch (plat.arch) {
.x86_64 => "amd64", .x86_64 => "amd64",
else => @tagName(plat.arch), 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); 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.step.dependOn(&kernel_nosym.step);
ksyms.addFileArg(kernel_nosym.getEmittedBin()); ksyms.addFileArg(kernel_nosym.getEmittedBin());
ksyms.addArg("--output"); ksyms.addArg("--output");
const ksyms_zig = ksyms.addOutputFileArg("ksyms.gen.zig"); const ksyms_s = ksyms.addOutputFileArg("ksyms.s");
const ksyms_module = b.createModule(.{
.root_source_file = ksyms_zig, const base_module = makeBaseModule(b, optimize, config_module, rtl_module, ksyms_module);
.optimize = optimize,
});
// second pass // second pass
const kernel = addKernel(b, plat, optimize, config_module, ksyms_module, rtl_module, base_module, arch_module); 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); b.default_step.dependOn(&b.addInstallArtifact(kernel, .{}).step);
@@ -181,6 +173,26 @@ fn targetQueryForPlatform(plat: config.Platform) std.Target.Query {
return q; 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 { 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)); 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("config", config_module);
kernel.root_module.addImport("ksyms", ksyms_module); kernel.root_module.addImport("ksyms", ksyms_module);
kernel.root_module.addImport("base", base); 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_llvm = true;
kernel.use_lld = true; kernel.use_lld = true;
kernel.root_module.omit_frame_pointer = false;
if (plat.os == .freestanding) { if (plat.os == .freestanding) {
kernel.linkage = .static; kernel.linkage = .static;
kernel.pie = false; kernel.pie = false;
+54 -32
View File
@@ -2,66 +2,88 @@
# Simple kernel symbol dumper # Simple kernel symbol dumper
# Usage: ksyms.py --input <kernel binary> --output <output file> # Usage: ksyms.py --input <kernel binary> --output <output file>
import sys
import subprocess
import argparse 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() parts = line.split()
if len(parts) < 3: if len(parts) < 3:
return None return None
addr_str, type, name = parts[0], parts[1], parts[2] addr_str, type, name = parts[0], parts[1], parts[2]
# Only get symbols in text # Only get symbols in text
if type.lower() != 't': if type.lower() != "t":
return None return None
if name in excluded_symbols: if name in excluded_symbols:
return None return None
addr = int(addr_str, 16) addr = int(addr_str, 16)
return addr, name 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) result = subprocess.run(["nm", "-n", binary], capture_output=True, text=True)
if result.returncode != 0: if result.returncode != 0:
raise RuntimeError(f"nm failed: {result.stderr}") raise RuntimeError(f"nm failed: {result.stderr}")
return result.stdout.splitlines() return result.stdout.splitlines()
parser = argparse.ArgumentParser(description="Generate kernel symbols from a binary") parser = argparse.ArgumentParser(description="Generate kernel symbols from a binary")
parser.add_argument("--input", help="Input kernel 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") parser.add_argument("--empty", action="store_true", help="Generate empty ksyms")
args = parser.parse_args() args = parser.parse_args()
if args.empty: with open(args.output, "w") as f:
with open(args.output, 'w') as f: f.write("/* Auto-generated by build/ksyms.py - do not edit */\n")
f.write("// Auto-generated by build/ksyms.py - do not edit\n\n") f.write(".section .rodata\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)
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 = [] lines = run_nm(args.input)
for line in lines: symbols = []
parsed = parse_nm_line(line) for line in lines:
if parsed: parsed = parse_nm_line(line)
symbols.append(parsed) if parsed:
symbols.append(parsed)
# Sort by address # Sort by address
symbols.sort(key=lambda x: x[0]) symbols.sort(key=lambda x: x[0])
with open(args.output, 'w') as f: for i, (addr, name) in enumerate(symbols):
f.write("// Auto-generated by build/ksyms.py - do not edit\n\n") escaped = name.replace("\\", "\\\\").replace('"', '\\"')
f.write("pub const KSym = struct {\n addr: u64,\n name: []const u8,\n};\n\n") f.write(f'.Lstr{i}: .asciz "{escaped}"\n')
f.write("pub const ksyms: []const KSym = &.{\n")
for addr, name in symbols: f.write("\n.global ksyms_array\n")
escaped = name.replace('\\', '\\\\').replace('"', '\\"') f.write(".global ksyms_count\n")
f.write(f" .{{ .addr = 0x{addr:016x}, .name = \"{escaped}\" }},\n") f.write(".align 8\n")
f.write("};\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")
+4 -1
View File
@@ -10,5 +10,8 @@ SECTIONS {
KEEP(*(.percpu_init_array)) KEEP(*(.percpu_init_array))
__init_array_percpu_end = .; __init_array_percpu_end = .;
} }
} INSERT AFTER .data; } INSERT AFTER .data;
PROVIDE(text_start_addr = ADDR(.text));
PROVIDE(text_end_addr = ADDR(.text) + SIZEOF(.text));
+27 -7
View File
@@ -1,6 +1,8 @@
const amd64 = @import("arch"); const amd64 = @import("arch");
const std = @import("std"); const std = @import("std");
const b = @import("base"); const b = @import("base");
const ke = b.ke;
const ki = ke.private;
extern const __interrupt_vectors: [256]usize; 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 { export fn isr_handler_main(frame: *const amd64.IrqFrame) callconv(.{ .x86_64_sysv = .{} }) void {
if (frame.intno < 32) { 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.err("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.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.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.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.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.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.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("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 cr2 = amd64.read_cr(2);
const cr3 = amd64.read_cr(3); const cr3 = amd64.read_cr(3);
const rflags = amd64.rflags(); 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);
} }
} }
+82 -5
View File
@@ -1,15 +1,92 @@
const std = @import("std"); const std = @import("std");
const config = @import("config");
const b = @import("base"); const b = @import("base");
const ksyms = @import("ksyms");
const ke = b.ke; const ke = b.ke;
const ki = ke.private; 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( pub fn panic(
msg: []const u8, msg: []const u8,
first_trace_addr: ?usize, first_trace_addr: ?usize,
) noreturn { ) noreturn {
_ = first_trace_addr; if (@hasDecl(config, "CONFIG_ARCH_UM")) {
std.debug.defaultPanic(msg, first_trace_addr);
std.log.err("KERNEL PANIC: {s}", .{msg}); }
panic_with_frame(msg, @frameAddress());
while (true) {}
} }
+13
View File
@@ -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];
}