"""Hardware-only origin trace for CardsDLL team-pair submissions. Distinguishes the three callers of the engine team-id service that can submit a full two-team pair, plus the mode-76 builder that prepares its pair: 0x1800c7583 correct fixture pair control 0x1800c6c23 generic pair submitter 0x1800c8dc1 mode-76 pair submitter 0x1800c8bf0 mode-76 pair builder entry No INT3/software breakpoints. No client memory writes. """ from __future__ import annotations import json import os import struct import time import traceback import gdb CARDS_IMAGE_BASE = 0x180000000 SITES = { 0x1800C7583: ("fixture_pair_submit", "r14", "rsi"), 0x1800C6C23: ("generic_pair_submit", "r14", "rsi"), 0x1800C8DC1: ("mode76_pair_submit", "r15", "rbp"), } MODE76_BUILDER = 0x1800C8BF0 _STATE = None def _reg(name: str) -> int: return int(gdb.parse_and_eval(f"${name}")) def _thread() -> dict: thread = gdb.selected_thread() if thread is None: return {} return {"name": thread.name, "ptid": list(thread.ptid), "global_num": thread.global_num} def _read(address: int, size: int) -> bytes | None: if not address or address < 0: return None try: return bytes(gdb.selected_inferior().read_memory(address, size)) except gdb.error: return None def _pair(address: int) -> list[int] | None: data = _read(address, 8) return list(struct.unpack("<2i", data)) if data else None def _registers() -> dict: names = ( "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", "rip", ) return {name: _reg(name) for name in names} class State: def __init__(self, log_path: str, cards_base: int): self.log_path = log_path self.cards_base = cards_base self.event_index = 0 def log(self, kind: str, **payload): self.event_index += 1 event = { "event": kind, "event_index": self.event_index, "time_unix": time.time(), "thread": _thread(), **payload, } with open(self.log_path, "a", encoding="utf-8") as handle: handle.write(json.dumps(event, sort_keys=True) + "\n") handle.flush() os.fsync(handle.fileno()) class PairSubmitBreakpoint(gdb.Breakpoint): def __init__(self, state: State, image_va: int, name: str, pointer_reg: str, index_reg: str): self.state = state self.image_va = image_va self.name = name self.pointer_reg = pointer_reg self.index_reg = index_reg address = state.cards_base + (image_va - CARDS_IMAGE_BASE) super().__init__(f"*0x{address:x}", type=gdb.BP_HARDWARE_BREAKPOINT, internal=False) self.silent = True def stop(self): try: pointer = _reg(self.pointer_reg) index = _reg(self.index_reg) & 0xFFFFFFFF pair_base = pointer - index * 4 self.state.log( self.name, instruction_image_va=self.image_va, source_value=_reg("r8") & 0xFFFFFFFF, side=_reg("rdx") & 0xFF, engine_base=_reg("rcx"), pair_pointer=pointer, pair_index=index, pair_base=pair_base, pair=_pair(pair_base), registers=_registers(), disassembly=gdb.execute("x/5i $pc", to_string=True), backtrace=gdb.execute("bt 24", to_string=True), ) except Exception as exc: self.state.log( "trace_error", where=self.name, error=str(exc), traceback=traceback.format_exc() ) return False class Mode76BuilderBreakpoint(gdb.Breakpoint): def __init__(self, state: State): self.state = state address = state.cards_base + (MODE76_BUILDER - CARDS_IMAGE_BASE) super().__init__(f"*0x{address:x}", type=gdb.BP_HARDWARE_BREAKPOINT, internal=False) self.silent = True def stop(self): try: self.state.log( "mode76_builder_entry", instruction_image_va=MODE76_BUILDER, object=_reg("rcx"), registers=_registers(), backtrace=gdb.execute("bt 24", to_string=True), ) except Exception as exc: self.state.log( "trace_error", where="mode76_builder", error=str(exc), traceback=traceback.format_exc() ) return False def _on_exit(event): if _STATE is not None: _STATE.log("inferior_exited", detail=str(event)) def start_trace(log_path: str, cards_base: int): global _STATE open(log_path, "w", encoding="utf-8").close() _STATE = State(log_path, cards_base) breakpoints = {} for image_va, (name, pointer_reg, index_reg) in SITES.items(): bp = PairSubmitBreakpoint(_STATE, image_va, name, pointer_reg, index_reg) breakpoints[name] = {"number": bp.number, "image_va": image_va} builder = Mode76BuilderBreakpoint(_STATE) breakpoints["mode76_builder"] = {"number": builder.number, "image_va": MODE76_BUILDER} gdb.events.exited.connect(_on_exit) _STATE.log( "trace_armed", breakpoints=breakpoints, hardware_only=True, client_memory_writes=False, )