Five bugs identified and patched in retail Asheron's Call client: - v3b: palette refcount over-increment (3-byte NOP at two sites) - v5: RenderSurface PurgeResource no-op stub (vtable slot 2 thunk) - v11: two dangling-pointer crash guards (NULL-check + reorder) - v14: CEnvCell::Destroy ClipPlaneList leak (18-byte JMP to cleanup thunk) - v22: unpacker stale-pointer SEH guard (whole-function __try/__except) All five ship in leakfix.dll (117 KB, SHA d282f23c…) which is loaded by acclient.exe at process start via PE import table patching by tools/install_leakfix.py. Controlled 15-client fleet soak: unpatched control died at 26h with palette exhaustion; all 14 patched clients survived past that point and reached ≥5-day uptime. Residual ~15 MB/h growth traced to d3d9.dll's internal slab allocator (260KB surface backing buffers retained after Release). See REPORT.md §10 for the full investigation; conclusion is that it's unfixable from outside d3d9. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
59 lines
1.7 KiB
Python
59 lines
1.7 KiB
Python
"""
|
|
inspect_regions.py <dump.dmp> [N]
|
|
Print the first 0x80 bytes of N sample 256-512 KB private RW regions.
|
|
Useful for eyeballing what the leaked allocations contain.
|
|
"""
|
|
import sys, os
|
|
from minidump.minidumpfile import MinidumpFile
|
|
|
|
|
|
def _enum_int(v):
|
|
if v is None: return 0
|
|
if hasattr(v, 'value'): return int(v.value)
|
|
return int(v)
|
|
|
|
|
|
def hexdump(buf, base):
|
|
lines = []
|
|
for i in range(0, len(buf), 16):
|
|
chunk = buf[i:i+16]
|
|
hexs = " ".join(f"{b:02x}" for b in chunk)
|
|
ascii = "".join(chr(b) if 32 <= b < 127 else "." for b in chunk)
|
|
lines.append(f" {base+i:08x} {hexs:<47} {ascii}")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main():
|
|
dump = sys.argv[1]
|
|
n = int(sys.argv[2]) if len(sys.argv) > 2 else 12
|
|
|
|
md = MinidumpFile.parse(dump)
|
|
reader = md.get_reader().get_buffered_reader()
|
|
|
|
cands = []
|
|
for r in md.memory_info.infos:
|
|
st = _enum_int(r.State)
|
|
ty = _enum_int(r.Type)
|
|
pr = _enum_int(r.Protect) & 0xFF
|
|
sz = r.RegionSize
|
|
if st == 0x1000 and ty == 0x20000 and pr in (0x04, 0x40) \
|
|
and 256*1024 <= sz < 512*1024:
|
|
cands.append((r.BaseAddress, sz))
|
|
|
|
print(f"=== {os.path.basename(dump)} : {len(cands)} candidate 256-512KB regions ===")
|
|
# Sample evenly across all candidates
|
|
step = max(1, len(cands) // n)
|
|
samples = cands[::step][:n]
|
|
|
|
for base, size in samples:
|
|
print(f"\n--- region 0x{base:08x} size={size} ({size/1024:.1f} KB) ---")
|
|
try:
|
|
reader.move(base)
|
|
buf = reader.read(0x80)
|
|
except Exception as e:
|
|
print(f" read failed: {e}"); continue
|
|
print(hexdump(buf, base))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|