Added better game crash diagnostics.
This commit is contained in:
@@ -866,7 +866,32 @@ def game_log_telemetry_summary(report: dict[str, object]) -> dict[str, object]:
|
||||
"missing_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]:
|
||||
|
||||
@@ -134,6 +134,11 @@ def _inspect_minidump_bytes(data: bytes) -> dict[str, object] | None:
|
||||
exception_code = _read_u32(data, exception_rva + 8)
|
||||
exception_flags = _read_u32(data, exception_rva + 12)
|
||||
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))
|
||||
exception_module = _module_for_address(modules, exception_address)
|
||||
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(
|
||||
data,
|
||||
streams.get(_THREAD_LIST_STREAM_TYPE),
|
||||
thread_id,
|
||||
stack_pointer=stack_pointer,
|
||||
)
|
||||
if stack is not None:
|
||||
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:
|
||||
result["application_stack"] = signatures
|
||||
return result
|
||||
@@ -211,6 +227,8 @@ def _read_exception_stack(
|
||||
data: bytes,
|
||||
stream: tuple[int, int] | None,
|
||||
thread_id: int,
|
||||
*,
|
||||
stack_pointer: int | None = None,
|
||||
) -> bytes | None:
|
||||
if stream is None or stream[1] < 4:
|
||||
return None
|
||||
@@ -222,10 +240,55 @@ def _read_exception_stack(
|
||||
offset = rva + 4 + index * _THREAD_ENTRY_SIZE
|
||||
if _read_u32(data, offset) != thread_id:
|
||||
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)
|
||||
_require_range(data, stack_rva, stack_size)
|
||||
return data[stack_rva : stack_rva + stack_size]
|
||||
stack_offset = 0
|
||||
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
|
||||
|
||||
|
||||
@@ -233,12 +296,16 @@ def _application_stack_signatures(
|
||||
stack: bytes,
|
||||
pointer_size: int,
|
||||
modules: tuple[_Module, ...],
|
||||
*,
|
||||
excluded_addresses: frozenset[int] = frozenset(),
|
||||
) -> list[dict[str, str]]:
|
||||
result: list[dict[str, str]] = []
|
||||
seen: set[tuple[str, int]] = set()
|
||||
format_code = "<Q" if pointer_size == 8 else "<I"
|
||||
for offset in range(0, len(stack) - pointer_size + 1, pointer_size):
|
||||
address = struct.unpack_from(format_code, stack, offset)[0]
|
||||
if address in excluded_addresses:
|
||||
continue
|
||||
module = _module_for_address(modules, address)
|
||||
if module is None or module.system:
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user