// Kernel symbol table — generated by scripts/generate_syms.zig at build time // and linked into the `_symbols` section. ksym_name_from_addr lets the trace // system print the name of the function that was hooked. const PL: i32 = 1; extern fn trace_output(interface: i32, str: [*:0]const u8) void; extern fn trace_output_u64(interface: i32, in: u64) void; extern var ksyms: u64; // Same constant as src/sys.zig / src/fork.zig / src/trace/trace_main.zig. // The compiler emits `&ksyms` through a literal-pool quad whose stored // value is the link-time low VA (e.g. 0x4009E648 on virt). Boot-time // callers of ksTable() are fine because TTBR0 still holds id_pg_dir, // which maps the low aliases. Once the first user process runs, TTBR0 // is swapped to a user pgd that does not map kernel low VAs — and // ksym_name_from_addr is now reached from the trace hook fired by // patched copy_process / _schedule / do_wait under user context. ORing // LINEAR_MAP_BASE here promotes the low VA to its TTBR1 alias before // any per-entry load. Idempotent if &ksyms is already high. const LINEAR_MAP_BASE: u64 = 0xFFFF000000000000; const KernelSymbol = extern struct { address: u64, name: [56]u8, }; fn ksTable() [*]KernelSymbol { return @ptrFromInt(@intFromPtr(&ksyms) | LINEAR_MAP_BASE); } var ksyms_count: u64 = 0; /// Linear scan for the symbol whose address matches `addr`. export fn ksym_name_from_addr(addr: u64) ?[*:0]const u8 { const table = ksTable(); var i: usize = 0; while (i < ksyms_count) : (i += 1) { if (table[i].address == addr) { return @ptrCast(&table[i].name[0]); } } return null; } /// Nearest symbol at or below `addr` — for return/interrupt addresses that /// land mid-function, where the exact-match scan above would miss. Picks the /// entry with the greatest address <= addr. Used only by the -Dtrace /// sampler (src/trace/sampler.zig); a non-trace build never references it, /// so it is not emitted and the kernel image stays byte-identical. pub fn ksym_nearest(addr: u64) ?[*:0]const u8 { const table = ksTable(); // Sampled PCs/LRs are TTBR1 high-half VAs (LINEAR_MAP_BASE set); the table // stores low link addresses (see symbol_area.S). Strip the alias bits so // the nearest-match runs in link-address space — otherwise every high VA // sits above all symbols and resolves to the topmost one (_kernel_pa_end). // Idempotent on an already-low address. const link = addr & ~LINEAR_MAP_BASE; var best: ?usize = null; var best_addr: u64 = 0; var i: usize = 0; while (i < ksyms_count) : (i += 1) { const a = table[i].address; if (a <= link and a >= best_addr) { best_addr = a; best = i; } } if (best) |b| return @ptrCast(&table[b].name[0]); return null; } /// Walk the table until the sentinel (zero-name) entry to find the count. export fn cal_ksyms_count() void { const table = ksTable(); var count: u64 = 0; var i: usize = 0; while (table[i].name[0] != 0) : (i += 1) { count += 1; } ksyms_count = count; } export fn ksyms_init() void { const table = ksTable(); cal_ksyms_count(); trace_output(PL, "found "); trace_output_u64(PL, ksyms_count); trace_output(PL, " kernel symbols\n"); var i: usize = 0; while (i < ksyms_count) : (i += 1) { trace_output_u64(PL, table[i].address); trace_output(PL, " "); trace_output(PL, @ptrCast(&table[i].name[0])); trace_output(PL, "\n"); } }