| #!/usr/bin/env python3 |
| # -*- coding: utf-8 -*- |
| # |
| # Create symbols, debug and mapping files for uftrace. |
| # |
| # Copyright 2025 Linaro Ltd |
| # Author: Pierrick Bouvier <pierrick.bouvier@linaro.org> |
| # |
| # 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}' |
| 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() |