"""Read crash metadata locally; never upload dump contents or scan private strings."""
import argparse
import hashlib
import json
import struct
from pathlib import Path
def inspect(path):
data = path.read_bytes()
def unpack(fmt, at):
return struct.unpack_from('<' + fmt, data, at)
if data[:4] != b'MDMP':
raise ValueError('Not a Windows minidump')
count, directory = unpack('II', 8)
streams = {unpack('III', directory + i * 12)[0]: unpack('III', directory + i * 12)[1:]
for i in range(count)}
def text(at):
size, = unpack('I', at)
return data[at + 4:at + 4 + size].decode('utf-16-le', errors='replace')
modules = []
if 4 in streams:
_, at = streams[4]
n, = unpack('I', at)
for i in range(n):
p = at + 4 + i * 108
base, size, checksum, stamp, name = unpack('QIIII', p)
modules.append(dict(base=base, size=size, checksum=checksum, timestamp=stamp, name=text(name)))
def location(address):
for m in modules:
if m['base'] <= address < m['base'] + m['size']:
return {'module': m['name'], 'rva': hex(address - m['base'])}
return None
memory = []
if 5 in streams:
_, at = streams[5]
n, = unpack('I', at)
memory += [unpack('QII', at + 4 + i * 16) for i in range(n)]
if 9 in streams:
_, at = streams[9]
n, file_rva = unpack('QQ', at)
for i in range(n):
address, size = unpack('QQ', at + 16 + i * 16)
memory.append((address, size, file_rva))
file_rva += size
def read(address, size):
for base, length, file_rva in memory:
if base <= address and address + size <= base + length:
return data[file_rva + address - base:file_rva + address - base + size]
return b''
result = {'file': str(path.resolve()), 'sha256': hashlib.sha256(data).hexdigest(),
'streams': {str(k): v[0] for k, v in streams.items()},
'modules': [{**m, 'base': hex(m['base']), 'size': hex(m['size']), 'timestamp': hex(m['timestamp'])} for m in modules]}
if 6 not in streams:
return result
_, ex = streams[6]
thread, = unpack('I', ex)
code, flags, record, address, params, _ = unpack('IIQQII', ex + 8)
infos = unpack('15Q', ex + 40)[:min(params, 15)]
context_size, context_rva = unpack('II', ex + 160)
regs = {name: unpack('Q', context_rva + 0x78 + i * 8)[0] for i, name in enumerate(
['rax', 'rcx', 'rdx', 'rbx', 'rsp', 'rbp', 'rsi', 'rdi', 'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15', 'rip'])}
exception = {'thread': thread, 'code': hex(code), 'flags': flags, 'address': hex(address),
'location': location(address), 'parameters': [hex(x) for x in infos],
'registers': {k: hex(v) for k, v in regs.items()}, 'contextSize': context_size}
code_bytes = read(regs['rip'], 48)
exception['instructionBytes'] = code_bytes.hex()
try:
import capstone
dis = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64)
exception['instructions'] = [{'address': hex(i.address), 'instruction': i.mnemonic + ' ' + i.op_str}
for i in dis.disasm(code_bytes, regs['rip'])]
except ImportError:
pass
# These are candidates only, not unwound frames. A debugger must validate the calling stack.
candidates = []
for offset in range(0, 2048, 8):
value = read(regs['rsp'] + offset, 8)
if value:
pointer, = struct.unpack('<Q', value)
loc = location(pointer)
if loc:
candidates.append({'stackOffset': hex(offset), 'address': hex(pointer), **loc})
exception['stackPointerCandidates'] = candidates
result['exception'] = exception
if 7 in streams:
result['architecture'] = unpack('H', streams[7][1])[0]
if 15 in streams:
misc = streams[15][1]
size, flags, pid = unpack('III', misc)
if size >= 12 and flags & 1:
result['processId'] = pid
return result
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('dumps', type=Path, nargs='+')
parser.add_argument('--output', type=Path, required=True)
args = parser.parse_args()
results = [inspect(p) for p in args.dumps]
args.output.write_text(json.dumps(results, ensure_ascii=False, indent=2), encoding='utf-8')
for r in results:
ex = r.get('exception', {})
print(json.dumps({'dump': Path(r['file']).name, 'processId': r.get('processId'),
'exception': {k: v for k, v in ex.items() if k not in ('stackPointerCandidates',)},
'plugins': [m for m in r['modules'] if any(s in m['name'].lower() for s in ('dinput8', 'dbghelp', 'xfeed'))]}, ensure_ascii=False))
"""Read crash metadata locally; never upload dump contents or scan private strings."""
import argparse
import hashlib
import json
import struct
from pathlib import Path
def inspect(path):
data = path.read_bytes()
def unpack(fmt, at):
return struct.unpack_from('<' + fmt, data, at)
if data[:4] != b'MDMP':
raise ValueError('Not a Windows minidump')
count, directory = unpack('II', 8)
streams = {unpack('III', directory + i * 12)[0]: unpack('III', directory + i * 12)[1:]
for i in range(count)}
def text(at):
size, = unpack('I', at)
return data[at + 4:at + 4 + size].decode('utf-16-le', errors='replace')
modules = []
if 4 in streams:
_, at = streams[4]
n, = unpack('I', at)
for i in range(n):
p = at + 4 + i * 108
base, size, checksum, stamp, name = unpack('QIIII', p)
modules.append(dict(base=base, size=size, checksum=checksum, timestamp=stamp, name=text(name)))
def location(address):
for m in modules:
if m['base'] <= address < m['base'] + m['size']:
return {'module': m['name'], 'rva': hex(address - m['base'])}
return None
memory = []
if 5 in streams:
_, at = streams[5]
n, = unpack('I', at)
memory += [unpack('QII', at + 4 + i * 16) for i in range(n)]
if 9 in streams:
_, at = streams[9]
n, file_rva = unpack('QQ', at)
for i in range(n):
address, size = unpack('QQ', at + 16 + i * 16)
memory.append((address, size, file_rva))
file_rva += size
def read(address, size):
for base, length, file_rva in memory:
if base <= address and address + size <= base + length:
return data[file_rva + address - base:file_rva + address - base + size]
return b''
result = {'file': str(path.resolve()), 'sha256': hashlib.sha256(data).hexdigest(),
'streams': {str(k): v[0] for k, v in streams.items()},
'modules': [{**m, 'base': hex(m['base']), 'size': hex(m['size']), 'timestamp': hex(m['timestamp'])} for m in modules]}
if 6 not in streams:
return result
_, ex = streams[6]
thread, = unpack('I', ex)
code, flags, record, address, params, _ = unpack('IIQQII', ex + 8)
infos = unpack('15Q', ex + 40)[:min(params, 15)]
context_size, context_rva = unpack('II', ex + 160)
regs = {name: unpack('Q', context_rva + 0x78 + i * 8)[0] for i, name in enumerate(
['rax', 'rcx', 'rdx', 'rbx', 'rsp', 'rbp', 'rsi', 'rdi', 'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15', 'rip'])}
exception = {'thread': thread, 'code': hex(code), 'flags': flags, 'address': hex(address),
'location': location(address), 'parameters': [hex(x) for x in infos],
'registers': {k: hex(v) for k, v in regs.items()}, 'contextSize': context_size}
code_bytes = read(regs['rip'], 48)
exception['instructionBytes'] = code_bytes.hex()
try:
import capstone
dis = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64)
exception['instructions'] = [{'address': hex(i.address), 'instruction': i.mnemonic + ' ' + i.op_str}
for i in dis.disasm(code_bytes, regs['rip'])]
except ImportError:
pass
# These are candidates only, not unwound frames. A debugger must validate the calling stack.
candidates = []
for offset in range(0, 2048, 8):
value = read(regs['rsp'] + offset, 8)
if value:
pointer, = struct.unpack('<Q', value)
loc = location(pointer)
if loc:
candidates.append({'stackOffset': hex(offset), 'address': hex(pointer), **loc})
exception['stackPointerCandidates'] = candidates
result['exception'] = exception
if 7 in streams:
result['architecture'] = unpack('H', streams[7][1])[0]
if 15 in streams:
misc = streams[15][1]
size, flags, pid = unpack('III', misc)
if size >= 12 and flags & 1:
result['processId'] = pid
return result
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('dumps', type=Path, nargs='+')
parser.add_argument('--output', type=Path, required=True)
args = parser.parse_args()
results = [inspect(p) for p in args.dumps]
args.output.write_text(json.dumps(results, ensure_ascii=False, indent=2), encoding='utf-8')
for r in results:
ex = r.get('exception', {})
print(json.dumps({'dump': Path(r['file']).name, 'processId': r.get('processId'),
'exception': {k: v for k, v in ex.items() if k not in ('stackPointerCandidates',)},
'plugins': [m for m in r['modules'] if any(s in m['name'].lower() for s in ('dinput8', 'dbghelp', 'xfeed'))]}, ensure_ascii=False))