"""physobj_owner_inspect.py For a specific owner vtable, find all instances and report: - distinct instance count - typical instance footprint (count of physobj pointers per instance, max field offset) - histogram of physobj-field offsets within the owner - 5 sample instances dumped (first 0x100 bytes) """ import struct, sys from collections import Counter, defaultdict from minidump.minidumpfile import MinidumpFile CPHYSOBJ_VT = 0x007c78e0 def _ei(v): if v is None: return 0 if hasattr(v, 'value'): return int(v.value) return int(v) def main(): dump_path = sys.argv[1] target_vt = int(sys.argv[2], 0) md = MinidumpFile.parse(dump_path) reader = md.get_reader().get_buffered_reader() # First pass: find physobj instances scan_regions = [] for r in md.memory_info.infos: st, ty, pr = _ei(r.State), _ei(r.Type), _ei(r.Protect) & 0xff if st != 0x1000: continue if ty == 0x1000000: continue if pr not in (0x04, 0x40): continue scan_regions.append((r.BaseAddress, r.RegionSize)) region_bufs = [] physobj_addrs = set() owner_addrs = [] # va where DWORD == target_vt for base, size in scan_regions: try: reader.move(base) buf = reader.read(size) except Exception: continue if not buf: continue region_bufs.append((base, buf)) end = (len(buf) // 4) * 4 for off in range(0, end, 4): v = struct.unpack_from(" count physobjs_per_owner = [] samples = [] for ova, base, off, buf in owner_addrs[:50000]: end = min(len(buf), off + SCAN_AHEAD) nphysobj = 0 max_field = 0 for fo in range(0, end - off, 4): v = struct.unpack_from(" max_field: max_field = fo physobjs_per_owner.append((ova, nphysobj, max_field)) if len(samples) < 5 and nphysobj > 0: samples.append((ova, buf[off:off + 0x100])) print(f"\nphysobj-ptr field offsets within owner (top 20):") for fo, cnt in pfields.most_common(20): print(f" +0x{fo:03x} count={cnt}") # Distribution of physobj counts per owner counts = Counter(n for _, n, _ in physobjs_per_owner) print(f"\nphysobjs-per-owner distribution (top 10):") for n, cnt in counts.most_common(10): print(f" {n} physobjs in {cnt} owners") nz = [n for _, n, _ in physobjs_per_owner if n > 0] if nz: avg = sum(nz) / len(nz) print(f"\nowners with >=1 physobj: {len(nz)}, avg {avg:.2f} physobj/owner") total = sum(nz) print(f"total physobj-ptr edges from these owners: {total}") print(f"unique physobj instances pointed to (upper bound): {min(total, len(physobj_addrs))}") print(f"\nsample owner dumps (first 0x100 bytes):") for ova, data in samples[:3]: print(f" owner @ 0x{ova:08x}:") for i in range(0, len(data), 16): chunk = data[i:i + 16] print(f" +0x{i:03x}: {chunk.hex(' ')}") if __name__ == "__main__": main()