pebble/tools/analyze_fw_static_memory_usage.py

207 lines
6.3 KiB
Python
Raw Permalink Normal View History

# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import os
import re
import sys
sys.path.append(os.path.dirname(__file__))
import analyze_static_memory_usage
from binutils import nm_generator, analyze_elf
def cleanup_path(f):
f = os.path.normpath(f)
# Check for .c.3.o style suffixes and strip them back to just .c
if len(f) > 6 and f[-6:-3] == '.c.' and f[-2:] == '.o':
f = f[:-4]
if f.startswith('src/'):
f = f[4:]
newlib_index = f.rfind('/newlib/libc')
if newlib_index != -1:
f = f[newlib_index+1:]
libgcc_index = f.rfind('/libgcc/')
if libgcc_index != -1:
f = f[libgcc_index+1:]
libc_index = f.rfind('/arm-none-eabi/lib/')
if libc_index != -1:
f = f[libc_index+1:]
tintin_index = f.rfind('/tintin/src/')
if tintin_index != -1:
f = f[tintin_index + len('/tintin/src/'):]
tintin_build_index = f.rfind('/tintin/build/src/')
if tintin_build_index != -1:
f = f[tintin_build_index + len('/tintin/'):]
return f
analyze_static_memory_usage.cleanup_path_func = cleanup_path
def analyze_map(map_file, sections):
# Now that we have a list of all the symbols listed in the nm output, we need to go back
# and dig through the map file to find filenames for the symbols with an "Unknown" filename
# We only care about the .text section here
if not 't' in sections:
return
text_section = sections['t']
def line_generator(map_file):
with open(map_file, 'r') as f:
for line in f:
yield line
lines = line_generator(map_file)
for line in lines:
if line.startswith('Linker script and memory map'):
break
for line in lines:
if line.startswith('.text'):
break
# We're looking for groups of lines like the following...
#
# .text.do_tap_handle
# 0x0000000008010e08 0x28 src/fw/applib/accel_service.c.3.o
symbol_pattern = re.compile(r""" \.?[^\.\s]*\.(\S+)""")
for line in lines:
match = symbol_pattern.match(line.rstrip())
if match is None:
continue
symbol = match.group(1)
line = lines.next()
cols = line.split()
if len(cols) < 3:
continue
filename = cols[2]
symbol_with_unknown_file = text_section.remove_unknown_entry(symbol)
if symbol_with_unknown_file is None:
continue
text_section.add_entry(symbol, filename, symbol_with_unknown_file.size)
def analyze_libs(root_directory, sections, use_fast):
def analyze_lib(lib_filename):
for (_, section, symbol_name, filename, line, size) in nm_generator(lib_filename, use_fast):
if not section in sections:
continue
section_info = sections[section]
symbol_with_unknown_file = section_info.remove_unknown_entry(symbol_name)
if symbol_with_unknown_file is None:
continue
section_info.add_entry(symbol_name, lib_filename, size)
for (dirpath, dirnames, filenames) in os.walk(root_directory):
for f in filenames:
if f.endswith('.a'):
analyze_lib(os.path.join(dirpath, f))
def print_groups(text_section, verbose):
mappings = [
('fw/vendor/FreeRTOS/', 'FreeRTOS'),
('core/vendor/STM32F2xx_StdPeriph_Lib_V1.0.0', 'STM32'),
('newlib/', 'newlib'),
('libgcc/', 'libgcc'),
('arm-none-eabi/lib/', 'libc'),
('fw/applib/', 'FW Applib'),
('fw/apps/', 'FW Apps'),
('fw/comm/ble/', 'FW Comm LE'),
('fw/comm/', 'FW Comm'),
('fw/kernel/services/', 'FW Kernel Services'),
('fw/', 'FW Other'),
('core/', 'FW Other'),
('build/src/fw', 'FW Other')
]
class Group(object):
def __init__(self, name):
self.name = name
self.total_size = 0
self.files = []
def add_file(self, f):
self.total_size += f.size
self.files.append(f)
group_sizes = {}
for f in text_section.get_files():
found = False
for prefix, value in mappings:
if f.filename.startswith(prefix):
if not value in group_sizes:
group_sizes[value] = Group(f.filename)
group_sizes[value].add_file(f)
found = True
break
if not found:
if not 'Unknown' in group_sizes:
group_sizes['Unknown'] = Group(f.filename)
group_sizes['Unknown'].add_file(f)
sorted_items = sorted(group_sizes.iteritems(), key=lambda x: -x[1].total_size)
for group_name, group in sorted_items:
print "%-20s %u" % (group_name, group.total_size)
if verbose:
sorted_files = sorted(group.files, key=lambda x: -x.size)
for f in sorted_files:
print " %6u %-20s" % (f.size, f.filename)
if (__name__ == '__main__'):
parser = argparse.ArgumentParser()
parser.add_argument('--text_groups', action='store_true')
parser.add_argument('--verbose', action='store_true')
parser.add_argument('--summary', action='store_true')
parser.add_argument('--sections', default='bdt')
parser.add_argument('--fast', action='store_true')
args = parser.parse_args()
if args.text_groups:
args.sections = 't'
tintin_dir = os.path.join(os.path.dirname(__file__), '..')
elf_path = os.path.join(tintin_dir, 'build', 'src', 'fw', 'tintin_fw.elf')
sections = analyze_elf(elf_path, args.sections, args.fast)
analyze_map(os.path.join(tintin_dir, 'build', 'tintin_fw.map'), sections)
if args.text_groups:
print_groups(sections['t'], args.verbose)
else:
for s in args.sections:
sections[s].pprint(args.summary, args.verbose)