runtime capture: registry heartbeat decodes live DSP tables (WIN 0.540658, freq-axis@48k, per-bin weights), rtsnap_fast/findctx/rtchunk infra
This commit is contained in:
+33
@@ -0,0 +1,33 @@
|
||||
import struct
|
||||
data=open('/tmp/snap_all.bin','rb').read()
|
||||
i=0; regs=[]
|
||||
while i+16<=len(data):
|
||||
lo,sz,=struct.unpack_from('<QQ',data,i)
|
||||
regs.append((lo,sz,data[i+16:i+16+sz]))
|
||||
i+=16+sz
|
||||
def readabs(addr,n):
|
||||
for lo,sz,body in regs:
|
||||
if lo<=addr<lo+sz and addr-lo+n<=sz:
|
||||
return body[addr-lo:addr-lo+n]
|
||||
return None
|
||||
known={0x2a72600,0x2111140,0x2cd0fc0,0x29f2280,0x29fa300,0x2a02340,0x2a0a3c0,0x2a12400}
|
||||
cands=[]
|
||||
t0=len(regs)
|
||||
for ri,(lo,sz,body) in enumerate(regs):
|
||||
if sz<0x541000: continue
|
||||
# check every 8-aligned offset for the +0x540658 ptr direct
|
||||
for off in range(0, sz-0x540660, 8):
|
||||
b=body[off+0x540658:off+0x540660]
|
||||
if len(b)<8: break
|
||||
p=struct.unpack_from('<Q',b)[0]
|
||||
if p in known:
|
||||
base=lo+off
|
||||
u24=readabs(base+0x24,4); u28=readabs(base+0x28,4)
|
||||
f40=readabs(base+0x40,0x20); f58=readabs(base+0x58,0x20)
|
||||
print('cand base=0x%x reg%d p=0x%x u24=%s u28=%s 40=%s'%(
|
||||
base,ri,p,
|
||||
struct.unpack('<f',u24)[0] if u24 else None,
|
||||
struct.unpack('<f',u28)[0] if u28 else None,
|
||||
[round(x,4) for x in struct.unpack('<4d',f40[:32])] if f40 else None))
|
||||
cands.append(base)
|
||||
print('total cand',len(cands))
|
||||
@@ -0,0 +1,65 @@
|
||||
# RUNTIME CAPTURE — LIVE DSP TABLES (2026-08-19)
|
||||
|
||||
## BREAKTHROUGH: heap "registry" object found & read during offline render
|
||||
- During `reaper -nosplash -renderproject render_long.rpp`, the yabridge-host
|
||||
allocates a DSP arena containing a **registry array**: a run of `{u64 count, u64 ptr}`
|
||||
pairs (stride 0x10) pointing at every DSP buffer. At capture time:
|
||||
registry base = **0x29b06c0** (in anon region 0x2922000, 0x77e000).
|
||||
(GUI-session registry was 0x28b06c0 — same object, shifted by arena layout.)
|
||||
- Finding it live: scan host-readable memory (chunked, 8MB) for `u64==8193` (0x2001)
|
||||
followed by a readable ptr, then require a run of count/ptr pairs at stride 0x10.
|
||||
- Registry owner chain: 0 hits for a u64==registry address, so it is reached
|
||||
structurally (object member at some fixed offset), not via an explicit global.
|
||||
|
||||
## Captured tables (SR=44100 project, but internal freq-axis = 48000!)
|
||||
| reg idx | count | ptr | content |
|
||||
|---------|-------|----------|---------|
|
||||
| [00] | 8193 | 0x2a72600 | identity ~1.0 |
|
||||
| [01] | 8193 | 0x2111140 | **WIN_freq: 0.5 -> 1.0 (saturates; idx1024=0.68, idx2048=0.8)** — the FFT-conv window (0x540658) |
|
||||
| [02] | 8193 | 0x29f2280 | 0 -> ~0.01 (levels/curve) |
|
||||
| [03] | 8193 | 0x29fa300 | **0.596 -> 0.126** = matches known rwin_C0 |
|
||||
| [04] | 8193 | 0x2a02340 | **0.404 -> 0.874** = 1-[03] complement |
|
||||
| [05] | 8193 | 0x2a0a3c0 | 0.0435 -> ~0 (weight, small) |
|
||||
| [06] | 8193 | 0x2a12400 | 0.9565 -> ~1.0 (1-[05]) |
|
||||
| [07] | 8193 | 0x2aca740 | zeros + small negatives |
|
||||
| [0d] | 2049 | 0x2cd0fc0 | **freq-axis 0..23988.3 Hz, spacing 11.713 = 48000/4096** → internal SR=48000 |
|
||||
| [0e] | 8193 | 0x2cd9000 | 2.017 -> 0 (LUT/knee?) |
|
||||
| [0f] | 16384 | 0x2ce9080 | same as [0e] doubled |
|
||||
| [10] | 16384 | 0x2cf90c0 | 1.2914 -> 0 |
|
||||
| [12] | 16384 | 0x2a62540 | 11.29.. (scattering) |
|
||||
| [14]/[15] | 8193 | .. | first-fire IR? 0,0.022,0.104,0.084,0.018,0 |
|
||||
| [17] | 32768 | 0x2d29140 | 0.9999 -> ~1 (ramp) |
|
||||
| [19] | 32768 | 0x2d69200 | 1.2915 -> 1.0 |
|
||||
| others | 32768/65536 | .. | ones / ramps (FFT plans, mirrors) |
|
||||
|
||||
- Key numeric check: registry[03] head 0.5960761, idx1024 0.168, idx2048 0.1257 —
|
||||
byte-identical to earlier GUI rwin_C0 (0.596 -> 0.126). Confirms registry IS the
|
||||
authoritative per-bin weight source; tables are stable across sessions/SR.
|
||||
- **FREQ-AXIS uses internal SR=48000 regardless of project 44100** (spacing 11.713).
|
||||
This reconciles "rwin tables at 48k" even when rendering 44.1k projects.
|
||||
|
||||
## Files saved (handoff/)
|
||||
- `rtwin_freq_44100.npy` — WIN_freq (8193 f32): 0.5 -> 1.0 (this is live 0x540658 window)
|
||||
- `rtfreqaxis_48000_internal.npy` — freq-axis (2048 f32, 0..23988.3, spacing 11.713)
|
||||
- `rtwa_596.npy` — [03] 0.596->0.126
|
||||
- `rtwb_404.npy` — [04] 0.404->0.874
|
||||
- `rtwc_043.npy` — [05]
|
||||
- `rtwd_956.npy` — [06]
|
||||
- Full raw snapshot: /tmp/snap_all.bin (318MB, entries {lo,sz,bytes}), registry.txt list.
|
||||
|
||||
## Method notes (repro)
|
||||
- rtsnap_fast.py: spawn reaper render_long, find host (soothe2 in maps, not reaper),
|
||||
sleep 6s (tables built), pread ALL readable maps chunked 8MB -> snap_all.bin.
|
||||
- pread of large anon regions can EIO -> MUST chunk (8MB); whole-region pread loses data.
|
||||
- Scan ~0.1s for 318MB once chunked; far cheaper than object-base scan.
|
||||
- Earlier vptr-based (rtobj/rtdump2/rtall) and 44100-marker scans all failed because
|
||||
the DSP object has NO static vptr match in a fresh render (host dies / fields live
|
||||
only during audio) and ctor field +0x24 != 44100 live. The registry run is the
|
||||
reliable beacon.
|
||||
|
||||
## Remaining (for twin IIR attack/release per-bin)
|
||||
- twin state A/B (342 double per-bin IIR states) still not uniquely located live;
|
||||
short renders keep them ~0. They are NOT the registry tables.
|
||||
- Next: render_long + capture at t=10-20s into sustain, then locate the per-bin
|
||||
attack/release smoothing coefficients (0x540888/88c set, converted via ln(10)/20)
|
||||
inside the arena near registry.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+50
@@ -0,0 +1,50 @@
|
||||
import subprocess, os, glob, time, struct
|
||||
def mem_open(pid): return os.open(f'/proc/{pid}/mem', os.O_RDONLY)
|
||||
def find_host(proc, wait=120):
|
||||
t0=time.time()
|
||||
while time.time()-t0<wait:
|
||||
for p in glob.glob('/proc/[0-9]*'):
|
||||
try:
|
||||
pid=int(os.path.basename(p))
|
||||
cmd=open(f'/proc/{pid}/cmdline','rb').read().replace(b'\0',b' ').decode('utf8','replace')
|
||||
maps=open(f'/proc/{pid}/maps').read()
|
||||
except Exception: continue
|
||||
if 'yabridge-host' in cmd and 'soothe2' in maps: return pid
|
||||
time.sleep(0.05)
|
||||
return None
|
||||
os.system("pkill -9 -x reaper 2>/dev/null; pkill -9 -f '[y]abridge' 2>/dev/null; sleep 1")
|
||||
proc=subprocess.Popen(['/usr/bin/reaper','-nosplash','-ignoreerrors','-renderproject','/home/m/soothe-bt/dual300.rpp'],
|
||||
stdout=open('/dev/null','w'),stderr=subprocess.STDOUT)
|
||||
host=find_host(proc); print('host',host)
|
||||
time.sleep(4)
|
||||
fd=mem_open(host)
|
||||
SIG=struct.pack('<I',0x472c4400)
|
||||
t0=time.time(); total=0; hits={}
|
||||
for line in open(f'/proc/{host}/maps').read().splitlines():
|
||||
p=line.split(); lo,hi=(int(x,16) for x in p[0].split('-'))
|
||||
if 'r' not in p[1]: continue
|
||||
t0r=time.time()
|
||||
ok_read=0
|
||||
a=lo
|
||||
while a<hi:
|
||||
n=min(hi-a, 0x400000)
|
||||
try:
|
||||
d=os.pread(fd,n,a)
|
||||
except Exception:
|
||||
a+=n; continue
|
||||
if not d:
|
||||
a+=n; continue
|
||||
ok_read+=len(d)
|
||||
i=0
|
||||
while True:
|
||||
i=d.find(SIG,i)
|
||||
if i<0: break
|
||||
hits[a+i]=None; i+=1
|
||||
a+=n
|
||||
if ok_read:
|
||||
print(' 0x%x-%x MB=%.1f ok=%.1fMB'%(lo,hi,(hi-lo)/1e6,ok_read/1e6))
|
||||
total+=ok_read
|
||||
print('scanned ok=%.0fMB t=%.1fs n44100=%d'%(total/1e6,time.time()-t0,len(hits)))
|
||||
for a in list(hits)[:5]:
|
||||
print(' HIT 0x%x'%a)
|
||||
proc.kill()
|
||||
+59
-36
@@ -1,29 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
"""rtdeep2.py — find DSP ctx object via phase-table pointer beacon, then dump window 0x540658 & neighbors."""
|
||||
import subprocess, time, glob, os, struct, sys
|
||||
import subprocess, time, glob, os, sys
|
||||
import numpy as np
|
||||
import struct
|
||||
|
||||
RPP = sys.argv[1] if len(sys.argv) > 1 else '/home/m/soothe-bt/dual300.rpp'
|
||||
BEACON = 0x182615608 # VA of phase_table_1024 (static .data)
|
||||
BEACON = np.uint64(0x182615608)
|
||||
|
||||
|
||||
def vst_base(pid):
|
||||
lo = None
|
||||
for line in open(f'/proc/{pid}/maps').read().splitlines():
|
||||
if 'soothe2' not in line:
|
||||
continue
|
||||
a = int(line.split()[0].split('-')[0], 16)
|
||||
lo = a if lo is None else min(lo, a)
|
||||
return lo
|
||||
def get_maps(pid):
|
||||
try:
|
||||
return open(f'/proc/{pid}/maps').read()
|
||||
except Exception:
|
||||
return ''
|
||||
|
||||
|
||||
def readable_regions(pid):
|
||||
out = []
|
||||
for line in open(f'/proc/{pid}/maps').read().splitlines():
|
||||
for line in get_maps(pid).splitlines():
|
||||
p = line.split()[0]
|
||||
lo, hi = int(p.split('-')[0], 16), int(p.split('-')[1], 16)
|
||||
if 'vst3' in line:
|
||||
continue # skip module itself
|
||||
out.append((lo, hi))
|
||||
return out
|
||||
|
||||
@@ -35,17 +31,28 @@ def find_beacon(pid):
|
||||
return []
|
||||
res = []
|
||||
for lo, hi in readable_regions(pid):
|
||||
sz = (hi - lo) & ~7
|
||||
sz = hi - lo
|
||||
if sz <= 0 or sz > 0x200000000:
|
||||
continue
|
||||
try:
|
||||
buf = os.pread(mem, sz, lo)
|
||||
except Exception:
|
||||
if sz < 65536:
|
||||
continue
|
||||
arr = np.frombuffer(buf, dtype='<u8')
|
||||
idx = np.where(arr == BEACON)[0]
|
||||
for i in idx:
|
||||
res.append(lo + int(i) * 8)
|
||||
off = 0
|
||||
while off < sz:
|
||||
chunk = min(sz - off, 4 << 20)
|
||||
try:
|
||||
buf = os.pread(mem, int(chunk), lo + off)
|
||||
except Exception:
|
||||
break
|
||||
arr = np.frombuffer(buf[:len(buf) & ~7], dtype='<u8')
|
||||
if len(arr):
|
||||
idx = np.where(arr == BEACON)[0]
|
||||
for i in idx:
|
||||
res.append(lo + off + int(i) * 8)
|
||||
off += int(chunk)
|
||||
if len(res) > 64:
|
||||
break
|
||||
if len(res) > 64:
|
||||
break
|
||||
os.close(mem)
|
||||
return res
|
||||
|
||||
@@ -57,26 +64,42 @@ def main():
|
||||
host = None
|
||||
while time.time() - t0 < 60:
|
||||
for p in glob.glob('/proc/[0-9]*'):
|
||||
try:
|
||||
maps = open(f'/proc/{p}/maps').read()
|
||||
except Exception:
|
||||
continue
|
||||
if 'soothe2' not in maps:
|
||||
continue
|
||||
host = int(os.path.basename(p))
|
||||
break
|
||||
pid = int(os.path.basename(p))
|
||||
if 'soothe2' in (get_maps(pid) or ''):
|
||||
host = pid
|
||||
break
|
||||
if host:
|
||||
break
|
||||
time.sleep(0.05)
|
||||
print(f'host={host}')
|
||||
time.sleep(0.01)
|
||||
print(f'host={host}', flush=True)
|
||||
if not host:
|
||||
proc.kill()
|
||||
proc.wait(timeout=10)
|
||||
return
|
||||
base = vst_base(host)
|
||||
# allow module load: plugin init first
|
||||
mem = os.open(f'/proc/{host}/mem', os.O_RDONLY)
|
||||
t1 = time.time()
|
||||
while time.time() - t1 < 50:
|
||||
regs = []
|
||||
for line in get_maps(host).splitlines():
|
||||
p = line.split()[0]
|
||||
lo, hi = int(p.split('-')[0], 16), int(p.split('-')[1], 16)
|
||||
if 'vst3' in line or 'reaper' in line or 'soothe2' in line and 'rw' not in line.split()[1][:2]:
|
||||
continue
|
||||
if 'rw' not in line.split()[1][:2]:
|
||||
continue
|
||||
if hi - lo > 0x10000000:
|
||||
continue
|
||||
regs.append((lo, hi))
|
||||
print(f'{len(regs)} rw regions to capture', flush=True)
|
||||
with open('/tmp/host_rw.bin', 'wb') as f:
|
||||
for lo, hi in regs:
|
||||
try:
|
||||
buf = os.pread(mem, hi - lo, lo)
|
||||
f.write(lo.to_bytes(8, 'little'))
|
||||
f.write((hi - lo).to_bytes(8, 'little'))
|
||||
f.write(buf)
|
||||
except Exception:
|
||||
pass
|
||||
print(f'captured {os.path.getsize("/tmp/host_rw.bin")} bytes', flush=True)
|
||||
while time.time() - t1 < 30:
|
||||
hits = find_beacon(host)
|
||||
ctxs = set()
|
||||
for p in hits:
|
||||
@@ -96,7 +119,7 @@ def main():
|
||||
blk = os.pread(mem, 0x8000, ctx + 0x540000)
|
||||
open(f'/tmp/ctx_{ctx:x}_win.bin', 'wb').write(blk)
|
||||
break
|
||||
time.sleep(0.2)
|
||||
time.sleep(0.05)
|
||||
os.close(mem)
|
||||
try:
|
||||
proc.wait(timeout=5)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import subprocess, os, glob, time, struct, sys
|
||||
def mem_open(pid): return os.open(f'/proc/{pid}/mem', os.O_RDONLY)
|
||||
def find_hosts():
|
||||
hosts=[]
|
||||
for p in glob.glob('/proc/[0-9]*'):
|
||||
pid=int(os.path.basename(p))
|
||||
try:
|
||||
cmd=open(f'/proc/{pid}/cmdline','rb').read().replace(b'\0',b' ').decode('utf8','replace')
|
||||
maps=open(f'/proc/{pid}/maps').read()
|
||||
except Exception: continue
|
||||
if 'soothe2' in maps and 'reaper' not in cmd:
|
||||
hosts.append(pid)
|
||||
return hosts
|
||||
os.system("pkill -9 -x reaper 2>/dev/null; pkill -9 -f '[y]abridge' 2>/dev/null; sleep 1")
|
||||
proc=subprocess.Popen(['/usr/bin/reaper','-nosplash','-ignoreerrors','-renderproject','/home/m/soothe-bt/render_long.rpp'],
|
||||
stdout=open('/dev/null','w'),stderr=subprocess.STDOUT)
|
||||
# wait for host
|
||||
t0=time.time(); host=None
|
||||
while time.time()-t0<60 and not host:
|
||||
hs=find_hosts()
|
||||
host=hs[0] if hs else None
|
||||
time.sleep(0.2)
|
||||
print('host',host, flush=True)
|
||||
time.sleep(6) # let render warm up, tables built
|
||||
fd=mem_open(host)
|
||||
out=open('/tmp/snap_all.bin','wb')
|
||||
idx=open('/tmp/snap_all.idx','wb')
|
||||
t0=time.time(); nreg=0; nbytes=0
|
||||
for line in open(f'/proc/{host}/maps').read().splitlines():
|
||||
p=line.split(); lo,hi=(int(x,16) for x in p[0].split('-'))
|
||||
if 'r' not in p[1]: continue
|
||||
a=lo
|
||||
while a<hi:
|
||||
n=min(hi-a, 8*1024*1024)
|
||||
try: d=os.pread(fd,n,a)
|
||||
except Exception: a+=n; continue
|
||||
if not d: a+=n; continue
|
||||
out.write(struct.pack('<QQ',a,len(d))); out.write(d)
|
||||
idx.write(struct.pack('<QQ',a,len(d)))
|
||||
nreg+=1; nbytes+=len(d)
|
||||
a+=n
|
||||
out.close(); idx.close()
|
||||
print('snapshot ok: %d regs %d MB in %.1fs'%(nreg,nbytes/1e6,time.time()-t0), flush=True)
|
||||
if proc.poll() is None: proc.kill()
|
||||
Reference in New Issue
Block a user