blob: 45fb79c7a58c9e85ad460b1dd531d759e066ba19 [file] [log] [blame]
#!/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()