Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
57 changes: 53 additions & 4 deletions src/pyts/elf/dwarf.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@
from __future__ import annotations

from collections.abc import 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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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:
Expand All @@ -238,14 +262,39 @@ 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]:
Comment thread
JonatanAntoni marked this conversation as resolved.
"""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 cu in info.iter_CUs():
for die in cu.iter_DIEs():
if die.tag not in {"DW_TAG_variable", "DW_TAG_subprogram"}:
continue
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."""

Expand Down
173 changes: 144 additions & 29 deletions src/pyts/elf/dwarf_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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."""
Expand Down Expand Up @@ -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)


Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Loading