#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Create symbols, debug and mapping files for uftrace. # # Copyright 2025 Linaro Ltd # Author: Pierrick Bouvier # # SPDX-License-Identifier: GPL-2.0-or-later import argparse import os import subprocess class Symbol: def __init__(self, name, addr, size): self.name = name # clamp addr to 48 bits, like uftrace entries self.addr = addr & 0xffffffffffff self.full_addr = addr self.size = size def set_loc(self, file, line): self.file = file self.line = line def get_symbols(elf_file): symbols=[] try: out = subprocess.check_output(['nm', '--print-size', elf_file], stderr=subprocess.STDOUT, text=True) except subprocess.CalledProcessError as e: print(e.output) raise out = out.strip().split('\n') for line in out: info = line.split(' ') if len(info) == 3: # missing size information continue addr, size, type, name = info # add only symbols from .text section if type.lower() != 't': continue addr = int(addr, 16) size = int(size, 16) symbols.append(Symbol(name, addr, size)) symbols.sort(key = lambda x: x.addr) return symbols def find_symbols_locations(elf_file, symbols): addresses = '\n'.join([hex(x.full_addr) for x in symbols]) try: out = subprocess.check_output(['addr2line', '--exe', elf_file], stderr=subprocess.STDOUT, input=addresses, text=True) except subprocess.CalledProcessError as e: print(e.output) raise out = out.strip().split('\n') assert len(out) == len(symbols) for i in range(len(symbols)): s = symbols[i] file, line = out[i].split(':') # addr2line may return 'line (discriminator [0-9]+)' sometimes, # remove this to keep only line number. line = line.split(' ')[0] s.set_loc(file, line) class BinaryFile: def __init__(self, path, map_offset): self.fullpath = os.path.realpath(path) self.map_offset = map_offset self.symbols = get_symbols(self.fullpath) find_symbols_locations(self.fullpath, self.symbols) def path(self): return self.fullpath def addr_start(self): return self.map_offset def addr_end(self): last_sym = self.symbols[-1] return last_sym.addr + last_sym.size + self.map_offset def generate_symbol_file(self, prefix_symbols): binary_name = os.path.basename(self.fullpath) sym_file_path = os.path.join('uftrace.data', f'{binary_name}.sym') print(f'{sym_file_path} ({len(self.symbols)} symbols)') with open(sym_file_path, 'w') as sym_file: # print hexadecimal addresses on 48 bits addrx = "0>12x" for s in self.symbols: addr = s.addr addr = f'{addr:{addrx}}' size = f'{s.size:{addrx}}' if prefix_symbols: name = f'{binary_name}:{s.name}' else: name = s.name print(addr, size, 'T', name, file=sym_file) def generate_debug_file(self): binary_name = os.path.basename(self.fullpath) dbg_file_path = os.path.join('uftrace.data', f'{binary_name}.dbg') with open(dbg_file_path, 'w') as dbg_file: for s in self.symbols: print(f'F: {hex(s.addr)} {s.name}', file=dbg_file) print(f'L: {s.line} {s.file}', file=dbg_file) def parse_parameter(p): s = p.split(":") path = s[0] if len(s) == 1: return path, 0 if len(s) > 2: raise ValueError('only one offset can be set') offset = s[1] if not offset.startswith('0x'): err = f'offset "{offset}" is not an hexadecimal constant. ' err += 'It should start with "0x".' raise ValueError(err) offset = int(offset, 16) return path, offset def is_from_user_mode(map_file_path): if os.path.exists(map_file_path): with open(map_file_path, 'r') as map_file: if not map_file.readline().startswith('# map stack on'): return True return False def generate_map(binaries): map_file_path = os.path.join('uftrace.data', 'sid-0.map') if is_from_user_mode(map_file_path): print(f'do not overwrite {map_file_path} generated from qemu-user') return mappings = [] # print hexadecimal addresses on 48 bits addrx = "0>12x" mappings += ['# map stack on highest address possible, to prevent uftrace'] mappings += ['# from considering any kernel address'] mappings += ['ffffffffffff-ffffffffffff rw-p 00000000 00:00 0 [stack]'] for b in binaries: m = f'{b.addr_start():{addrx}}-{b.addr_end():{addrx}}' m += f' r--p 00000000 00:00 0 {b.path()}' mappings.append(m) with open(map_file_path, 'w') as map_file: print('\n'.join(mappings), file=map_file) print(f'{map_file_path}') print('\n'.join(mappings)) def main(): parser = argparse.ArgumentParser(description= 'generate symbol files for uftrace. ' 'Require binutils (nm and addr2line).') parser.add_argument('elf_file', nargs='+', help='path to an ELF file. ' 'Use /path/to/file:0xdeadbeef to add a mapping offset.') parser.add_argument('--prefix-symbols', help='prepend binary name to symbols', action=argparse.BooleanOptionalAction) args = parser.parse_args() if not os.path.exists('uftrace.data'): os.mkdir('uftrace.data') binaries = [] for file in args.elf_file: path, offset = parse_parameter(file) b = BinaryFile(path, offset) binaries.append(b) binaries.sort(key = lambda b: b.addr_end()); for b in binaries: b.generate_symbol_file(args.prefix_symbols) b.generate_debug_file() generate_map(binaries) if __name__ == '__main__': main()