From 3e8f304138870b508eba6c72116a94e6c3b1dafe Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sun, 10 Aug 2025 23:39:16 -0500 Subject: [PATCH] scripts: ctx.py/structs.py: Worked around incomplete structs/unions Found when trying to measure ctx of yaffs2, which relies on incomplete structs to hide some internal state (yaffs_summary_tags, yaffs_DIR). This is less common in microcontroller filesystems since almost all structs end up statically/stack allocated, and you can't statically allocate incomplete structs. It's not too surprising, but incomplete structs have no associated DW_AT_byte_size in the relevant dwarf info, which broke ctx.py and structs.py... As a workaround, I'm now defaulting to size=0 if DW_AT_byte_size is missing. --- With this fix, at least structs.py is able to pick up the later internal definition of yaffs_summary_tags. ctx.py doesn't because it only looks at the unique dwarf offset referenced by the function definition, but I'm hesitant to try anything more clever here. yaffs_DIR is noteworthy in that there is simply no complete definition. Internally, yaffs_DIR pointers alias yaffsfs_DirSearchContext structs. In this case I think returning size=0 is the only reasonable option. --- scripts/ctx.py | 18 ++++++++++-------- scripts/structs.py | 16 +++++++++++++--- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/scripts/ctx.py b/scripts/ctx.py index 760cca05..ee4421d7 100755 --- a/scripts/ctx.py +++ b/scripts/ctx.py @@ -522,9 +522,16 @@ def collect_ctx(obj_paths, *, if 'DW_AT_type' in entry: type = info[int(entry['DW_AT_type'].strip('<>'), 0)] size += sizeof(type, seen | {entry.off}) + # base type? + elif entry.tag == 'DW_TAG_base_type': + size = int(entry['DW_AT_byte_size']) + # function pointer? + elif entry.tag == 'DW_TAG_subroutine_type': + size = 0 # struct? include any nested pointers elif entry.tag == 'DW_TAG_structure_type': - size = int(entry['DW_AT_byte_size']) + # note structs/unions can be incomplete + size = int(entry.get('DW_AT_byte_size', 0)) for child in entry.children: if child.tag != 'DW_TAG_member': continue @@ -536,7 +543,8 @@ def collect_ctx(obj_paths, *, size += sizeof(type__, seen | {entry.off}) # union? include any nested pointers elif entry.tag == 'DW_TAG_union_type': - size = int(entry['DW_AT_byte_size']) + # note structs/unions can be incomplete + size = int(entry.get('DW_AT_byte_size', 0)) size_ = 0 for child in entry.children: if child.tag != 'DW_TAG_member': @@ -555,12 +563,6 @@ def collect_ctx(obj_paths, *, for child in entry.children: if child.tag == 'DW_TAG_subrange_type': size *= int(child['DW_AT_upper_bound']) + 1 - # base type? - elif entry.tag == 'DW_TAG_base_type': - size = int(entry['DW_AT_byte_size']) - # function pointer? - elif entry.tag == 'DW_TAG_subroutine_type': - size = 0 # a modifier? elif (entry.tag in { 'DW_TAG_typedef', diff --git a/scripts/structs.py b/scripts/structs.py index 2b19b47b..be5bc9ae 100755 --- a/scripts/structs.py +++ b/scripts/structs.py @@ -422,9 +422,17 @@ def collect_structs(obj_paths, *, if entry.off in sizeof.cache: return sizeof.cache[entry.off] - # explicit size? - if 'DW_AT_byte_size' in entry: + # pointer? base type? + if entry.tag in { + 'DW_TAG_pointer_type', + 'DW_TAG_base_type'}: size = int(entry['DW_AT_byte_size']) + # struct? union? + elif entry.tag in { + 'DW_TAG_structure_type', + 'DW_TAG_union_type'}: + # note structs/unions can be incomplete + size = int(entry.get('DW_AT_byte_size', 0)) # array? multiply by size elif entry.tag == 'DW_TAG_array_type': type = info[int(entry['DW_AT_type'].strip('<>'), 0)] @@ -469,7 +477,9 @@ def collect_structs(obj_paths, *, elif entry.tag in { 'DW_TAG_structure_type', 'DW_TAG_union_type'}: - align = max(alignof(child) for child in entry.children) + align = max( + (alignof(child) for child in entry.children), + default=0) # indirect type? elif entry.tag in { 'DW_TAG_typedef',