24a: perf_event breakpoints definitive — wine reserves HW BP slots (ENOSPC on wine threads while self/cross-process native opens succeed), closing hardware-trap route and explaining all prior ptrace-DR silence; FFT-conv engine object identified at ctx+0x540530 (inline cfg {2,4096}/{16384,8192}, member vectors +0xb8..+0x130, methods dc30/fe00/dd30) — its process method is the kernel consumer candidate
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
#!/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('<Q', 0x1824AC210)
|
||||
while time.time() - t0 < 25 and firptr is None:
|
||||
for line in open(f'/proc/{host}/maps'):
|
||||
parts = line.split()
|
||||
if 'rw' not in parts[1]:
|
||||
continue
|
||||
lo, hi = (int(x, 16) for x in parts[0].split('-'))
|
||||
CH = 16 * 1024 * 1024
|
||||
a = lo
|
||||
while a < hi and firptr is None:
|
||||
d = rd(a, min(CH + 4096, hi - a))
|
||||
if not d:
|
||||
break
|
||||
j = d.find(vt)
|
||||
while j >= 0:
|
||||
cand = a + j
|
||||
sb = rd(cand + 0x540870, 4)
|
||||
if sb and struct.unpack('<f', sb)[0] > 100:
|
||||
ctx = cand
|
||||
firptr = struct.unpack('<Q', rd(cand + 0x540668, 8))[0]
|
||||
break
|
||||
j = d.find(vt, j + 1)
|
||||
a += CH
|
||||
if firptr:
|
||||
break
|
||||
print('host %d ctx %#x fir %#x at %.2fs'
|
||||
% (host, ctx or 0, firptr or 0, time.time() - t0), flush=True)
|
||||
if not firptr:
|
||||
return 1
|
||||
watch = firptr + 43 * 8 # bin43 re word
|
||||
|
||||
# attach to all current threads; remember to attach newborns too
|
||||
events = {}
|
||||
errs = {}
|
||||
|
||||
def attach(tid):
|
||||
fde, e = perf_open(tid, watch)
|
||||
if fde is None or fde < 0:
|
||||
errs[e] = errs.get(e, 0) + 1
|
||||
return None
|
||||
events[fde] = tid
|
||||
libc.mmap.restype = ctypes.c_void_p
|
||||
m = libc.mmap(None, 2 * 4096, 1 | 2, 2, fde, 0) # PROT_R|W, MAP_PRIVATE
|
||||
if m in (None, ctypes.c_void_p(-1).value):
|
||||
return None
|
||||
bufs[fde] = (m, (ctypes.c_char * (2 * 4096)).from_address(m))
|
||||
libc.ioctl.argtypes = [ctypes.c_int] * 3 + [ctypes.c_void_p]
|
||||
libc.ioctl(fde, IOCTL_ENABLE, 0)
|
||||
return fde
|
||||
|
||||
bufs = {}
|
||||
# pick top-CPU thread only (minimise BP resource demand)
|
||||
import time as _t
|
||||
def tcpu(tid):
|
||||
try:
|
||||
f = open(f'/proc/{host}/task/{tid}/stat').read().split()
|
||||
return int(f[13]) + int(f[14])
|
||||
except Exception:
|
||||
return -1
|
||||
tids = [int(os.path.basename(p)) for p in glob.glob(f'/proc/{host}/task/*')]
|
||||
c0 = {t: tcpu(t) for t in tids}
|
||||
_t.sleep(0.4)
|
||||
deltas = sorted(((tcpu(t) - c0.get(t, 0), t) for t in tids), reverse=True)
|
||||
target = deltas[0][1] if deltas else host
|
||||
print('target tid=%d cpuΔ=%s all=%s' % (target, deltas[0], deltas[:6]), flush=True)
|
||||
fde = attach(target)
|
||||
n_ok = 1 if fde is not None else 0
|
||||
print('attached %d fds; errno hist=%s' % (n_ok, errs), flush=True)
|
||||
|
||||
# let render run; drain buffers periodically
|
||||
ips = {}
|
||||
DUR = float(os.environ.get('BP_DUR', '12'))
|
||||
t_end = time.time() + DUR
|
||||
fresh_t = None
|
||||
DATA = 4096
|
||||
while time.time() < t_end:
|
||||
time.sleep(0.05)
|
||||
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)
|
||||
for fde, (m, arr) in list(bufs.items()):
|
||||
head, tail = struct.unpack_from('<QQ', bytes(arr[:16]), 0)
|
||||
n = head - tail
|
||||
if n == 0:
|
||||
continue
|
||||
blob = bytes(arr[4096:8192])
|
||||
p = tail % DATA
|
||||
consumed = 0
|
||||
while consumed < n:
|
||||
if p + 8 > DATA:
|
||||
p = 0
|
||||
typ, misc, recsz = struct.unpack_from('<IHH', blob, p)
|
||||
if recsz < 8:
|
||||
break
|
||||
rec = blob[p:p + recsz]
|
||||
if typ == PERF_RECORD_SAMPLE and recsz >= 16:
|
||||
ip = struct.unpack_from('<Q', rec, 8)[0]
|
||||
if 0x180001000 <= ip < 0x181baa000:
|
||||
key = ip >> 4 << 4
|
||||
ips[key] = ips.get(key, 0) + 1
|
||||
adv = max(recsz, 8)
|
||||
consumed += adv
|
||||
p = (p + adv) % DATA
|
||||
struct.pack_into('<Q', arr, 8, head)
|
||||
print('distinct ips:', len(ips), 'total:', sum(ips.values()))
|
||||
for a_, c in sorted(ips.items(), key=lambda x: -x[1])[:20]:
|
||||
print('%#x %d' % (a_, c))
|
||||
proc.kill()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user