Add 'annotate-usecode' command to import USECODE IR JSON annotations
- Introduced a new command 'annotate-usecode' to import USECODE IR JSON annotation hints as Ghidra comments on compiled anchors. - Added argument parsing for multiple IR JSON files, comment type selection, and a dry-run option. - Implemented logic to read annotation records from the provided IR files and set comments on the corresponding addresses in Ghidra. - Enhanced JSON schema to include response structure for the new command.
This commit is contained in:
parent
4d3c8cd81b
commit
daa363c3d2
39 changed files with 41450 additions and 871 deletions
|
|
@ -214,6 +214,15 @@ JSON_SCHEMAS = {
|
|||
"list-classes": {"type": "array", "items": CLASS_SCHEMA},
|
||||
"run-script": STATUS_SCHEMA,
|
||||
"apply-plan": STATUS_SCHEMA,
|
||||
"annotate-usecode": {
|
||||
"type": "object",
|
||||
"required": ["annotated", "skipped", "files"],
|
||||
"properties": {
|
||||
"annotated": {"type": "integer"},
|
||||
"skipped": {"type": "integer"},
|
||||
"files": {"type": "integer"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -523,6 +532,31 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
help="Validate and print the plan without modifying the project.",
|
||||
)
|
||||
|
||||
annotate_usecode_parser = subparsers.add_parser(
|
||||
"annotate-usecode",
|
||||
aliases=["annotate_usecode"],
|
||||
help="Import USECODE IR JSON annotation hints as Ghidra comments on compiled anchors.",
|
||||
)
|
||||
annotate_usecode_parser.add_argument(
|
||||
"--ir-file",
|
||||
dest="ir_files",
|
||||
metavar="IR_FILE",
|
||||
action="append",
|
||||
required=True,
|
||||
help="Path to an IR JSON file produced by poc_crusader_usecode_parser.py. May be given multiple times.",
|
||||
)
|
||||
annotate_usecode_parser.add_argument(
|
||||
"--comment-type",
|
||||
choices=["pre", "plate", "eol", "repeatable", "post"],
|
||||
default="plate",
|
||||
help="Ghidra comment type to use for anchor annotations (default: plate).",
|
||||
)
|
||||
annotate_usecode_parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Print what would be annotated without modifying the project.",
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
|
|
@ -947,6 +981,67 @@ def _print_plan(plan: dict) -> None:
|
|||
print(json.dumps(plan, indent=2, sort_keys=True))
|
||||
|
||||
|
||||
def command_annotate_usecode(config: ProjectConfig, args: argparse.Namespace) -> int:
|
||||
"""Import USECODE IR JSON files and set Ghidra comments on compiled anchor addresses."""
|
||||
import json as _json
|
||||
|
||||
# Collect all annotation records from every IR file.
|
||||
pending: list[tuple[str, str]] = [] # (address, comment_text)
|
||||
for ir_path_str in args.ir_files:
|
||||
ir_path = Path(ir_path_str).resolve()
|
||||
if not ir_path.is_file():
|
||||
raise RuntimeError(f"IR file not found: {ir_path}")
|
||||
ir = _json.loads(ir_path.read_text(encoding="utf-8"))
|
||||
cls = ir.get("class", {})
|
||||
evt = ir.get("event", {})
|
||||
hints = ir.get("annotation_hints", {})
|
||||
class_name = cls.get("class_name", "?")
|
||||
slot = evt.get("slot", 0)
|
||||
event_name_hint = evt.get("event_name_hint") or f"slot_0x{slot:02X}"
|
||||
body_start = evt.get("derived_body_start", 0)
|
||||
body_end = evt.get("derived_body_end", 0)
|
||||
raw_word = evt.get("raw_event_entry_word", 0)
|
||||
for anchor in hints.get("compiled_anchors", []):
|
||||
address = anchor.get("address", "")
|
||||
role = anchor.get("role", "")
|
||||
if not address:
|
||||
continue
|
||||
comment = (
|
||||
f"POC USECODE: {class_name} slot=0x{slot:02X} [{event_name_hint}]"
|
||||
f" body=0x{body_start:04X}..0x{body_end:04X}"
|
||||
f" raw_word=0x{raw_word:04X} | {role}"
|
||||
)
|
||||
pending.append((address, comment))
|
||||
|
||||
if args.dry_run:
|
||||
for address, comment in pending:
|
||||
print(f"DRY-RUN {address} {comment}")
|
||||
return _emit(
|
||||
args,
|
||||
{"annotated": 0, "skipped": len(pending), "files": len(args.ir_files)},
|
||||
f"dry-run: {len(pending)} annotations would be written from {len(args.ir_files)} file(s)",
|
||||
)
|
||||
|
||||
annotated = 0
|
||||
skipped = 0
|
||||
with open_program(config, read_only=False) as (project, program):
|
||||
with transaction(program, "USECODE annotation import"):
|
||||
for address, comment in pending:
|
||||
try:
|
||||
set_comment(program, address, comment, args.comment_type)
|
||||
annotated += 1
|
||||
except Exception as exc:
|
||||
print(f"SKIP {address}: {exc}")
|
||||
skipped += 1
|
||||
save_program(project, program)
|
||||
|
||||
return _emit(
|
||||
args,
|
||||
{"annotated": annotated, "skipped": skipped, "files": len(args.ir_files)},
|
||||
f"annotated {annotated} anchors ({skipped} skipped) from {len(args.ir_files)} file(s)",
|
||||
)
|
||||
|
||||
|
||||
def command_apply_plan(config: ProjectConfig, args: argparse.Namespace) -> int:
|
||||
plan = _load_plan(args.plan)
|
||||
if args.dry_run:
|
||||
|
|
@ -1037,6 +1132,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||
"get-function-xrefs": command_get_function_xrefs,
|
||||
"run-script": command_run_script,
|
||||
"apply-plan": command_apply_plan,
|
||||
"annotate-usecode": command_annotate_usecode,
|
||||
}
|
||||
return command_map[args.command](config, args)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue