#!/usr/bin/env python3 """fnexec.py — hardware EXEC breakpoints on 529fe0 entry and FIR-loop head. Answers definitively whether the decoded mask chain executes during render.""" import ctypes import glob import os import signal import struct import numpy as np import subprocess import sys import time DUR = float(os.environ.get('EXEC_DUR', '20')) PTRACE_CONT = 7 PTRACE_GETREGS = 12 PTRACE_SETREGS = 13 PTRACE_PEEKUSER = 3 PTRACE_POKEUSER = 6 PTRACE_SINGLESTEP = 9 PTRACE_SEIZE = 0x4206 PTRACE_INTERRUPT = 0x4207 PTRACE_O_TRACECLONE = 2 DR_BASE = 0x350 DR7_OFF = DR_BASE + 56 # DR0=exec 529fe0, DR1=exec 52b550; RW=00 LEN=00 both; L0,L1 enabled DR7_VAL = 0x00000003 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)) return None if r == -1 else 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(): subprocess.run("pkill -9 -x reaper; pkill -9 -f '[y]abridge'; " "rm -rf /run/user/1000/yabridge-soothe2_x64-*; sleep 1", shell=True) wav = '/home/m/soothe-bt/dual_b1q_0.5.wav' wt0 = os.path.getmtime(wav) if os.path.exists(wav) else 0 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 %.2fs' % (host, time.time() - t0), flush=True) # EARLY arm: exec watch on DESIGN bodies (inherited by future clones) fd0 = os.open(f'/proc/{host}/mem', os.O_RDONLY) pt(PTRACE_SEIZE, host, 0, PTRACE_O_TRACECLONE) pt(PTRACE_INTERRUPT, host) for _ in range(60): try: wpid, _st = os.waitpid(host, os.WUNTRACED | os.WNOHANG) except ChildProcessError: break if wpid == host: break time.sleep(0.001) pt(PTRACE_POKEUSER, host, DR_BASE + 0, 0x1802A24C0) pt(PTRACE_POKEUSER, host, DR_BASE + 8, 0x1802FA420) pt(PTRACE_POKEUSER, host, DR_BASE + 16, 0x180535A70) pt(PTRACE_POKEUSER, host, DR_BASE + 24, 0x18052D650) pt(PTRACE_POKEUSER, host, DR7_OFF, 0xF) ok0 = pt(PTRACE_PEEKUSER, host, DR7_OFF) print('early arm dr7=%#x' % (ok0 or 0), flush=True) pt(PTRACE_CONT, host, 0, 0) # phase B: seize ALL tids NOW and arm exec watches def arm(tid): pt(PTRACE_POKEUSER, tid, DR_BASE + 0, 0x180529FE0) pt(PTRACE_POKEUSER, tid, DR_BASE + 8, 0x18052B550) pt(PTRACE_POKEUSER, tid, DR7_OFF, DR7_VAL) v = pt(PTRACE_PEEKUSER, tid, DR7_OFF) return v is not None and (v & ~0x400) == DR7_VAL seized = {host} armed = set() for tid_s in glob.glob(f'/proc/{host}/task/*') or [host]: tid = int(os.path.basename(tid_s)) pt(PTRACE_SEIZE, tid, 0, PTRACE_O_TRACECLONE) pt(PTRACE_INTERRUPT, tid) for _ in range(40): try: wpid, _st = os.waitpid(tid, os.WUNTRACED | os.WNOHANG) except ChildProcessError: break if wpid == tid: break time.sleep(0.001) if arm(tid): armed.add(tid) try: pt(PTRACE_CONT, tid, 0, 0) except OSError: pass print('armed %d/%d tids' % (len(armed), len(seized)), flush=True) n0 = n1 = 0 samples = [] t_end = time.time() + DUR last_sweep = 0.0 fresh_t = None while time.time() < t_end: now = time.time() if now - last_sweep > 0.03: last_sweep = now for tid_s in glob.glob(f'/proc/{host}/task/*'): tid = int(os.path.basename(tid_s)) if tid not in seized: if pt(PTRACE_SEIZE, tid, 0, PTRACE_O_TRACECLONE) is not None: seized.add(tid) pt(PTRACE_INTERRUPT, tid) for _ in range(40): try: wpid, _st = os.waitpid(tid, os.WUNTRACED | os.WNOHANG) except ChildProcessError: break if wpid == tid: break time.sleep(0.001) if arm(tid): armed.add(tid) try: pt(PTRACE_CONT, tid, 0, 0) except OSError: pass try: pid, status = os.waitpid(-1, os.WSTOPPED | os.WNOHANG) except ChildProcessError: break if pid == 0: time.sleep(0.0004) continue sig = status >> 8 if os.WIFEXITED(status) or os.WIFSIGNALED(status): continue if os.WIFSTOPPED(pid) and sig == signal.SIGTRAP: regs = UserRegs() if pt(PTRACE_GETREGS, pid, 0, ctypes.addressof(regs)) is None: continue rip = regs.rip - (1 if False else 0) if pid not in armed: seized.add(pid) if arm(pid): armed.add(pid) # re-read regs after arming? DR change does not touch GPRs if rip not in (0x180529FE0, 0x18052B550, 0x1802A24C0, 0x1802FA420, 0x180535A70, 0x18052D650): pt(PTRACE_CONT, pid, 0, 0) continue if rip == 0x180529FE0: n0 += 1 elif rip == 0x18052B550: n1 += 1 rec = dict(which='design' if rip in (0x1802A24C0, 0x1802FA420) else ('529fe0' if rip == 0x180529FE0 else 'loop'), rip=rip, rcx=regs.rcx, rdx=regs.rdx, cnt=regs.r8 & 0xffffffff, t=round(time.time() - t0, 3), tid=pid) # dump band curve (rcx) and scratch (rdx) for design hits if rec['which'] == 'design': def rdarr(ptr, n=2049): try: b = os.pread(fd, n * 4, ptr) return np.frombuffer(b, dtype=' 400: break # pass exec trap: RF flag suppresses next report pt(PTRACE_POKEUSER, pid, DR_BASE + 48, 0xFFFF0FF0) regs.eflags |= 0x10000 pt(PTRACE_SETREGS, pid, 0, ctypes.addressof(regs)) pt(PTRACE_CONT, pid, 0, 0) elif os.WIFSTOPPED(pid): if pid not in armed and sig == signal.SIGTRAP: seized.add(pid) if arm(pid): armed.add(pid) pt(PTRACE_CONT, pid, 0, sig if 0 < sig < 32 else 0) if fresh_t is None and os.path.exists(wav) and os.path.getmtime(wav) > wt0: fresh_t = time.time() - t0 print('wav fresh at %.2fs' % fresh_t, flush=True) fresh = fresh_t is not None print('hits: 529fe0=%d loop=%d render_fresh=%s armed=%d/%d' % (n0, n1, fresh, len(armed), len(seized))) import json json.dump(samples[:300], open('/tmp/opencode/fnexec/hits.json', 'w'), indent=1, default=str) proc.kill() return 0 def rdsp(sp, pid): return None if __name__ == '__main__': os.makedirs('/tmp/opencode/fnexec', exist_ok=True) sys.exit(main())