rea/docs/ghidra-provider.md

20 KiB

Ghidra provider semantics

REA imports one target into an ephemeral Ghidra database. Linux/macOS support read-only analysis and atomic function-name/entry-comment edits; Windows P0 is read-only. GUI controls and persistent database mutation are unavailable. Executable bytes remain unchanged.

Use installation for supported hosts, prerequisites, configuration, startup deadlines, resource controls, and cleanup recovery. Windows P0 has its own admission and lifecycle boundary; NativeAOT observations include a built-in read-only PE snapshot parser and a separate optional annotation extension. Check the release boundary when using npm.

See import language and analysis seeds to override the header-detected language or seed analysis.

Provider selection and profile identity follow MCP contracts. One deep provider stays bound to the target; runtime failure never selects another provider automatically. The contracts below describe Ghidra observations, not equivalence with another engine's pseudocode or analysis database.

Admitted inventory semantics

Concern Ghidra contract
Program identity One analyzeHeadless import produces exactly one Program; list_documents therefore returns exactly one name.
Addresses Default memory uses lowercase 0x hexadecimal. Non-default and external spaces use <percent-encoded-space>:0x<hex> and remain round-trippable. The handshake commits image base and default address-space name.
Symbols list_names includes address-bearing memory and external symbols, including dynamic symbols, while excluding variable and no-address namespace records. Each item reports primary, dynamic, external, symbol type, and source facts.
Procedures Both non-external and external functions are listed. A local thunk remains distinct from its resolved target; exact and qualified name lookup fails on ambiguity rather than guessing.
Strings Only Ghidra-defined string Data is observed. Items report charset, byte length, and whether a required null terminator is missing. The API cannot distinguish a present terminator from fixed/Pascal layouts when no terminator is missing, so that state is named present_or_not_required.
Memory Memory-block end addresses are exclusive. Read/write/execute, initialization, overlay, address space, and image base are direct Ghidra observations.
Inventory Procedure, symbol, and string listings plus searches return the complete matching collection in one response; callers do not provide offsets or result-count limits.
Search Literal and Java-regex searches return every match in the immutable provider-defined inventory; string search does not cover every text sequence in artifact bytes. Search waits for a reply or caller cancellation; there are no caller-supplied offsets or result-count limits.
Analysis state The socket is exposed only after default auto-analysis completes. Established operations wait for their reply or caller cancellation; there is no fixed response-size ceiling.

String-search completeness applies only to Ghidra-defined string Data. Packed or undefined literals, including Rust strings, may be absent; an empty result does not prove text is absent. Both list and search Evidence carry this limitation. When an address is known, use read_bytes and xrefs to inspect bytes and references.

PE debug-symbol coverage

The post-analysis handshake reports Ghidra's PDB Program Information attributes: the referenced file, GUID, hexadecimal age, legacy signature, version, and PDB Loaded state. Absent attributes remain null. PE Evidence carries a limitation when a PDB reference is present but Ghidra reports it as not loaded, or its load state is unknown. A bridge without this report leaves PDB coverage unknown. A reported loaded state is a Ghidra observation, not an independent PDB identity or complete symbol-coverage attestation, and retains an inline coverage caveat. Handshake PDB strings share a 4096 UTF-16-unit serialization budget (at most 24 KiB after JSON escaping); whole attributes that do not fit are omitted with an explicit resource limitation, rather than truncated.

Original names and types may be missing even when auto-analysis completes. An empty name search does not establish that a function is absent; use list_procedures and address-based analysis to inspect recovered functions. REA imports a private executable copy and does not admit a caller-selected PDB companion. This reporting does not search symbol servers, copy a sibling PDB, or change analyzer settings.

Loaded addresses and file offsets

rea analyze <path> --provider ghidra --json and MCP binary_overview report the observed loaded image_base in normalized_result. They retain each segment's reported image_base and address_space. A provider that does not report a base, or whose segments disagree, omits the overview field, following the existing contract on main. REA does not substitute zero or the first segment's address. All reported bases and address spaces remain visible in the complete segment inventory, including conflicting observations.

