leakhunt/bin/take_snapshot.ps1
acbot 57b5e43d0e 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>
2026-05-23 21:07:58 +02:00

63 lines
2.5 KiB
PowerShell

# take_snapshot.ps1
# One-shot umdh snapshot helper. Auto-finds acclient PID and the next snap_NNN filename.
#
# Usage:
# take_snapshot.ps1 -PhaseDir artifacts\phase1
#
# Requires: gflags +ust on acclient.exe (one-time, see Phase 1 step 1),
# _NT_SYMBOL_PATH set to the directory containing acclient.pdb.
param(
[Parameter(Mandatory=$true)][string]$PhaseDir,
[int]$ProcessId = 0, # 0 = auto-discover via Get-Process
[string]$UmdhExe = "C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\umdh.exe",
[int]$TimeoutSec = 600 # umdh on a fat 32-bit AC client can take 60-200s
)
$ErrorActionPreference = "Stop"
if (-not (Test-Path $PhaseDir)) { New-Item -ItemType Directory -Path $PhaseDir -Force | Out-Null }
if (-not (Test-Path $UmdhExe)) { throw "umdh.exe not found at $UmdhExe" }
if (-not $env:_NT_SYMBOL_PATH) { $env:_NT_SYMBOL_PATH = "C:\Users\acbot\leakhunt\pdb" }
if ($ProcessId -eq 0) {
$procs = Get-Process -Name acclient -ErrorAction SilentlyContinue
if (-not $procs) { throw "acclient.exe not running" }
if ($procs.Count -gt 1) { throw "multiple acclient.exe instances; specify -ProcessId" }
$ProcessId = $procs[0].Id
}
# Find next snap_NNN filename
$existing = Get-ChildItem -Path $PhaseDir -Filter "snap_*.txt" -ErrorAction SilentlyContinue |
ForEach-Object { [int]([regex]::Match($_.BaseName, '\d+').Value) }
$next = if ($existing) { ($existing | Measure-Object -Maximum).Maximum + 1 } else { 1 }
$Out = Join-Path $PhaseDir ("snap_{0:D3}.txt" -f $next)
Write-Host "[$(Get-Date -Format HH:mm:ss)] umdh -p:$ProcessId -f:$Out"
$argList = @("-p:$ProcessId", "-f:$Out")
$p = Start-Process -FilePath $UmdhExe -ArgumentList $argList -NoNewWindow -PassThru -RedirectStandardError "$Out.err"
if (-not $p.WaitForExit($TimeoutSec * 1000)) {
Stop-Process -Id $p.Id -Force
throw "umdh timed out after $TimeoutSec s"
}
if (-not (Test-Path $Out)) { throw "umdh produced no output (exit $($p.ExitCode))" }
$size = (Get-Item $Out).Length
# Pull the leading metadata + a quick stats summary
$head = (Get-Content $Out -TotalCount 6) -join "`n"
$totalAllocs = (Select-String -Path $Out -Pattern '^\d+ bytes \+' -AllMatches).Matches.Count
Write-Host ("[$(Get-Date -Format HH:mm:ss)] OK: {0} ({1:N0} bytes; {2} alloc-stack records)" -f $Out, $size, $totalAllocs)
Write-Host "--- head ---"
Write-Host $head
# Emit metadata for the orchestrator
[pscustomobject]@{
Path = $Out
SizeBytes = $size
Records = $totalAllocs
Pid = $ProcessId
Timestamp = (Get-Date).ToString("o")
}