Added better game crash diagnostics.
This commit is contained in:
@@ -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