pebble/tools/struct_sizes.py
2025-01-27 11:38:16 -08:00

116 lines
4 KiB
Python
Executable file

#!/usr/bin/env python
# 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 logging
import sys
from elftools.elf.elffile import ELFFile
from elftools.dwarf.die import DIE
from elftools.common.utils import preserve_stream_pos
def _extract_struct_sizes(die, struct_names_by_size):
def add(name, size):
if size not in struct_names_by_size:
struct_names_by_size[size] = set()
struct_names_by_size[size].add(name)
logging.debug('%s => %s Bytes' % (name, size))
# Handle typedef'd anonymous structs:
if die.tag == 'DW_TAG_typedef':
assert(die.attributes['DW_AT_type'].form == 'DW_FORM_ref4')
offset = die.attributes['DW_AT_type'].value
ref_die_offset = offset + die.cu.cu_offset
with preserve_stream_pos(die.stream):
ref_die = DIE(die.cu, die.stream, ref_die_offset)
if ref_die.tag == 'DW_TAG_structure_type':
if 'DW_AT_byte_size' in ref_die.attributes:
name = die.attributes['DW_AT_name'].value
size = int(ref_die.attributes['DW_AT_byte_size'].value)
add(name, size)
else:
# Most likely a fwd declaration has been used
pass
return
# Handle named structs:
if die.tag == 'DW_TAG_structure_type':
if 'DW_AT_name' in die.attributes and \
'DW_AT_byte_size' in die.attributes:
size = int(die.attributes['DW_AT_byte_size'].value)
name = die.attributes['DW_AT_name'].value
add(name, size)
def get_struct_names_by_size(elffile, print_progress=False):
if not elffile.has_dwarf_info():
logging.error('File has no DWARF info')
return None
dwarfinfo = elffile.get_dwarf_info()
struct_names_by_size = dict()
for CU in dwarfinfo.iter_CUs():
logging.debug('Found a compile unit at offset %s, length %s' % (
CU.cu_offset, CU['unit_length']))
if print_progress:
sys.stdout.write('.')
sys.stdout.flush()
def die_info_recurse(die):
_extract_struct_sizes(die, struct_names_by_size)
for child in die.iter_children():
die_info_recurse(child)
die_info_recurse(CU.get_top_DIE())
if print_progress:
sys.stdout.write('\n')
return struct_names_by_size
def _process_elf(filename, verbose=False):
logging.info('Processing .elf file: %s' % filename)
with open(filename, 'rb') as f:
elffile = ELFFile(f)
print_progress = not(verbose)
struct_names_by_size = get_struct_names_by_size(elffile,
print_progress)
for size in sorted(struct_names_by_size.keys(), reverse=True):
logging.info("%6u bytes: %s", size,
' '.join(struct_names_by_size[size]))
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-v', '--verbose', action='store_true')
parser.add_argument('-o', '--logfile',
help='Log file to output to. Output printed to console '
'otherwise.')
parser.add_argument('elf_file',
help='Extracts all struct sizes from elf file.')
args = parser.parse_args()
level = logging.INFO
if args.verbose:
level = logging.DEBUG
logging.basicConfig(level=level, filename=args.logfile)
_process_elf(args.elf_file, verbose=args.verbose)