mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-18 16:27:34 +01:00
146 lines
4.0 KiB
Python
146 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import urllib.parse
|
|
|
|
# ELF analysis
|
|
|
|
def loadable_sections(elf_file):
|
|
objdump_section_headers_pattern = re.compile("^\s+\d+\s+(\.[\w\.]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+)", flags=re.MULTILINE)
|
|
objdump_output = subprocess.check_output(["arm-none-eabi-objdump", "-h", "-w", elf_file]).decode('utf-8')
|
|
sections = []
|
|
for (name, size, vma, lma, offset) in re.findall(objdump_section_headers_pattern, objdump_output):
|
|
int_size = int(size,16)
|
|
if (int_size > 0):
|
|
sections.append({'name': name, 'size': int_size, 'vma': int(vma,16), 'lma': int(lma,16), 'offset':int(offset,16)})
|
|
return sections
|
|
|
|
|
|
# Data filtering
|
|
|
|
def row_for_elf(elf, requested_section_prefixes):
|
|
sections = loadable_sections(elf)
|
|
result = {}
|
|
for prefix in requested_section_prefixes:
|
|
for s in sections:
|
|
section_name = s['name']
|
|
if s['name'].startswith(prefix):
|
|
if not prefix in result:
|
|
result[prefix] = 0
|
|
result[prefix] += s['size']
|
|
return result
|
|
|
|
|
|
# String formatting
|
|
|
|
def iso_separate(string):
|
|
space = ' ' # We may want to use a thin non-breaking space as thousands separator
|
|
return string.replace('_',space).replace('+','+'+space).replace('-','-'+space)
|
|
|
|
def format_bytes(value, force_sign=False):
|
|
if value is None:
|
|
return ''
|
|
number_format = '{:'
|
|
if force_sign:
|
|
number_format += '+'
|
|
number_format += '_} bytes'
|
|
return iso_separate(number_format.format(value))
|
|
|
|
def format_percentage(value):
|
|
if value is None:
|
|
return ''
|
|
return iso_separate("{:+.1f} %".format(100*value))
|
|
|
|
|
|
# Markdown
|
|
|
|
def emphasize(string):
|
|
if string:
|
|
return '_' + string + '_'
|
|
else:
|
|
return ''
|
|
|
|
def strong(string):
|
|
if string:
|
|
return '**' + string + '**'
|
|
else:
|
|
return ''
|
|
|
|
|
|
# Deltas
|
|
|
|
def absolute_delta(x,y):
|
|
if x is None or y is None:
|
|
return None
|
|
return x-y
|
|
|
|
def ratio_delta(x,y):
|
|
if x is None or y is None:
|
|
return None
|
|
return (x-y)/y
|
|
|
|
|
|
# Table formatting
|
|
|
|
def format_row(row, header=False):
|
|
result = '|'
|
|
if header:
|
|
result += strong(header)
|
|
result += '|'
|
|
for v in row:
|
|
result += v
|
|
result += '|'
|
|
result += '\n'
|
|
return result
|
|
|
|
def format_table(table):
|
|
base = table[0]['values']
|
|
listed_sections = base.keys()
|
|
result = ''
|
|
result += format_row(listed_sections)
|
|
result += '|-|' + '-:|'*len(listed_sections) + '\n'
|
|
for i,row in enumerate(table):
|
|
v = row['values']
|
|
result += format_row((format_bytes(v.get(s)) for s in listed_sections), header=row['label'])
|
|
if i != 0:
|
|
result += format_row(emphasize(format_bytes(absolute_delta(v.get(s), base.get(s)), force_sign=True)) for s in listed_sections)
|
|
result += format_row(emphasize(format_percentage(ratio_delta(v.get(s), base.get(s)))) for s in listed_sections)
|
|
return result
|
|
|
|
|
|
# Argument parsing
|
|
|
|
parser = argparse.ArgumentParser(description='Compute binary size metrics')
|
|
parser.add_argument('files', type=str, nargs='+', help='an ELF file')
|
|
parser.add_argument('--labels', type=str, nargs='+', help='label for ELF file')
|
|
parser.add_argument('--sections', type=str, nargs='+', help='Section (prefix) to list')
|
|
parser.add_argument('--escape', action='store_true', help='Escape the output')
|
|
parser.add_argument('--custom', type=str, action='append', nargs='+', help='Custom sections, made from the addition of other sections.')
|
|
args = parser.parse_args()
|
|
|
|
|
|
# Execution
|
|
|
|
table = []
|
|
for i,filename in enumerate(args.files):
|
|
label = os.path.basename(filename)
|
|
if args.labels and i < len(args.labels):
|
|
label = args.labels[i]
|
|
values = row_for_elf(filename, args.sections)
|
|
for custom_section in args.custom:
|
|
if (len(custom_section) >= 2):
|
|
custom_section_size = 0
|
|
for i in range(len(custom_section) - 1):
|
|
custom_section_size += values[custom_section[i + 1]]
|
|
values[custom_section[0]] = custom_section_size
|
|
table.append({'label': label, 'values': values})
|
|
formatted_table = format_table(table)
|
|
|
|
if args.escape:
|
|
print(urllib.parse.quote(formatted_table, safe='| :*+'))
|
|
else:
|
|
print(formatted_table)
|