#!/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("