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_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]:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user