diff --git a/kernel-src/arch/x86-64/kernel.ld b/kernel-src/arch/x86-64/kernel.ld index 86c7921d..348deb3a 100644 --- a/kernel-src/arch/x86-64/kernel.ld +++ b/kernel-src/arch/x86-64/kernel.ld @@ -68,6 +68,25 @@ SECTIONS *(.bss .bss.*) } :data + . = ALIGN(4); + .dbg_sym_table : { + dbg_sym_table = .; + KEEP(*(.dbg_sym_table)); + dbg_sym_table_end = .; + } :data + + .dbg_fn_name_table : { + dbg_fn_name_table = .; + KEEP(*(.dbg_fn_name_table)); + dbg_fn_name_table_end = .; + } :data + + .dbg_file_name_table : { + dbg_file_name_table = .; + KEEP(*(.dbg_file_name_table)); + dbg_file_name_table_end = .; + } :data + . = ALIGN(4K); _data_end = .; } diff --git a/kernel-src/gen_fn_sym.py b/kernel-src/gen_fn_sym.py new file mode 100755 index 00000000..df34dcf1 --- /dev/null +++ b/kernel-src/gen_fn_sym.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +# USAGE: +# python3 gen_fn_sym.py kernel_exec base_addr source_cwd output_file +# +# - output_file is an ELF object exporting dbg_sym_table, dbg_fn_name_table, +# dbg_file_name_table, and their corresponding _end symbols. +# - Each symbol record is . Addresses are relative to base_addr and string +# offsets are byte offsets into the corresponding string table. +# - Select matching tools with --nm, --objdump and --objcopy (or NM, +# OBJDUMP and OBJCOPY envs). + +import argparse +import os +from pathlib import Path +import shlex +import struct +import subprocess +import tempfile +from typing import NamedTuple + +UINT32_MAX = (1 << 32) - 1 +SECTION_MARKERS = {"_text_start", "_text_end"} + +class BfdTarget(NamedTuple): + file_format: str + architecture: str + +def run_tool(command): + return subprocess.run( + command, check=True, capture_output=True, + encoding="utf-8", errors="surrogateescape", + env={**os.environ, "LC_ALL": "C"}, + ).stdout + +def tool_command(value): + command = shlex.split(value) + if not command: + raise argparse.ArgumentTypeError("tool command must not be empty") + + return command + +def get_bfd_target(kernel_exec, objdump): + output = run_tool([*objdump, "-f", "--", kernel_exec]) + file_format = architecture = None + + for line in output.splitlines(): + if "file format " in line: + file_format = line.rsplit("file format ", 1)[1].strip() + + if line.startswith("architecture: "): + architecture = line[len("architecture: "):].split(",", 1)[0].strip() + + if not file_format or not architecture or architecture == "UNKNOWN!": + raise ValueError("objdump could not identify the kernel target; select a matching --objdump") + + return BfdTarget(file_format, architecture) + +def source_filename(location, source_prefix): + if not location: + return "" + + path, separator, line_number = location.rpartition(":") + filename = path if separator and line_number.isdigit() else location + filename = os.path.normpath(filename) + + if filename.startswith(source_prefix): + filename = filename[len(source_prefix):] + + return filename + +def collect_symbols(kernel_exec, base_addr, source_cwd, nm): + output = run_tool( + [*nm, "-n", "-l", "--defined-only", "--format=bsd", + "--no-demangle", "--radix=x", "--", kernel_exec], + ) + source_prefix = os.path.abspath(source_cwd).rstrip(os.sep) + os.sep + symbols = [] + + for line in output.splitlines(): + symbol_text, _, location = line.partition("\t") + fields = symbol_text.split(None, 2) + + if len(fields) != 3 or fields[1] not in ("T", "t"): + continue + + address, _, name = fields + if name in SECTION_MARKERS: + continue + + offset = int(address, 16) - base_addr + if not 0 <= offset <= UINT32_MAX: + raise ValueError(f"address of {name} does not fit in uint32_t relative to base_addr") + + filename = source_filename(location, source_prefix) + symbols.append((offset, name, filename)) + + if not symbols: + raise ValueError("kernel binary contains no text symbols") + + return sorted(symbols, key=lambda symbol: (symbol[0], symbol[1])) + +def intern_string(value, table, offsets): + if value not in offsets: + encoded = value.encode("utf-8", errors="surrogateescape") + b"\0" + if len(table) + len(encoded) - 1 > UINT32_MAX: + raise ValueError("string table does not fit in uint32_t offsets") + + offsets[value] = len(table) + table.extend(encoded) + + return offsets[value] + +def make_tables(symbols): + records = bytearray() + functions = bytearray(b"\0") + files = bytearray(b"\0") + function_offsets = {"": 0} + file_offsets = {"": 0} + + for address, name, filename in symbols: + function_offset = intern_string(name, functions, function_offsets) + file_offset = intern_string(filename, files, file_offsets) + records.extend(struct.pack("