Initial commit — leak-hunt project complete
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>
This commit is contained in:
commit
57b5e43d0e
199 changed files with 1648333 additions and 0 deletions
53
tools/inspect_vtable.py
Normal file
53
tools/inspect_vtable.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
"""inspect_vtable.py <dump.dmp> <vtable_va> [num_slots]
|
||||
Read N dwords starting at vtable_va. For each, mark whether it is in
|
||||
code memory (image, executable). A valid vtable is a row of >= 4
|
||||
contiguous code pointers.
|
||||
"""
|
||||
import struct, sys
|
||||
from minidump.minidumpfile import MinidumpFile
|
||||
|
||||
|
||||
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])
|
||||
vt = int(sys.argv[2], 16)
|
||||
n = int(sys.argv[3]) if len(sys.argv) > 3 else 32
|
||||
|
||||
# Module map
|
||||
mods = []
|
||||
for m in md.modules.modules:
|
||||
mods.append((m.baseaddress, m.size, m.name.split("\\")[-1]))
|
||||
def mod_of(a):
|
||||
for b, s, nm in mods:
|
||||
if b <= a < b + s: return nm
|
||||
return None
|
||||
|
||||
# Executable image-region cache
|
||||
exec_ranges = []
|
||||
for r in md.memory_info.infos:
|
||||
st, ty, pr = _ei(r.State), _ei(r.Type), _ei(r.Protect) & 0xff
|
||||
if st == 0x1000 and ty == 0x1000000 and pr in (0x20, 0x80):
|
||||
exec_ranges.append((r.BaseAddress, r.BaseAddress + r.RegionSize))
|
||||
def is_exec(a):
|
||||
for lo, hi in exec_ranges:
|
||||
if lo <= a < hi: return True
|
||||
return False
|
||||
|
||||
rdr = md.get_reader().get_buffered_reader()
|
||||
rdr.move(vt)
|
||||
buf = rdr.read(n * 4)
|
||||
print(f"vtable @ 0x{vt:08x} ({mod_of(vt) or '?'}):")
|
||||
for i in range(n):
|
||||
v = struct.unpack_from("<I", buf, i*4)[0]
|
||||
owner = mod_of(v) or "?"
|
||||
exe = "CODE" if is_exec(v) else " "
|
||||
print(f" [{i:2d}] +0x{i*4:02x} 0x{v:08x} {exe} ({owner})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue