23b: mask->FIR chain decoded — DESIGN body is vectorized LOG2 of band curve (poly fingerprinted), WIN_freq identified as periodic Hann(4096) falling half applied to FIR[n/2..n), full ILT stub->impl table resolved offline (ilt_resolve.py), live buffer catalog extended (SIMD lane masks at 0x540598, complex identity reset between callbacks, overlap buffer at 0x5406f8 non-zero), plugin output proven nondeterministic across renders (LCG dither) — spectral metrics only; ptrace lab scripts + lessons (TRACECLONE before CONT, sub-second host lifecycle under -renderproject)
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python3
|
||||
"""firstop.py — catch the plugin INSIDE its audio callback by repeatedly
|
||||
SIGSTOP-ing the yabridge host and reading only the ctx-referenced arrays.
|
||||
|
||||
Between callbacks the FIR work buffer (ctx+0x540668) is reset to the complex
|
||||
identity (1,0)x2049. A snapshot where FIR != identity means we stopped after
|
||||
the mask->FIR build stage; those snapshots are saved with the full pipeline
|
||||
state (bands/scratch/f6f8/track/R curves).
|
||||
|
||||
Usage: python3 scripts/firstop.py [rpp] [nattempts]
|
||||
"""
|
||||
import os
|
||||
import signal
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
|
||||
SNAPDIR = '/tmp/opencode/firstop'
|
||||
SLOTS = [0x540548, 0x540550, 0x540598, 0x540628, 0x540668, 0x540678, 0x540688,
|
||||
0x540698, 0x5406a8, 0x5406b8, 0x5406c8, 0x5406d8, 0x5406e8, 0x5406f8,
|
||||
0x540708, 0x540718, 0x540728, 0x540738, 0x540748, 0x540758,
|
||||
0x540768, 0x540778, 0x540788, 0x540798, 0x5407a8, 0x5407b8,
|
||||
0x5407c8, 0x5407d8, 0x5407e8, 0x5407f8, 0x540808, 0x540818,
|
||||
0x540828, 0x540838, 0x540848]
|
||||
NARR = 8194
|
||||
|
||||
|
||||
def find_host():
|
||||
import glob
|
||||
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:
|
||||
return pid
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
rpp = sys.argv[1] if len(sys.argv) > 1 else '/home/m/soothe-bt/dual_b1q_0.5.rpp'
|
||||
nattempts = int(sys.argv[2]) if len(sys.argv) > 2 else 120
|
||||
os.makedirs(SNAPDIR, exist_ok=True)
|
||||
subprocess.run('pkill -9 -x reaser 2>/dev/null; pkill -9 -x reaper 2>/dev/null; '
|
||||
"pkill -9 -f '[y]abridge' 2>/dev/null; sleep 1", shell=True)
|
||||
proc = subprocess.Popen(
|
||||
['/usr/bin/reaper', '-nosplash', '-ignoreerrors', rpp,
|
||||
'/home/m/re-tools/play_loop.lua'],
|
||||
stdout=open('/dev/null', 'w'), stderr=subprocess.STDOUT)
|
||||
t0 = time.time()
|
||||
host = None
|
||||
while time.time() - t0 < 60 and not host:
|
||||
host = find_host()
|
||||
time.sleep(0.2)
|
||||
if not host:
|
||||
print('NO HOST')
|
||||
return 1
|
||||
print('host', host, flush=True)
|
||||
time.sleep(12)
|
||||
|
||||
fd = os.open(f'/proc/{host}/mem', os.O_RDONLY)
|
||||
|
||||
def rd(addr, n):
|
||||
try:
|
||||
return os.pread(fd, n, addr)
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
def get_ctx():
|
||||
# cheap probe: sens scalar must be >100 at known offset
|
||||
for base in (CTX.get('base'),):
|
||||
pass
|
||||
return CTX.get('base')
|
||||
|
||||
# locate ctx once (while running): marker scan over heap only is heavy;
|
||||
# reuse known-good address from prior sessions, validate via sens scalar.
|
||||
CTX = {}
|
||||
ctx = None
|
||||
b = rd(0x2370040 + 0x540870, 4)
|
||||
if b and struct.unpack('<f', b)[0] > 100:
|
||||
ctx = 0x2370040
|
||||
else:
|
||||
print('known ctx invalid; full scan needed')
|
||||
return 1
|
||||
print('ctx', hex(ctx), flush=True)
|
||||
|
||||
hits = 0
|
||||
saved = []
|
||||
rng = np.random.default_rng(7)
|
||||
for k in range(nattempts):
|
||||
os.kill(host, signal.SIGSTOP)
|
||||
try:
|
||||
fir_p = struct.unpack('<Q', rd(ctx + 0x540668, 8))[0]
|
||||
fb = rd(fir_p, 64 * 4)
|
||||
ident = False
|
||||
if fb:
|
||||
arr = np.frombuffer(fb[:256], dtype='<f4')
|
||||
ident = bool(np.all(np.abs(arr[0::2] - 1.0) < 1e-6))
|
||||
ident = ident and bool(np.all(arr[1::2] == 0))
|
||||
if not ident:
|
||||
hits += 1
|
||||
store = {}
|
||||
for off in SLOTS:
|
||||
pb = rd(ctx + off, 8)
|
||||
if not pb:
|
||||
continue
|
||||
p = struct.unpack('<Q', pb)[0]
|
||||
if p < 0x10000:
|
||||
continue
|
||||
ab = rd(p, NARR * 4)
|
||||
if not ab:
|
||||
continue
|
||||
store[hex(off)] = np.frombuffer(ab, dtype='<f4').astype(np.float32)
|
||||
fn = f'{SNAPDIR}/hit{k:03d}.npz'
|
||||
np.savez_compressed(fn, **store)
|
||||
saved.append(fn)
|
||||
sc = rd(ctx + 0x2404dc, 4)
|
||||
print(f'[{k}] HIT fir!=identity -> {fn} '
|
||||
f'(fir[0..5]={np.round(store["0x540668"][:6],4)})', flush=True)
|
||||
finally:
|
||||
os.kill(host, signal.SIGCONT)
|
||||
time.sleep(float(rng.uniform(0.02, 0.09)))
|
||||
os.close(fd)
|
||||
if proc.poll() is None:
|
||||
proc.kill()
|
||||
print(f'done: {hits} hits / {nattempts} attempts -> {SNAPDIR}')
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
"""firtrace.py — live capture of mask->FIR pipeline buffers during realtime
|
||||
playback, with GUI-publish flag forced ON so bands[i] curves are copied into
|
||||
the 0x540728+ slots inside the audio callback (decomp 529fe0:144-183).
|
||||
|
||||
Dumps full vector-object arrays for every ctx slot in [0x540500,0x540900):
|
||||
reads {data_ptr, end_ptr, cap_ptr} (std::vector layout) when available,
|
||||
falls back to fixed-length float view.
|
||||
|
||||
Usage: python3 scripts/firtrace.py [rpp] [nsnap]
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
|
||||
SNAPDIR = '/tmp/opencode/firtrace'
|
||||
|
||||
|
||||
def find_host():
|
||||
import glob
|
||||
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:
|
||||
return pid
|
||||
return None
|
||||
|
||||
|
||||
def mem_read(fd, addr, n):
|
||||
try:
|
||||
return os.pread(fd, n, addr)
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def mem_write(fd, addr, data):
|
||||
try:
|
||||
os.pwrite(fd, data, addr)
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def parse_snap(path):
|
||||
data = open(path, 'rb').read()
|
||||
regs = []
|
||||
i = 0
|
||||
while i + 16 <= len(data):
|
||||
lo, sz = struct.unpack_from('<QQ', data, i)
|
||||
regs.append((lo, data[i + 16:i + 16 + sz]))
|
||||
i += 16 + sz
|
||||
return regs
|
||||
|
||||
|
||||
def readabs(regs, addr, n):
|
||||
for lo, body in regs:
|
||||
if lo <= addr < lo + len(body) and addr - lo + n <= len(body):
|
||||
return body[addr - lo:addr - lo + n]
|
||||
return None
|
||||
|
||||
|
||||
def find_ctx(regs):
|
||||
sig = struct.pack('<I', 0x473b8000)
|
||||
cands = []
|
||||
for lo, body in regs:
|
||||
j = body.find(sig)
|
||||
while j >= 0:
|
||||
base = lo + j - 0x24
|
||||
b = readabs(regs, base + 0x540870, 4)
|
||||
if b and struct.unpack('<f', b)[0] > 100:
|
||||
cands.append(base)
|
||||
j = body.find(sig, j + 1)
|
||||
return sorted(set(cands))
|
||||
|
||||
|
||||
SLOTS = list(range(0x540600, 0x540900, 8))
|
||||
EXTRA = [0x540548, 0x540550, 0x540558, 0x540560, 0x540568, 0x540570,
|
||||
0x540578, 0x540580, 0x540588, 0x540590, 0x540598, 0x5405a0,
|
||||
0x5405a8, 0x5405b0, 0x5405b8, 0x5405c0, 0x5405c8, 0x5405d0,
|
||||
0x5405d8, 0x5405e0, 0x5405e8, 0x5405f0, 0x5405f8]
|
||||
|
||||
|
||||
def dump_slot(regs, ctx, off, maxf=65536):
|
||||
"""Read qword at ctx+off; if it looks like a heap array, dump floats."""
|
||||
b = readabs(regs, ctx + off, 8)
|
||||
if not b:
|
||||
return None
|
||||
ptr = struct.unpack('<Q', b)[0]
|
||||
if ptr < 0x10000 or ptr > 0x7fffffffffff:
|
||||
return None
|
||||
body = readabs(regs, ptr, min(maxf, 262144) * 4)
|
||||
if body is None or len(body) < 64:
|
||||
return None
|
||||
a = np.frombuffer(body, dtype='<f4').astype(np.float64)
|
||||
# trim trailing zeros beyond a floor of 2049 samples
|
||||
nz = np.nonzero(a != 0)[0]
|
||||
keep = max(2049, (nz[-1] + 1) if len(nz) else 0)
|
||||
return a[:min(len(a), ((keep + 63) // 64) * 64)]
|
||||
|
||||
|
||||
def main():
|
||||
rpp = sys.argv[1] if len(sys.argv) > 1 else '/home/m/soothe-bt/dual_b1q_0.5.rpp'
|
||||
nsnap = int(sys.argv[2]) if len(sys.argv) > 2 else 4
|
||||
os.makedirs(SNAPDIR, exist_ok=True)
|
||||
subprocess.run('pkill -9 -x reaser 2>/dev/null; pkill -9 -x reaper 2>/dev/null; '
|
||||
"pkill -9 -f '[y]abridge' 2>/dev/null; sleep 1", shell=True)
|
||||
proc = subprocess.Popen(
|
||||
['/usr/bin/reaper', '-nosplash', '-ignoreerrors', rpp,
|
||||
'/home/m/re-tools/play_loop.lua'],
|
||||
stdout=open('/dev/null', 'w'), stderr=subprocess.STDOUT)
|
||||
t0 = time.time()
|
||||
host = None
|
||||
while time.time() - t0 < 60 and not host:
|
||||
host = find_host()
|
||||
time.sleep(0.2)
|
||||
if not host:
|
||||
print('NO HOST')
|
||||
return 1
|
||||
print('host', host, flush=True)
|
||||
time.sleep(12)
|
||||
|
||||
fd = os.open(f'/proc/{host}/mem', os.O_RDONLY)
|
||||
# locate ctx via one full snapshot
|
||||
snap0 = f'{SNAPDIR}/probe.bin'
|
||||
snapshot(fd, host, snap0)
|
||||
regs = parse_snap(snap0)
|
||||
cands = find_ctx(regs)
|
||||
print('ctx candidates:', ['0x%x' % c for c in cands][:5], flush=True)
|
||||
if not cands:
|
||||
return 1
|
||||
ctx = cands[0]
|
||||
|
||||
# force GUI publish flag (byte at ctx+0x2404bc)
|
||||
ok = mem_write(fd, ctx + 0x2404bc, b'\x01')
|
||||
print('GUI flag write:', ok, flush=True)
|
||||
|
||||
store = {}
|
||||
meta = {}
|
||||
for k in range(nsnap):
|
||||
time.sleep(1.5)
|
||||
# re-arm flag (callback consumes it)
|
||||
mem_write(fd, ctx + 0x2404bc, b'\x01')
|
||||
time.sleep(0.05)
|
||||
p = f'{SNAPDIR}/s{k}.bin'
|
||||
snapshot(fd, host, p)
|
||||
rg = parse_snap(p)
|
||||
cs = find_ctx(rg)
|
||||
if not cs:
|
||||
continue
|
||||
c = cs[0]
|
||||
tag = f's{k}'
|
||||
scal = {}
|
||||
for a in range(0x540860, 0x5408a8, 4):
|
||||
bb = readabs(rg, c + a, 4)
|
||||
scal[hex(a)] = struct.unpack('<f', bb)[0] if bb else None
|
||||
store[f'{tag}_scalars'] = json.dumps(scal)
|
||||
for off in SLOTS + EXTRA:
|
||||
arr = dump_slot(rg, c, off)
|
||||
if arr is not None:
|
||||
store[f'{tag}_{hex(off)}'] = arr
|
||||
meta.setdefault(hex(off), []).append(len(arr))
|
||||
print(f'snap {k}: dumped {sum(1 for x in store if x.startswith(tag+"_") and x!=tag+"_scalars")} arrays',
|
||||
flush=True)
|
||||
os.close(fd)
|
||||
if proc.poll() is None:
|
||||
proc.kill()
|
||||
|
||||
json.dump(meta, open(f'{SNAPDIR}/meta.json', 'w'), indent=1)
|
||||
np.savez_compressed(f'{SNAPDIR}/firtrace.npz', **store)
|
||||
print('saved', f'{SNAPDIR}/firtrace.npz')
|
||||
return 0
|
||||
|
||||
|
||||
def snapshot(fd, host, path):
|
||||
out = open(path, 'wb')
|
||||
nreg = nbytes = 0
|
||||
for line in open(f'/proc/{host}/maps').read().splitlines():
|
||||
p = line.split()
|
||||
if len(p) < 2 or 'r' not in p[1]:
|
||||
continue
|
||||
lo, hi = (int(x, 16) for x in p[0].split('-'))
|
||||
a = lo
|
||||
while a < hi:
|
||||
n = min(hi - a, 8 * 1024 * 1024)
|
||||
d = mem_read(fd, a, n)
|
||||
if d:
|
||||
out.write(struct.pack('<QQ', a, len(d)))
|
||||
out.write(d)
|
||||
nreg += 1
|
||||
nbytes += len(d)
|
||||
a += n
|
||||
out.close()
|
||||
return nreg, nbytes
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
"""fnall.py — safe full-thread INT3 tracer.
|
||||
|
||||
Invariant: every thread is SEIZE+INTERRUPT-stopped BEFORE the breakpoint is
|
||||
armed, so any later clone descends from a traced thread and its SIGTRAPs come
|
||||
to us instead of killing the host.
|
||||
|
||||
Env: FN_ADDR, FN_DUR, FN_MAXHITS
|
||||
"""
|
||||
import ctypes
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
FN = int(os.environ.get('FN_ADDR', '0x180529FE0'), 16)
|
||||
DUR = float(os.environ.get('FN_DUR', '15'))
|
||||
MAXHITS = int(os.environ.get('FN_MAXHITS', '50'))
|
||||
|
||||
PTRACE_CONT = 7
|
||||
PTRACE_GETREGS = 12
|
||||
PTRACE_SETREGS = 13
|
||||
PTRACE_PEEKDATA = 2
|
||||
PTRACE_POKETEXT = 4
|
||||
PTRACE_SINGLESTEP = 9
|
||||
PTRACE_SEIZE = 0x4206
|
||||
PTRACE_INTERRUPT = 0x4207
|
||||
PTRACE_O_TRACECLONE = 0x00000002
|
||||
|
||||
libc = ctypes.CDLL('libc.so.6', use_errno=True)
|
||||
|
||||
|
||||
class UserRegs(ctypes.Structure):
|
||||
_fields_ = [(n, ctypes.c_ulonglong) for n in (
|
||||
'r15', 'r14', 'r13', 'r12', 'rbp', 'rbx', 'r11', 'r10',
|
||||
'r9', 'r8', 'rax', 'rcx', 'rdx', 'rsi', 'rdi', 'orig_rax',
|
||||
'rip', 'cs', 'eflags', 'rsp', 'ss', 'fs_base', 'gs_base',
|
||||
'ds', 'es', 'fs', 'gs')]
|
||||
|
||||
|
||||
def pt(req, pid, addr=0, data=0):
|
||||
libc.ptrace.restype = ctypes.c_long
|
||||
r = libc.ptrace(req, pid, ctypes.c_void_p(addr), ctypes.c_void_p(data))
|
||||
if r == -1:
|
||||
return None, ctypes.get_errno()
|
||||
return r, 0
|
||||
|
||||
|
||||
def find_host():
|
||||
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:
|
||||
return pid
|
||||
return None
|
||||
|
||||
|
||||
def drain_waits():
|
||||
out = []
|
||||
while True:
|
||||
try:
|
||||
pid, status = os.waitpid(-1, os.WSTOPPED | os.WUNTRACED | os.WNOHANG)
|
||||
except ChildProcessError:
|
||||
break
|
||||
if pid == 0:
|
||||
break
|
||||
out.append((pid, status))
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
subprocess.run("pkill -9 -x reaper; pkill -9 -f '[y]abridge'; sleep 1", shell=True)
|
||||
proc = subprocess.Popen(['/usr/bin/reaper', '-nosplash', '-ignoreerrors',
|
||||
'-renderproject', '/home/m/soothe-bt/dual_b1q_0.5.rpp'],
|
||||
stdout=open('/dev/null', 'w'), stderr=subprocess.STDOUT)
|
||||
host = None
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < 30 and not host:
|
||||
host = find_host()
|
||||
time.sleep(0.002)
|
||||
if not host:
|
||||
print('NO HOST')
|
||||
return 1
|
||||
print('host %d at %.3fs' % (host, time.time() - t0), flush=True)
|
||||
|
||||
# phase 1: seize main, interrupt, wait stop
|
||||
seized = []
|
||||
r, e = pt(PTRACE_SEIZE, host, 0, PTRACE_O_TRACECLONE)
|
||||
if r is None:
|
||||
print('seize fail', e)
|
||||
return 1
|
||||
seized.append(host)
|
||||
pt(PTRACE_INTERRUPT, host)
|
||||
|
||||
# phase 2: pump events, seize newcomers until set stabilizes
|
||||
stable_until = time.time() + 2.5
|
||||
deadline = time.time() + 8.0
|
||||
while time.time() < deadline:
|
||||
for pid, st in drain_waits():
|
||||
pass # they stay stopped; we hold them
|
||||
grew = False
|
||||
for tid_s in glob.glob(f'/proc/{host}/task/*'):
|
||||
tid = int(os.path.basename(tid_s))
|
||||
if tid not in seized:
|
||||
r, e = pt(PTRACE_SEIZE, tid, 0, PTRACE_O_TRACECLONE)
|
||||
if r is not None or e != 3:
|
||||
seized.append(tid)
|
||||
pt(PTRACE_INTERRUPT, tid)
|
||||
grew = True
|
||||
if grew:
|
||||
stable_until = time.time() + 0.5
|
||||
elif time.time() > stable_until:
|
||||
break
|
||||
time.sleep(0.002)
|
||||
print('stopped %d threads' % len(seized), flush=True)
|
||||
|
||||
orig, _ = pt(PTRACE_PEEKDATA, host, FN)
|
||||
cc = (orig & ~0xFF) | 0xCC
|
||||
r, e = pt(PTRACE_POKETEXT, host, FN, cc)
|
||||
if r is None:
|
||||
print('arm fail', e)
|
||||
return 1
|
||||
print('armed %#x' % FN, flush=True)
|
||||
|
||||
log = []
|
||||
hits = 0
|
||||
EVENTS = []
|
||||
for tid in seized:
|
||||
pt(PTRACE_CONT, tid, 0, 0)
|
||||
|
||||
t_end = time.time() + DUR
|
||||
last_resweep = time.time()
|
||||
while hits < MAXHITS and time.time() < t_end:
|
||||
try:
|
||||
pid, status = os.waitpid(-1, os.WSTOPPED | os.WUNTRACED | os.WNOHANG)
|
||||
except ChildProcessError:
|
||||
break
|
||||
EVENTS.append((time.time()-t0, pid, hex(status), status >> 16))
|
||||
if pid == 0:
|
||||
if time.time() - last_resweep > 0.05:
|
||||
last_resweep = time.time()
|
||||
for tid_s in glob.glob(f'/proc/{host}/task/*'):
|
||||
tid = int(os.path.basename(tid_s))
|
||||
if tid not in seized:
|
||||
r, e = pt(PTRACE_SEIZE, tid, 0, PTRACE_O_TRACECLONE)
|
||||
if r is not None or e != 3:
|
||||
seized.append(tid)
|
||||
pt(PTRACE_INTERRUPT, tid)
|
||||
drain_waits()
|
||||
pt(PTRACE_CONT, tid, 0, 0)
|
||||
time.sleep(0.0005)
|
||||
continue
|
||||
sig = status >> 8
|
||||
ev = status >> 16
|
||||
if os.WIFEXITED(status) or os.WIFSIGNALED(status):
|
||||
EVENTS.append((time.time()-t0, pid, hex(status), ev))
|
||||
continue
|
||||
if ev == 3 or ev == 1:
|
||||
pt(PTRACE_CONT, pid, 0, 0)
|
||||
continue
|
||||
if os.WIFSTOPPED(pid) and sig == signal.SIGTRAP and ev == 0:
|
||||
regs = UserRegs()
|
||||
r, _ = pt(PTRACE_GETREGS, pid, 0, ctypes.addressof(regs))
|
||||
if r is None:
|
||||
continue
|
||||
if regs.rip - 1 == FN:
|
||||
ret, _ = pt(PTRACE_PEEKDATA, pid, regs.rsp)
|
||||
rec = dict(ctx=regs.rcx, a2=regs.rdx, cnt=regs.r8 & 0xffffffff,
|
||||
r9=regs.r9 & 0xffffffff, ret=ret,
|
||||
rbx=regs.rbx, r12=regs.r12, r13=regs.r13,
|
||||
r14=regs.r14, r15=regs.r15, rsp=regs.rsp, tid=pid)
|
||||
log.append(rec)
|
||||
hits += 1
|
||||
if hits <= 20:
|
||||
print('HIT ctx=%#x a2=%#x cnt=%#x ret=%#x'
|
||||
% (rec['ctx'], rec['a2'], rec['cnt'], rec['ret']), flush=True)
|
||||
pt(PTRACE_POKETEXT, pid, FN, orig)
|
||||
regs.rip = FN
|
||||
pt(PTRACE_SETREGS, pid, 0, ctypes.addressof(regs))
|
||||
pt(PTRACE_SINGLESTEP, pid, 0, 0)
|
||||
os.waitpid(pid, os.WUNTRACED)
|
||||
pt(PTRACE_POKETEXT, pid, FN, cc)
|
||||
pt(PTRACE_CONT, pid, 0, 0)
|
||||
else:
|
||||
pt(PTRACE_CONT, pid, 0, 0)
|
||||
elif os.WIFSTOPPED(pid):
|
||||
pt(PTRACE_CONT, pid, 0, sig if 0 < sig < 32 else 0)
|
||||
|
||||
pt(PTRACE_POKETEXT, host, FN, orig)
|
||||
print('total hits:', hits)
|
||||
json.dump(dict(log=log, events=EVENTS[:8000], nseized=len(seized)),
|
||||
open('/tmp/opencode/fntrace/allhits.json', 'w'), indent=1)
|
||||
proc.kill()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
#!/usr/bin/env python3
|
||||
"""fnhw.py — hardware-breakpoint tracer (DR0) for the yabridge host.
|
||||
Safe for un-traced threads (code is never patched). Auto-arms new threads.
|
||||
|
||||
Env: FN_ADDR (target VA), FN_DUR (seconds), FN_SKIP (skip first N hits)
|
||||
"""
|
||||
import ctypes
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
FN = int(os.environ.get('FN_ADDR', '0x180529FE0'), 16)
|
||||
DUR = float(os.environ.get('FN_DUR', '15'))
|
||||
|
||||
PTRACE_CONT = 7
|
||||
PTRACE_GETREGS = 12
|
||||
PTRACE_SETREGS = 13
|
||||
PTRACE_PEEKUSER = 3
|
||||
PTRACE_POKEUSER = 6
|
||||
PTRACE_SINGLESTEP = 9
|
||||
PTRACE_SEIZE = 0x4206
|
||||
PTRACE_INTERRUPT = 0x4207
|
||||
|
||||
libc = ctypes.CDLL('libc.so.6', use_errno=True)
|
||||
|
||||
|
||||
class UserRegs(ctypes.Structure):
|
||||
_fields_ = [(n, ctypes.c_ulonglong) for n in (
|
||||
'r15', 'r14', 'r13', 'r12', 'rbp', 'rbx', 'r11', 'r10',
|
||||
'r9', 'r8', 'rax', 'rcx', 'rdx', 'rsi', 'rdi', 'orig_rax',
|
||||
'rip', 'cs', 'eflags', 'rsp', 'ss', 'fs_base', 'gs_base',
|
||||
'ds', 'es', 'fs', 'gs')]
|
||||
|
||||
|
||||
def pt(req, pid, addr=0, data=0):
|
||||
libc.ptrace.restype = ctypes.c_long
|
||||
r = libc.ptrace(req, pid, ctypes.c_void_p(addr), ctypes.c_void_p(data))
|
||||
if r == -1:
|
||||
return None, ctypes.get_errno()
|
||||
return r, 0
|
||||
|
||||
|
||||
def find_host():
|
||||
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:
|
||||
return pid
|
||||
return None
|
||||
|
||||
|
||||
def find_dr0_off(pid):
|
||||
"""Locate DR0 slot inside the USER area by probing."""
|
||||
probe = 0x1234567890abcdef
|
||||
for off in range(0x200, 0x400, 8):
|
||||
r, e = pt(PTRACE_POKEUSER, pid, off, probe)
|
||||
if r is None:
|
||||
continue
|
||||
v, _ = pt(PTRACE_PEEKUSER, pid, off)
|
||||
if v == (probe & 0xFFFFFFFFFFFFFFFF):
|
||||
# restore zero & sanity-check neighbours exist
|
||||
pt(PTRACE_POKEUSER, pid, off, 0)
|
||||
return off
|
||||
return None
|
||||
|
||||
|
||||
def arm(tid, dr0_off):
|
||||
"""Arm DR0/DR7 - requires tid to be STOPPED; caller handles stop/cont."""
|
||||
pt(PTRACE_POKEUSER, tid, dr0_off, FN)
|
||||
# DR7 (index 7): L0=1, RW0=00 (exec), LEN0=00
|
||||
pt(PTRACE_POKEUSER, tid, dr0_off + 7 * 8, 0x1)
|
||||
v, _ = pt(PTRACE_PEEKUSER, tid, dr0_off + 7 * 8)
|
||||
return v == 1
|
||||
|
||||
|
||||
def disarm(tid, dr0_off):
|
||||
pt(PTRACE_POKEUSER, tid, dr0_off + 7 * 8, 0)
|
||||
pt(PTRACE_POKEUSER, tid, dr0_off, 0)
|
||||
|
||||
|
||||
def arm_stopped(tid, dr0_off):
|
||||
"""INTERRUPT -> (bounded) wait -> arm -> CONT. True if DR7 verified."""
|
||||
pt(PTRACE_INTERRUPT, tid)
|
||||
for _ in range(30):
|
||||
try:
|
||||
pid, st = os.waitpid(tid, os.WUNTRACED | os.WNOHANG)
|
||||
except ChildProcessError:
|
||||
return False
|
||||
if pid == tid:
|
||||
break
|
||||
time.sleep(0.002)
|
||||
r0, e0 = pt(PTRACE_POKEUSER, tid, dr0_off, FN)
|
||||
r7, e7 = pt(PTRACE_POKEUSER, tid, dr0_off + 7 * 8, 0x1)
|
||||
v, _ = pt(PTRACE_PEEKUSER, tid, dr0_off + 7 * 8)
|
||||
print(' arm tid=%d poke_dr0=%s(e%s) poke_dr7=%s(e%s) dr7_read=%s'
|
||||
% (tid, 'ok' if r0 is not None else 'FAIL', e0,
|
||||
'ok' if r7 is not None else 'FAIL', e7, hex(v or 0)), flush=True)
|
||||
pt(PTRACE_CONT, tid, 0, 0)
|
||||
return v == 1
|
||||
|
||||
|
||||
def main():
|
||||
subprocess.run("pkill -9 -x reaper; pkill -9 -f '[y]abridge'; sleep 1", shell=True)
|
||||
proc = subprocess.Popen(['/usr/bin/reaper', '-nosplash', '-ignoreerrors',
|
||||
'-renderproject', '/home/m/soothe-bt/dual_b1q_0.5.rpp'],
|
||||
stdout=open('/dev/null', 'w'), stderr=subprocess.STDOUT)
|
||||
host = None
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < 30 and not host:
|
||||
host = find_host()
|
||||
time.sleep(0.002)
|
||||
if not host:
|
||||
print('NO HOST')
|
||||
return 1
|
||||
print('host %d at %.3fs' % (host, time.time() - t0), flush=True)
|
||||
|
||||
seized = []
|
||||
for tid in [int(os.path.basename(p)) for p in glob.glob(f'/proc/{host}/task/*')] or [host]:
|
||||
if tid not in seized:
|
||||
r, e = pt(PTRACE_SEIZE, tid, 0, 0)
|
||||
if r is not None or e != 3:
|
||||
seized.append(tid)
|
||||
|
||||
# stop one thread briefly to locate DR0 offset
|
||||
tgt = seized[0]
|
||||
pt(PTRACE_INTERRUPT, tgt)
|
||||
os.waitpid(tgt, os.WUNTRACED)
|
||||
dr0_off = find_dr0_off(tgt)
|
||||
print('DR0 user-offset:', hex(dr0_off) if dr0_off else 'NOT FOUND', flush=True)
|
||||
if not dr0_off:
|
||||
return 1
|
||||
pt(PTRACE_CONT, tgt, 0, 0)
|
||||
n_ok = 0
|
||||
for tid in seized:
|
||||
if arm_stopped(tid, dr0_off):
|
||||
n_ok += 1
|
||||
print('armed %#x on %d/%d tids (DR7 verified)' % (FN, n_ok, len(seized)), flush=True)
|
||||
|
||||
log = []
|
||||
hits = 0
|
||||
skip = int(os.environ.get('FN_SKIP', '0'))
|
||||
t_end = time.time() + DUR
|
||||
last_resweep = 0.0
|
||||
while time.time() < t_end:
|
||||
now = time.time()
|
||||
if now - last_resweep > 0.03:
|
||||
last_resweep = now
|
||||
for tid_s in glob.glob(f'/proc/{host}/task/*'):
|
||||
tid = int(os.path.basename(tid_s))
|
||||
if tid not in seized:
|
||||
r, e = pt(PTRACE_SEIZE, tid, 0, 0)
|
||||
if r is not None or e != 3:
|
||||
seized.append(tid)
|
||||
if arm_stopped(tid, dr0_off):
|
||||
print('+tid', tid, flush=True)
|
||||
try:
|
||||
pid, status = os.waitpid(-1, os.WSTOPPED | os.WUNTRACED | os.WNOHANG)
|
||||
except ChildProcessError:
|
||||
break
|
||||
if pid == 0:
|
||||
time.sleep(0.0005)
|
||||
continue
|
||||
sig = status >> 8
|
||||
ev = status >> 16
|
||||
if os.WIFEXITED(status) or os.WIFSIGNALED(status):
|
||||
continue
|
||||
if ev == 3 or ev == 1:
|
||||
pt(PTRACE_CONT, pid, 0, 0)
|
||||
continue
|
||||
if os.WIFSTOPPED(pid) and sig == signal.SIGTRAP:
|
||||
regs = UserRegs()
|
||||
r, _ = pt(PTRACE_GETREGS, pid, 0, ctypes.addressof(regs))
|
||||
if r is None:
|
||||
continue
|
||||
if regs.rip == FN or regs.rip == FN + 1:
|
||||
rip = FN
|
||||
ret, _ = pt(PTRACE_PEEKDATA, pid, regs.rsp)
|
||||
rec = dict(ctx=regs.rcx, a2=regs.rdx, cnt=regs.r8 & 0xffffffff,
|
||||
r9=regs.r9 & 0xffffffff, ret=ret,
|
||||
rbx=regs.rbx, r12=regs.r12, r13=regs.r13,
|
||||
r14=regs.r14, r15=regs.r15, rsp=regs.rsp, tid=pid)
|
||||
if hits >= skip:
|
||||
log.append(rec)
|
||||
print('HIT ctx=%#x a2=%#x cnt=%#x r9d=%#x ret=%#x'
|
||||
% (rec['ctx'], rec['a2'], rec['cnt'], rec['r9'], rec['ret']),
|
||||
flush=True)
|
||||
hits += 1
|
||||
# pass bp: clear DR0 temporarily, single-step, re-arm
|
||||
pt(PTRACE_POKEUSER, pid, dr0_off + 7 * 8, 0)
|
||||
regs.eflags |= 0x100 # TF
|
||||
pt(PTRACE_SETREGS, pid, 0, ctypes.addressof(regs))
|
||||
pt(PTRACE_SINGLESTEP, pid, 0, 0)
|
||||
os.waitpid(pid, os.WUNTRACED)
|
||||
regs2 = UserRegs()
|
||||
pt(PTRACE_GETREGS, pid, 0, ctypes.addressof(regs2))
|
||||
regs2.eflags &= ~0x100
|
||||
pt(PTRACE_SETREGS, pid, 0, ctypes.addressof(regs2))
|
||||
arm(pid, dr0_off)
|
||||
pt(PTRACE_CONT, pid, 0, 0)
|
||||
else:
|
||||
# stray SIGTRAP (wine internal): forward
|
||||
pt(PTRACE_CONT, pid, 0, signal.SIGTRAP)
|
||||
elif os.WIFSTOPPED(pid):
|
||||
pt(PTRACE_CONT, pid, 0, sig if 0 < sig < 32 else 0)
|
||||
|
||||
print('total hits:', hits, 'logged:', len(log))
|
||||
json.dump(log, open('/tmp/opencode/fntrace/hwhits.json', 'w'), indent=1)
|
||||
for tid in seized:
|
||||
disarm(tid, dr0_off)
|
||||
proc.kill()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,225 @@
|
||||
#!/usr/bin/env python3
|
||||
"""fntrace.py — ptrace INT3 tracer for FUN_180529fe0 (mask chain vtbl slot 6)
|
||||
in the live yabridge host. The wine module is mapped at its preferred base,
|
||||
so dump VAs == runtime addresses (verified: exec map 0x180001000-0x181baa000).
|
||||
|
||||
On each hit logs: RIP, RCX (ctx), RDX, R8D (count), R9D, [RSP] (return addr),
|
||||
plus xmm0/xmm1 low scalars if available via GETFPREGS (skipped: not portable).
|
||||
|
||||
Usage: python3 scripts/fntrace.py [rpp] [nhits]
|
||||
"""
|
||||
import ctypes
|
||||
import glob
|
||||
import os
|
||||
import signal
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
FN = int(os.environ.get('FN_ADDR', '0x180529FE0'), 16)
|
||||
SNAPDIR = '/tmp/opencode/fntrace'
|
||||
|
||||
PTRACE_TRACEME = 0
|
||||
PTRACE_PEEKTEXT = 1
|
||||
PTRACE_PEEKDATA = 2
|
||||
PTRACE_POKETEXT = 4
|
||||
PTRACE_CONT = 7
|
||||
PTRACE_SINGLESTEP = 9
|
||||
PTRACE_GETREGS = 12
|
||||
PTRACE_SETREGS = 13
|
||||
PTRACE_ATTACH = 16
|
||||
PTRACE_DETACH = 17
|
||||
PTRACE_SEIZE = 0x4206
|
||||
PTRACE_INTERRUPT = 0x4207
|
||||
PTRACE_O_TRACECLONE = 0x00000002
|
||||
PTRACE_EVENT_CLONE = 3 # status >> 16 == 4 (event+1)? actually event = status>>16, CLONE==3 -> 4? use raw compare below
|
||||
PTRACE_EVENT_FORK = 1
|
||||
|
||||
|
||||
class UserRegs(ctypes.Structure):
|
||||
_fields_ = [(n, ctypes.c_ulonglong) for n in (
|
||||
'r15', 'r14', 'r13', 'r12', 'rbp', 'rbx', 'r11', 'r10',
|
||||
'r9', 'r8', 'rax', 'rcx', 'rdx', 'rsi', 'rdi', 'orig_rax',
|
||||
'rip', 'cs', 'eflags', 'rsp', 'ss', 'fs_base', 'gs_base',
|
||||
'ds', 'es', 'fs', 'gs')]
|
||||
|
||||
|
||||
libc = ctypes.CDLL('libc.so.6', use_errno=True)
|
||||
|
||||
|
||||
def ptrace(req, pid, addr=0, data=0):
|
||||
libc.ptrace.restype = ctypes.c_long
|
||||
r = libc.ptrace(req, pid, ctypes.c_void_p(addr), ctypes.c_void_p(data))
|
||||
if r == -1:
|
||||
e = ctypes.get_errno()
|
||||
if req not in (PTRACE_PEEKTEXT, PTRACE_PEEKDATA):
|
||||
raise OSError(e, f'ptrace({req:#x},{pid}) failed')
|
||||
return None
|
||||
return r
|
||||
|
||||
|
||||
def find_host():
|
||||
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:
|
||||
return pid
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
rpp = sys.argv[1] if len(sys.argv) > 1 else '/home/m/soothe-bt/dual_b1q_0.5.rpp'
|
||||
nhits = int(sys.argv[2]) if len(sys.argv) > 2 else 24
|
||||
render = '--render' in sys.argv
|
||||
os.makedirs(SNAPDIR, exist_ok=True)
|
||||
subprocess.run('pkill -9 -x reaser 2>/dev/null; pkill -9 -x reaper 2>/dev/null; '
|
||||
"pkill -9 -f '[y]abridge' 2>/dev/null; sleep 1", shell=True)
|
||||
if render:
|
||||
proc = subprocess.Popen(
|
||||
['/usr/bin/reaper', '-nosplash', '-ignoreerrors',
|
||||
'-renderproject', rpp],
|
||||
stdout=open('/dev/null', 'w'), stderr=subprocess.STDOUT)
|
||||
else:
|
||||
proc = subprocess.Popen(
|
||||
['/usr/bin/reaper', '-nosplash', '-ignoreerrors', rpp,
|
||||
'/home/m/re-tools/play_loop.lua'],
|
||||
stdout=open('/dev/null', 'w'), stderr=subprocess.STDOUT)
|
||||
t0 = time.time()
|
||||
host = None
|
||||
while time.time() - t0 < 60 and not host:
|
||||
host = find_host()
|
||||
time.sleep(0.05)
|
||||
if not host:
|
||||
print('NO HOST')
|
||||
return 1
|
||||
print('host', host, 'at %.1fs' % (time.time() - t0), flush=True)
|
||||
|
||||
tids = [int(os.path.basename(p)) for p in glob.glob(f'/proc/{host}/task/*')]
|
||||
print('tids:', tids, flush=True)
|
||||
|
||||
seized = []
|
||||
for tid in tids:
|
||||
try:
|
||||
ptrace(PTRACE_SEIZE, tid, 0, PTRACE_O_TRACECLONE)
|
||||
seized.append(tid)
|
||||
except OSError as e:
|
||||
print('seize fail', tid, e)
|
||||
if not seized:
|
||||
return 1
|
||||
# stop everyone
|
||||
stopped = []
|
||||
for tid in seized:
|
||||
try:
|
||||
ptrace(PTRACE_INTERRUPT, tid)
|
||||
os.waitpid(tid, os.WUNTRACED)
|
||||
stopped.append(tid)
|
||||
except (OSError, ChildProcessError):
|
||||
pass
|
||||
|
||||
orig = ptrace(PTRACE_PEEKTEXT, stopped[0], FN)
|
||||
cc = (orig & ~0xFF) | 0xCC
|
||||
ptrace(PTRACE_POKETEXT, stopped[0], FN, cc)
|
||||
print('breakpoint armed at %#x (orig=%#x)' % (FN, orig), flush=True)
|
||||
|
||||
for tid in stopped:
|
||||
try:
|
||||
ptrace(PTRACE_CONT, tid, 0, 0)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
hits = 0
|
||||
log = []
|
||||
import select
|
||||
import time as _t
|
||||
t_last = _t.time()
|
||||
idle_deadline = float(os.environ.get('FNTRACE_IDLE', '20'))
|
||||
while hits < nhits and _t.time() - t_last < idle_deadline:
|
||||
try:
|
||||
pid, status = os.waitpid(-1, os.WUNTRACED | os.WSTOPPED | os.WNOHANG)
|
||||
except ChildProcessError:
|
||||
break
|
||||
if pid == 0:
|
||||
_t.sleep(0.005)
|
||||
continue
|
||||
sig = status >> 8
|
||||
if os.WIFEXITED(pid and status or status) or os.WIFSIGNALED(status):
|
||||
# thread/process exited (normal in wine): forget it
|
||||
if pid in seized:
|
||||
seized.remove(pid)
|
||||
if pid in stopped:
|
||||
stopped.remove(pid)
|
||||
continue
|
||||
if not os.WIFSTOPPED(pid):
|
||||
continue
|
||||
t_last = _t.time()
|
||||
ev = status >> 16
|
||||
if ev == PTRACE_EVENT_CLONE or ev == PTRACE_EVENT_FORK:
|
||||
if pid not in seized:
|
||||
seized.append(pid)
|
||||
ptrace(PTRACE_CONT, pid, 0, 0)
|
||||
continue
|
||||
if sig == signal.SIGTRAP:
|
||||
regs = UserRegs()
|
||||
try:
|
||||
ptrace(PTRACE_GETREGS, pid, 0, ctypes.addressof(regs))
|
||||
except OSError:
|
||||
ptrace(PTRACE_CONT, pid, 0, 0)
|
||||
continue
|
||||
if regs.rip - 1 == FN:
|
||||
ret = ptrace(PTRACE_PEEKDATA, pid, regs.rsp)
|
||||
rec = dict(rip=regs.rip - 1, ctx=regs.rcx, a2=regs.rdx,
|
||||
cnt=regs.r8 & 0xffffffff, r9=regs.r9 & 0xffffffff,
|
||||
ret=ret, tid=pid,
|
||||
rbx=regs.rbx, rbp_=regs.rbp, rsi=regs.rsi, rdi=regs.rdi)
|
||||
log.append(rec)
|
||||
hits += 1
|
||||
print(f'hit {hits}: tid={pid} ctx={regs.rcx:#x} '
|
||||
f'a2={regs.rdx:#x} cnt={regs.r8:#x} r9d={regs.r9:#x} '
|
||||
f'ret={ret:#x}', flush=True)
|
||||
# step over int3
|
||||
lo = struct.unpack('<Q', struct.pack('<Q', orig ^ ((orig ^ cc) & 0xFF)))[0]
|
||||
ptrace(PTRACE_POKETEXT, pid, FN, orig)
|
||||
regs.rip = FN
|
||||
ptrace(PTRACE_SETREGS, pid, 0, ctypes.addressof(regs))
|
||||
ptrace(PTRACE_SINGLESTEP, pid, 0, 0)
|
||||
os.waitpid(pid, os.WUNTRACED)
|
||||
ptrace(PTRACE_POKETEXT, pid, FN, cc)
|
||||
ptrace(PTRACE_CONT, pid, 0, 0)
|
||||
else:
|
||||
ptrace(PTRACE_CONT, pid, 0, 0)
|
||||
else:
|
||||
# other stop signals: deliver and continue
|
||||
ptrace(PTRACE_CONT, pid, 0, sig if 0 < sig < 0x20 else 0)
|
||||
|
||||
# cleanup: remove breakpoint, detach
|
||||
print('done hits=', hits, 'cleaning up...', flush=True)
|
||||
for tid in seized:
|
||||
try:
|
||||
ptrace(PTRACE_INTERRUPT, tid)
|
||||
os.waitpid(tid, os.WUNTRACED)
|
||||
except (OSError, ChildProcessError):
|
||||
continue
|
||||
try:
|
||||
ptrace(PTRACE_POKETEXT, stopped[0], FN, orig)
|
||||
except Exception as e:
|
||||
print('restore fail', e)
|
||||
for tid in seized:
|
||||
try:
|
||||
ptrace(PTRACE_DETACH, tid, 0, 0)
|
||||
except OSError:
|
||||
pass
|
||||
import json
|
||||
json.dump(log, open(f'{SNAPDIR}/hits.json', 'w'), indent=1)
|
||||
print('saved', f'{SNAPDIR}/hits.json')
|
||||
if proc.poll() is None:
|
||||
proc.kill()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env python3
|
||||
"""fntrace2.py — fast single-point INT3 tracer for the live yabridge host.
|
||||
|
||||
Flow: poll for host spawn (5ms), immediately PTRACE_SEIZE the main thread,
|
||||
poke INT3 at FN (no stop required - word write is atomic), then serve
|
||||
waitpid events (clone children are auto-traced and continued). Logs args at
|
||||
each hit. Works best against `reaper -renderproject` where all DSP work
|
||||
happens in a burst right after host spawn.
|
||||
|
||||
Usage: python3 scripts/fntrace2.py [rpp] [nhits] [--render]
|
||||
Env: FN_ADDR (default 0x180529fe0)
|
||||
"""
|
||||
import ctypes
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
FN = int(os.environ.get('FN_ADDR', '0x180529FE0'), 16)
|
||||
SNAPDIR = '/tmp/opencode/fntrace'
|
||||
|
||||
PTRACE_CONT = 7
|
||||
PTRACE_GETREGS = 12
|
||||
PTRACE_SETREGS = 13
|
||||
PTRACE_PEEKDATA = 2
|
||||
PTRACE_POKETEXT = 4
|
||||
PTRACE_SINGLESTEP = 9
|
||||
PTRACE_DETACH = 17
|
||||
PTRACE_SEIZE = 0x4206
|
||||
PTRACE_INTERRUPT = 0x4207
|
||||
PTRACE_O_TRACECLONE = 0x00000002
|
||||
|
||||
|
||||
class UserRegs(ctypes.Structure):
|
||||
_fields_ = [(n, ctypes.c_ulonglong) for n in (
|
||||
'r15', 'r14', 'r13', 'r12', 'rbp', 'rbx', 'r11', 'r10',
|
||||
'r9', 'r8', 'rax', 'rcx', 'rdx', 'rsi', 'rdi', 'orig_rax',
|
||||
'rip', 'cs', 'eflags', 'rsp', 'ss', 'fs_base', 'gs_base',
|
||||
'ds', 'es', 'fs', 'gs')]
|
||||
|
||||
|
||||
libc = ctypes.CDLL('libc.so.6', use_errno=True)
|
||||
|
||||
|
||||
def ptrace(req, pid, addr=0, data=0):
|
||||
libc.ptrace.restype = ctypes.c_long
|
||||
r = libc.ptrace(req, pid, ctypes.c_void_p(addr), ctypes.c_void_p(data))
|
||||
if r == -1:
|
||||
e = ctypes.get_errno()
|
||||
if req not in (PTRACE_PEEKDATA,):
|
||||
raise OSError(e, f'ptrace({req:#x},{pid}) failed')
|
||||
return None
|
||||
return r
|
||||
|
||||
|
||||
def find_host():
|
||||
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:
|
||||
return pid
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
rpp = sys.argv[1] if len(sys.argv) > 1 else '/home/m/soothe-bt/dual_b1q_0.5.rpp'
|
||||
nhits = int(sys.argv[2]) if len(sys.argv) > 2 else 12
|
||||
render = '--render' in sys.argv
|
||||
os.makedirs(SNAPDIR, exist_ok=True)
|
||||
subprocess.run('pkill -9 -x reaser 2>/dev/null; pkill -9 -x reaper 2>/dev/null; '
|
||||
"pkill -9 -f '[y]abridge' 2>/dev/null; sleep 1", shell=True)
|
||||
if render:
|
||||
proc = subprocess.Popen(
|
||||
['/usr/bin/reaper', '-nosplash', '-ignoreerrors',
|
||||
'-renderproject', rpp],
|
||||
stdout=open('/dev/null', 'w'), stderr=subprocess.STDOUT)
|
||||
else:
|
||||
proc = subprocess.Popen(
|
||||
['/usr/bin/reaper', '-nosplash', '-ignoreerrors', rpp,
|
||||
'/home/m/re-tools/play_loop.lua'],
|
||||
stdout=open('/dev/null', 'w'), stderr=subprocess.STDOUT)
|
||||
|
||||
host = None
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < 60 and not host:
|
||||
host = find_host()
|
||||
time.sleep(0.005)
|
||||
if not host:
|
||||
print('NO HOST')
|
||||
return 1
|
||||
print('host %d at %.2fs' % (host, time.time() - t0), flush=True)
|
||||
|
||||
ptrace(PTRACE_SEIZE, host, 0, PTRACE_O_TRACECLONE)
|
||||
try:
|
||||
ptrace(PTRACE_INTERRUPT, host)
|
||||
os.waitpid(host, os.WUNTRACED)
|
||||
except (OSError, ChildProcessError) as e:
|
||||
print('interrupt fail', e)
|
||||
orig = ptrace(PTRACE_PEEKDATA, host, FN)
|
||||
cc = (orig & ~0xFF) | 0xCC
|
||||
ptrace(PTRACE_POKETEXT, host, FN, cc)
|
||||
print('armed %#x orig=%#x' % (FN, orig), flush=True)
|
||||
try:
|
||||
ptrace(PTRACE_CONT, host, 0, 0)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
known = {host}
|
||||
log = []
|
||||
hits = 0
|
||||
t_last = time.time()
|
||||
deadline_idle = float(os.environ.get('FNTRACE_IDLE', '25'))
|
||||
while hits < nhits and time.time() - t_last < deadline_idle:
|
||||
try:
|
||||
pid, status = os.waitpid(-1, os.WSTOPPED | os.WUNTRACED)
|
||||
except ChildProcessError:
|
||||
break
|
||||
sig = status >> 8
|
||||
ev = status >> 16
|
||||
known.add(pid)
|
||||
t_last = time.time()
|
||||
if ev == 3 or ev == 1: # CLONE/FORK event stop
|
||||
try:
|
||||
ptrace(PTRACE_CONT, pid, 0, 0)
|
||||
except OSError:
|
||||
pass
|
||||
continue
|
||||
if not os.WIFSTOPPED(pid):
|
||||
continue
|
||||
if sig == signal.SIGTRAP:
|
||||
regs = UserRegs()
|
||||
try:
|
||||
ptrace(PTRACE_GETREGS, pid, 0, ctypes.addressof(regs))
|
||||
except OSError:
|
||||
continue
|
||||
if regs.rip - 1 == FN:
|
||||
ret = ptrace(PTRACE_PEEKDATA, pid, regs.rsp)
|
||||
rec = dict(ctx=regs.rcx, a2=regs.rdx, cnt=regs.r8 & 0xffffffff,
|
||||
r9=regs.r9 & 0xffffffff, ret=ret, rsp=regs.rsp,
|
||||
rbx=regs.rbx, r12=regs.r12, r13=regs.r13,
|
||||
r14=regs.r14, r15=regs.r15, rsi=regs.rsi, rdi=regs.rdi,
|
||||
rip=regs.rip - 1, tid=pid)
|
||||
log.append(rec)
|
||||
hits += 1
|
||||
print('hit %d tid=%d ctx=%#x a2=%#x cnt=%#x r9d=%#x ret=%#x'
|
||||
% (hits, pid, regs.rcx, regs.rdx,
|
||||
regs.r8 & 0xffffffff, regs.r9 & 0xffffffff, ret),
|
||||
flush=True)
|
||||
# step over
|
||||
ptrace(PTRACE_POKETEXT, pid, FN, orig)
|
||||
regs.rip = FN
|
||||
ptrace(PTRACE_SETREGS, pid, 0, ctypes.addressof(regs))
|
||||
ptrace(PTRACE_SINGLESTEP, pid, 0, 0)
|
||||
try:
|
||||
os.waitpid(pid, os.WUNTRACED)
|
||||
except ChildProcessError:
|
||||
pass
|
||||
ptrace(PTRACE_POKETEXT, pid, FN, cc)
|
||||
try:
|
||||
ptrace(PTRACE_CONT, pid, 0, 0)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
ptrace(PTRACE_CONT, pid, 0, 0)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
ptrace(PTRACE_CONT, pid, 0, sig if 0 < sig < 0x20 else 0)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
print('hits:', hits, flush=True)
|
||||
json.dump(log, open(f'{SNAPDIR}/hits.json', 'w'), indent=1)
|
||||
for pid in list(known):
|
||||
try:
|
||||
os.kill(pid, signal.SIGKILL)
|
||||
except OSError:
|
||||
pass
|
||||
proc.kill()
|
||||
print('saved', f'{SNAPDIR}/hits.json')
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env python3
|
||||
"""fntrace3.py — decisive INT3 experiment: seize ALL tids within milliseconds
|
||||
of host spawn, arm breakpoint, log EVERY waitpid event for N seconds."""
|
||||
import ctypes
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
FN = int(os.environ.get('FN_ADDR', '0x180529FE0'), 16)
|
||||
DUR = float(os.environ.get('FN_DUR', '25'))
|
||||
|
||||
PTRACE_CONT = 7
|
||||
PTRACE_GETREGS = 12
|
||||
PTRACE_SETREGS = 13
|
||||
PTRACE_PEEKDATA = 2
|
||||
PTRACE_POKETEXT = 4
|
||||
PTRACE_SINGLESTEP = 9
|
||||
PTRACE_SEIZE = 0x4206
|
||||
PTRACE_INTERRUPT = 0x4207
|
||||
PTRACE_LISTEN = 0x4208
|
||||
PTRACE_O_TRACECLONE = 2
|
||||
|
||||
libc = ctypes.CDLL('libc.so.6', use_errno=True)
|
||||
|
||||
|
||||
class UserRegs(ctypes.Structure):
|
||||
_fields_ = [(n, ctypes.c_ulonglong) for n in (
|
||||
'r15', 'r14', 'r13', 'r12', 'rbp', 'rbx', 'r11', 'r10',
|
||||
'r9', 'r8', 'rax', 'rcx', 'rdx', 'rsi', 'rdi', 'orig_rax',
|
||||
'rip', 'cs', 'eflags', 'rsp', 'ss', 'fs_base', 'gs_base',
|
||||
'ds', 'es', 'fs', 'gs')]
|
||||
|
||||
|
||||
def pt(req, pid, addr=0, data=0):
|
||||
libc.ptrace.restype = ctypes.c_long
|
||||
r = libc.ptrace(req, pid, ctypes.c_void_p(addr), ctypes.c_void_p(data))
|
||||
if r == -1:
|
||||
return None, ctypes.get_errno()
|
||||
return r, 0
|
||||
|
||||
|
||||
def find_host():
|
||||
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:
|
||||
return pid
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
rpp = '/home/m/soothe-bt/dual_b1q_0.5.rpp'
|
||||
subprocess.run('pkill -9 -x reaper; pkill -9 -f \'[y]abridge\'; sleep 1', shell=True)
|
||||
proc = subprocess.Popen(['/usr/bin/reaper', '-nosplash', '-ignoreerrors',
|
||||
'-renderproject', rpp],
|
||||
stdout=open('/dev/null', 'w'), stderr=subprocess.STDOUT)
|
||||
host = None
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < 30 and not host:
|
||||
host = find_host()
|
||||
time.sleep(0.002)
|
||||
if not host:
|
||||
print('NO HOST')
|
||||
return 1
|
||||
print('host %d at %.3fs' % (host, time.time() - t0), flush=True)
|
||||
|
||||
seized = []
|
||||
for tid in [host] + [int(os.path.basename(p)) for p in glob.glob(f'/proc/{host}/task/*')]:
|
||||
if tid in seized:
|
||||
continue
|
||||
r, e = pt(PTRACE_SEIZE, tid, 0, PTRACE_O_TRACECLONE)
|
||||
if r is None and e == 3: # ESRCH - gone
|
||||
continue
|
||||
seized.append(tid)
|
||||
# interrupt to allow poke below (poke needs SOME stopped thread)
|
||||
print('seized:', seized, flush=True)
|
||||
# stop one thread to enable POKETEXT
|
||||
tgt = seized[0]
|
||||
pt(PTRACE_INTERRUPT, tgt)
|
||||
os.waitpid(tgt, os.WUNTRACED)
|
||||
orig, _ = pt(PTRACE_PEEKDATA, tgt, FN)
|
||||
cc = (orig & ~0xFF) | 0xCC
|
||||
pt(PTRACE_POKETEXT, tgt, FN, cc)
|
||||
print('armed orig=%#x' % orig, flush=True)
|
||||
# seize any tids spawned meanwhile
|
||||
for tid in [int(os.path.basename(p)) for p in glob.glob(f'/proc/{host}/task/*')]:
|
||||
if tid not in seized:
|
||||
r, e = pt(PTRACE_SEIZE, tid, 0, PTRACE_O_TRACECLONE)
|
||||
if r is not None or e != 3:
|
||||
seized.append(tid)
|
||||
for tid in seized:
|
||||
try:
|
||||
pt(PTRACE_CONT, tid, 0, 0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
log = []
|
||||
events = []
|
||||
hits = 0
|
||||
t_end = time.time() + DUR
|
||||
last_resweep = 0.0
|
||||
while time.time() < t_end:
|
||||
now = time.time()
|
||||
if now - last_resweep > 0.05:
|
||||
last_resweep = now
|
||||
for tid_s in glob.glob(f'/proc/{host}/task/*'):
|
||||
tid = int(os.path.basename(tid_s))
|
||||
if tid not in seized:
|
||||
r, e = pt(PTRACE_SEIZE, tid, 0, PTRACE_O_TRACECLONE)
|
||||
if r is not None or e != 3:
|
||||
seized.append(tid)
|
||||
print('+tid', tid, flush=True)
|
||||
pid, status = os.waitpid(-1, os.WSTOPPED | os.WUNTRACED | os.WNOHANG)
|
||||
if pid == 0:
|
||||
time.sleep(0.001)
|
||||
continue
|
||||
sig = status >> 8
|
||||
ev = status >> 16
|
||||
events.append((time.time() - t0, pid, hex(status), ev))
|
||||
if pid not in seized:
|
||||
r, e = pt(PTRACE_SEIZE, pid, 0, PTRACE_O_TRACECLONE)
|
||||
if r is not None or e != 3:
|
||||
seized.append(pid)
|
||||
if ev == 3 or ev == 1:
|
||||
pt(PTRACE_CONT, pid, 0, 0)
|
||||
continue
|
||||
if os.WIFSTOPPED(pid) and sig == signal.SIGTRAP:
|
||||
regs = UserRegs()
|
||||
r, _ = pt(PTRACE_GETREGS, pid, 0, ctypes.addressof(regs))
|
||||
if r is None:
|
||||
continue
|
||||
if regs.rip - 1 == FN:
|
||||
ret, _ = pt(PTRACE_PEEKDATA, pid, regs.rsp)
|
||||
rec = dict(ctx=regs.rcx, a2=regs.rdx, cnt=regs.r8 & 0xffffffff,
|
||||
r9=regs.r9 & 0xffffffff, ret=ret, rbx=regs.rbx,
|
||||
r12=regs.r12, r13=regs.r13, r14=regs.r14,
|
||||
r15=regs.r15, rsp=regs.rsp, tid=pid)
|
||||
log.append(rec)
|
||||
hits += 1
|
||||
print('HIT ctx=%#x a2=%#x cnt=%#x ret=%#x'
|
||||
% (regs.rcx, regs.rdx, regs.r8 & 0xffffffff, ret), flush=True)
|
||||
pt(PTRACE_POKETEXT, pid, FN, orig)
|
||||
regs.rip = FN
|
||||
pt(PTRACE_SETREGS, pid, 0, ctypes.addressof(regs))
|
||||
pt(PTRACE_SINGLESTEP, pid, 0, 0)
|
||||
os.waitpid(pid, os.WUNTRACED)
|
||||
pt(PTRACE_POKETEXT, pid, FN, cc)
|
||||
pt(PTRACE_CONT, pid, 0, 0)
|
||||
else:
|
||||
pt(PTRACE_CONT, pid, 0, 0)
|
||||
elif os.WIFEXITED(status) or os.WIFSIGNALED(status):
|
||||
continue
|
||||
else:
|
||||
pt(PTRACE_CONT, pid, 0, sig if 0 < sig < 32 else 0)
|
||||
|
||||
print('total hits:', hits, 'events:', len(events))
|
||||
json.dump(dict(log=log, events=events[:400]),
|
||||
open('/tmp/opencode/fntrace/hits3.json', 'w'), indent=1)
|
||||
proc.kill()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""hotips.py — sample thread RIPs aggressively during the render burst to find
|
||||
which module code actually executes (the real DSP path)."""
|
||||
import collections
|
||||
import glob
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
MOD_LO, MOD_HI = 0x180001000, 0x181baa000
|
||||
|
||||
|
||||
def find_host():
|
||||
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:
|
||||
return pid
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
subprocess.run("pkill -9 -x reaper; pkill -9 -f '[y]abridge'; sleep 1", shell=True)
|
||||
proc = subprocess.Popen(['/usr/bin/reaper', '-nosplash', '-ignoreerrors',
|
||||
'-renderproject', '/home/m/soothe-bt/dual_b1q_0.5.rpp'],
|
||||
stdout=open('/dev/null', 'w'), stderr=subprocess.STDOUT)
|
||||
host = None
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < 30 and not host:
|
||||
host = find_host()
|
||||
time.sleep(0.002)
|
||||
print('host', host, 'at %.3fs' % (time.time() - t0))
|
||||
ips = collections.Counter()
|
||||
t_end = time.time() + float(os.environ.get('HOT_DUR', '6'))
|
||||
n_ok = n_fail = 0
|
||||
while time.time() < t_end:
|
||||
for tid_s in glob.glob(f'/proc/{host}/task/*'):
|
||||
try:
|
||||
parts = open(f'{tid_s}/syscall').read().split()
|
||||
ip = int(parts[-1], 16)
|
||||
n_ok += 1
|
||||
if MOD_LO <= ip < MOD_HI:
|
||||
ips[ip] += 1
|
||||
except Exception:
|
||||
n_fail += 1
|
||||
proc.kill()
|
||||
print('samples ok=%d fail=%d, in-module=%d' % (n_ok, n_fail, sum(ips.values())))
|
||||
# bucket to 64-byte lines, aggregate by function-ish regions
|
||||
agg = collections.Counter()
|
||||
for ip, c in ips.items():
|
||||
agg[(ip >> 6) << 6] += c
|
||||
for addr, c in agg.most_common(40):
|
||||
print('%#x %d' % (addr, c))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
"""ilt_resolve.py — resolve ILT dispatch stubs of soothe_mem.bin (raw dump, base 0x180000000).
|
||||
|
||||
Stub pattern (7+7+4 bytes):
|
||||
48 63 05 rel32 mov eax, [rip+rel32] ; slot index (runtime-fixed after reloc)
|
||||
4c 8d 15 rel32 lea r10, [rip+rel32] ; pointer table base
|
||||
41 ff 24 d2 jmp qword ptr [r10+rax*8]
|
||||
|
||||
Resolution (all offline from the dump, post-relocation values):
|
||||
idx_addr = stub + 7 + rel32_a
|
||||
tbl_addr = stub + 14 + rel32_b
|
||||
impl = u64[tbl_addr + idx * 8]
|
||||
|
||||
Usage: ilt_resolve.py [VA ...] (default: FIR-loop + main-loop stub set)
|
||||
"""
|
||||
import struct
|
||||
import sys
|
||||
|
||||
BASE = 0x180000000
|
||||
BIN = '/home/m/re-tools/soothe_mem.bin'
|
||||
_data = open(BIN, 'rb').read()
|
||||
|
||||
|
||||
def rd(va, n):
|
||||
off = va - BASE
|
||||
return _data[off:off + n]
|
||||
|
||||
|
||||
def u32(va):
|
||||
return struct.unpack('<I', rd(va, 4))[0]
|
||||
|
||||
|
||||
def u64(va):
|
||||
return struct.unpack('<Q', rd(va, 8))[0]
|
||||
|
||||
|
||||
def resolve_stub(va):
|
||||
"""Return dict describing the ILT stub at va, or None if pattern mismatch."""
|
||||
b = rd(va, 18)
|
||||
if len(b) < 18 or b[0] != 0x48 or b[1] != 0x63 or b[2] != 0x05:
|
||||
return None
|
||||
rel_a = struct.unpack_from('<i', b, 3)[0]
|
||||
if b[7] != 0x4C or b[8] != 0x8D or b[9] != 0x15:
|
||||
return None
|
||||
rel_b = struct.unpack_from('<i', b, 10)[0]
|
||||
idx_addr = va + 7 + rel_a
|
||||
tbl_addr = va + 14 + rel_b
|
||||
idx = u32(idx_addr)
|
||||
impl = u64(tbl_addr + idx * 8)
|
||||
return dict(stub=va, idx_addr=idx_addr, idx=idx,
|
||||
tbl_addr=tbl_addr, impl=impl)
|
||||
|
||||
|
||||
DEFAULT_STUBS = [
|
||||
# FIR-build loop 52b550..52b8bb
|
||||
0x180002210, # copy scratch->FIR ?
|
||||
0x180002180, # complex-op A/C (float)
|
||||
0x180001bb0, # complex-op A/C (double)
|
||||
0x180001a90, # complex-op B/D (float)
|
||||
0x1800019d0, # complex-op B/D (double)
|
||||
0x180001880, # paired-scalar op 1 (flag branch)
|
||||
0x180001ca0, # paired-scalar op 2 (flag branch)
|
||||
0x180001df0, # final op (float)
|
||||
0x180001f70, # final op (double)
|
||||
# import thunks of 535a70 dispatcher
|
||||
0x180140a10,
|
||||
0x180140a70,
|
||||
# main-loop transforms referenced by BLOCKMAP
|
||||
0x180002030, 0x180001d30, 0x180002270, 0x1800022a0,
|
||||
0x180001970, 0x180001a60, 0x180001850, 0x180002000,
|
||||
0x180001c40, 0x180001fa0, 0x180001940, 0x180001f10,
|
||||
0x180001c70, 0x180001d60, 0x1800019a0, 0x180001a00,
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
args = [int(a, 16) if not a.startswith('0x') else int(a, 0)
|
||||
for a in sys.argv[1:]] or DEFAULT_STUBS
|
||||
print(f'{"stub":>12} {"idx@":>12} {"idx":>4} {"table":>12} {"impl":>12}')
|
||||
rows = []
|
||||
for va in args:
|
||||
r = resolve_stub(va)
|
||||
if r is None:
|
||||
print(f'{va:#12x} PATTERN MISMATCH')
|
||||
continue
|
||||
rows.append(r)
|
||||
print(f'{r["stub"]:#12x} {r["idx_addr"]:#12x} {r["idx"]:4d} '
|
||||
f'{r["tbl_addr"]:#12x} {r["impl"]:#12x}')
|
||||
return rows
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user