wip(nexus): debug crash 0xc000041d khi mở GUI Nexus — lưu trạng thái điều tra (attached() hang; probe exact11 PASS, bridge hang; g_engine hypothesis disproven; chờ phân tích waitscan wait-object)
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
# Stack scan v7: read thread stack in small chunks, skip unreadable pages
|
||||
# (v6 failed reading upward from rsp when the thread's stack was shallow and
|
||||
# the 0x4000 chunk crossed the committed top). Also dumps full registers for
|
||||
# the window-owner thread so the wait handle/params can be read.
|
||||
# Usage: python stackscan.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
|
||||
wnd_class = "SonicForge_Native_VST3_Class"
|
||||
else:
|
||||
proc = subprocess.Popen([PROBE, NEXUS, "bridge_exact11", str(SECS)],
|
||||
stdout=open(LOG, 'wb'), stderr=subprocess.STDOUT)
|
||||
marker = "openGUI: attached=0"
|
||||
wait_after = 2
|
||||
wnd_class = "SonicForge_Native_VST3_Class"
|
||||
|
||||
print("pid", proc.pid, "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
|
||||
k32.OpenProcess.argtypes = [w.DWORD, w.BOOL, w.DWORD]
|
||||
k32.OpenProcess.restype = ctypes.c_void_p
|
||||
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 None
|
||||
|
||||
# --- find the target window + its owning thread (main) ---
|
||||
user32 = ctypes.windll.user32
|
||||
user32.EnumWindows.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
||||
user32.GetWindowThreadProcessId.argtypes = [ctypes.c_void_p, ctypes.POINTER(w.DWORD)]
|
||||
user32.GetClassNameA.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int]
|
||||
WNDENUMPROC = ctypes.WINFUNCTYPE(w.BOOL, ctypes.c_void_p, ctypes.c_void_p)
|
||||
found = {"hwnd": None, "tid": None}
|
||||
|
||||
@WNDENUMPROC
|
||||
def enumcb(hwnd, lparam):
|
||||
wpid = w.DWORD(0)
|
||||
tid = user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid))
|
||||
if wpid.value != pid:
|
||||
return True
|
||||
cls = ctypes.create_string_buffer(256)
|
||||
user32.GetClassNameA(hwnd, cls, 256)
|
||||
if cls.value.decode('utf-8', 'replace') == wnd_class:
|
||||
found["hwnd"] = hwnd
|
||||
found["tid"] = tid
|
||||
return False
|
||||
return True
|
||||
|
||||
user32.EnumWindows(enumcb, None)
|
||||
print("target window hwnd=%s tid=%s" % (found["hwnd"], found["tid"]))
|
||||
|
||||
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)]
|
||||
|
||||
TH32CS_SNAPTHREAD = 0x4
|
||||
THREAD_SUSPEND_RESUME = 0x0002
|
||||
THREAD_GET_CONTEXT = 0x0008
|
||||
THREAD_QUERY_INFORMATION = 0x0040
|
||||
CONTEXT_CTRL_INT_SEG = 0x100001 | 0x2 | 0x20
|
||||
|
||||
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),
|
||||
]
|
||||
|
||||
k32.CreateToolhelp32Snapshot.argtypes = [w.DWORD, w.DWORD]
|
||||
k32.CreateToolhelp32Snapshot.restype = ctypes.c_void_p
|
||||
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))
|
||||
|
||||
INTEREST = ("Nexus", "daw_", "USER32", "ntdll", "win32u", "KERNELBASE", "ole32",
|
||||
"combase", "uxtheme", "d2d1", "d3d11", "MSCTF", "IMM32", "SHELL32")
|
||||
|
||||
def read_mem(addr, size):
|
||||
"""ReadProcessMemory with proper argtypes; returns bytes or None."""
|
||||
chunk = ctypes.create_string_buffer(size)
|
||||
read = ctypes.c_size_t(0)
|
||||
k32.ReadProcessMemory.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)]
|
||||
if not k32.ReadProcessMemory(hProc, ctypes.c_void_p(addr), chunk, size, ctypes.byref(read)):
|
||||
return None
|
||||
return chunk.raw[:read.value]
|
||||
|
||||
def scan_stack(tid, depth=0x20000):
|
||||
k32.OpenThread.argtypes = [w.DWORD, w.BOOL, w.DWORD]
|
||||
k32.OpenThread.restype = ctypes.c_void_p
|
||||
hT = k32.OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, False, tid)
|
||||
if not hT:
|
||||
return None, [], None
|
||||
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:
|
||||
return None, [], None
|
||||
rsp = ctx.Rsp
|
||||
chunks = []
|
||||
fails = []
|
||||
# read downward a bit first (trap frame may sit below rsp), then upward
|
||||
for base in (rsp - 0x1000, rsp, rsp + 0x1000, rsp + 0x2000):
|
||||
if base > rsp + depth:
|
||||
break
|
||||
d = read_mem(base, 0x1000)
|
||||
if d is None:
|
||||
fails.append(hex(base))
|
||||
else:
|
||||
chunks.append((base, d))
|
||||
data = b"".join(d for _, d in chunks)
|
||||
seen = []
|
||||
for addr, d in chunks:
|
||||
for i in range(0, len(d) - 8, 8):
|
||||
val = struct.unpack_from('<Q', d, i)[0]
|
||||
mn = modname(val)
|
||||
if mn:
|
||||
base_mod = mn.split("+")[0]
|
||||
if any(base_mod.startswith(ig) for ig in INTEREST):
|
||||
seen.append((addr + i, mn))
|
||||
seen.sort()
|
||||
return ctx, seen, fails, rsp
|
||||
|
||||
regnames = ["Rax", "Rcx", "Rdx", "Rbx", "Rsp", "Rbp", "Rsi", "Rdi",
|
||||
"R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15", "Rip"]
|
||||
|
||||
for tid in threads:
|
||||
ctx, stack, fails, rsp = scan_stack(tid)
|
||||
tag = " <== WINDOW OWNER (main)" if tid == found["tid"] else ""
|
||||
if not ctx:
|
||||
print("=== tid %d (no context) ===" % tid)
|
||||
continue
|
||||
rip = ctx.Rip
|
||||
print("=== tid %d rip=%s%s ===" % (tid, modname(rip) if modname(rip) else hex(rip), tag))
|
||||
if tag:
|
||||
print(" regs:", " ".join("%s=%s" % (n, hex(getattr(ctx, n))) for n in regnames))
|
||||
print(" stack read fails:", fails if fails else "none")
|
||||
shown = [s for s in stack if s[0] >= rsp] or stack
|
||||
for a, mn in shown[-60:]:
|
||||
print(" ", "%s @%x" % (mn, a))
|
||||
|
||||
print("--- log tail ---")
|
||||
try:
|
||||
with open(LOG, 'rb') as f:
|
||||
f.seek(max(0, os.path.getsize(LOG) - 1000))
|
||||
print(f.read().decode('utf-8', 'replace'))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
Reference in New Issue
Block a user