#!/usr/bin/env python3 """symcrash.py -- turn a tester's [crash] line into function names. BTCrashFilter (btl4main.cpp) writes module-relative offsets into the day log: [crash] UNHANDLED EXCEPTION code=0xc0000005 addr=0xb8260 (btl4+0x8260) ... [crash] stack: btl4+0x8260 btl4+0xfe7ed 0x763ffcc9 ... Those offsets mean nothing without the symbols for the EXACT build that produced them -- which is why every session header carries `build=4.11. ()` and why the linker .map is archived per build alongside the PDB. Usage: python tools/symcrash.py "btl4+0x8260" python tools/symcrash.py (scans for [crash]) Resolution is nearest-preceding-symbol, so the answer is the function the address falls INSIDE, plus the byte offset into it. Addresses outside the module (kernel32, ntdll) are passed through untouched -- they are not ours. """ import re import sys def load_map(path): """Return (sorted [(rva, name)], preferred_base).""" syms = [] base = None with open(path, "r", encoding="utf-8", errors="replace") as fh: for line in fh: if base is None: m = re.search(r"Preferred load address is ([0-9A-Fa-f]+)", line) if m: base = int(m.group(1), 16) continue # " 0001:00000a10 ?Foo@Bar@@QAEXXZ 00401a10 f thing.obj" m = re.match(r"\s+[0-9A-Fa-f]{4}:[0-9A-Fa-f]{8}\s+(\S+)\s+([0-9A-Fa-f]{8,16})\s", line) if m and base is not None: syms.append((int(m.group(2), 16) - base, m.group(1))) syms.sort(key=lambda s: s[0]) return syms, base def resolve(syms, rva): lo, hi, best = 0, len(syms) - 1, None while lo <= hi: # nearest preceding symbol mid = (lo + hi) // 2 if syms[mid][0] <= rva: best = syms[mid] lo = mid + 1 else: hi = mid - 1 return best def main(): if len(sys.argv) < 3: sys.exit(__doc__) syms, base = load_map(sys.argv[1]) if not syms: sys.exit("no symbols parsed from %s -- is it a linker .map?" % sys.argv[1]) print("%d symbols, preferred base 0x%x\n" % (len(syms), base)) arg = sys.argv[2] try: # a log file? text = open(arg, "r", encoding="utf-8", errors="replace").read() except OSError: text = arg # ...no, a literal offset for line in text.splitlines() or [text]: if "btl4+" not in line: continue if line.lstrip().startswith("[crash]"): print(line.strip()) for off in re.findall(r"btl4\+0x([0-9A-Fa-f]+)", line): rva = int(off, 16) hit = resolve(syms, rva) if hit is None: print(" btl4+0x%-8x " % rva) else: print(" btl4+0x%-8x %s +0x%x" % (rva, hit[1], rva - hit[0])) print() if __name__ == "__main__": main()