"""find_all_noop_slots.py Scan the .rdata-like image regions for vtable-shaped structures and report every slot that points to the no-op stub at 0x004154a0 (the `mov al,1; ret` stub used by GraphicsResource::PurgeResource, DBObj::ReleaseSubObjects, and similar "should be overridden" virtuals). Each finding is a vtable slot where the base implementation lies ("returns success without doing the work") and a subclass should have overridden it. The leaks happen where subclasses didn't override. Heuristic for "this is a vtable": - A run of aligned DWORDs where each DWORD points to an address inside an executable image region (.text). - At least N consecutive such DWORDs (N=4) qualifies as a vtable. For each vtable found, count how many slots == no-op-stub. Output: vtables ranked by no-op-slot count, with slot indices. """ import struct, sys from collections import defaultdict from minidump.minidumpfile import MinidumpFile NOOP_STUB = 0x004154a0 MIN_VTABLE_SLOTS = 4 def _ei(v): if v is None: return 0 if hasattr(v, 'value'): return int(v.value) return int(v) def main(): md = MinidumpFile.parse(sys.argv[1]) reader = md.get_reader().get_buffered_reader() mods = [(m.baseaddress, m.size, m.name) for m in md.modules.modules] def mod_of(addr): for b, s, n in mods: if b <= addr < b + s: return n.split("\\")[-1] return None # Image-region ranges image_ranges = [] for r in md.memory_info.infos: st, ty = _ei(r.State), _ei(r.Type) if st == 0x1000 and ty == 0x1000000: image_ranges.append((r.BaseAddress, r.BaseAddress + r.RegionSize, _ei(r.Protect) & 0xff)) image_ranges.sort() # exec image ranges (function pointers should point into these) exec_ranges = [(lo, hi) for lo, hi, pr in image_ranges if pr in (0x20, 0x40)] def is_exec(addr): for lo, hi in exec_ranges: if lo <= addr < hi: return True if addr < lo: return False return False # Scan readable image ranges for vtables scan_ranges = [(lo, hi) for lo, hi, pr in image_ranges if pr in (0x02, 0x04, 0x40)] # vtable_addr -> list of (slot_idx, slot_val) vtables = {} # only those with at least 1 no-op slot for lo, hi in scan_ranges: size = hi - lo try: reader.move(lo) buf = reader.read(size) except Exception: continue if not buf: continue n = len(buf) // 4 # Sliding: find runs where DWORD[i] is exec-image pointer. # Treat each such run >= MIN_VTABLE_SLOTS as a potential vtable. i = 0 while i < n: slot0 = struct.unpack_from("= MIN_VTABLE_SLOTS: noop_slots = [(k, v) for k, v in enumerate(slots) if v == NOOP_STUB] if noop_slots: vt_addr = lo + i*4 vtables[vt_addr] = (slots, noop_slots) i = j if len(slots) >= MIN_VTABLE_SLOTS else i + 1 print(f"vtables with at least one no-op-stub slot: {len(vtables)}") # Rank by no-op slot count ranked = sorted(vtables.items(), key=lambda x: len(x[1][1]), reverse=True) print() print("=== Top vtables by no-op-slot count ===") print(f"{'vtable':<12} {'#slots':>6} {'#noop':>6} noop_slot_indices") for vt, (slots, noops) in ranked[:50]: idxs = ", ".join(str(k) for k, _ in noops) print(f"0x{vt:08x} {len(slots):>6} {len(noops):>6} [{idxs}]") print() print(f"=== Total no-op-stub references across all vtables ===") total_noop = sum(len(noops) for _, (_, noops) in ranked) print(f"{total_noop} total no-op slot references across {len(vtables)} vtables") # Also save full list out = sys.argv[2] if len(sys.argv) > 2 else None if out: with open(out, "w", encoding="utf8") as f: f.write(f"vtable\tn_slots\tn_noop\tnoop_indices\n") for vt, (slots, noops) in ranked: idxs = ",".join(str(k) for k, _ in noops) f.write(f"0x{vt:08x}\t{len(slots)}\t{len(noops)}\t{idxs}\n") print(f"full list written to {out}") if __name__ == "__main__": main()