ajhahn.de
← FlashOS
Zig 101 lines
// 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");
    }
}