function and most MCP procedure selectors use procedure names or provider analysis addresses. Ghidra decompile / MCP procedure_pseudo_code also accept source-file byte offsets. Existing symbols and analysis addresses take precedence; a 0x-prefixed number that resolves to no procedure falls back to its observed source mapping. Use --file-offset=0x... on the CLI or procedure: "file-offset:0x..." through MCP to select file coordinates explicitly. Multiple mapping candidates fail as ambiguous rather than selecting a bank or address space. A successful conversion retains the file offset, mapped address and procedure entry in raw Evidence and locations.

If an offset maps to loaded target bytes but no procedure starts at or contains the mapped address, the failure preserves both the original file offset and analysis address. This differs from an unmapped offset and does not recommend retrying the same offset syntax. If the function resolves but the backend fails or cancels decompilation, the failure retains the selector, mapped address and procedure entry without changing the backend failure code.

Both mapping directions require a complete original FileBytes source whose SHA-256 matches the admitted target. Auxiliary files and partial source objects remain visible in the load-image inventory but do not establish offsets in the original target. Ghidra's object-local offsets are not silently treated as original-file coordinates. Original-source identity is checked once per source object per session; analysis-memory edits do not change the original bytes.

ELF virtual addresses and file offsets are different coordinates. Ghidra can rebase a PIE ELF during import: in the source-owned verification fixture, ELF entry 0x1013 becomes Ghidra procedure 0x101013 with image base 0x100000. That example is not a general conversion rule.

Use list_procedures to select an observed procedure address. To relate file bytes to loaded memory, call MCP inspect_native_load_image or:

rea search <path> . --kind procedures --mode regex --provider ghidra --json
rea inspect-native-load-image <path> --provider ghidra --json
rea decompile <path> <observed-procedure-address> --provider ghidra --json
rea decompile <path> --file-offset=0x1013 --provider ghidra --json
rea address-to-file-offset <path> <observed-procedure-address> --provider ghidra --json

The load-image result preserves source-file indexes, file offsets, address spaces, and inclusive mapping ranges. Follow the mapping for the selected source file and region; adding image_base to an arbitrary file offset is not valid. For a linear byte-addressed, file-backed mapping, a file offset F in [mapping.file_offset, mapping.file_offset + mapping.length) maps to mapping.start + (F - mapping.file_offset) in the reported address_space. If several mappings cover the same file offset, keep those candidates distinct and select the procedure from the observed inventory. Uninitialized regions may have no file backing. ELF mappings are observed without independent format-specific load-image verification, as the result's status and limitations state.

An unsuccessful numeric procedure lookup reports the current Ghidra image base and gives the CLI commands above alongside the corresponding MCP operations. Repeat any target-format, architecture, and entry-point options used to open the target. Other procedure operations keep their analysis-address semantics; file-offset fallback applies only to decompilation.

Overview workflow revision 3 binds this output to snapshot identity. Older serialized overview Evidence remains readable; it is not replayed as a current overview merely because its source artifact is unchanged.

Admitted function-analysis semantics

