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
47
dll/leakfix/tools/list_imports.py
Normal file
47
dll/leakfix/tools/list_imports.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
"""list_imports.py <pe.exe> — list all DLLs imported by a PE file."""
|
||||
import struct, sys
|
||||
|
||||
with open(sys.argv[1], "rb") as f:
|
||||
data = f.read()
|
||||
pe_off = struct.unpack_from("<I", data, 0x3c)[0]
|
||||
opt_off = pe_off + 24
|
||||
num_sec = struct.unpack_from("<H", data, pe_off + 6)[0]
|
||||
size_opt = struct.unpack_from("<H", data, pe_off + 20)[0]
|
||||
sec_off = opt_off + size_opt
|
||||
imp_rva, imp_size = struct.unpack_from("<II", data, opt_off + 96 + 1*8)
|
||||
|
||||
# Build RVA->offset map
|
||||
secs = []
|
||||
for i in range(num_sec):
|
||||
sh = sec_off + i*40
|
||||
vaddr = struct.unpack_from("<I", data, sh+12)[0]
|
||||
vsize = struct.unpack_from("<I", data, sh+8)[0]
|
||||
rsize = struct.unpack_from("<I", data, sh+16)[0]
|
||||
roff = struct.unpack_from("<I", data, sh+20)[0]
|
||||
secs.append((vaddr, max(vsize, rsize), roff, bytes(data[sh:sh+8]).rstrip(b"\0").decode()))
|
||||
|
||||
def rva2off(rva):
|
||||
for vaddr, vsize, roff, _ in secs:
|
||||
if vaddr <= rva < vaddr + vsize:
|
||||
return roff + (rva - vaddr)
|
||||
return None
|
||||
|
||||
print(f"IMPORT dir RVA=0x{imp_rva:08x} size={imp_size}")
|
||||
print(f"sections:")
|
||||
for v, sz, r, n in secs:
|
||||
print(f" {n:>10} vaddr=0x{v:08x} vsize={sz:>8} roff=0x{r:x}")
|
||||
|
||||
print("imports:")
|
||||
pos = rva2off(imp_rva)
|
||||
i = 0
|
||||
while True:
|
||||
desc = data[pos:pos+20]
|
||||
if desc == b"\0"*20:
|
||||
print(f" [{i}] (null terminator)")
|
||||
break
|
||||
ilt_rva, ts, fwc, name_rva, iat_rva = struct.unpack("<IIIII", desc)
|
||||
name_off = rva2off(name_rva)
|
||||
name = bytes(data[name_off:data.index(0, name_off)]).decode("ascii", "replace") if name_off else "?"
|
||||
print(f" [{i}] {name} (ILT=0x{ilt_rva:08x} IAT=0x{iat_rva:08x})")
|
||||
pos += 20
|
||||
i += 1
|
||||
Loading…
Add table
Add a link
Reference in a new issue