scripts: Adopted ctx.py-related changes in structs.py

- Dropped --internal flag, structs.py includes all structs now.

  No reason to limit structs.py to public structs if ctx.py exists.

- Added struct/union/enum prefixes to results (enums were missing in
  ctx.py).

- Only sort children layers if explicitly requested. This should
  preserve field order, which is nice.

- Adopt more advanced FileInfo/DwarfInfo classes.

- Adopted table renderer changes (notes rendering).
This commit is contained in:
Christopher Haster
2024-11-30 23:34:44 -06:00
parent c8a4ee91a6
commit 55d01f69f9
2 changed files with 230 additions and 94 deletions
+3 -1
View File
@@ -369,11 +369,13 @@ def collect_dwarf_info(obj_path, filter=None, *,
def name(self): def name(self):
if 'DW_AT_name' in self: if 'DW_AT_name' in self:
name = self['DW_AT_name'].split(':')[-1].strip() name = self['DW_AT_name'].split(':')[-1].strip()
# prefix with struct/union # prefix with struct/union/enum
if self.tag == 'DW_TAG_structure_type': if self.tag == 'DW_TAG_structure_type':
name = 'struct ' + name name = 'struct ' + name
elif self.tag == 'DW_TAG_union_type': elif self.tag == 'DW_TAG_union_type':
name = 'union ' + name name = 'union ' + name
elif self.tag == 'DW_TAG_enumeration_type':
name = 'enum ' + name
return name return name
else: else:
return None return None
+227 -93
View File
@@ -14,8 +14,8 @@ __import__('sys').path.pop(0)
import collections as co import collections as co
import csv import csv
import difflib
import itertools as it import itertools as it
import functools as ft
import math as mt import math as mt
import os import os
import re import re
@@ -136,10 +136,10 @@ class StructResult(co.namedtuple('StructResult', [
_types = {'size': RInt, 'align': RInt} _types = {'size': RInt, 'align': RInt}
__slots__ = () __slots__ = ()
def __new__(cls, file='', struct='', size=0, align=0, children=[]): def __new__(cls, file='', struct='', size=0, align=0, children=None):
return super().__new__(cls, file, struct, return super().__new__(cls, file, struct,
RInt(size), RInt(align), RInt(size), RInt(align),
children or []) children if children is not None else [])
def __add__(self, other): def __add__(self, other):
return StructResult(self.file, self.struct, return StructResult(self.file, self.struct,
@@ -161,14 +161,36 @@ def openio(path, mode='r', buffering=-1):
def collect_dwarf_files(obj_path, *, def collect_dwarf_files(obj_path, *,
objdump_path=OBJDUMP_PATH, objdump_path=OBJDUMP_PATH,
**args): **args):
class FileInfo:
def __init__(self, files):
self.files = files
def get(self, k, d=None):
return self.files.get(k, d)
def __getitem__(self, k):
v = self.get(k)
if v is None:
raise KeyError(k)
return v
def __contains__(self, k):
return self.get(k) is not None
def __len__(self):
return len(self.files)
def __iter__(self):
return (v for k, v in self.files.items())
line_pattern = re.compile( line_pattern = re.compile(
'^\s*(?P<no>[0-9]+)' '^\s*(?P<no>[0-9]+)'
'(?:\s+(?P<dir>[0-9]+))?' '(?:\s+(?P<dir>[0-9]+))?'
'.*\s+(?P<path>[^\s]+)\s*$') '.*\s+(?P<path>[^\s]+)\s*$')
# find source paths # find source paths
dirs = {} dirs = co.OrderedDict()
files = {} files = co.OrderedDict()
# note objdump-path may contain extra args # note objdump-path may contain extra args
cmd = objdump_path + ['--dwarf=rawline', obj_path] cmd = objdump_path + ['--dwarf=rawline', obj_path]
if args.get('verbose'): if args.get('verbose'):
@@ -211,7 +233,7 @@ def collect_dwarf_files(obj_path, *,
files_[no] = os.path.abspath(file) files_[no] = os.path.abspath(file)
files = files_ files = files_
return files return FileInfo(files)
def collect_dwarf_info(obj_path, filter=None, *, def collect_dwarf_info(obj_path, filter=None, *,
objdump_path=OBJDUMP_PATH, objdump_path=OBJDUMP_PATH,
@@ -227,6 +249,9 @@ def collect_dwarf_info(obj_path, filter=None, *,
self.ats = ats or {} self.ats = ats or {}
self.children = children or [] self.children = children or []
def get(self, k, d=None):
return self.ats.get(k, d)
def __getitem__(self, k): def __getitem__(self, k):
return self.ats[k] return self.ats[k]
@@ -241,6 +266,71 @@ def collect_dwarf_info(obj_path, filter=None, *,
self.tag, self.tag,
self.ats) self.ats)
@ft.cached_property
def name(self):
if 'DW_AT_name' in self:
name = self['DW_AT_name'].split(':')[-1].strip()
# prefix with struct/union
if self.tag == 'DW_TAG_structure_type':
name = 'struct ' + name
elif self.tag == 'DW_TAG_union_type':
name = 'union ' + name
elif self.tag == 'DW_TAG_enumeration_type':
name = 'enum ' + name
return name
else:
return None
# a collection of dwarf entries
class DwarfInfo:
def __init__(self, entries):
self.entries = entries
def get(self, k, d=None):
# allow lookup by both offset and dwarf name
if not isinstance(k, str):
return self.entries.get(k, d)
else:
import difflib
# organize entries by name
if not hasattr(self, '_by_name'):
self._by_name = {}
for entry in self.entries.values():
if entry.name is not None:
self._by_name[entry.name] = entry
# exact match? avoid difflib if we can for speed
if k in self._by_name:
return self._by_name[k]
# find the best matching dwarf entry with difflib
#
# this can be different from the actual symbol because
# of optimization passes
else:
name, entry = max(
self._by_name.items(),
key=lambda entry: difflib.SequenceMatcher(
None, entry[0], k, False).ratio(),
default=(None, None))
return entry
def __getitem__(self, k):
v = self.get(k)
if v is None:
raise KeyError(k)
return v
def __contains__(self, k):
return self.get(k) is not None
def __len__(self):
return len(self.entries)
def __iter__(self):
return (v for k, v in self.entries.items())
info_pattern = re.compile( info_pattern = re.compile(
'^\s*(?:<(?P<level>[^>]*)>' '^\s*(?:<(?P<level>[^>]*)>'
'\s*<(?P<off>[^>]*)>' '\s*<(?P<off>[^>]*)>'
@@ -250,7 +340,7 @@ def collect_dwarf_info(obj_path, filter=None, *,
'\s*:(?P<v>.*))\s*$') '\s*:(?P<v>.*))\s*$')
# collect dwarf entries # collect dwarf entries
entries = co.OrderedDict() info = co.OrderedDict()
entry = None entry = None
levels = {} levels = {}
# note objdump-path may contain extra args # note objdump-path may contain extra args
@@ -276,7 +366,7 @@ def collect_dwarf_info(obj_path, filter=None, *,
if (entry.level == 1 and ( if (entry.level == 1 and (
# unless this entry is filtered # unless this entry is filtered
filter_ is None or entry.tag in filter_)): filter_ is None or entry.tag in filter_)):
entries[entry.off] = entry info[entry.off] = entry
# store entry in parent # store entry in parent
levels[entry.level] = entry levels[entry.level] = entry
if entry.level-1 in levels: if entry.level-1 in levels:
@@ -289,12 +379,11 @@ def collect_dwarf_info(obj_path, filter=None, *,
if proc.returncode != 0: if proc.returncode != 0:
raise sp.CalledProcessError(proc.returncode, proc.args) raise sp.CalledProcessError(proc.returncode, proc.args)
return entries return DwarfInfo(info)
def collect(obj_paths, *, def collect(obj_paths, *,
sources=None, sources=None,
everything=False, everything=False,
internal=False,
**args): **args):
results = [] results = []
for obj_path in obj_paths: for obj_path in obj_paths:
@@ -304,21 +393,126 @@ def collect(obj_paths, *,
# find dwarf info # find dwarf info
info = collect_dwarf_info(obj_path, **args) info = collect_dwarf_info(obj_path, **args)
# collect structs and other types # recursive+cached size finder
typedefs = {} def sizeof(entry):
typedefed = set() # cached?
types = {} if not hasattr(sizeof, 'cache'):
for no, entry in info.items(): sizeof.cache = {}
# skip non-types if entry.off in sizeof.cache:
if entry.tag not in { return sizeof.cache[entry.off]
'DW_TAG_typedef',
# explicit size?
if 'DW_AT_byte_size' in entry:
size = int(entry['DW_AT_byte_size'])
# array? multiply by size
elif entry.tag == 'DW_TAG_array_type':
type = info[int(entry['DW_AT_type'].strip('<>'), 0)]
size = sizeof(type)
for child in entry.children:
if child.tag == 'DW_TAG_subrange_type':
size *= int(child['DW_AT_upper_bound']) + 1
# indirect type?
elif 'DW_AT_type' in entry:
type = info[int(entry['DW_AT_type'].strip('<>'), 0)]
size = sizeof(type)
else:
assert False, "Unknown dwarf entry? %r" % entry.tag
sizeof.cache[entry.off] = size
return size
# recursive+cached alignment finder
#
# Dwarf doesn't seem to give us this info, so we infer it from
# the size of children pointer/base types. This is _usually_
# correct.
def alignof(entry):
# cached?
if not hasattr(alignof, 'cache'):
alignof.cache = {}
if entry.off in alignof.cache:
return alignof.cache[entry.off]
# pointer? base type? assume this size == alignment
if entry.tag in {
'DW_TAG_pointer_type',
'DW_TAG_base_type'}:
align = int(entry['DW_AT_byte_size'])
# struct? union? take max alignment of children
elif entry.tag in {
'DW_TAG_structure_type', 'DW_TAG_structure_type',
'DW_TAG_union_type', 'DW_TAG_union_type'}:
'DW_TAG_enumeration_type'}: align = max(alignof(child) for child in entry.children)
# indirect type?
elif 'DW_AT_type' in entry:
type = int(entry['DW_AT_type'].strip('<>'), 0)
align = alignof(info[type])
else:
assert False, "Unknown dwarf entry? %r" % entry.tag
alignof.cache[entry.off] = align
return align
# recursive+cached children finder
def childrenof(entry):
# cached?
if not hasattr(childrenof, 'cache'):
childrenof.cache = {}
if entry.off in childrenof.cache:
return childrenof.cache[entry.off]
# pointer? base type?
if entry.tag in {
'DW_TAG_pointer_type',
'DW_TAG_base_type'}:
children = []
# struct? union?
elif entry.tag in {
'DW_TAG_structure_type',
'DW_TAG_union_type'}:
children = []
for child in entry.children:
name_ = child.name
size_ = sizeof(child)
align_ = alignof(child)
children_ = childrenof(child)
children.append(StructResult(
file, name_, size_, align_, children_))
# indirect type?
elif 'DW_AT_type' in entry:
type = int(entry['DW_AT_type'].strip('<>'), 0)
children = childrenof(info[type])
else:
assert False, "Unknown dwarf entry? %r" % entry.tag
childrenof.cache[entry.off] = children
return children
# collect structs and other types
typedefs = co.OrderedDict()
typedefed = set()
types = co.OrderedDict()
for entry in info:
# skip non-types and types with no name
if (entry.tag not in {
'DW_TAG_typedef',
'DW_TAG_structure_type',
'DW_TAG_union_type',
'DW_TAG_enumeration_type'}
or entry.name is None):
continue continue
# discard internal types
if not everything and entry.name.startswith('__'):
continue
# if we have no file guess from obj path
if 'DW_AT_decl_file' in entry:
file = files.get(int(entry['DW_AT_decl_file']), '?')
else:
file = re.sub('(\.o)?$', '.c', obj_path, 1)
# ignore filtered sources # ignore filtered sources
file = files.get(int(entry['DW_AT_decl_file']), '?')
if sources is not None: if sources is not None:
if not any(os.path.abspath(file) == os.path.abspath(s) if not any(os.path.abspath(file) == os.path.abspath(s)
for s in sources): for s in sources):
@@ -330,91 +524,33 @@ def collect(obj_paths, *,
os.path.abspath(file)]) == os.getcwd()): os.path.abspath(file)]) == os.getcwd()):
continue continue
# limit to .h files unless --internal # find name
if not internal and not file.endswith('.h'): name = entry.name
continue
# skip types with no names
if 'DW_AT_name' not in entry:
continue
name = entry['DW_AT_name'].split(':')[-1].strip()
# find the size of a type, recursing if necessary # find the size of a type, recursing if necessary
def sizeof(entry):
# explicit size?
if 'DW_AT_byte_size' in entry:
return int(entry['DW_AT_byte_size'])
# indirect type?
elif 'DW_AT_type' in entry:
type = int(entry['DW_AT_type'].strip('<>'), 0)
size = sizeof(info[type])
# wait are we an array?
if entry.tag == 'DW_TAG_array_type':
for child in entry.children:
if child.tag == 'DW_TAG_subrange_type':
size *= int(child['DW_AT_upper_bound']) + 1
return size
else:
assert False
size = sizeof(entry) size = sizeof(entry)
# find alignment, recursing if necessary # find alignment, recursing if necessary
#
# Dwarf doesn't seem to give us this info, so we infer it from
# the size of children pointer/base types. This is _usually_
# correct.
def alignof(entry):
# pointer/base type? assume this size == alignment
if entry.tag in {
'DW_TAG_pointer_type',
'DW_TAG_base_type'}:
return int(entry['DW_AT_byte_size'])
# indirect type?
elif 'DW_AT_type' in entry:
type = int(entry['DW_AT_type'].strip('<>'), 0)
return alignof(info[type])
# struct/union probably
elif entry.children:
return max(alignof(child) for child in entry.children)
else:
assert False
align = alignof(entry) align = alignof(entry)
# find children, recursing if necessary # find children, recursing if necessary
def childrenof(entry):
# pointer? these end up recursive but the underlying
# type doesn't really matter here
if entry.tag == 'DW_TAG_pointer_type':
return []
# indirect type?
elif 'DW_AT_type' in entry:
type = int(entry['DW_AT_type'].strip('<>'), 0)
return childrenof(info[type])
# struct/union probably
else:
children = []
for child in entry.children:
name = child['DW_AT_name'].split(':')[-1].strip()
size = sizeof(child)
align = alignof(child)
children.append(StructResult(file, name, size, align,
childrenof(child)))
return children
children = childrenof(entry) children = childrenof(entry)
# typdefs exist in a separate namespace, so we need to track # typdefs exist in a separate namespace, so we need to track
# these separately # these separately
if entry.tag == 'DW_TAG_typedef': if entry.tag == 'DW_TAG_typedef':
typedefs[no] = StructResult(file, name, size, align, children) typedefs[entry.off] = StructResult(
file, name, size, align, children)
typedefed.add(int(entry['DW_AT_type'].strip('<>'), 0)) typedefed.add(int(entry['DW_AT_type'].strip('<>'), 0))
else: else:
types[no] = StructResult(file, name, size, align, children) types[entry.off] = StructResult(
file, name, size, align, children)
# let typedefs take priority # let typedefs take priority
results.extend(typedefs.values()) results.extend(typedefs.values())
results.extend(type results.extend(type
for no, type in types.items() for off, type in types.items()
if no not in typedefed) if off not in typedefed)
return results return results
@@ -646,6 +782,9 @@ def table(Result, results, diff_results=None, *,
types[k].ratio( types[k].ratio(
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None))))) getattr(diff_r, k, None)))))
# append any notes
if hasattr(r, 'notes'):
entry[-1][1].extend(r.notes)
return entry return entry
# recursive entry helper, only used by some scripts # recursive entry helper, only used by some scripts
@@ -658,8 +797,7 @@ def table(Result, results, diff_results=None, *,
for r in results_} for r in results_}
names_ = list(table_.keys()) names_ = list(table_.keys())
# sort the children layer # only sort the children layer if explicitly requested
names_.sort()
if sort: if sort:
for k, reverse in reversed(sort): for k, reverse in reversed(sort):
names_.sort( names_.sort(
@@ -950,10 +1088,6 @@ if __name__ == "__main__":
'--everything', '--everything',
action='store_true', action='store_true',
help="Include builtin and libc specific symbols.") help="Include builtin and libc specific symbols.")
parser.add_argument(
'--internal',
action='store_true',
help="Also show structs in .c files.")
parser.add_argument( parser.add_argument(
'-z', '--depth', '-z', '--depth',
nargs='?', nargs='?',