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
66
tools/histogram_eor_alloc_sizes.py
Normal file
66
tools/histogram_eor_alloc_sizes.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
"""
|
||||
histogram_eor_alloc_sizes.py
|
||||
Decompile every EoR operator_new caller, extract the size constant, histogram.
|
||||
"""
|
||||
import re, urllib.request
|
||||
from collections import Counter
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
EOR = "http://192.168.1.98:8081"
|
||||
|
||||
def http(path, **p):
|
||||
qs = "&".join(f"{k}={v}" for k, v in p.items())
|
||||
url = f"{EOR}{path}?{qs}" if qs else f"{EOR}{path}"
|
||||
with urllib.request.urlopen(url, timeout=60) as r:
|
||||
return r.read().decode("utf-8", errors="replace")
|
||||
|
||||
# Get all xrefs to operator_new
|
||||
all_refs = []; off = 0
|
||||
while True:
|
||||
raw = http("/xrefs_to", address="0x005df0f5", offset=off, limit=500)
|
||||
batch = [m.group(2) for m in re.finditer(r"From ([0-9a-f]+) in (\S+)", raw)]
|
||||
if not batch: break
|
||||
all_refs.extend(batch)
|
||||
if len(batch) < 500: break
|
||||
off += 500
|
||||
|
||||
# Dedup owner names
|
||||
owners = sorted(set(all_refs))
|
||||
addrs = [int(m.group(1), 16) for n in owners for m in [re.match(r"FUN_([0-9a-f]+)", n)] if m]
|
||||
print(f"{len(addrs)} unique owners to scan")
|
||||
|
||||
def scan(addr):
|
||||
try:
|
||||
body = http("/decompile_function_by_address", address=f"0x{addr:08x}")
|
||||
except Exception:
|
||||
return (addr, [])
|
||||
# Extract operator_new size args
|
||||
sizes = []
|
||||
for m in re.finditer(r"FUN_005df0f5\((0x[0-9a-fA-F]+|\d+)\)", body):
|
||||
v = m.group(1)
|
||||
sizes.append(int(v, 0))
|
||||
for m in re.finditer(r"thunk_FUN_005df0f5\((0x[0-9a-fA-F]+|\d+)\)", body):
|
||||
v = m.group(1)
|
||||
sizes.append(int(v, 0))
|
||||
return (addr, sizes)
|
||||
|
||||
results = {}
|
||||
size_hist = Counter()
|
||||
with ThreadPoolExecutor(max_workers=24) as ex:
|
||||
futures = [ex.submit(scan, a) for a in addrs]
|
||||
for fut in as_completed(futures):
|
||||
addr, sizes = fut.result()
|
||||
results[addr] = sizes
|
||||
for s in sizes:
|
||||
size_hist[s] += 1
|
||||
|
||||
print(f"\n=== size histogram of operator_new calls ===")
|
||||
for sz, cnt in sorted(size_hist.items(), key=lambda x: -x[1])[:40]:
|
||||
print(f" 0x{sz:08x} ({sz:>10}) : {cnt}")
|
||||
|
||||
print(f"\n=== sizes near 0x100-0x200 (RenderSurface candidates) ===")
|
||||
for sz in sorted(size_hist):
|
||||
if 0xf0 <= sz <= 0x250:
|
||||
# Find addrs that call operator_new with this size
|
||||
callers = [a for a, sizes in results.items() if sz in sizes]
|
||||
print(f" 0x{sz:x} ({sz}) -> {len(callers)} callers: {[hex(a) for a in callers[:8]]}")
|
||||
Loading…
Add table
Add a link
Reference in a new issue