"""clone_dump.py Take a non-disruptive full memory dump of a live process using process reflection (PssCaptureSnapshot) — the same mechanism procdump uses with -r 1 -ma. The target is only paused for ~1ms while the COW snapshot is created; the dump itself runs against the snapshot, not the live process. This avoids the multi-second pause that MiniDumpWriteDump on a live PID would cause (and which disconnects AC clients from Coldeve). """ import argparse import ctypes import ctypes.wintypes as wt import sys import os # PSS flags PSS_CAPTURE_VA_CLONE = 0x00000001 PSS_CAPTURE_HANDLES = 0x00000004 PSS_CAPTURE_HANDLE_NAME_INFORMATION = 0x00000008 PSS_CAPTURE_HANDLE_BASIC_INFORMATION= 0x00000010 PSS_CAPTURE_HANDLE_TYPE_SPECIFIC_INFORMATION = 0x00000020 PSS_CAPTURE_HANDLE_TRACE = 0x00000040 PSS_CAPTURE_THREADS = 0x00000080 PSS_CAPTURE_THREAD_CONTEXT = 0x00000100 PSS_CAPTURE_THREAD_CONTEXT_EXTENDED = 0x00000200 PSS_CAPTURE_VA_SPACE = 0x00000800 PSS_CAPTURE_VA_SPACE_SECTION_INFORMATION = 0x00001000 PSS_CREATE_RELEASE_SECTION = 0x80000000 PSS_CREATE_FORCE_BREAKAWAY = 0x40000000 PSS_CREATE_USE_VM_ALLOCATIONS = 0x20000000 # Process access PROCESS_ALL_ACCESS = 0x1F0FFF # MiniDumpType MINI_DUMP_WITH_FULL_MEMORY = 0x00000002 MINI_DUMP_WITH_HANDLE_DATA = 0x00000004 MINI_DUMP_WITH_UNLOADED_MODULES = 0x00000020 MINI_DUMP_WITH_FULL_MEMORY_INFO = 0x00000800 MINI_DUMP_WITH_THREAD_INFO = 0x00001000 MINI_DUMP_WITH_TOKEN_INFORMATION = 0x00040000 DUMP_TYPE = ( MINI_DUMP_WITH_FULL_MEMORY | MINI_DUMP_WITH_HANDLE_DATA | MINI_DUMP_WITH_UNLOADED_MODULES | MINI_DUMP_WITH_FULL_MEMORY_INFO | MINI_DUMP_WITH_THREAD_INFO ) k32 = ctypes.WinDLL('kernel32', use_last_error=True) dbghelp = ctypes.WinDLL('dbghelp', use_last_error=True) OpenProcess = k32.OpenProcess OpenProcess.argtypes = [wt.DWORD, wt.BOOL, wt.DWORD] OpenProcess.restype = wt.HANDLE CloseHandle = k32.CloseHandle CloseHandle.argtypes = [wt.HANDLE] CloseHandle.restype = wt.BOOL PssCaptureSnapshot = k32.PssCaptureSnapshot PssCaptureSnapshot.argtypes = [wt.HANDLE, wt.DWORD, wt.DWORD, ctypes.POINTER(wt.HANDLE)] PssCaptureSnapshot.restype = wt.DWORD PssFreeSnapshot = k32.PssFreeSnapshot PssFreeSnapshot.argtypes = [wt.HANDLE, wt.HANDLE] PssFreeSnapshot.restype = wt.DWORD CreateFileW = k32.CreateFileW CreateFileW.argtypes = [wt.LPCWSTR, wt.DWORD, wt.DWORD, ctypes.c_void_p, wt.DWORD, wt.DWORD, wt.HANDLE] CreateFileW.restype = wt.HANDLE MiniDumpWriteDump = dbghelp.MiniDumpWriteDump MiniDumpWriteDump.argtypes = [wt.HANDLE, wt.DWORD, wt.HANDLE, wt.DWORD, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] MiniDumpWriteDump.restype = wt.BOOL # MINIDUMP_CALLBACK_INFORMATION + IsProcessSnapshotCallback support # to tell dbghelp the hProcess is actually a snapshot handle. class MINIDUMP_CALLBACK_INFORMATION(ctypes.Structure): _fields_ = [ ("CallbackRoutine", ctypes.c_void_p), ("CallbackParam", ctypes.c_void_p), ] # Callback type: BOOL CALLBACK MiniDumpCallback(PVOID CallbackParam, const PMINIDUMP_CALLBACK_INPUT, PMINIDUMP_CALLBACK_OUTPUT) MiniDumpCallback_T = ctypes.WINFUNCTYPE(wt.BOOL, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p) IS_PROCESS_SNAPSHOT_CALLBACK = 16 def _callback(callback_param, input_ptr, output_ptr): # CallbackType is at offset 4 in MINIDUMP_CALLBACK_INPUT (after ProcessId DWORD) if not input_ptr: return True cb_type = ctypes.cast(input_ptr + 4, ctypes.POINTER(wt.ULONG))[0] if cb_type == IS_PROCESS_SNAPSHOT_CALLBACK: # Set ULONG at start of MINIDUMP_CALLBACK_OUTPUT to MiniDumpValidCallback (1) if output_ptr: ctypes.cast(output_ptr, ctypes.POINTER(wt.ULONG))[0] = 1 return True _callback_inst = MiniDumpCallback_T(_callback) GetCurrentProcess = k32.GetCurrentProcess GetCurrentProcess.argtypes = [] GetCurrentProcess.restype = wt.HANDLE GENERIC_WRITE = 0x40000000 CREATE_ALWAYS = 2 FILE_ATTRIBUTE_NORMAL = 0x80 INVALID_HANDLE_VALUE = wt.HANDLE(-1).value def main(): ap = argparse.ArgumentParser() ap.add_argument("pid", type=int) ap.add_argument("out", help="output dump path (.dmp)") args = ap.parse_args() out_path = os.path.abspath(args.out) h_proc = OpenProcess(PROCESS_ALL_ACCESS, False, args.pid) if not h_proc: err = ctypes.get_last_error() print(f"OpenProcess({args.pid}) failed err={err}") sys.exit(2) print(f"PID {args.pid}: opened, capturing COW snapshot...") capture_flags = ( PSS_CAPTURE_VA_CLONE | PSS_CAPTURE_HANDLES | PSS_CAPTURE_HANDLE_NAME_INFORMATION | PSS_CAPTURE_HANDLE_BASIC_INFORMATION | PSS_CAPTURE_HANDLE_TYPE_SPECIFIC_INFORMATION | PSS_CAPTURE_HANDLE_TRACE | PSS_CAPTURE_THREADS | PSS_CAPTURE_THREAD_CONTEXT | PSS_CAPTURE_THREAD_CONTEXT_EXTENDED | PSS_CAPTURE_VA_SPACE | PSS_CAPTURE_VA_SPACE_SECTION_INFORMATION ) h_snap = wt.HANDLE() # ContextFlags param: 0x0010001F = CONTEXT_FULL | CONTEXT_i386 rc = PssCaptureSnapshot(h_proc, capture_flags, 0x0010001F, ctypes.byref(h_snap)) if rc != 0: print(f"PssCaptureSnapshot failed rc={rc}") CloseHandle(h_proc) sys.exit(3) print(f" snapshot handle 0x{h_snap.value:x}") h_file = CreateFileW(out_path, GENERIC_WRITE, 0, None, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, None) if h_file == INVALID_HANDLE_VALUE or h_file is None: err = ctypes.get_last_error() print(f"CreateFile {out_path} failed err={err}") PssFreeSnapshot(GetCurrentProcess(), h_snap) CloseHandle(h_proc) sys.exit(4) print(f" writing dump to {out_path}...") cb_info = MINIDUMP_CALLBACK_INFORMATION() cb_info.CallbackRoutine = ctypes.cast(_callback_inst, ctypes.c_void_p).value cb_info.CallbackParam = None ok = MiniDumpWriteDump(h_snap, args.pid, h_file, DUMP_TYPE, None, None, ctypes.byref(cb_info)) if not ok: err = ctypes.get_last_error() print(f"MiniDumpWriteDump failed err={err}") CloseHandle(h_file) PssFreeSnapshot(GetCurrentProcess(), h_snap) CloseHandle(h_proc) sys.exit(5) CloseHandle(h_file) PssFreeSnapshot(GetCurrentProcess(), h_snap) CloseHandle(h_proc) sz = os.path.getsize(out_path) print(f" OK: {sz/1e6:.1f} MB written") if __name__ == "__main__": main()