228 lines
9.3 KiB
Python
228 lines
9.3 KiB
Python
|
|
import tempfile
|
|
import os
|
|
from os import path
|
|
import sys
|
|
import re
|
|
import subprocess
|
|
from contextlib import contextmanager
|
|
from collections import defaultdict
|
|
import colorsys
|
|
|
|
import cxxfilt
|
|
from elftools.elf.elffile import ELFFile
|
|
from elftools.elf.enums import ENUM_ST_SHNDX
|
|
from elftools.elf.descriptions import describe_symbol_type, describe_sh_type
|
|
import libarchive
|
|
import matplotlib.cm
|
|
|
|
@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])
|
|
index = entry['st_shndx']
|
|
if index in ENUM_ST_SHNDX:
|
|
return 'no bits'
|
|
sec = elf.get_section(index)
|
|
if describe_sh_type(sec['sh_type']) == 'NOBITS':
|
|
return 'no bits'
|
|
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()) }
|
|
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'])
|
|
name = refsym.name if refsym.name else elf.get_section(refsym['st_shndx']).name.split('.')[-1]
|
|
s.add(name)
|
|
|
|
if name not in defs:
|
|
syms[name] = fn, lookup_size(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']) in ('FUNC', 'OBJECT'):
|
|
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)
|
|
|
|
hexcolor = lambda r, g, b, *_a: f'#{int(r*255):02x}{int(g*255):02x}{int(b*255):02x}'
|
|
def vhex(val):
|
|
r,g,b,_a = matplotlib.cm.viridis(1.0-val)
|
|
fc = hexcolor(r, g, b)
|
|
h,s,v = colorsys.rgb_to_hsv(r,g,b)
|
|
cc = '#000000' if v > 0.8 else '#ffffff'
|
|
return fc, cc
|
|
|
|
if __name__ == '__main__':
|
|
import argparse
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--trace-sections', type=str, action='append', default=[])
|
|
parser.add_argument('--trim-stubs', type=str, action='append', default=[])
|
|
parser.add_argument('--highlight-subdirs', type=str, default=None)
|
|
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)
|
|
|
|
for name, (obj, size) in syms.items():
|
|
if path.basename(obj) in args.trim_stubs and (not isinstance(size, int) or size <= 8) and not refs.get(name):
|
|
syms_out.discard(name)
|
|
|
|
clusters = defaultdict(lambda: [])
|
|
for sym, (obj, size) in syms.items():
|
|
if sym in syms_out:
|
|
clusters[obj].append((sym, size))
|
|
|
|
obj_size = defaultdict(lambda: 0)
|
|
for sym, (obj, size) in syms.items():
|
|
if isinstance(size, int) and sym in syms_out:
|
|
obj_size[obj] += size
|
|
max_size = max([ size for _obj, size in syms.values() if isinstance(size, int) ])
|
|
|
|
max_osize = max(obj_size.values())
|
|
|
|
subdir_prefix = path.abspath(args.highlight_subdirs) + '/' if args.highlight_subdirs else '### NO HIGHLIGHT ###'
|
|
first_comp = lambda le_path: path.dirname(le_path).partition(os.sep)[0]
|
|
subdir_colors = sorted({ first_comp(obj[len(subdir_prefix):]) for obj in obj_size if obj.startswith(subdir_prefix) })
|
|
subdir_colors = { path: hexcolor(*matplotlib.cm.Pastel1(i/len(subdir_colors))) for i, path in enumerate(subdir_colors) }
|
|
|
|
subdir_sizes = defaultdict(lambda: 0)
|
|
for obj, size in obj_size.items():
|
|
if not isinstance(size, int):
|
|
continue
|
|
if obj.startswith(subdir_prefix):
|
|
subdir_sizes[first_comp(obj[len(subdir_prefix):])] += size
|
|
else:
|
|
subdir_sizes['<others>'] += size
|
|
|
|
print('Subdir sizes:', file=sys.stderr)
|
|
for subdir, size in sorted(subdir_sizes.items(), key=lambda x: x[1]):
|
|
print(f'{subdir:>20}: {size:>6,d} B', file=sys.stderr)
|
|
|
|
def lookup_highlight(path):
|
|
if args.highlight_subdirs:
|
|
if obj.startswith(subdir_prefix):
|
|
highlight_head = first_comp(path[len(subdir_prefix):])
|
|
return subdir_colors[highlight_head], highlight_head
|
|
else:
|
|
return '#e0e0e0', None
|
|
else:
|
|
return '#ddf7f4', None
|
|
|
|
with wrap('digraph G', print) as lvl1print:
|
|
print('rankdir=LR;')
|
|
print('ranksep=1;')
|
|
print('nodesep=0.2;')
|
|
print()
|
|
|
|
for i, (obj, syms) in enumerate(clusters.items()):
|
|
with wrap(f'subgraph cluster_{i}', lvl1print) as lvl2print:
|
|
print('style = "filled";')
|
|
highlight_color, highlight_head = lookup_highlight(obj)
|
|
print(f'bgcolor = "{highlight_color}";')
|
|
print('pencolor = none;')
|
|
fc, cc = vhex(obj_size[obj]/max_osize)
|
|
highlight_subdir_part = f'<font face="carlito" color="{cc}" point-size="12">{highlight_head} / </font>' if highlight_head else ''
|
|
lvl2print(f'label = <<table border="0"><tr><td border="0" cellpadding="5" bgcolor="{fc}">'
|
|
f'{highlight_subdir_part}'
|
|
f'<font face="carlito" color="{cc}"><b>{path.basename(obj)} ({obj_size[obj]}B)</b></font>'
|
|
f'</td></tr></table>>;')
|
|
lvl2print()
|
|
for sym, size in syms:
|
|
if sym in syms_out:
|
|
size_s = f'{size}B' if isinstance(size, int) else size
|
|
fc, cc = vhex(size/max_size if isinstance(size, int) else 0)
|
|
lvl2print(f'{mangle(sym)}[label = "{sym} ({size_s})", style="rounded,filled", shape="box", fillcolor="{fc}", fontname="carlito", fontcolor="{cc}" color=none];')
|
|
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)} [style="bold", color="#333333"];')
|
|
|
|
for sec in trace_sections:
|
|
lvl1print(f'{sec.replace(".", "_")} [label = "section {sec}", shape="box", style="filled,bold"];')
|
|
|