218 lines
8.9 KiB
Python
218 lines
8.9 KiB
Python
# Stack sampler v4: thread start addrs (identify main) + poor-man stack walk.
|
|
# Usage: python stack_sample2.py --bridge | --probe [secs]
|
|
import ctypes, ctypes.wintypes as w, struct, subprocess, sys, os, time
|
|
|
|
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
|
|
MODE = sys.argv[1] if len(sys.argv) > 1 else "--bridge"
|
|
SECS = int(sys.argv[2]) if len(sys.argv) > 2 else 15
|
|
SHM_NAME = "SonicForge_DAW_IPC_VERIFY"
|
|
SIZE = 32768
|
|
BRIDGE = r"C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\daw_vst_bridge.exe"
|
|
PROBE = r"C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\gui_probe.exe"
|
|
NEXUS = r"C:\Program Files\Common Files\VST3\Nexus.vst3"
|
|
LOG = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
"stack_sample.log" if MODE == "--bridge" else "stack_probe.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
|
|
|
|
if MODE == "--bridge":
|
|
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"})
|
|
proc = subprocess.Popen([BRIDGE, "--shm", SHM_NAME], env=extra,
|
|
stdout=open(LOG, 'wb'), stderr=subprocess.STDOUT)
|
|
marker = "isPlatformTypeSupported=0"
|
|
wait_after = 3
|
|
else:
|
|
proc = subprocess.Popen([PROBE, NEXUS, "bridge_exact11", str(SECS)],
|
|
stdout=open(LOG, 'wb'), stderr=subprocess.STDOUT)
|
|
marker = "openGUI: attached=0"
|
|
wait_after = 2
|
|
|
|
print("pid", proc.pid, "poll", proc.poll(), "mode", MODE)
|
|
if not wait_log(marker, 120):
|
|
print("TIMEOUT waiting for", marker)
|
|
sys.exit(1)
|
|
time.sleep(wait_after)
|
|
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
|
|
class MODULEINFO(ctypes.Structure):
|
|
_fields_ = [("lpBaseOfDll", ctypes.c_void_p), ("SizeOfImage", w.DWORD),
|
|
("pad", w.DWORD), ("EntryPoint", ctypes.c_void_p)]
|
|
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.POINTER(MODULEINFO), w.DWORD]
|
|
mods = []
|
|
buf = (ctypes.c_void_p * 1024)()
|
|
needed = w.DWORD(0)
|
|
if psapi.EnumProcessModulesEx(hProc, buf, ctypes.sizeof(buf), ctypes.byref(needed), 3):
|
|
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')))
|
|
mods.sort(key=lambda m: m[0])
|
|
|
|
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
|
|
|
|
ntdll = ctypes.WinDLL("ntdll", use_last_error=True)
|
|
class THREAD_BASIC_INFORMATION(ctypes.Structure):
|
|
_fields_ = [("ExitStatus", ctypes.c_long), ("TebBaseAddress", ctypes.c_void_p),
|
|
("UniqueProcess", ctypes.c_void_p), ("UniqueThread", ctypes.c_void_p),
|
|
("AffinityMask", ctypes.c_void_p), ("Priority", ctypes.c_long),
|
|
("BasePriority", ctypes.c_long)]
|
|
|
|
def thread_start_addr(hT):
|
|
tbi = THREAD_BASIC_INFORMATION()
|
|
buf = (ctypes.c_uint64 * 4)()
|
|
try:
|
|
r = ntdll.NtQueryInformationThread(hT, 9, ctypes.byref(buf), 32, None) # ThreadQuerySetWin32StartAddress
|
|
if r == 0:
|
|
return buf[0]
|
|
except Exception:
|
|
pass
|
|
return 0
|
|
|
|
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))
|
|
|
|
exe_name = mods[0][2] if mods else "exe"
|
|
main_tid = None
|
|
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:
|
|
continue
|
|
start = thread_start_addr(hT)
|
|
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:
|
|
continue
|
|
if mods and mods[0][0] <= start < mods[0][0] + mods[0][1] and main_tid is None:
|
|
main_tid = tid
|
|
print("tid %d rip=%s rsp=%s start=%s%s" % (
|
|
tid, modname(ctx.Rip), modname(ctx.Rsp), modname(start),
|
|
" <== MAIN?" if (mods and mods[0][0] <= start < mods[0][0] + mods[0][1]) else ""))
|
|
|
|
# poor-man stack walk for MAIN thread
|
|
if main_tid:
|
|
print("--- stack walk MAIN tid=%d ---" % main_tid)
|
|
hT = k32.OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, False, main_tid)
|
|
k32.SuspendThread(hT)
|
|
ctx = CONTEXT64(); ctx.ContextFlags = CONTEXT_CTRL_INT_SEG
|
|
k32.GetThreadContext(hT, ctypes.byref(ctx))
|
|
k32.ResumeThread(hT)
|
|
k32.CloseHandle(hT)
|
|
rsp = ctx.Rsp
|
|
PAGE = 0x1000
|
|
seen = []
|
|
# scan a few pages of stack memory
|
|
base = rsp & ~0xF
|
|
data = b""
|
|
try:
|
|
buf2 = ctypes.create_string_buffer(PAGE * 64)
|
|
read = ctypes.c_size_t(0)
|
|
if k32.ReadProcessMemory(hProc, ctypes.c_void_p(base), buf2, PAGE * 64, ctypes.byref(read)):
|
|
data = buf2.raw[:read.value]
|
|
except Exception:
|
|
data = b""
|
|
for off in range(0, len(data) - 8, 8):
|
|
val = struct.unpack_from('<Q', data, off)[0]
|
|
if val >= 0x7ff000000000:
|
|
mn = modname(val)
|
|
if mn not in seen and (mn.startswith("Nexus") or mn.startswith("daw") or mn.startswith("USER32") or mn.startswith("ntdll") or mn.startswith("win32u") or mn.startswith("KERNELBASE") or mn.startswith("ole32") or mn.startswith("combase")):
|
|
seen.append(mn)
|
|
print("main stack (module+offset, dedup):")
|
|
for s in seen:
|
|
print(" ", s)
|
|
print("--- log tail ---")
|
|
try:
|
|
with open(LOG, 'rb') as f:
|
|
f.seek(max(0, os.path.getsize(LOG) - 1500))
|
|
print(f.read().decode('utf-8', 'replace'))
|
|
except FileNotFoundError:
|
|
pass
|