From 36f3175a7fee0a8681eaa63b990ae2b0947e6105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Gr=C3=A9goire?= Date: Thu, 20 Aug 2026 19:53:49 -0400 Subject: [PATCH] build: rewrite ksyms script in zig --- build.zig | 20 +++--- build/ksyms.py | 89 -------------------------- build/ksyms.zig | 161 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+), 96 deletions(-) delete mode 100755 build/ksyms.py create mode 100644 build/ksyms.zig diff --git a/build.zig b/build.zig index a8d071d..00a62ed 100644 --- a/build.zig +++ b/build.zig @@ -36,11 +36,19 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); - // first pass - const empty_cmd = b.addSystemCommand(&.{ "python3", "build/ksyms.py", "--empty" }); - empty_cmd.addArg("--output"); + const ksyms_exe = b.addExecutable(.{ + .name = "ksyms", + .root_module = b.createModule(.{ + .root_source_file = b.path("build/ksyms.zig"), + .target = b.graph.host, + }), + }); + + const empty_cmd = b.addRunArtifact(ksyms_exe); const empty_ksyms_s = empty_cmd.addOutputFileArg("ksyms_empty.s"); + empty_cmd.stdio = .inherit; + const rtl_module = b.createModule(.{ .root_source_file = b.path("src/rtl/root.zig"), .optimize = optimize, @@ -69,12 +77,10 @@ pub fn build(b: *std.Build) void { b.step("kernel-nosym", "Build nosym kernel").dependOn(&kernel_nosym.step); // generate symbols - const ksyms = b.addSystemCommand(&.{ "python3", "build/ksyms.py" }); - ksyms.addArg("--input"); + const ksyms = b.addRunArtifact(ksyms_exe); ksyms.step.dependOn(&kernel_nosym.step); - ksyms.addFileArg(kernel_nosym.getEmittedBin()); - ksyms.addArg("--output"); const ksyms_s = ksyms.addOutputFileArg("ksyms.s"); + ksyms.addFileArg(kernel_nosym.getEmittedBin()); // second pass const kernel = addKernel(b, plat, optimize, config_module, ksyms_module, rtl_module, arch_module); diff --git a/build/ksyms.py b/build/ksyms.py deleted file mode 100755 index ffd266e..0000000 --- a/build/ksyms.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -# Simple kernel symbol dumper -# Usage: ksyms.py --input --output - -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", -] - - -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": - return None - - if name in excluded_symbols: - return None - - addr = int(addr_str, 16) - return addr, name - - -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 Assembly file (.s)") -parser.add_argument("--empty", action="store_true", help="Generate empty ksyms") -args = parser.parse_args() - -with open(args.output, "w") as f: - f.write("/* Auto-generated by build/ksyms.py - do not edit */\n") - f.write(".section .rodata\n") - - 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) - - 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]) - - 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/ksyms.zig b/build/ksyms.zig new file mode 100644 index 0000000..12d0d36 --- /dev/null +++ b/build/ksyms.zig @@ -0,0 +1,161 @@ +//! Script to generate ksyms +//! Usage: ksyms.zig +const std = @import("std"); +const builtin = @import("builtin"); +const elf = std.elf; +const Io = std.Io; +const Allocator = std.mem.Allocator; + +const Symbol = struct { + addr: usize, + name: []const u8, +}; + +const file_hdr = + \\/* Auto-generated by build/ksyms.zig - do not edit */ + \\.section .rodata + \\.global ksyms_array + \\.global ksyms_count + \\.align 8 + \\ +; + +const empty_str = file_hdr ++ + \\ksyms_array: + \\ksyms_count: + \\.quad 0 +; + +fn getElfString(strtab: []const u8, offset: u32) []const u8 { + const remainder = strtab[offset..]; + const str_ptr: [*:0]const u8 = @ptrCast(remainder.ptr); + const len = std.mem.len(str_ptr); + return remainder[0..len]; +} + +fn getSymbols(allocator: Allocator, bytes: []u8) !std.ArrayList(Symbol) { + var reader = Io.Reader.fixed(bytes); + + const header = try std.elf.Header.read(&reader); + + const shstrhdr_addr = header.shoff + (header.shentsize * header.shstrndx); + + const shstrhdr = std.mem.bytesAsValue(elf.Shdr, bytes[shstrhdr_addr..]); + const section_names: []const u8 = bytes[shstrhdr.sh_offset..]; + + reader.seek = header.shoff; + + var text_section_index: usize = undefined; + var symtab: elf.Shdr = undefined; + + const shdrs = std.mem.bytesAsSlice( + elf.Shdr, + bytes[header.shoff..][0 .. header.shnum * header.shentsize], + ); + + for (0..header.shnum) |i| { + const name = getElfString(section_names, shdrs[i].sh_name); + + if (std.mem.eql(u8, name, ".text")) { + text_section_index = i; + } else if (shdrs[i].sh_type == elf.SHT_SYMTAB) { + symtab = shdrs[i]; + } + } + + const sym_strtab_shdr_addr = header.shoff + (header.shentsize * symtab.sh_link); + const sym_strtab_shdr = std.mem.bytesAsValue(elf.Shdr, bytes[sym_strtab_shdr_addr..]); + const symbol_names: []const u8 = bytes[sym_strtab_shdr.sh_offset..]; + + const how_many = symtab.sh_size / symtab.sh_entsize; + const symbols: [*]elf.Sym = @ptrCast(@alignCast(bytes[symtab.sh_offset..].ptr)); + + var ret = try std.ArrayList(Symbol).initCapacity(allocator, how_many); + + for (0..how_many) |i| { + const sym = symbols[i]; + + if (sym.st_shndx == elf.SHN_UNDEF) + continue; + + if (sym.st_name == 0) + continue; + + if (sym.st_shndx != text_section_index) { + continue; + } + + const name = getElfString(symbol_names, sym.st_name); + + try ret.append(allocator, .{ + .addr = sym.st_value, + .name = name, + }); + } + + return ret; +} + +fn lessThan(_: void, a: Symbol, b: Symbol) bool { + return a.addr < b.addr; +} + +fn generateSymbols(alloc: Allocator, file: []u8, writer: *Io.Writer) !void { + const symbols = try getSymbols(alloc, file); + + std.mem.sort(Symbol, symbols.items, {}, lessThan); + + _ = try writer.write(file_hdr); + _ = try writer.write("ksyms_array:\n"); + + for (symbols.items, 0..) |sym, i| { + try writer.print(".quad 0x{x}\n", .{sym.addr}); + try writer.print(".quad .Lstr{}\n", .{i}); + try writer.print(".quad {}\n", .{sym.name.len}); + } + + try writer.print("ksyms_count:\n .quad {}\n", .{symbols.items.len}); + + for (symbols.items, 0..) |sym, i| { + if (std.mem.indexOfScalar(u8, sym.name, '"') == null) { + try writer.print(".Lstr{}: .asciz \"{s}\"\n", .{ i, sym.name }); + } else { + const size = std.mem.replacementSize(u8, sym.name, "\"", "\\\""); + const escaped = try alloc.alloc(u8, size); + + _ = std.mem.replace(u8, sym.name, "\"", "\\\"", escaped); + try writer.print(".Lstr{}: .asciz \"{s}\" \n", .{ i, escaped }); + } + } +} + +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + const args = init.minimal.args; + const args_slice = try args.toSlice(arena); + + const outfile = try std.Io.Dir.cwd().createFile(io, args_slice[1], .{}); + defer outfile.close(io); + + const write_buffer = try arena.alloc(u8, 64 * 1024); + var writer = outfile.writer(io, write_buffer); + + if (args_slice.len == 2) { + // Only output, generate an empty file. + try writer.interface.writeAll(empty_str); + } + + if (args_slice.len == 3) { + // Got an input, parse the ELF. + const buf = try std.Io.Dir.cwd().readFileAlloc( + io, + args_slice[2], + arena, + .unlimited, + ); + try generateSymbols(arena, buf, &writer.interface); + } + + try writer.flush(); +}