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

@@ -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