#!/usr/bin/env python3 import os, glob, sys, time, subprocess REAPER = "/usr/bin/reaper" RPP = "/home/m/soothe-bt/render_long.rpp" OUTDIR = "/home/m/re-tools/regions_render" os.makedirs(OUTDIR, exist_ok=True) def all_procs(): out = {} for p in glob.glob('/proc/[0-9]*'): try: pid = int(os.path.basename(p)) cmd = open(p + '/cmdline','rb').read().replace(b'\0',b' ').decode('utf8','replace') st = open(p + '/stat','rb').read().decode('utf8','replace') ppid = int(st.split(')')[-1].split()[3]) # rough: parse later state = st.split(')')[-1].split()[0] out[pid] = (ppid, state, cmd.strip()) except Exception: pass return out def wait_full(pid, base=0x180000000, extent=0x7000000, tmo=40): start = time.time() while time.time() - start < tmo: try: maps = open(f'/proc/{pid}/maps').read() except Exception: return False seen = 0 for line in maps.splitlines(): parts = line.split() if len(parts) < 6: continue lo, hi = (int(x,16) for x in parts[0].split('-')) if 'r' in parts[1] and base <= lo < base+extent: seen += 1 if lo <= 0x184000000 < hi: return True if seen < 8: time.sleep(1) continue # big enough set of readable regions present; dump what exists return True def dump_module(pid, base=0x180000000, extent=0x7000000): maps_path = f'/proc/{pid}/maps' maps = open(maps_path).read() readable = [] for line in maps.splitlines(): parts = line.split() if len(parts) < 6: continue addr, perms, off, dev, ino, *rest = parts lo, hi = (int(x,16) for x in addr.split('-')) if 'r' in perms and (base <= lo < base+extent): readable.append((lo, hi, perms)) print(f' pid {pid}: {len(readable)} readable regions in [{base:#x},{base+extent:#x})') if not readable: print(' LISTING MAPS around base:') for line in maps.splitlines(): parts = line.split() if len(parts) < 6: continue addr, perms, *rest = parts lo = int(addr.split('-')[0],16) if base-0x100000 <= lo <= base+0x1000000: print(' ', line) mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY) total = 0 for lo, hi, perms in readable: size = hi - lo try: data = os.pread(mem, size, lo) total += len(data) fn = os.path.join(OUTDIR, f'pid{pid}_{lo:08x}_{hi:08x}_{perms.replace("-","")}.bin') with open(fn,'wb') as f: f.write(data) print(f' dumped {lo:#x}-{hi:#x} {len(data):#x} bytes {perms}') except Exception as e: print(f' {lo:#x}-{hi:#x} {perms} FAIL {e}') os.close(mem) print(f' pid {pid} total {total} bytes') print("spawning reaper...") proc = subprocess.Popen( [REAPER, "-nosplash", "-renderproject", RPP], stdout=open('/tmp/harness.log','w'), stderr=subprocess.STDOUT, env={**os.environ}) print("reaper pid", proc.pid) deadline = time.time() + 120 dumped = False while time.time() < deadline and proc.poll() is None: time.sleep(1) procs = all_procs() for pid,(ppid,state,cmd) in list(procs.items()): if state != 'S': continue if 'yabridge' in cmd or 'wine' in cmd: with open(f'/proc/{pid}/maps') as f: if 'soothe2' in f.read(): ok = wait_full(pid) print(f' wait_full(pid {pid}): {ok}') if not ok: continue dump_module(pid) dumped = True time.sleep(2) if dumped: break if not dumped: print("no live soothe2 process observed") print("killing reaper") proc.terminate() time.sleep(1) if proc.poll() is None: proc.kill()