@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Create symbols and mapping files for uftrace.
# Create symbols, debug and mapping files for uftrace.
#
# Copyright 2025 Linaro Ltd
# Author: Pierrick Bouvier <pierrick.bouvier@linaro.org>
@ -9,44 +9,71 @@
# 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 ) )
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 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
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
with open ( path , ' rb ' ) as f :
self . elf = ELFFile ( f )
self . symbols = elf_func_symbols ( self . elf )
self . symbols = get_symbols ( self . fullpath )
find_symbols_locations ( self . fullpath , self . symbols )
def path ( self ) :
return self . fullpath
@ -56,24 +83,31 @@ class BinaryFile:
def addr_end ( self ) :
last_sym = self . symbols [ - 1 ]
return symbol_addr ( last_sym ) + symbol_size ( last_sym ) + self . map_offset
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 = f ' ./ uftrace.data/ { binary_name } .sym '
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 = symbol_addr ( s )
addr = s . addr
addr = f ' { addr : { addrx } } '
size = f ' { symbol_size ( s ) : { addrx } } '
name = symbol_name ( s )
size = f ' { s . size : { addrx } } '
if prefix_symbols :
name = f ' { binary_name } : { name } '
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 ]
@ -84,7 +118,7 @@ def parse_parameter(p):
offset = s [ 1 ]
if not offset . startswith ( ' 0x ' ) :
err = f ' offset " { offset } " is not an hexadecimal constant. '
err + = ' It should starts with " 0x " . '
err + = ' It should start with " 0x " . '
raise ValueError ( err )
offset = int ( offset , 16 )
return path , offset
@ -97,7 +131,7 @@ def is_from_user_mode(map_file_path):
return False
def generate_map ( binaries ) :
map_file_path = ' ./ uftrace.data/s id-0.map'
map_file_path = os . path . join ( ' uftrace.data ' , ' s id-0.map' )
if is_from_user_mode ( map_file_path ) :
print ( f ' do not overwrite { map_file_path } generated from qemu-user ' )
@ -124,7 +158,8 @@ def generate_map(binaries):
def main ( ) :
parser = argparse . ArgumentParser ( description =
' generate symbol files for uftrace ' )
' 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. ' )
@ -133,8 +168,8 @@ def main():
action = argparse . BooleanOptionalAction )
args = parser . parse_args ( )
if not os . path . exists ( ' ./ uftrace.data' ) :
os . mkdir ( ' ./ uftrace.data' )
if not os . path . exists ( ' uftrace.data ' ) :
os . mkdir ( ' uftrace.data ' )
binaries = [ ]
for file in args . elf_file :
@ -145,6 +180,7 @@ def main():
for b in binaries :
b . generate_symbol_file ( args . prefix_symbols )
b . generate_debug_file ( )
generate_map ( binaries )