[PR #1635] [MERGED] feat(ghidra): admit set_address_name and set_addresses_names #4714

Closed
opened 2026-10-31 00:54:15 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/1635
Author: @giri256
Created: 10/10/2026
Status: ✅ Merged
Merged: 10/10/2026
Merged by: @morluto

Base: main ← Head: feat/ghidra-address-naming


📝 Commits (2)

  • c319044 feat(ghidra): admit set_address_name and set_addresses_names
  • acf9009 fix(ghidra): normalize qualified symbol names and reject empty leaves

📊 Changes

13 files changed (+351 additions, -24 deletions)

View changed files

📝 .agents/skills/reverse-engineer-anything/references/native-and-artifacts.md (+1 -1)
📝 bridge/ghidra/ReaGhidraBridge.java (+81 -10)
📝 docs/installation.md (+4 -2)
📝 docs/native-investigation.md (+6 -0)
📝 scripts/lib/real-ghidra-namespace-annotations.mjs (+108 -0)
📝 src/ghidra/GhidraInventoryValues.ts (+27 -1)
📝 src/ghidra/GhidraProviderCapabilities.ts (+11 -2)
📝 src/ghidra/GhidraSessionValues.test.ts (+22 -1)
📝 src/ghidra/GhidraSessionValues.ts (+11 -2)
📝 tests/boundary/mcp/ghidraEvidenceMcp.test.ts (+60 -0)
📝 tests/boundary/providers/ghidra/ghidraClient.test.ts (+5 -2)
📝 tests/conformance/ghidra/namespaces.c (+7 -2)
📝 tests/fixtures/fakeGhidra.mjs (+8 -1)

📄 Description

Summary

Ghidra sessions now admit set_address_name and set_addresses_names. Before this change both failed with capability_unavailable: operation is not declared by this provider, so agents had to rename through annotate_native_function, which returns a full function dossier per call and cannot name data addresses.

Fixes #1634

Problem and expected behavior

  • Trigger: open_binary on any x86/x64 PE with Ghidra, then set_address_name {address, name}. The result is capability_unavailable.
  • Expected: the same contract Hopper offers. set_address_name returns a boolean; set_addresses_names returns a record of per-address booleans. Function entries and data addresses are both nameable, and the names show up in later pseudocode, address_name and list_names.

Change and scope

  • Bridge (ReaGhidraBridge.java): applyAddressName runs one transaction per address.
    • At a local function entry it renames the function. It reuses annotationLeafName, so qualified names behave as in annotate_native_function.
    • At any other mapped address it renames the existing non-dynamic primary symbol, or creates a USER_DEFINED label and makes it primary.
    • It verifies by primary-symbol readback. An invalid or duplicate name, or a readback mismatch, returns false and rolls back only that address. Unmapped addresses fail the single operation with invalid_request and are false inside a batch.
  • Adapter:
    • Both operations join GHIDRA_INVENTORY_OPERATIONS, with strict input schemas (a batch needs at least one entry) and boolean result schemas.
    • The new GHIDRA_MUTATING_OPERATIONS (annotate_native_function plus the naming operations) is the single source for:
      • read-only handshake filtering;
      • mutatesArtifact;
      • opting out of snapshot replay.
    • Windows P0 stays read-only.
  • Not included: set_comment / set_inline_comment / bookmarks for Ghidra, and persistence beyond the ephemeral session.

Contract and boundary impact

  • Semantic owner and earliest changed stage: the Ghidra adapter (src/ghidra/*Values.ts, GhidraProviderCapabilities.ts) and the Java bridge.
  • CLI and MCP/tool-catalog contract: no new tools. Existing tool contracts and output schemas are reused unchanged; the Ghidra provider now declares two more operations.
  • Provider, bridge, target-format, or platform compatibility:
    • The Linux/macOS unix-socket bridge advertises two more capabilities, and the handshake requires them.
    • Windows P0 excludes them, like annotate_native_function.
    • Hopper is unchanged.
  • Evidence, artifact, provenance, or reconstruction contract: unchanged. Edits are session metadata; source bytes are never written.
  • Process execution, authorization, cleanup, or containment impact: none beyond the existing mutation authority.
  • Generated metadata, package, or installation impact: none committed. Authored docs and the skill reference are updated.

Evidence and regression coverage

  • Tests added or updated:
    • GhidraSessionValues.test.ts: naming is treated as mutation, and a missing naming capability fails the handshake.
    • ghidraEvidenceMcp.test.ts: MCP routing and exact provider inputs; compact boolean and record results; an empty batch is rejected before the provider; malformed bridge output gives unreadable_output.
    • The fake bridge fixture and the ghidraClient handshake now include the new capabilities.
  • Base reproduction: on main, set_address_name against Ghidra returns capability_unavailable (operation is not declared by this provider).
  • User-visible MCP output: real Ghidra 12.1.4, Win32 PE (1996 game executable):
    set_address_name {address:"0x409ad0", name:"melee_blow_landed"} -> true
    set_addresses_names {"0x4052c0":"bike_take_hit","0x4642d8":"g_player_bike","0x4b8a12":"g_player_level","0x409ad1":"bad name with spaces"}
      -> {"0x4052c0":true,"0x4642d8":true,"0x4b8a12":true,"0x409ad1":false}
    address_name {address:"0x4642d8"} -> "g_player_bike"
    procedure_pseudo_code 0x4096f0 -> now calls melee_blow_landed(...) and reads g_player_bike / g_player_level
    
  • Remaining proof gaps: no macOS run; no Windows (by design read-only).

Validation performed

All on the rebased commit (base be44b50), Node 26.10, Arch Linux x64:

  • npx tsc --noEmit -p tsconfig.json: pass
  • npx vitest run src/ghidra: 243 passed
  • npm run test:focused -- tests/boundary/mcp/ghidraEvidenceMcp.test.ts tests/boundary/providers/ghidra/{ghidraClient,ghidraTransport,ghidraTargetSnapshot}.test.ts: 4 files, 38 passed
  • npm run check:fast: pass (pre-commit oxfmt/oxlint also pass)
  • Manual MCP stdio run against real Ghidra 12.1.4: output shown above.
  • npm run verify:ghidra (real provider) did not pass locally, and the failure does not look related to this change:
    • With the patch, the entry-aliases scenario failed in close_binary with cleanup_incomplete: owned launcher command identity did not match (observed=[java]; expected=.../bin/java). This looks like a shutdown race where the JVM is already defunct when the ownership check reads it.
    • The unpatched base commit timed out locally after 25 minutes without finishing.
    • Another Ghidra session was running on the machine at the same time, so I'd rely on CI for this suite.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. A custom bridge that pins the old capability list would fail the exact handshake, as with any capability addition.
  • Real Hopper/Ghidra, browser, or OS coverage: Ghidra 12.1.4 with Temurin JDK 21 on Arch Linux x64. No Hopper or macOS.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: only session-database metadata is written; source bytes are untouched; Windows P0 is unaffected.

Review checklist

  • This PR addresses a concrete problem or an agreed enhancement.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/1635 **Author:** [@giri256](https://github.com/giri256) **Created:** 10/10/2026 **Status:** ✅ Merged **Merged:** 10/10/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `feat/ghidra-address-naming` --- ### 📝 Commits (2) - [`c319044`](https://github.com/morluto/rea/commit/c31904431b5b6fa60b339d653a0dacbca280c461) feat(ghidra): admit set_address_name and set_addresses_names - [`acf9009`](https://github.com/morluto/rea/commit/acf900902da842546be56400efd5feb77715231c) fix(ghidra): normalize qualified symbol names and reject empty leaves ### 📊 Changes **13 files changed** (+351 additions, -24 deletions) <details> <summary>View changed files</summary> 📝 `.agents/skills/reverse-engineer-anything/references/native-and-artifacts.md` (+1 -1) 📝 `bridge/ghidra/ReaGhidraBridge.java` (+81 -10) 📝 `docs/installation.md` (+4 -2) 📝 `docs/native-investigation.md` (+6 -0) 📝 `scripts/lib/real-ghidra-namespace-annotations.mjs` (+108 -0) 📝 `src/ghidra/GhidraInventoryValues.ts` (+27 -1) 📝 `src/ghidra/GhidraProviderCapabilities.ts` (+11 -2) 📝 `src/ghidra/GhidraSessionValues.test.ts` (+22 -1) 📝 `src/ghidra/GhidraSessionValues.ts` (+11 -2) 📝 `tests/boundary/mcp/ghidraEvidenceMcp.test.ts` (+60 -0) 📝 `tests/boundary/providers/ghidra/ghidraClient.test.ts` (+5 -2) 📝 `tests/conformance/ghidra/namespaces.c` (+7 -2) 📝 `tests/fixtures/fakeGhidra.mjs` (+8 -1) </details> ### 📄 Description ## Summary Ghidra sessions now admit `set_address_name` and `set_addresses_names`. Before this change both failed with `capability_unavailable: operation is not declared by this provider`, so agents had to rename through `annotate_native_function`, which returns a full function dossier per call and cannot name data addresses. Fixes #1634 ## Problem and expected behavior - **Trigger:** `open_binary` on any x86/x64 PE with Ghidra, then `set_address_name {address, name}`. The result is `capability_unavailable`. - **Expected:** the same contract Hopper offers. `set_address_name` returns a boolean; `set_addresses_names` returns a record of per-address booleans. Function entries and data addresses are both nameable, and the names show up in later pseudocode, `address_name` and `list_names`. ## Change and scope - **Bridge (`ReaGhidraBridge.java`):** `applyAddressName` runs one transaction per address. - At a local function entry it renames the function. It reuses `annotationLeafName`, so qualified names behave as in `annotate_native_function`. - At any other mapped address it renames the existing non-dynamic primary symbol, or creates a USER_DEFINED label and makes it primary. - It verifies by primary-symbol readback. An invalid or duplicate name, or a readback mismatch, returns `false` and rolls back only that address. Unmapped addresses fail the single operation with `invalid_request` and are `false` inside a batch. - **Adapter:** - Both operations join `GHIDRA_INVENTORY_OPERATIONS`, with strict input schemas (a batch needs at least one entry) and boolean result schemas. - The new `GHIDRA_MUTATING_OPERATIONS` (`annotate_native_function` plus the naming operations) is the single source for: - read-only handshake filtering; - `mutatesArtifact`; - opting out of snapshot replay. - Windows P0 stays read-only. - **Not included:** `set_comment` / `set_inline_comment` / bookmarks for Ghidra, and persistence beyond the ephemeral session. ## Contract and boundary impact - Semantic owner and earliest changed stage: the Ghidra adapter (`src/ghidra/*Values.ts`, `GhidraProviderCapabilities.ts`) and the Java bridge. - CLI and MCP/tool-catalog contract: no new tools. Existing tool contracts and output schemas are reused unchanged; the Ghidra provider now declares two more operations. - Provider, bridge, target-format, or platform compatibility: - The Linux/macOS unix-socket bridge advertises two more capabilities, and the handshake requires them. - Windows P0 excludes them, like `annotate_native_function`. - Hopper is unchanged. - Evidence, artifact, provenance, or reconstruction contract: unchanged. Edits are session metadata; source bytes are never written. - Process execution, authorization, cleanup, or containment impact: none beyond the existing mutation authority. - Generated metadata, package, or installation impact: none committed. Authored docs and the skill reference are updated. ## Evidence and regression coverage - **Tests added or updated:** - `GhidraSessionValues.test.ts`: naming is treated as mutation, and a missing naming capability fails the handshake. - `ghidraEvidenceMcp.test.ts`: MCP routing and exact provider inputs; compact boolean and record results; an empty batch is rejected before the provider; malformed bridge output gives `unreadable_output`. - The fake bridge fixture and the `ghidraClient` handshake now include the new capabilities. - **Base reproduction:** on `main`, `set_address_name` against Ghidra returns `capability_unavailable` (`operation is not declared by this provider`). - **User-visible MCP output:** real Ghidra 12.1.4, Win32 PE (1996 game executable): ``` set_address_name {address:"0x409ad0", name:"melee_blow_landed"} -> true set_addresses_names {"0x4052c0":"bike_take_hit","0x4642d8":"g_player_bike","0x4b8a12":"g_player_level","0x409ad1":"bad name with spaces"} -> {"0x4052c0":true,"0x4642d8":true,"0x4b8a12":true,"0x409ad1":false} address_name {address:"0x4642d8"} -> "g_player_bike" procedure_pseudo_code 0x4096f0 -> now calls melee_blow_landed(...) and reads g_player_bike / g_player_level ``` - **Remaining proof gaps:** no macOS run; no Windows (by design read-only). ## Validation performed All on the rebased commit (base `be44b50`), Node 26.10, Arch Linux x64: - `npx tsc --noEmit -p tsconfig.json`: pass - `npx vitest run src/ghidra`: 243 passed - `npm run test:focused -- tests/boundary/mcp/ghidraEvidenceMcp.test.ts tests/boundary/providers/ghidra/{ghidraClient,ghidraTransport,ghidraTargetSnapshot}.test.ts`: 4 files, 38 passed - `npm run check:fast`: pass (pre-commit oxfmt/oxlint also pass) - Manual MCP stdio run against real Ghidra 12.1.4: output shown above. - `npm run verify:ghidra` (real provider) did **not** pass locally, and the failure does not look related to this change: - With the patch, the `entry-aliases` scenario failed in `close_binary` with `cleanup_incomplete`: `owned launcher command identity did not match (observed=[java]; expected=.../bin/java)`. This looks like a shutdown race where the JVM is already defunct when the ownership check reads it. - The unpatched base commit timed out locally after 25 minutes without finishing. - Another Ghidra session was running on the machine at the same time, so I'd rely on CI for this suite. ## Compatibility, safety, and release - **Breaking changes or migration steps:** none. A custom bridge that pins the old capability list would fail the exact handshake, as with any capability addition. - **Real Hopper/Ghidra, browser, or OS coverage:** Ghidra 12.1.4 with Temurin JDK 21 on Arch Linux x64. No Hopper or macOS. - **Package or release metadata impact:** none. - **Security, privacy, process, or containment review:** only session-database metadata is written; source bytes are untouched; Windows P0 is unaffected. ## Review checklist - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 00:54:15 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#4714
No description provided.