diff --git a/README.md b/README.md index 723de50..5f6569e 100644 --- a/README.md +++ b/README.md @@ -77,8 +77,12 @@ The `ctrace-run.ctrace-setup` node retains the original source `ctrace.setup` content. Enriched location metadata is used internally when generating `ctrace-refs`. Symbol extents and types are emitted as `data[].symbol-size` and `data[].symbol-type` in the generated references; `data[].size` remains -the trace access size. Addresses and register values and masks are written as -32-bit hexadecimal YAML integers. The generated +the trace access size. DWARF types use language-neutral categories derived from +their tags and encodings, such as `signed`, `unsigned`, `bool`, `float`, +`pointer`, `array`, and `struct`; source-language type names are not emitted. +When a symbol type cannot be deduced from DWARF, `symbol-type` is omitted. +Addresses and register values and masks are written as 32-bit hexadecimal YAML +integers. The generated `ctrace-run` mapping contains only `generated-by`, `ctrace-setup`, and `ctrace-refs`; other source `ctrace` properties are not copied. Location-style and legacy `symbol`/`address` entries may coexist in one trace diff --git a/src/pyts/elf/dwarf.py b/src/pyts/elf/dwarf.py index 7147646..e899028 100644 --- a/src/pyts/elf/dwarf.py +++ b/src/pyts/elf/dwarf.py @@ -18,15 +18,16 @@ from __future__ import annotations -from collections.abc import Sequence +from collections.abc import Iterator, Sequence +from dataclasses import replace from typing import Any from pyts.elf.dwarf_members import ( + canonical_dwarf_type, die_size, die_symbol_type, die_type, dwarf_name, - dwarf_type_name, dwarf_type_size, find_dwarf_variable, iter_object_members, @@ -56,6 +57,20 @@ def __init__(self, elf_file: DwarfELFLike) -> None: self._members_by_expression: dict[str, MemberInfo | None] = {} self._all_members: list[MemberInfo] | None = None self._source_files: set[str] | None = None + self._symbol_types: dict[str, str] | None = None + + def with_symbol_types( + self, symbols: Sequence[SymbolInfo] + ) -> list[SymbolInfo]: + """Return symbols with types deduced exclusively from DWARF.""" + + if not symbols: + return [] + symbol_types = self._plain_symbol_types() + return [ + replace(symbol, type=symbol_types.get(symbol.name, "")) + for symbol in symbols + ] def resolve_members( self, @@ -163,15 +178,17 @@ def _resolve_member( return None offset, member_type = resolved address = base_address + offset + type_info = canonical_dwarf_type(member_type) return MemberInfo( name=expression, address=address, size=dwarf_type_size(cu, member_type), - type=dwarf_type_name(member_type), + type=type_info.name, base_symbol=base_name, member_path=".".join(member_names), offset=offset, source_file=die_source_file(info, cu, die), + source_type=type_info.source_name, ) def _resolve_plain_symbols( @@ -218,18 +235,25 @@ def _plain_symbol_from_die( ): return None die_source = die_source_file(info, cu, die) + symbol_type = die_symbol_type(die) + source_type = ( + canonical_dwarf_type(die_type(die)).source_name + if die.tag != "DW_TAG_subprogram" + else None + ) symbol = exact_symbols.get(name) if symbol is not None: return SymbolInfo( name=symbol.name, address=symbol.address, size=symbol.size, - type=symbol.type, + type=symbol_type, binding=symbol.binding, visibility=symbol.visibility, section=symbol.section, table=symbol.table, source_file=die_source, + source_type=source_type, ) address = resolve_die_address(info, cu, die) if address is None: @@ -238,14 +262,36 @@ def _plain_symbol_from_die( name=name, address=address, size=die_size(cu, die), - type=die_symbol_type(die), + type=symbol_type, binding="", visibility="", section=None, table="dwarf", source_file=die_source, + source_type=source_type, ) + def _plain_symbol_types(self) -> dict[str, str]: + """Return unambiguous DWARF types indexed by plain symbol name.""" + + if self._symbol_types is not None: + return self._symbol_types + info = self._dwarf_info() + candidates: dict[str, set[str]] = {} + if info is not None: + for die in iter_plain_symbol_dies(info): + name = dwarf_name(die) + if name: + candidates.setdefault(name, set()).add( + die_symbol_type(die) + ) + self._symbol_types = { + name: next(iter(types)) + for name, types in candidates.items() + if len(types) == 1 and "" not in types + } + return self._symbol_types + def _member_cache(self) -> list[MemberInfo]: """Discover and cache every addressable DWARF object member.""" @@ -297,3 +343,12 @@ def source_cache_key(expression: str, source_file: str | None) -> str: if source_file is None: return expression return f"{normalize_source_file(source_file)}::{expression}" + + +def iter_plain_symbol_dies(info: Any) -> Iterator[Any]: + """Yield DWARF DIEs that can describe a plain symbol type.""" + + for cu in info.iter_CUs(): + for die in cu.iter_DIEs(): + if die.tag in {"DW_TAG_variable", "DW_TAG_subprogram"}: + yield die diff --git a/src/pyts/elf/dwarf_members.py b/src/pyts/elf/dwarf_members.py index 62b762e..486ec82 100644 --- a/src/pyts/elf/dwarf_members.py +++ b/src/pyts/elf/dwarf_members.py @@ -19,7 +19,8 @@ from __future__ import annotations from collections.abc import Iterable, Sequence -from typing import Any, cast +from dataclasses import dataclass +from typing import Any from elftools.dwarf.dwarf_expr import DWARFExprParser @@ -28,12 +29,80 @@ TYPE_WRAPPER_TAGS = { + "DW_TAG_atomic_type", "DW_TAG_const_type", "DW_TAG_restrict_type", "DW_TAG_typedef", "DW_TAG_volatile_type", } +AGGREGATE_TYPE_TAGS = { + "DW_TAG_class_type", + "DW_TAG_structure_type", + "DW_TAG_union_type", +} + +CANONICAL_TAG_TYPES = { + "DW_TAG_array_type": "array", + "DW_TAG_class_type": "class", + "DW_TAG_enumeration_type": "enum", + "DW_TAG_pointer_type": "pointer", + "DW_TAG_ptr_to_member_type": "pointer", + "DW_TAG_reference_type": "reference", + "DW_TAG_rvalue_reference_type": "reference", + "DW_TAG_set_type": "set", + "DW_TAG_string_type": "string", + "DW_TAG_structure_type": "struct", + "DW_TAG_subroutine_type": "function", + "DW_TAG_union_type": "union", +} + +CANONICAL_ENCODING_TYPES = { + "DW_ATE_address": "address", + "DW_ATE_ASCII": "char", + "DW_ATE_boolean": "bool", + "DW_ATE_complex_float": "complex", + "DW_ATE_decimal_float": "float", + "DW_ATE_float": "float", + "DW_ATE_imaginary_float": "complex", + "DW_ATE_numeric_string": "string", + "DW_ATE_signed": "signed", + "DW_ATE_signed_char": "signed", + "DW_ATE_signed_fixed": "signed", + "DW_ATE_UCS": "char", + "DW_ATE_unsigned": "unsigned", + "DW_ATE_unsigned_char": "unsigned", + "DW_ATE_unsigned_fixed": "unsigned", + "DW_ATE_UTF": "char", +} + +DWARF_ENCODING_NAMES = { + 0x01: "DW_ATE_address", + 0x02: "DW_ATE_boolean", + 0x03: "DW_ATE_complex_float", + 0x04: "DW_ATE_float", + 0x05: "DW_ATE_signed", + 0x06: "DW_ATE_signed_char", + 0x07: "DW_ATE_unsigned", + 0x08: "DW_ATE_unsigned_char", + 0x09: "DW_ATE_imaginary_float", + 0x0B: "DW_ATE_numeric_string", + 0x0D: "DW_ATE_signed_fixed", + 0x0E: "DW_ATE_unsigned_fixed", + 0x0F: "DW_ATE_decimal_float", + 0x10: "DW_ATE_UTF", + 0x11: "DW_ATE_UCS", + 0x12: "DW_ATE_ASCII", +} + + +@dataclass(frozen=True) +class DwarfTypeInfo: + """Language-neutral type category and optional source-level spelling.""" + + name: str + source_name: str | None + def split_member_expression(expression: str) -> tuple[str, list[str]] | None: """Split a valid dotted member expression into base and member names.""" @@ -106,24 +175,25 @@ def resolve_member_path( variable_die: Any, member_names: Sequence[str], ) -> tuple[int, Any] | None: - """Walk nested structure members and return total offset and final type.""" + """Walk nested members and return total offset and declared final type.""" - current_type = unwrap_dwarf_type(die_type(variable_die)) + current_type = die_type(variable_die) offset = 0 for member_name in member_names: - if current_type is None or current_type.tag not in { - "DW_TAG_structure_type", - "DW_TAG_union_type", - }: + aggregate_type = unwrap_dwarf_type(current_type) + if ( + aggregate_type is None + or aggregate_type.tag not in AGGREGATE_TYPE_TAGS + ): return None - member_die = find_member(current_type, member_name) + member_die = find_member(aggregate_type, member_name) if member_die is None: return None next_offset = member_offset(member_die) if next_offset is None: return None offset += next_offset - current_type = unwrap_dwarf_type(die_type(member_die)) + current_type = die_type(member_die) return None if current_type is None else (offset, current_type) @@ -159,15 +229,17 @@ def iter_type_members( ) -> Iterable[MemberInfo]: """Recursively yield named members for a structure or union type.""" - if type_die.tag not in {"DW_TAG_structure_type", "DW_TAG_union_type"}: + if type_die.tag not in AGGREGATE_TYPE_TAGS: return for member_die in type_die.iter_children(): if member_die.tag != "DW_TAG_member": continue relative_offset = member_offset(member_die) - resolved_type = unwrap_dwarf_type(die_type(member_die)) + declared_type = die_type(member_die) + resolved_type = unwrap_dwarf_type(declared_type) if relative_offset is None or resolved_type is None: continue + type_info = canonical_dwarf_type(declared_type) name = dwarf_name(member_die) member_path = parent_path + ([name] if name else []) offset = parent_offset + relative_offset @@ -177,11 +249,12 @@ def iter_type_members( name=f"{base_symbol}.{'.'.join(member_path)}", address=address, size=dwarf_type_size(cu, resolved_type), - type=dwarf_type_name(resolved_type), + type=type_info.name, base_symbol=base_symbol, member_path=".".join(member_path), offset=offset, source_file=source_file, + source_type=type_info.source_name, ) yield from iter_type_members( source_file, @@ -234,28 +307,73 @@ def member_offset(member_die: Any) -> int | None: def dwarf_type_size(cu: Any, die: Any) -> int: """Return a DWARF type's byte size, including pointer fallback size.""" - byte_size = die.attributes.get("DW_AT_byte_size") + resolved_type = unwrap_dwarf_type(die) + if resolved_type is None: + return 0 + byte_size = resolved_type.attributes.get("DW_AT_byte_size") if byte_size is not None: return int(byte_size.value) - if die.tag == "DW_TAG_pointer_type": + if resolved_type.tag in { + "DW_TAG_pointer_type", + "DW_TAG_ptr_to_member_type", + "DW_TAG_reference_type", + "DW_TAG_rvalue_reference_type", + }: header = getattr(cu, "header", {}) address_size = header.get("address_size") if hasattr(header, "get") else None return int(address_size or 0) return 0 +def canonical_dwarf_type(die: Any | None) -> DwarfTypeInfo: + """Derive a language-neutral category from DWARF type semantics.""" + + if die is None: + return DwarfTypeInfo("", None) + source_name = dwarf_source_type_name(die) + resolved_type = unwrap_dwarf_type(die) + if resolved_type is None: + return DwarfTypeInfo("", source_name) + if source_name is None: + source_name = dwarf_name(resolved_type) or None + canonical_name = CANONICAL_TAG_TYPES.get(resolved_type.tag) + if canonical_name is None and resolved_type.tag == "DW_TAG_base_type": + canonical_name = canonical_encoding_type(resolved_type) + return DwarfTypeInfo(canonical_name or "", source_name) + + +def dwarf_source_type_name(die: Any) -> str | None: + """Return the first declared name across type and qualifier wrappers.""" + + current = die + while current is not None: + name = dwarf_name(current) + if name: + return name + if current.tag not in TYPE_WRAPPER_TAGS: + return None + current = die_type(current) + return None + + +def canonical_encoding_type(die: Any) -> str: + """Return the canonical category for a base type's DW_AT_encoding.""" + + attribute = die.attributes.get("DW_AT_encoding") + if attribute is None: + return "" + encoding = attribute.value + if isinstance(encoding, int): + encoding_name = DWARF_ENCODING_NAMES.get(encoding, "") + else: + encoding_name = str(encoding) + return CANONICAL_ENCODING_TYPES.get(encoding_name, "") + + def dwarf_type_name(die: Any) -> str: - """Return a human-readable name for a DWARF type DIE.""" + """Return the language-neutral category for a DWARF type DIE.""" - name = dwarf_name(die) - if name: - return name - known = { - "DW_TAG_pointer_type": "pointer", - "DW_TAG_structure_type": "struct", - "DW_TAG_union_type": "union", - } - return known.get(die.tag, cast(str, die.tag).removeprefix("DW_TAG_").lower()) + return canonical_dwarf_type(die).name def dwarf_name(die: Any) -> str: @@ -288,8 +406,5 @@ def die_symbol_type(die: Any) -> str: """Return the normalized symbol type represented by a DWARF DIE.""" if die.tag == "DW_TAG_subprogram": - return "func" - resolved_type = unwrap_dwarf_type(die_type(die)) - if resolved_type is None: - return str(die.tag).removeprefix("DW_TAG_") - return dwarf_type_name(resolved_type) + return "function" + return canonical_dwarf_type(die_type(die)).name diff --git a/src/pyts/elf/model.py b/src/pyts/elf/model.py index a404f23..97f07bc 100644 --- a/src/pyts/elf/model.py +++ b/src/pyts/elf/model.py @@ -64,6 +64,7 @@ class SymbolInfo: section: str | None table: str source_file: str | None = None + source_type: str | None = None @property def address_hex(self) -> str: @@ -84,6 +85,7 @@ class MemberInfo: offset: int source: str = "dwarf" source_file: str | None = None + source_type: str | None = None @property def address_hex(self) -> str: diff --git a/src/pyts/elf/resolver.py b/src/pyts/elf/resolver.py index d159db8..876611a 100644 --- a/src/pyts/elf/resolver.py +++ b/src/pyts/elf/resolver.py @@ -88,12 +88,17 @@ def resolve_symbols( include_undefined=include_undefined, ) if not requested: - return list(self._symbols().symbols(include_undefined)) + return list( + self._dwarf().with_symbol_types( + self._symbols().symbols(include_undefined) + ) + ) - results, exact_matches = self._symbols().resolve_names( + elf_results, exact_matches = self._symbols().resolve_names( requested, include_undefined=include_undefined, ) + results = self._dwarf().with_symbol_types(elf_results) member_expressions = [ name for name in requested if "." in name and name not in exact_matches ] @@ -109,7 +114,10 @@ def resolve_address(self, address: int) -> SymbolInfo | None: """Return the first defined symbol at an exact address.""" self._check_open() - return self._symbols().resolve_address(address) + symbol = self._symbols().resolve_address(address) + if symbol is None: + return None + return self._dwarf().with_symbol_types([symbol])[0] def resolve_object_members_by_address( self, diff --git a/src/pyts/elf/symbol_table.py b/src/pyts/elf/symbol_table.py index 3225386..8d6cbd4 100644 --- a/src/pyts/elf/symbol_table.py +++ b/src/pyts/elf/symbol_table.py @@ -114,17 +114,19 @@ def defined_symbols( section = section_name(elf_file, entry_value(symbol, "st_shndx")) if section is None and not include_undefined: continue - symbol_type = enum_value(mapping_entry(symbol, "st_info").get("type")) + elf_symbol_type = enum_value( + mapping_entry(symbol, "st_info").get("type") + ) address = normalize_symbol_address( int_entry(symbol, "st_value"), - symbol_type, + elf_symbol_type, ) results.append( SymbolInfo( name=name, address=address, size=int_entry(symbol, "st_size"), - type=symbol_type, + type="", binding=enum_value(mapping_entry(symbol, "st_info").get("bind")), visibility=enum_value( mapping_entry(symbol, "st_other").get("visibility") @@ -197,7 +199,7 @@ def enum_value(value: object) -> str: return text.lower() -def normalize_symbol_address(address: int, symbol_type: str) -> int: +def normalize_symbol_address(address: int, elf_symbol_type: str) -> int: """Return a half-word-aligned address for an ARM code symbol.""" - return address & ~1 if symbol_type in {"func", "gnu_ifunc"} else address + return address & ~1 if elf_symbol_type in {"func", "gnu_ifunc"} else address diff --git a/tests/test_elf.py b/tests/test_elf.py index 47656be..3516427 100644 --- a/tests/test_elf.py +++ b/tests/test_elf.py @@ -31,7 +31,13 @@ SymbolInfo, missing_symbols, ) -from pyts.elf.dwarf_members import resolve_die_address +from pyts.elf.dwarf_members import ( + canonical_dwarf_type, + die_symbol_type, + dwarf_type_name, + dwarf_type_size, + resolve_die_address, +) ResolverFactory = Callable[[Any], ElfResolver] @@ -186,6 +192,106 @@ def line_program_for_CU(self, cu: FakeCU) -> FakeLineProgram: return FakeLineProgram() +@pytest.mark.parametrize( + ("source_name", "encoding", "expected"), + [ + ("int", 0x05, "signed"), + ("i32", "DW_ATE_signed", "signed"), + ("unsigned int", 0x07, "unsigned"), + ("u32", "DW_ATE_unsigned", "unsigned"), + ("bool", 0x02, "bool"), + ("float", 0x04, "float"), + ("char32_t", 0x10, "char"), + ], +) +def test_canonical_dwarf_base_types_ignore_language_spelling( + source_name: str, + encoding: int | str, + expected: str, +) -> None: + die = FakeDie( + "DW_TAG_base_type", + source_name, + attrs={"DW_AT_encoding": FakeAttr(encoding)}, + ) + + type_info = canonical_dwarf_type(die) + + assert type_info.name == expected + assert type_info.source_name == source_name + + +@pytest.mark.parametrize( + ("tag", "expected"), + [ + ("DW_TAG_pointer_type", "pointer"), + ("DW_TAG_reference_type", "reference"), + ("DW_TAG_array_type", "array"), + ("DW_TAG_enumeration_type", "enum"), + ("DW_TAG_structure_type", "struct"), + ("DW_TAG_class_type", "class"), + ("DW_TAG_union_type", "union"), + ("DW_TAG_subroutine_type", "function"), + ("DW_TAG_string_type", "string"), + ], +) +def test_canonical_dwarf_types_use_structural_tags( + tag: str, + expected: str, +) -> None: + assert canonical_dwarf_type(FakeDie(tag)).name == expected + + +def test_canonical_dwarf_type_unwraps_qualifiers_and_preserves_typedef() -> None: + base_type = FakeDie( + "DW_TAG_base_type", + "unsigned int", + attrs={"DW_AT_encoding": FakeAttr(0x07)}, + ) + typedef = FakeDie("DW_TAG_typedef", "uint32_t", type_die=base_type) + qualified = FakeDie("DW_TAG_const_type", type_die=typedef) + + type_info = canonical_dwarf_type(qualified) + + assert type_info.name == "unsigned" + assert type_info.source_name == "uint32_t" + + +def test_unknown_dwarf_encoding_does_not_leak_source_type_name() -> None: + die = FakeDie( + "DW_TAG_base_type", + "vendor_specific_number", + attrs={"DW_AT_encoding": FakeAttr(0x80)}, + ) + + type_info = canonical_dwarf_type(die) + + assert type_info.name == "" + assert type_info.source_name == "vendor_specific_number" + + +def test_missing_dwarf_type_information_remains_undefined() -> None: + missing_type = canonical_dwarf_type(None) + dangling_wrapper = FakeDie("DW_TAG_const_type") + wrapped_type = canonical_dwarf_type(dangling_wrapper) + + assert missing_type.name == "" + assert missing_type.source_name is None + assert wrapped_type.name == "" + assert wrapped_type.source_name is None + assert dwarf_type_size(FakeCU([]), dangling_wrapper) == 0 + + +def test_base_type_without_encoding_remains_undefined() -> None: + die = FakeDie("DW_TAG_base_type", "implementation-defined") + + assert dwarf_type_name(die) == "" + + +def test_dwarf_subprogram_has_generic_function_type() -> None: + assert die_symbol_type(FakeDie("DW_TAG_subprogram", "main")) == "function" + + class FakeLineProgram: def __init__(self) -> None: self.header = { @@ -369,7 +475,7 @@ def test_resolves_symbol_metadata( assert symbols[0].address == 0x08000100 assert symbols[0].address_hex == "0x8000100" assert symbols[0].size == 64 - assert symbols[0].type == "func" + assert symbols[0].type == "" assert isinstance(symbols[0], SymbolInfo) assert symbols[0].binding == "global" assert symbols[0].section == ".text" @@ -420,6 +526,62 @@ def test_reports_missing_symbols( assert missing_symbols(symbols, ["main", "missing"]) == ["missing"] +def test_deduces_plain_symbol_type_from_dwarf( + monkeypatch: Any, + resolver_factory: ResolverFactory, +) -> None: + monkeypatch.setattr("pyts.elf.symbol_table.SymbolTableSection", FakeSymbolTable) + unsigned_type = FakeDie( + "DW_TAG_base_type", + "unsigned int", + attrs={"DW_AT_encoding": FakeAttr(0x07)}, + ) + dwarf_info = FakeDwarfInfo( + [FakeCU([FakeDie("DW_TAG_variable", "counter", type_die=unsigned_type)])] + ) + resolver = resolver_factory( + FakeDwarfELF(dwarf_info, sections=FakeELF().sections) + ) + + assert resolver.resolve_symbols(["counter"])[0].type == "unsigned" + address_symbol = resolver.resolve_address(0x20000000) + assert address_symbol is not None + assert address_symbol.type == "unsigned" + + +def test_leaves_plain_symbol_type_undefined_when_dwarf_type_is_ambiguous( + monkeypatch: Any, + resolver_factory: ResolverFactory, +) -> None: + monkeypatch.setattr("pyts.elf.symbol_table.SymbolTableSection", FakeSymbolTable) + dwarf_info = FakeDwarfInfo( + [ + FakeCU( + [ + FakeDie( + "DW_TAG_variable", + "counter", + type_die=FakeDie("DW_TAG_pointer_type"), + ), + FakeDie( + "DW_TAG_variable", + "counter", + type_die=FakeDie("DW_TAG_structure_type"), + ), + ] + ) + ] + ) + + symbols = resolve_symbols_from_elf( + resolver_factory, + FakeDwarfELF(dwarf_info, sections=FakeELF().sections), + ["counter"], + ) + + assert symbols[0].type == "" + + def test_elf_resolver_reuses_symbol_table_cache( monkeypatch: Any, resolver_factory: ResolverFactory, @@ -432,6 +594,7 @@ def test_elf_resolver_reuses_symbol_table_cache( counter = resolver.resolve_address(0x20000000) assert counter is not None assert counter.name == "counter" + assert resolver.resolve_address(0xDEADBEEF) is None assert [symbol.name for symbol in resolver.resolve_symbols()] == [ "main", "counter", @@ -509,7 +672,18 @@ def _fake_os_rtx_info_dwarf() -> FakeDwarfInfo: "DW_TAG_structure_type", attrs={"DW_AT_byte_size": FakeAttr(164)}, children=[ - _member("version", 4, FakeDie("DW_TAG_base_type", "unsigned int")), + _member( + "version", + 4, + FakeDie( + "DW_TAG_base_type", + "unsigned int", + attrs={ + "DW_AT_byte_size": FakeAttr(4), + "DW_AT_encoding": FakeAttr(0x07), + }, + ), + ), _member("thread", 20, thread_type), ], ) @@ -532,7 +706,10 @@ def test_resolves_nested_object_member( monkeypatch: Any, resolver_factory: ResolverFactory, ) -> None: - monkeypatch.setattr("pyts.elf.dwarf_members.DWARFExprParser", FakeDwarfExprParser) + monkeypatch.setattr( + "pyts.elf.dwarf_members.DWARFExprParser", + FakeDwarfExprParser, + ) members = resolve_object_members_from_elf( resolver_factory, @@ -553,6 +730,23 @@ def test_resolves_nested_object_member( ] +def test_resolved_member_uses_canonical_type_and_keeps_source_spelling( + monkeypatch: Any, + resolver_factory: ResolverFactory, +) -> None: + monkeypatch.setattr("pyts.elf.dwarf_members.DWARFExprParser", FakeDwarfExprParser) + + members = resolve_object_members_from_elf( + resolver_factory, + FakeDwarfELF(_fake_os_rtx_info_dwarf()), + ["osRtxInfo.version"], + ) + + assert members[0].type == "unsigned" + assert members[0].source_type == "unsigned int" + assert members[0].size == 4 + + def test_resolves_object_member_with_source_file_filter( monkeypatch: Any, resolver_factory: ResolverFactory, @@ -610,7 +804,7 @@ def test_resolves_plain_symbol_with_source_file_filter( name="main", address=0x08000100, size=64, - type="func", + type="function", binding="global", visibility="default", section=".text", diff --git a/tests/test_trace.py b/tests/test_trace.py index 305f83f..32c4eb1 100644 --- a/tests/test_trace.py +++ b/tests/test_trace.py @@ -139,7 +139,7 @@ def _tick_member() -> MemberInfo: name="osRtxInfo.kernel.tick", address=0x20000024, size=4, - type="int", + type="signed", base_symbol="osRtxInfo", member_path="kernel.tick", offset=0x10, @@ -288,6 +288,39 @@ def fake_resolve_symbols( } +def test_setup_trace_omits_unknown_resolved_symbol_type( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + project, cbuild_run, trace_name = _write_trace_project( + tmp_path, + symbols=["mystery"], + ) + symbol = SymbolInfo( + name="mystery", + address=0x20000000, + size=4, + type="", + binding="", + visibility="", + section=None, + table="dwarf", + source_type="vendor_specific_number", + ) + _patch_trace_resolver( + monkeypatch, + resolve_symbols=lambda *_args: [symbol], + ) + + setup_trace(cbuild_run) + + output = read_yaml(project / ".trace" / f"{trace_name}.ctrace-run.yml") + entry = output["ctrace"]["data"][0] + assert entry["address"] == "0x20000000" + assert entry["symbol-size"] == 4 + assert "symbol-type" not in entry + + def test_setup_trace_enriches_spec_location_entries( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, @@ -380,7 +413,7 @@ def fake_resolve_symbols( "symbol": "osRtxInfo.kernel.tick", "address": "0x20000024", "symbol-size": 4, - "symbol-type": "int", + "symbol-type": "signed", }, { "location": 'Blinky\u00ad.axf|"rtx_kernel.c"::osRtxInfo.kernel.tick', @@ -388,7 +421,7 @@ def fake_resolve_symbols( "symbol": "osRtxInfo.kernel.tick", "address": "0x20000024", "symbol-size": 4, - "symbol-type": "int", + "symbol-type": "signed", }, ]