From f483fe398da6a9d15fd5208b98e514a36e0418f2 Mon Sep 17 00:00:00 2001 From: Matiq Date: Mon, 24 Aug 2026 02:43:04 +0300 Subject: [PATCH] =?UTF-8?q?24a:=20perf=5Fevent=20breakpoints=20definitive?= =?UTF-8?q?=20=E2=80=94=20wine=20reserves=20HW=20BP=20slots=20(ENOSPC=20on?= =?UTF-8?q?=20wine=20threads=20while=20self/cross-process=20native=20opens?= =?UTF-8?q?=20succeed),=20closing=20hardware-trap=20route=20and=20explaini?= =?UTF-8?q?ng=20all=20prior=20ptrace-DR=20silence;=20FFT-conv=20engine=20o?= =?UTF-8?q?bject=20identified=20at=20ctx+0x540530=20(inline=20cfg=20{2,409?= =?UTF-8?q?6}/{16384,8192},=20member=20vectors=20+0xb8..+0x130,=20methods?= =?UTF-8?q?=20dc30/fe00/dd30)=20=E2=80=94=20its=20process=20method=20is=20?= =?UTF-8?q?the=20kernel=20consumer=20candidate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handoff/NOTES_LEVEL.md | 24 +++++ scripts/perfbp.py | 217 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 scripts/perfbp.py diff --git a/handoff/NOTES_LEVEL.md b/handoff/NOTES_LEVEL.md index 85e8e6e..695ed3f 100644 --- a/handoff/NOTES_LEVEL.md +++ b/handoff/NOTES_LEVEL.md @@ -2502,3 +2502,27 @@ DR/GetThreadContext, wow64-переключения, либо события т аномалии доставки (например, трассировать через PTRACE_SEIZE опции или perf_event_open с breakpoint типом HW). 3. RT_FIRCONV=1 + контрольные числа dual + гейт корпуса. + +## ============ UPDATE 2026-08-23n (24a): ПЕРФ-ДИКТОР — WINE ДЕРЖИТ СЛОТЫ; ДВИЖОК ОПОЗНАН ============ +1. perf_event_open (PERF_TYPE_BREAKPOINT) на слово кернела: собственные треды и + ЧУЖИЕ same-uid процессы открываются (fd>0), а wine-треды дают ENOSPC ⇒ + **wine резервирует аппаратные BP-слоты** под эмуляцию Windows-DR. Это же + объясняет глобальную тишину ptrace-DR: ядро хранит значения, но в кремний + их программировать нечем. Маршрут аппаратных ловушек внутри wine ЗАКРЫТ + окончательно (23h–24a). Скрипт scripts/perfbp.py готов для нативных целей. +2. Опознан объект conv-движка: **ctx+0x540530**, инлайн-конфиг {2,4096}, + {16384,8192}, {2,257}; члены-векторы +0xb8..+0x130; методы FUN_18052dc30 + (config), FUN_18052fe00 (resize), FUN_18052dd30 (dtor). Его process-метод — + следующий кандидат на «потребителя кернела» (ищется рядом по декомпу/вызовам + с arg=ctx+0x540530 из аудио-клея). +3. INT3-маршрут остаётся единственным динамическим (слоты не нужны): требует + дисциплины fnwatch4 (INTERRUPT→wait→arm→CONT всем тредам) + контроля + render_fresh в каждом прогоне (флак ~50%). +### NEXT +1. Найти process-метод движка 0x540530: перечислить функции, получающие + ctx+0x540530 (grep decomp), восстановить vtbl объекта из дампа памяти + ([ctx+0x540530]-объект начинается с указателя? проверить офлайн из s1.bin), + декодировать метод → источник ×1.805. +2. INT3-ловушка на найденный метод + на design-тела в param-окно (рендер- + свежесть обязательна в логе). +3. RT_FIRCONV=1 после декода масштаба; контрольные числа dual; гейт корпуса. diff --git a/scripts/perfbp.py b/scripts/perfbp.py new file mode 100644 index 0000000..4f7f917 --- /dev/null +++ b/scripts/perfbp.py @@ -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('= 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('