PoC: Minidumps w/ symbolification

Plenty of TODOs left; but this proves we can find:

* file names
* function names
* line nos
* source context

See #82
This commit is contained in:
Klaas van Schelven
2025-11-12 14:57:02 +01:00
parent ab065a6329
commit b60980c8f3
3 changed files with 177 additions and 33 deletions

View File

@@ -2,27 +2,28 @@
# https://github.com/getsentry/sentry/blob/f0ac91f2ec6b45ad18e5eea6df72c5c72573e964/src/sentry/models/minidump.py#L53
# with (as it stands) minor modifications.
import logging
from symbolic import ProcessState
import symbolic
from files.minidump import build_cfi_map_from_minidump_bytes, event_threads_for_process_state
def merge_minidump_event(data, minidump_bytes):
state = ProcessState.from_minidump_buffer(minidump_bytes)
frame_info_map = build_cfi_map_from_minidump_bytes(minidump_bytes)
process_state = symbolic.ProcessState.from_minidump_buffer(minidump_bytes, frame_infos=frame_info_map)
data['level'] = 'fatal' if state.crashed else 'info'
data['level'] = 'fatal' if process_state.crashed else 'info'
exception_value = 'Assertion Error: %s' % state.assertion if state.assertion \
else 'Fatal Error: %s' % state.crash_reason
exception_value = 'Assertion Error: %s' % process_state.assertion if process_state.assertion \
else 'Fatal Error: %s' % process_state.crash_reason
# NO_BANANA: data['message'] is not the right target
# data['message'] = exception_value
if state.timestamp:
data['timestamp'] = float(state.timestamp)
if process_state.timestamp:
data['timestamp'] = float(process_state.timestamp)
# Extract as much system information as we can. TODO: We should create
# a custom context and implement a specific minidump view in the event
# UI.
info = state.system_info
info = process_state.system_info
context = data.setdefault('contexts', {})
os = context.setdefault('os', {})
device = context.setdefault('device', {})
@@ -30,46 +31,42 @@ def merge_minidump_event(data, minidump_bytes):
os['version'] = info.os_version
device['arch'] = info.cpu_family
# We can extract stack traces here already but since CFI is not
# available yet (without debug symbols), the stackwalker will
# resort to stack scanning which yields low-quality results. If
# the user provides us with debug symbols, we will reprocess this
# minidump and add improved stacktraces later.
threads = [{
'id': thread.thread_id,
'crashed': False,
'stacktrace': {
'frames': [{
'instruction_addr': '0x%x' % frame.instruction,
'function': '<unknown>', # Required by interface
} for frame in thread.frames()],
},
} for thread in state.threads()]
data.setdefault('threads', {})['values'] = threads
threads = event_threads_for_process_state(process_state)
data.setdefault("threads", {})["values"] = threads
# Mark the crashed thread and add its stacktrace to the exception
crashed_thread = threads[state.requesting_thread]
crashed_thread = threads[process_state.requesting_thread]
crashed_thread['crashed'] = True
# Extract the crash reason and infos
exception = {
'value': exception_value,
'thread_id': crashed_thread['id'],
'type': state.crash_reason,
'type': process_state.crash_reason,
# Move stacktrace here from crashed_thread (mutating!)
'stacktrace': crashed_thread.pop('stacktrace'),
}
for frame in exception['stacktrace']['frames']:
frame['in_app'] = True # minidumps don't distinguish in_app frames; assume all are in_app
exception['stacktrace']['frames'].reverse() # "Frames should be sorted from oldest to newest."
# TODO we don't have display-info for threads yet, I think?
# we may need to revert the per-thread stacktraces above as well then
data.setdefault('exception', {}) \
.setdefault('values', []) \
.append(exception)
# Extract referenced (not all loaded) images
images = [{
'type': 'apple', # Required by interface
# 'uuid': module.uuid, NO_BANANA
'type': 'elf', # TODO not sure what this should _actually_ be
'image_addr': module.addr,
'image_size': module.size,
# 'name': module.name, NO_BANANA
} for module in state.modules()]
'code_file': module.code_file,
'code_id': module.code_id,
'debug_file': module.debug_file,
'debug_id': symbolic.debuginfo.id_from_breakpad(module.debug_id) if module.debug_id else None,
} for module in process_state.modules()]
data.setdefault('debug_meta', {})['images'] = images