Add size tracing tool
This commit is contained in:
parent
03e8443e2e
commit
bf7e8701c7
5 changed files with 596 additions and 0 deletions
161
controller/fw/tools/linkmem.py
Normal file
161
controller/fw/tools/linkmem.py
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
|
||||
import tempfile
|
||||
import os
|
||||
from os import path
|
||||
import sys
|
||||
import re
|
||||
import subprocess
|
||||
from contextlib import contextmanager
|
||||
from collections import defaultdict
|
||||
|
||||
import cxxfilt
|
||||
|
||||
from elftools.elf.elffile import ELFFile
|
||||
from elftools.elf.descriptions import describe_symbol_type
|
||||
import libarchive
|
||||
|
||||
@contextmanager
|
||||
def chdir(newdir):
|
||||
old_cwd = os.getcwd()
|
||||
try:
|
||||
os.chdir(newdir)
|
||||
yield
|
||||
finally:
|
||||
os.chdir(old_cwd)
|
||||
|
||||
|
||||
def trace_source_files(linker, cmdline, trace_sections=[]):
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
out_path = path.join(tempdir, 'output.elf')
|
||||
output = subprocess.check_output([linker, '-o', out_path, f'-Wl,--cref', *cmdline])
|
||||
lines = [ line.strip() for line in output.decode().splitlines() ]
|
||||
# FIXME also find isr vector table references
|
||||
|
||||
defs = {}
|
||||
for line in lines[lines.index('Cross Reference Table')+3:]:
|
||||
try:
|
||||
*left, right = line.split()
|
||||
if left:
|
||||
defs[' '.join(left)] = right
|
||||
except:
|
||||
pass
|
||||
|
||||
refs = defaultdict(lambda: set())
|
||||
syms = {}
|
||||
for sym, obj in defs.items():
|
||||
fn, _, member = re.match('^([^()]+)(\((.+)\))?$', obj).groups()
|
||||
fn = path.abspath(fn)
|
||||
|
||||
if member:
|
||||
subprocess.check_call(['ar', 'x', '--output', tempdir, fn, member])
|
||||
fn = path.join(tempdir, member)
|
||||
|
||||
with open(fn, 'rb') as f:
|
||||
elf = ELFFile(f)
|
||||
|
||||
symtab = elf.get_section_by_name('.symtab')
|
||||
|
||||
symtab_demangled = { cxxfilt.demangle(nsym.name).replace(' ', ''): i
|
||||
for i, nsym in enumerate(symtab.iter_symbols()) }
|
||||
|
||||
def lookup_size(name):
|
||||
name_normalized = name.replace(' ', '')
|
||||
if name_normalized in symtab_demangled:
|
||||
entry = symtab.get_symbol(symtab_demangled[name_normalized])
|
||||
return entry['st_size']
|
||||
else:
|
||||
return None
|
||||
|
||||
syms[sym] = fn, lookup_size(sym)
|
||||
|
||||
s = set()
|
||||
sec_map = { sec.name: i for i, sec in enumerate(elf.iter_sections()) }
|
||||
sec_name = f'.rel.text.{sym}'
|
||||
matches = [ i for name, i in sec_map.items() if re.match(f'\.rel\..*\.{sym}', name) ]
|
||||
if matches:
|
||||
sec = elf.get_section(matches[0])
|
||||
for reloc in sec.iter_relocations():
|
||||
refsym = symtab.get_symbol(reloc['r_info_sym'])
|
||||
s.add(refsym.name)
|
||||
|
||||
if refsym.name not in defs:
|
||||
syms[refsym.name] = fn, lookup_size(refsym.name)
|
||||
refs[sym] = s
|
||||
|
||||
for tsec in trace_sections:
|
||||
matches = [ i for name, i in sec_map.items() if name == f'.rel{tsec}' ]
|
||||
s = set()
|
||||
if matches:
|
||||
sec = elf.get_section(matches[0])
|
||||
for reloc in sec.iter_relocations():
|
||||
refsym = symtab.get_symbol(reloc['r_info_sym'])
|
||||
s.add(refsym.name)
|
||||
refs[tsec.replace('.', '_')] |= s
|
||||
|
||||
syms_out = set()
|
||||
with open(out_path, 'rb') as f:
|
||||
elf = ELFFile(f)
|
||||
symtab = elf.get_section_by_name('.symtab')
|
||||
for sym in symtab.iter_symbols():
|
||||
if describe_symbol_type(sym['st_info']['type']) == 'FUNC':
|
||||
syms_out.add(sym.name)
|
||||
#for sym in defs:
|
||||
# entry = symtab.get_symbol_by_name(sym)
|
||||
# if entry is None:
|
||||
# syms[sym] = defs[sym], None
|
||||
# else:
|
||||
# syms[sym] = defs[sym], entry[0]['st_size']
|
||||
|
||||
return syms, refs, syms_out
|
||||
|
||||
@contextmanager
|
||||
def wrap(leader='', print=print, left='{', right='}'):
|
||||
print(leader, left)
|
||||
yield lambda *args, **kwargs: print(' ', *args, **kwargs)
|
||||
print(right)
|
||||
|
||||
def mangle(name):
|
||||
return re.sub('[^a-zA-Z0-9_]', '_', name)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--trace-sections', type=str, action='append', default=[])
|
||||
parser.add_argument('linker_binary')
|
||||
parser.add_argument('linker_args', nargs=argparse.REMAINDER)
|
||||
args = parser.parse_args()
|
||||
|
||||
trace_sections = args.trace_sections
|
||||
trace_sections_mangled = { sec.replace('.', '_') for sec in trace_sections }
|
||||
syms, refs, syms_out = trace_source_files(args.linker_binary, args.linker_args, trace_sections)
|
||||
|
||||
clusters = defaultdict(lambda: [])
|
||||
for sym, (obj, size) in syms.items():
|
||||
clusters[obj].append((sym, size))
|
||||
|
||||
obj_size = defaultdict(lambda: 0)
|
||||
for name, (obj, size) in syms.items():
|
||||
if size is not None:
|
||||
obj_size[obj] += size
|
||||
|
||||
with wrap('digraph G', print) as lvl1print:
|
||||
print('rankdir=LR;')
|
||||
print()
|
||||
|
||||
for i, (obj, syms) in enumerate(clusters.items()):
|
||||
with wrap(f'subgraph cluster_{i}', lvl1print) as lvl2print:
|
||||
lvl2print(f'label = "{obj} <{obj_size[obj]}>";')
|
||||
lvl2print()
|
||||
for sym, size in syms:
|
||||
if sym in syms_out:
|
||||
lvl2print(f'{mangle(sym)}[label = "{sym} <{size}>"];')
|
||||
lvl1print()
|
||||
|
||||
for start, ends in refs.items():
|
||||
for end in ends:
|
||||
if end and (start in syms_out or start in trace_sections_mangled) and end in syms_out:
|
||||
lvl1print(f'{mangle(start)} -> {mangle(end)};')
|
||||
|
||||
for sec in trace_sections:
|
||||
lvl1print(f'{sec.replace(".", "_")} [label = "section {sec}"];')
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue