Add shared reverse-engineering lookups (re_disassemble, re_xrefs, re_bytes, re_lookup)

Read the project's flat image, facts.jsonl and annotations with the team's
current names; exposed as MCP tools and as 'harbor re'. Configured by a
[reverse] table in the project file.
This commit is contained in:
KeelrLeek committed 2026-10-05 11:16:56 -05:00
1 parent dbc0c45ef2
commit 3f6df88f95
5 files changed
+410 -2

No files matched your search

+49 -1
View File
@@ -33,7 +33,9 @@ review queue, presence and stop flags. Protocol:
7. Put finished work in the review queue with submit(); do not announce commits in chat. A coordinator lands it.
8. When you are waiting on someone, use wait_for_messages() instead of polling or sleeping.
9. A correction is send(..., corrects=<id of your earlier message>).
10. Before your session ends or your allowance runs out, write handoff(): finished, in flight, next."""
10. Before your session ends or your allowance runs out, write handoff(): finished, in flight, next.
11. To read the original machine code use re_disassemble, re_xrefs, re_bytes and re_lookup: they read the project's
own image with the team's current names, so do not write your own disassembler or offset maths."""
KindName = Literal["note", "question", "answer", "request", "finding", "retraction", "decision", "status", "handoff"]
Importance = Literal["normal", "high", "urgent"]
@@ -436,6 +438,52 @@ def resume(target: str, relay_of: int | None = None, reason: str | None = None)
return run(do)
_reverse = {}
def _re(fn):
"""Run a reverse-engineering lookup against the project's [reverse] files (read-only, no Harbor lock needed)."""
try:
h = harbor()
r = _reverse.get(h.project.name)
if r is None:
from .reverse import Reverse
r = _reverse[h.project.name] = Reverse(h.project.reverse)
return fn(r)
except (HarborError, projects.ProjectError) as error:
raise ToolError(str(error)) from None
@mcp.tool
def re_disassemble(target: str, max_bytes: int | None = None) -> str:
"""Disassemble the whole function at an address or name (or max_bytes from the address), from the project's own
image of the executable, with call targets, imports and known globals named from the team's annotations. Use
this instead of writing your own disassembler script."""
return _re(lambda r: r.disassemble(target, max_bytes))
@mcp.tool
def re_xrefs(target: str) -> dict:
"""Callers and callees of the function at an address or name, plus the globals, imports and strings it uses,
each with its current name."""
return _re(lambda r: r.xrefs(target))
@mcp.tool
def re_bytes(target: str, count: int = 16, as_type: Literal["hex", "int32", "uint32", "float", "double",
"string"] = "hex") -> dict:
"""Bytes at an address or name, read as hex, int32, uint32, float, double or a C string (constants, tables,
initial values of globals)."""
return _re(lambda r: r.bytes_at(target, count, as_type))
@mcp.tool
def re_lookup(target: str) -> dict:
"""What the team has recorded about an address or name: the names.csv, functions.csv and globals.csv rows, the
prototype, the containing function, and the decompiled file with its status."""
return _re(lambda r: r.lookup(target))
def _report_in():
"""Tell Harbor this agent's app has its tools running: at start, then every 5 minutes while the app keeps the
server open (updates the connection only, not the agent's last-seen time)."""