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,
});
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;
+51 -29
View File
@@ -2,13 +2,21 @@
# Simple kernel symbol dumper
# Usage: ksyms.py --input <kernel binary> --output <output file>
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
@@ -16,7 +24,7 @@ def parse_nm_line(line: str) -> tuple[int, str]:
addr_str, type, name = parts[0], parts[1], parts[2]
# Only get symbols in text
if type.lower() != 't':
if type.lower() != "t":
return None
if name in excluded_symbols:
@@ -25,43 +33,57 @@ def parse_nm_line(line: str) -> tuple[int, str]:
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])
for i, (addr, name) in enumerate(symbols):
escaped = name.replace("\\", "\\\\").replace('"', '\\"')
f.write(f'.Lstr{i}: .asciz "{escaped}"\n')
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")
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")
+3
View File
@@ -12,3 +12,6 @@ SECTIONS {
}
} 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 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);
}
}
+82 -5
View File
@@ -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());
}
+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];
}