# Stack sampler v2: correct x64 CONTEXT layout + psapi module names. import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time sys.stdout.reconfigure(encoding='utf-8', errors='replace') SHM_NAME = "SonicForge_DAW_IPC_VERIFY" SIZE = 32768 BRIDGE = r"C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\daw_vst_bridge.exe" LOG = os.path.join(os.path.dirname(os.path.abspath(__file__)), "stack_sample.log") k32 = ctypes.windll.kernel32 k32.CreateFileMappingA.argtypes = [ctypes.c_void_p, ctypes.c_void_p, w.DWORD, w.DWORD, w.DWORD, ctypes.c_char_p] k32.CreateFileMappingA.restype = ctypes.c_void_p k32.MapViewOfFile.argtypes = [ctypes.c_void_p, w.DWORD, w.DWORD, w.DWORD, ctypes.c_size_t] k32.MapViewOfFile.restype = ctypes.c_void_p INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value hMap = k32.CreateFileMappingA(INVALID_HANDLE_VALUE, None, 0x04, 0, SIZE, SHM_NAME.encode()) ptr = k32.MapViewOfFile(hMap, 0xF001F, 0, 0, 0) def wait_log(pattern, timeout): end = time.time() + timeout while time.time() < end: try: with open(LOG, 'r', encoding='utf-8', errors='replace') as f: if pattern in f.read(): return True except FileNotFoundError: pass time.sleep(0.2) return False extra = dict(os.environ) for k in list(extra): if k.startswith("SF_"): del extra[k] extra.update({"SF_SHM_NAME": SHM_NAME, "SF_SAMPLE_RATE": "48000", "SF_BLOCK_SIZE": "256", "SF_ONCE": "1", "SF_AUTOGUI": "0"}) for gate in sys.argv[1:]: k, v = gate.split("=", 1) extra[k] = v proc = subprocess.Popen([BRIDGE, "--shm", SHM_NAME], env=extra, stdout=open(LOG, 'wb'), stderr=subprocess.STDOUT) print("bridge pid", proc.pid, "poll", proc.poll()) if not wait_log("isPlatformTypeSupported=0", 120): print("TIMEOUT waiting for hang marker") sys.exit(1) time.sleep(3) pid = proc.pid PROCESS_QUERY_INFORMATION = 0x0400 PROCESS_VM_READ = 0x0010 hProc = k32.OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, pid) if not hProc: print("OpenProcess failed", ctypes.get_last_error()) sys.exit(1) psapi = ctypes.windll.psapi psapi.EnumProcessModulesEx.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p), w.DWORD, ctypes.POINTER(w.DWORD), w.DWORD] psapi.GetModuleBaseNameA.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_char_p, w.DWORD] psapi.GetModuleInformation.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, w.DWORD] class MODULEINFO(ctypes.Structure): _fields_ = [("lpBaseOfDll", ctypes.c_void_p), ("SizeOfImage", w.DWORD), ("pad", w.DWORD), ("EntryPoint", ctypes.c_void_p)] mods = [] buf = (ctypes.c_void_p * 1024)() needed = w.DWORD(0) if psapi.EnumProcessModulesEx(hProc, buf, ctypes.sizeof(buf), ctypes.byref(needed), 3): # LIST_MODULES_ALL n = needed.value // ctypes.sizeof(ctypes.c_void_p) for i in range(min(n, 1024)): base = buf[i] name = ctypes.create_string_buffer(260) psapi.GetModuleBaseNameA(hProc, base, name, 260) info = MODULEINFO() psapi.GetModuleInformation(hProc, base, ctypes.byref(info), ctypes.sizeof(info)) mods.append((base, info.SizeOfImage, name.value.decode('utf-8', 'replace'))) # base, size, name mods.sort(key=lambda m: m[0]) print("modules:", [(hex(b), s, n) for b, s, n in mods]) def modname(addr): if addr == 0: return "null" for b, s, n in mods: if b <= addr < b + s: return "%s+%x" % (n, addr - b) return "??%x" % addr class THREADENTRY32(ctypes.Structure): _fields_ = [("dwSize", w.DWORD), ("cntUsage", w.DWORD), ("th32ThreadID", w.DWORD), ("th32OwnerProcessID", w.DWORD), ("tpBasePri", ctypes.c_long), ("tpDeltaPri", ctypes.c_long), ("dwFlags", w.DWORD)] class CONTEXT64(ctypes.Structure): _fields_ = [ ("P1Home", ctypes.c_uint64), ("P2Home", ctypes.c_uint64), ("P3Home", ctypes.c_uint64), ("P4Home", ctypes.c_uint64), ("P5Home", ctypes.c_uint64), ("P6Home", ctypes.c_uint64), ("ContextFlags", w.DWORD), ("MxCsr", w.DWORD), ("SegCs", w.WORD), ("SegDs", w.WORD), ("SegEs", w.WORD), ("SegFs", w.WORD), ("SegGs", w.WORD), ("SegSs", w.WORD), ("EFlags", w.DWORD), ("Dr0", ctypes.c_uint64), ("Dr1", ctypes.c_uint64), ("Dr2", ctypes.c_uint64), ("Dr3", ctypes.c_uint64), ("Dr6", ctypes.c_uint64), ("Dr7", ctypes.c_uint64), ("Rax", ctypes.c_uint64), ("Rcx", ctypes.c_uint64), ("Rdx", ctypes.c_uint64), ("Rbx", ctypes.c_uint64), ("Rsp", ctypes.c_uint64), ("Rbp", ctypes.c_uint64), ("Rsi", ctypes.c_uint64), ("Rdi", ctypes.c_uint64), ("R8", ctypes.c_uint64), ("R9", ctypes.c_uint64), ("R10", ctypes.c_uint64), ("R11", ctypes.c_uint64), ("R12", ctypes.c_uint64), ("R13", ctypes.c_uint64), ("R14", ctypes.c_uint64), ("R15", ctypes.c_uint64), ("Rip", ctypes.c_uint64), ] TH32CS_SNAPTHREAD = 0x4 THREAD_SUSPEND_RESUME = 0x0002 THREAD_GET_CONTEXT = 0x0008 THREAD_QUERY_INFORMATION = 0x0040 CONTEXT_CTRL_INT_SEG = 0x100001 | 0x2 | 0x20 # CONTROL | INTEGER | SEGMENTS snap = k32.CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0) te = THREADENTRY32(); te.dwSize = ctypes.sizeof(THREADENTRY32) threads = [] ok = k32.Thread32First(snap, ctypes.byref(te)) while ok: if te.th32OwnerProcessID == pid: threads.append(te.th32ThreadID) ok = k32.Thread32Next(snap, ctypes.byref(te)) print("threads(%d): %s" % (len(threads), threads)) for tid in threads: hT = k32.OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, False, tid) if not hT: print("tid %d: OpenThread fail" % tid) continue susp = k32.SuspendThread(hT) ctx = CONTEXT64(); ctx.ContextFlags = CONTEXT_CTRL_INT_SEG r = k32.GetThreadContext(hT, ctypes.byref(ctx)) k32.ResumeThread(hT) k32.CloseHandle(hT) if not r: print("tid %d: GetThreadContext fail %d" % (tid, ctypes.get_last_error())) continue print("tid %d rip=%s rsp=%s" % (tid, modname(ctx.Rip), modname(ctx.Rsp))) print("--- log tail ---") try: with open(LOG, 'rb') as f: f.seek(max(0, os.path.getsize(LOG) - 2500)) print(f.read().decode('utf-8', 'replace')) except FileNotFoundError: pass