#!/usr/bin/env python3 """perfbp.py — hardware data-breakpoint via perf_event_open (no ptrace). Watches READS of the live FIR kernel word across all host threads during offline render; collects sampler IPs -> the consumer.""" import ctypes import glob import mmap import os import struct import subprocess import sys import time PERF_TYPE_BREAKPOINT = 5 PERF_SAMPLE_IP = 1 << 0 HW_BREAKPOINT_R = 2 PERF_RECORD_SAMPLE = 9 SYS_perf_event_open = 298 IOCTL_ENABLE = 0x2400 # PERF_EVENT_IOC_ENABLE IOC_FLAG_GROUP = 0 class PerfAttr(ctypes.Structure): _fields_ = [ ('type', ctypes.c_uint32), ('size', ctypes.c_uint32), ('config', ctypes.uint64 if hasattr(ctypes, 'uint64') else ctypes.c_uint64), ('sample_period', ctypes.c_uint64), ('sample_type', ctypes.c_uint64), ('read_format', ctypes.c_uint64), ('flags', ctypes.c_uint64), # bitfield packed: disabled=bit0 ... ('wakeup_events', ctypes.c_uint32), ('bp_type', ctypes.c_uint32), ('bp_addr', ctypes.c_uint64), ('bp_len', ctypes.c_uint64), ] libc = ctypes.CDLL('libc.so.6', use_errno=True) 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 perf_open(tid, addr): a = PerfAttr() a.type = PERF_TYPE_BREAKPOINT a.size = ctypes.sizeof(a) a.config = 0 a.sample_period = 1 a.sample_type = PERF_SAMPLE_IP a.read_format = 0 a.flags = 1 | (1 << 5) # disabled=1, exclude_kernel=1 a.wakeup_events = 1 a.bp_type = HW_BREAKPOINT_R a.bp_addr = addr a.bp_len = 4 libc.syscall.restype = ctypes.c_long r = libc.syscall(ctypes.c_long(SYS_perf_event_open), ctypes.byref(a), ctypes.c_int(tid), ctypes.c_int(-1), ctypes.c_uint(-1), ctypes.c_void_p(0)) if r == -1: e = ctypes.get_errno() return None, e return r, 0 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 fd = os.open(f'/proc/{host}/mem', os.O_RDONLY) def rd(a, n): try: return os.pread(fd, n, a) except OSError: return None # wait for instance ctx = firptr = None vt = struct.pack('= 0: cand = a + j sb = rd(cand + 0x540870, 4) if sb and struct.unpack(' 100: ctx = cand firptr = struct.unpack(' wt0: fresh_t = time.time() - t0 print('wav fresh at %.2fs' % fresh_t, flush=True) for fde, (m, arr) in list(bufs.items()): head, tail = struct.unpack_from(' DATA: p = 0 typ, misc, recsz = struct.unpack_from('= 16: ip = struct.unpack_from('> 4 << 4 ips[key] = ips.get(key, 0) + 1 adv = max(recsz, 8) consumed += adv p = (p + adv) % DATA struct.pack_into('