Concern Ghidra contract
Decompiler lifetime One persistent DecompInterface is opened for the imported Program and disposed during bridge shutdown. Native decompilation has no fixed per-function deadline. External functions or functions without bodies return null; cancellation and native failure remain distinct.
Serialization A FIFO sends one Program request at a time. Caller cancellation removes queued work promptly and is passed to active socket waits. This is an adapter safety commitment, not a claim that every Ghidra API is thread-safe.
Function identity Every function result carries the entry address and Ghidra FunctionManager classification for external, thunk, and resolved thunk target. These are observations, not proof that unresolved targetless calls have been recovered.
Assembly and pseudocode Assembly is complete Ghidra Listing text; pseudocode is Ghidra decompiler output. Neither is original source, and cross-provider comparison never treats Hopper and Ghidra text as equal or unequal semantic facts.
Instruction fast path read_function_instructions returns every raw Listing instruction for the requested function without invoking the decompiler or whole-program name/string inventories.
Calls and references Callers/callees contain only resolved functions. Reference edges preserve exact ReferenceManager type and call/jump/data/read/write/indirect/computed/conditional/terminal/external facts. Targetless computed flow remains unknown. Synthetic entry-point references without actionable memory sources are omitted explicitly.
CFG Dossiers use BasicBlockModel and retain only non-call successors inside the function body. CFG topology is address-normalized for comparison; provider-specific block construction remains a declared difference.
P-code value flow Function dossiers include up to 3,000 high-p-code operations, at most 64 inputs per operation, and 12,000 def-use edges. Results identify memory reads/writes, branches, and calls by p-code opcode, and mark decompiler-dead operations. Large results report truncation, an omitted-operation lower bound, and known omitted-input/edge counts. The dossier is an intra-function graph; trace_native_values composes derived parameter/argument and return/output dependencies across resolved calls. It does not resolve memory aliasing, call side effects, runtime behavior, persistent state, or business meaning.
Result extent Function instruction scans and native API boundary observations remain complete. P-code flow is the explicit bounded exception; its count fields distinguish exact retained counts from lower-bound or known-omitted counts. Caller cancellation and provider failures remain distinct.

Function extents and instruction completeness refer to Ghidra's analyzed body. An inferred no-return call can terminate that body before subsequent bytes. Pseudocode, assembly, procedure info, instruction windows, callee and caller reads, and dossiers report observed terminal call sites in Evidence limitations, including resolved callee names, addresses, and FunctionManager flags when available. Instruction windows and dossiers also retain these warnings in their result limitations. An unresolved callee or unestablished terminal-flow basis remains explicit. These observations qualify the analysis; they do not prove that a no-return flag is incorrect or recover excluded code. Analysis profiles version this observation contract so older snapshots cannot replay unqualified results.

On non-Windows hosts, the ephemeral import repair clears no-return flags on the known returning external ABI names malloc, __tls_get_addr, memcpy, memset, strlen, and __errno_location. This relies on their expected ABI behavior; external implementations are not inspected. The repair re-disassembles affected terminal calls and refits their owning functions, preserving independent function entries. If a recovered caller reveals a decoded return, its propagated no-return flag and affected callers are repaired in the same pass. Unknown imports and genuine no-return imports keep their flags. Analysis profiles version this repair so snapshots produced before it are not reused. Windows P0 does not run it.

npm run verify:ghidra compiles the versioned C oracle into debug and stripped host-native targets (x86-64/AArch64 ELF on Linux or Mach-O on macOS), plus a native DWARF 4 type-layout object. It proves the admitted operations, external functions, resolved thunks, exports, stripped-name behavior, direct and targetless indirect calls, typed references, strings/xrefs, multi-block CFG, bounded p-code def-use/effect links, semantic enhanced workflows, cancellation, startup deadlines, serialized concurrency, malformed-target rejection, profile identity, and process/project cleanup against real Ghidra 12.1.4. Unit fixtures separately cover startup deadlines, process exit, queued cancellation, and malformed wire output.

npm run verify:ghidra:cross-format adds AArch64 ELF, x86-64 PE, and x86-64 Mach-O target coverage. It requires clang, LLD, and lld-link; set REA_CLANG, REA_LLD, or REA_LLD_LINK to select them. The verifier checks these tools before compilation. Keeping this matrix separate lets Linux host/provider acceptance run with only the host compiler.

npm run verify:ghidra:windows checks source-owned native x86-64 PE applications; -- --x86 selects x86 and -- --dll selects DLLs, and the flags can be combined. These lanes check all 25 read-only operations, target/snapshot/import SHA-256 linkage, and project, endpoint, process, and runtime cleanup. The independent native lane checks DACLs, handle admission, cancellation, and Job Object lifecycle. npm run verify:ghidra:windows:package packs and installs REA into an isolated prefix, then checks ordinary-user CLI/MCP operations against canonical contracts. It accepts the same architecture and DLL flags and verifies the selected PE role in the analysis profile. All lanes require matching native controls; package startup alone establishes only package compatibility.