"""Ghidra bridge: symbols in, hit annotations out. Two flows: *out of Ghidra*: export function names/addresses/sizes to a green-eye JSON symbol database (from ``names.tsv``, ``decompiled_index.tsv``, exports or ELF); *hits*: turn a recorded session into ``greeneye_hits.tsv`` (``addrhitssymbol``) that ``MarkGreenEyeHits.java`` can annotate in the Ghidra project. This is the static/dynamic join: Ghidra owns the address space and decompilation, green-eye owns what actually executed. """ from __future__ import annotations import os from collections import Counter from .record import load_events from .symbols import SymbolDB def build_symbol_db( *, names_tsv: str | None = None, index_tsv: str | None = None, exports_tsv: str | None = None, callgraph_tsv: str | None = None, elf: str | None = None, bias: int = 0, ) -> SymbolDB: db = SymbolDB() if elf: db.load_elf(elf, bias=0) # ELF symbol values are already runtime addresses if names_tsv: db.load_names_tsv(names_tsv, bias=bias) if index_tsv: db.load_index_tsv(index_tsv, bias=bias) if exports_tsv: db.load_index_tsv(exports_tsv, bias=bias) if callgraph_tsv: db.load_callgraph(callgraph_tsv) return db def export_hits(session_dir: str, out_tsv: str, symbols: SymbolDB | None = None) -> str: events = load_events(session_dir) hits: Counter = Counter() for event in events: if event.get("type") == "bp_hit": hits[event.get("pc")] += 1 elif event.get("type") == "watch_hit": hits[event.get("addr")] += 1 with open(out_tsv, "w", encoding="utf-8") as fh: fh.write("addr\thits\tsymbol\tannotation\n") for addr, count in sorted(hits.items(), key=lambda kv: -kv[1]): if not isinstance(addr, int): continue sym = symbols.resolve(addr) if symbols else None name = sym.name if sym else "" annotation = symbols.annotate(addr) if symbols else f"{addr:#x}" fh.write(f"{addr:#x}\t{count}\t{name}\t{annotation}\n") return os.path.abspath(out_tsv) def corpus_location(symbol, corpus_root: str) -> str | None: """Return ``:`` for a symbol with Ghidra index extras.""" bucket = symbol.extras.get("bucket") line = symbol.extras.get("line") if not bucket: return None return os.path.join(corpus_root, bucket) + (f":{line}" if line else "") def render_ghidra_command(project_dir: str, project_name: str, program: str, hits_tsv: str) -> str: return ( "analyzeHeadless {proj} {name} -process {program} -noanalysis " "-scriptPath /debug/risc-v-gsp-rm-vm/ghidra " "-postScript MarkGreenEyeHits.java {hits}" ).format(proj=project_dir, name=project_name, program=program, hits=hits_tsv)