From: Pierrick Bouvier <pierrick.bouvier@linaro.org>
usage: contrib/plugins/uftrace_symbols.py \
--prefix-symbols \
arm-trusted-firmware/build/qemu/debug/bl1/bl1.elf \
arm-trusted-firmware/build/qemu/debug/bl2/bl2.elf \
arm-trusted-firmware/build/qemu/debug/bl31/bl31.elf \
u-boot/u-boot:0x60000000 \
u-boot/u-boot.relocated:0x000000023f6b6000 \
linux/vmlinux
Will generate symbols and memory mapping files for uftrace, allowing to
have an enhanced trace, instead of raw addresses.
It takes a collection of elf files, and automatically find all their
symbols, and generate an ordered memory map based on that.
This script uses the python (native) pyelftools module.
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Signed-off-by: Pierrick Bouvier <pierrick.bouvier@linaro.org>
Acked-by: Alex Bennée <alex.bennee@linaro.org>
Message-ID: <20250902075042.223990-9-pierrick.bouvier@linaro.org>
Signed-off-by: Alex Bennée <alex.bennee@linaro.org>
Message-ID: <20250922093711.2768983-25-alex.bennee@linaro.org>
diff --git a/contrib/plugins/uftrace_symbols.py b/contrib/plugins/uftrace_symbols.py
new file mode 100755
index 00000000000..b49e03203c8
--- /dev/null
+++ b/contrib/plugins/uftrace_symbols.py
@@ -0,0 +1,152 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Create symbols 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 elftools # pip install pyelftools
+import os
+
+from elftools.elf.elffile import ELFFile
+from elftools.elf.sections import SymbolTableSection
+
+def elf_func_symbols(elf):
+ symbol_tables = [(idx, s) for idx, s in enumerate(elf.iter_sections())
+ if isinstance(s, SymbolTableSection)]
+ symbols = []
+ for _, section in symbol_tables:
+ for _, symbol in enumerate(section.iter_symbols()):
+ if symbol_size(symbol) == 0:
+ continue
+ type = symbol['st_info']['type']
+ if type == 'STT_FUNC' or type == 'STT_NOTYPE':
+ symbols.append(symbol)
+ symbols.sort(key = lambda x: symbol_addr(x))
+ return symbols
+
+def symbol_size(symbol):
+ return symbol['st_size']
+
+def symbol_addr(symbol):
+ addr = symbol['st_value']
+ # clamp addr to 48 bits, like uftrace entries
+ return addr & 0xffffffffffff
+
+def symbol_name(symbol):
+ return symbol.name
+
+class BinaryFile:
+ def __init__(self, path, map_offset):
+ self.fullpath = os.path.realpath(path)
+ self.map_offset = map_offset
+ with open(path, 'rb') as f:
+ self.elf = ELFFile(f)
+ self.symbols = elf_func_symbols(self.elf)
+
+ def path(self):
+ return self.fullpath
+
+ def addr_start(self):
+ return self.map_offset
+
+ def addr_end(self):
+ last_sym = self.symbols[-1]
+ return symbol_addr(last_sym) + symbol_size(last_sym) + self.map_offset
+
+ def generate_symbol_file(self, prefix_symbols):
+ binary_name = os.path.basename(self.fullpath)
+ sym_file_path = f'./uftrace.data/{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 = symbol_addr(s)
+ addr = f'{addr:{addrx}}'
+ size = f'{symbol_size(s):{addrx}}'
+ name = symbol_name(s)
+ if prefix_symbols:
+ name = f'{binary_name}:{name}'
+ print(addr, size, 'T', name, file=sym_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 starts 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 = './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')
+ 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)
+
+ generate_map(binaries)
+
+if __name__ == '__main__':
+ main()
--
2.47.3