Added better game crash diagnostics.

This commit is contained in:
2026-09-18 21:36:12 +02:00
parent 9ef0eff2f5
commit 6a508476b9
2 changed files with 97 additions and 5 deletions

View File

@@ -866,7 +866,32 @@ def game_log_telemetry_summary(report: dict[str, object]) -> dict[str, object]:
"missing_image_reference_count", "missing_image_reference_count",
"missing_pack_image_reference_count", "missing_pack_image_reference_count",
) )
return {key: report[key] for key in keys if key in report} summary = {key: report[key] for key in keys if key in report}
asset_references = report.get("asset_references")
if isinstance(asset_references, dict):
asset_summary = {
key: asset_references[key]
for key in (
"total_events",
"unique_valid_references",
"invalid_reference_events",
"catalog_available",
"managed_integrity_failed",
)
if key in asset_references
}
for source_key, output_key in (
("managed_present_paths", "managed_present_count"),
("managed_missing_paths", "managed_missing_count"),
("publisher_unmanaged_paths", "publisher_unmanaged_count"),
("unclassified_paths", "unclassified_count"),
):
paths = asset_references.get(source_key)
if isinstance(paths, (list, tuple)):
asset_summary[output_key] = len(paths)
if asset_summary:
summary["asset_reference_summary"] = asset_summary
return summary
def crash_dump_telemetry_summary(report: dict[str, object]) -> dict[str, object]: def crash_dump_telemetry_summary(report: dict[str, object]) -> dict[str, object]:

View File

@@ -134,6 +134,11 @@ def _inspect_minidump_bytes(data: bytes) -> dict[str, object] | None:
exception_code = _read_u32(data, exception_rva + 8) exception_code = _read_u32(data, exception_rva + 8)
exception_flags = _read_u32(data, exception_rva + 12) exception_flags = _read_u32(data, exception_rva + 12)
exception_address = _read_u64(data, exception_rva + 24) exception_address = _read_u64(data, exception_rva + 24)
exception_parameters = _read_exception_parameters(
data,
exception_rva,
_exception_size,
)
modules = _read_modules(data, streams.get(_MODULE_LIST_STREAM_TYPE)) modules = _read_modules(data, streams.get(_MODULE_LIST_STREAM_TYPE))
exception_module = _module_for_address(modules, exception_address) exception_module = _module_for_address(modules, exception_address)
architecture = _read_architecture(data, streams.get(_SYSTEM_INFO_STREAM_TYPE)) architecture = _read_architecture(data, streams.get(_SYSTEM_INFO_STREAM_TYPE))
@@ -154,14 +159,25 @@ def _inspect_minidump_bytes(data: bytes) -> dict[str, object] | None:
), ),
} }
) )
stack_pointer = _read_exception_stack_pointer(
data,
exception_stream,
architecture,
)
stack = _read_exception_stack( stack = _read_exception_stack(
data, data,
streams.get(_THREAD_LIST_STREAM_TYPE), streams.get(_THREAD_LIST_STREAM_TYPE),
thread_id, thread_id,
stack_pointer=stack_pointer,
) )
if stack is not None: if stack is not None:
pointer_size = 8 if architecture in {"x86_64", "arm64", "ia64"} else 4 pointer_size = 8 if architecture in {"x86_64", "arm64", "ia64"} else 4
signatures = _application_stack_signatures(stack, pointer_size, modules) signatures = _application_stack_signatures(
stack,
pointer_size,
modules,
excluded_addresses=frozenset(exception_parameters),
)
if signatures: if signatures:
result["application_stack"] = signatures result["application_stack"] = signatures
return result return result
@@ -211,6 +227,8 @@ def _read_exception_stack(
data: bytes, data: bytes,
stream: tuple[int, int] | None, stream: tuple[int, int] | None,
thread_id: int, thread_id: int,
*,
stack_pointer: int | None = None,
) -> bytes | None: ) -> bytes | None:
if stream is None or stream[1] < 4: if stream is None or stream[1] < 4:
return None return None
@@ -222,10 +240,55 @@ def _read_exception_stack(
offset = rva + 4 + index * _THREAD_ENTRY_SIZE offset = rva + 4 + index * _THREAD_ENTRY_SIZE
if _read_u32(data, offset) != thread_id: if _read_u32(data, offset) != thread_id:
continue continue
stack_size = min(_read_u32(data, offset + 32), _MAX_STACK_BYTES) stack_start = _read_u64(data, offset + 24)
reported_stack_size = _read_u32(data, offset + 32)
stack_rva = _read_u32(data, offset + 36) stack_rva = _read_u32(data, offset + 36)
_require_range(data, stack_rva, stack_size) stack_offset = 0
return data[stack_rva : stack_rva + stack_size] if (
stack_pointer is not None
and stack_start <= stack_pointer < stack_start + reported_stack_size
):
stack_offset = stack_pointer - stack_start
stack_size = min(
reported_stack_size - stack_offset,
_MAX_STACK_BYTES,
)
_require_range(data, stack_rva + stack_offset, stack_size)
return data[stack_rva + stack_offset : stack_rva + stack_offset + stack_size]
return None
def _read_exception_parameters(
data: bytes,
exception_rva: int,
exception_size: int,
) -> tuple[int, ...]:
parameter_count = _read_u32(data, exception_rva + 32)
if parameter_count > 15 or 40 + parameter_count * 8 > exception_size:
raise ValueError("invalid minidump exception parameters")
return tuple(
_read_u64(data, exception_rva + 40 + index * 8)
for index in range(parameter_count)
)
def _read_exception_stack_pointer(
data: bytes,
stream: tuple[int, int],
architecture: str,
) -> int | None:
rva, size = stream
if size < 168:
return None
context_size = _read_u32(data, rva + 160)
context_rva = _read_u32(data, rva + 164)
if context_size == 0 or context_rva == 0:
return None
_require_range(data, context_rva, context_size)
if architecture == "x86" and context_size >= 200:
return _read_u32(data, context_rva + 196)
if architecture == "x86_64" and context_size >= 160:
return _read_u64(data, context_rva + 152)
return None return None
@@ -233,12 +296,16 @@ def _application_stack_signatures(
stack: bytes, stack: bytes,
pointer_size: int, pointer_size: int,
modules: tuple[_Module, ...], modules: tuple[_Module, ...],
*,
excluded_addresses: frozenset[int] = frozenset(),
) -> list[dict[str, str]]: ) -> list[dict[str, str]]:
result: list[dict[str, str]] = [] result: list[dict[str, str]] = []
seen: set[tuple[str, int]] = set() seen: set[tuple[str, int]] = set()
format_code = "<Q" if pointer_size == 8 else "<I" format_code = "<Q" if pointer_size == 8 else "<I"
for offset in range(0, len(stack) - pointer_size + 1, pointer_size): for offset in range(0, len(stack) - pointer_size + 1, pointer_size):
address = struct.unpack_from(format_code, stack, offset)[0] address = struct.unpack_from(format_code, stack, offset)[0]
if address in excluded_addresses:
continue
module = _module_for_address(modules, address) module = _module_for_address(modules, address)
if module is None or module.system: if module is None or module.system:
continue continue