1.2

rea function, end to end

One rea function call opens the file, binds Ghidra, imports a private copy, asks one question over a socket, and prints one Evidence record in about eight seconds.

You typed rea function fixtures/ledger-arm64-O1 _percent_change --provider ghidra --format json. About eight seconds later, 1881 lines of JSON arrived on stdout, and the exit status was 0. Somewhere in there is the answer to your question about before == 0, next to a decompiler artifact that the source never had.

When you finish this section, you can read every top-level field of that record and say which part of the system wrote it. You can also point at the one place where Ghidra's pseudocode and your C disagree.

The command

the recorded commandtextFull log
$ rea function fixtures/ledger-arm64-O1 _percent_change --provider ghidra --format json
[exit 0] (… s) -> 06-function-percent-change.json
The time is cut, because it changes with every recording and stays between 7 and 9 s, as the kit notes record.

function takes a path and a procedure, and it runs the MCP operation analyze_function with { procedure } as its parameters. --provider ghidra names the engine, and --format json and --filter-output are read at the executable boundary before the command runs. Without --format, the terminal output is TOON, and exit 0 means that the operation finished. The result can still hold partial evidence, warnings, or open questions. Figure 1.2 follows the call through the system.

Fig. 1.2one rea function run, step by stepsequence
One rea function run, step by step Six lifelines: you, the rea 6.7.0 one-shot CLI, the BinarySession, the GhidraClient, the headless Ghidra JVM behind bridge.sock, and the artifact ledger-arm64-O1. Twelve numbered rows: rea function _percent_change with --provider ghidra --format json, runDirectAnalysis for analyze_function with the procedure _percent_change, the session parses the Mach-O header and hashes the file in 64 KiB chunks, ghidra is bound with selection source request and profile digest 3fc72c9b4b78498f, the client launches java AnalyzeHeadless with -import, -readOnly and -deleteProject under a 330 second deadline, the file is copied as target-01b5d08b08fb.bin into a private runtime root, the ping handshake returns analysis_complete true after the run_id and token are checked, one JSON line asks analyze_function, the reply carries ok true with the procedure, the pseudocode and 6 instructions, the client returns Evidence ev_b492c85e with confidence observed and 10 limitations, shutdown deletes the project within 10 seconds, and the JSON reaches stdout with exit 0 after 7 to 9 seconds. you the reader rea 6.7.0 one-shot CLI session BinarySession client GhidraClient Ghidra JVM bridge.sock artifact ledger-arm64-O1 1 rea function _percent_change --provider ghidra --format json 2 runDirectAnalysis analyze_function { procedure: _percent_change } 3 parse header, SHA-256 mach-o magic, 64 KiB chunks 4 ghidra bound, selection request profile digest 3fc72c9b 5 java AnalyzeHeadless -import -readOnly -deleteProject, 330 s deadline 6 copy as target-01b5d08b08fb.bin private runtime root rea-ghidra-* 7 analysis_complete: true ping handshake, run_id and token checked 8 analyze_function one JSON line on bridge.sock 9 ok: true, result procedure, pseudocode, 6 instructions 10 Evidence ev_b492c85e confidence observed, 10 limitations 11 shutdown project deleted, 10 s deadline 12 JSON on stdout, exit 0 7 to 9 s on the capture Mac
One command opens the file, binds Ghidra, launches a headless JVM on a private copy, asks one question on a socket, and returns one Evidence record. Read top to bottom, one numbered row per step. Rows 1 and 12 come from the command record, and the names and the digest from the function capture. The file copy name comes from the overview capture. The deadlines come from the source at the tag.

From the command to the bridge

  1. The session parses the Mach-O header, reads the architecture, and hashes the file. The record's subject carries the result: mach-o, arm64, and the SHA-256 that 00-fixtures.txt lists for ledger-arm64-O1.
  2. The registry selects ghidra, because the command asked for it, and records selection_source: "request".
  3. The provider builds the analysis profile, a list of versioned contract names such as function_body_evidence: "complete-inclusive-ranges-v1", and hashes it. The digest 3fc72c9b… in the record is this hash.
  4. The client makes a private runtime root and allocates bridge.sock. It copies the file as target-<sha256[0:12]>.bin while hashing it again. Then it launches java with ghidra.Ghidra ghidra.app.util.headless.AnalyzeHeadless, -import, -readOnly, and -deleteProject. The copy is target-01b5d08b08fb.bin, and the overview record shows that name as its document (the capture). The startup deadline is 330,000 ms.
  5. The client sends ping. It accepts the session only when run_id, the token, the profile digest, the target SHA-256, and analysis_complete all match.
  6. The client writes one JSON line that asks for analyze_function. Ghidra runs the analysis and writes one JSON line back.
  7. rea adds the operation's limitation sentences to the answer and stores the result as one Evidence record.
  8. The client sends shutdown with a 10 s deadline, and Ghidra deletes the project.

The record, field by field

the head of the recordjsonFull log
{
  "evidence_id": "ev_b492c85e…",
  "subject": {
    "name": "ledger-arm64-O1",
    "digest": {
      "sha256": "01b5d08b…"
    },
    "local_path": "$CAPTURE/fixtures/ledger-arm64-O1",
    "format": "mach-o",
    "architecture": "arm64"
  },
  "provider": {
    "id": "ghidra",
    "name": "Ghidra",
    "version": "12.1.4"
  },
…
Cut after the provider. The analysis profile and the results follow in the next listings.

evidence_id hashes the semantic content and leaves out subject.name and subject.local_path, so the same bytes under another path give the same id. provider names the engine and its version. The next field binds the answer to the engine's settings:

the analysis profilejsonFull log
  "analysis_profile": {
    "provider": {
      "id": "ghidra",
      "name": "Ghidra",
      "version": "12.1.4"
    },
    "parameters": {
      "target_kind": "executable",
      "target_format": "mach-o",
      "architecture": "arm64",
      …
      "import_mode": "ephemeral-source-immutable",
      …
      "loader": "auto-from-header",
      …
    },
    "digest": "3fc72c9b…"
  },
  "predicate_type": "rea.analysis",
  "operation": "analyze_function",
  "parameters": {
    "procedure": "_percent_change"
  },
Cut to five of the 25 parameters and the digest.

Analysis profile: the provider's committed settings, hashed over RFC 8785 canonical JSON. Change one contract version and the digest changes, which is how rea refuses an old snapshot (section 5.3). predicate_type is the default tag rea.analysis, and operation is the MCP name. parameters is the caller's input, and it is part of the identity.

raw_result holds the provider-native dossier under value with its own empty limitations, and normalized_result holds the same dossier as the canonical result. confidence is observed and authority is shipped-artifact, the defaults for a static read of the file on disk. environment is null, because nothing ran.

limitations has ten strings: four that every Ghidra operation carries, five for analyze_function, and one for macOS about the native decompiler (the captures). locations and evidence_links are empty. relationship_coverage has the status complete, and its one diagnostic says that only resolved Ghidra call references are returned. Figure 1.3 lays the 16 fields out.

Fig. 1.3the 16 fields of one Evidence recordstructure
The 16 fields of one Evidence record The Evidence record for analyze_function on _percent_change drawn as a table of its 16 fields with the value each one holds in the capture and a note on the right. evidence_id is ev_ and 64 hex digits, a SHA-256 of the semantic content. subject is ledger-arm64-O1, mach-o, arm64 with the sha256 01b5d08b08fb and a local_path. provider is ghidra, Ghidra, 12.1.4. analysis_profile has 25 parameters and the digest 3fc72c9b. predicate_type is rea.analysis. operation is analyze_function. parameters is the procedure _percent_change. raw_result holds value and limitations. normalized_result is the dossier with procedure, pseudocode, assembly, and blocks. confidence is observed, authority is the value for a static read of the file on disk, environment is null, limitations has 10 strings, relationship_coverage is complete with one diagnostic on call references, and locations and evidence_links are empty. ONE EVIDENCE RECORD WHAT THE FIELD HOLDS Evidence ev_b492c85e for analyze_function evidence_id ev_ + 64 hex digits SHA-256 of the semantic content subject ledger-arm64-O1, arm64 mach-o, sha256 starts 01b5d08b provider ghidra, Ghidra, 12.1.4 the engine that answered analysis_profile digest 3fc72c9b 25 contract parameters, hashed predicate_type rea.analysis the default tag operation analyze_function the MCP name behind rea function parameters procedure _percent_change part of the identity raw_result value, limitations the provider-native dossier normalized_result the dossier procedure, pseudocode, assembly, blocks confidence observed or derived, or inferred authority the file on disk one of five authorities environment null nothing was run limitations 10 strings 4 session, 5 dossier, 1 macOS relationship_coverage complete one diagnostic on call references locations [] none for this operation evidence_links [] no parent records
Every field of the record is either identity, provenance, the answer, or a limit on the answer, and the id covers all of them except the display names. Read the field, its value in this record, and the note.

Inside normalized_result

Procedure: the function identity. _percent_change is at 0x100000500, its body is one range from 0x100000500 to 0x100000517 inclusive, 24 bytes, and contains_entry is true. The FunctionManager signature reads undefined _percent_change(), because Ghidra's symbol carries no types. The decompiler's view under native_api reads ulong _percent_change(ulong param_1,int param_2) with confidence: "low" on each parameter. Your source says int percent_change(int before, int after). Both are observations, as the limitation says: "Types and calling conventions are Ghidra decompiler observations, not source declarations."

the six instructionsjsonFull log
    "assembly": [
      "0x100000500: cbz w0, 0x100000514",
      "0x100000504: sub w8, w1, w0",
      "0x100000508: mov w9, #0x64",
      "0x10000050c: mul w8, w8, w9",
      "0x100000510: sdiv w0, w8, w0",
      "0x100000514: ret"
    ],
Nothing is cut from the assembly array.

Six instructions of four bytes each make the 24 bytes. cbz w0 is your if (before == 0) return 0;, with before in w0, and mov w9, #0x64 is the 100. The pseudocode tells a longer story:

the pseudocode, with its newlines expandedtextFull log
ulong _percent_change(ulong param_1,int param_2)
{
  uint uVar1;
  int iVar2;
  iVar2 = (int)param_1;
  if (iVar2 != 0) {
    uVar1 = 0;
    if (iVar2 != 0) {
      uVar1 = ((param_2 - iVar2) * 100) / iVar2;
    }
    param_1 = (ulong)uVar1;
  }
  return param_1;
}
The pseudocode field is one JSON string. This listing shows it line by line, and every line is a substring of that string.

The outer if is your guard. The inner if is not in your source. native_value_flow shows where it comes from. The operations 0x100000510#7, #8, #9, and #10 are a COPY of the constant 0, an INT_NOTEQUAL, a CBRANCH, and an INT_SDIV. All four carry the address of the sdiv instruction. Ghidra's model of sdiv gives 0 when the divisor is 0, and the decompiler printed that model as a second check.

Both checks are correct, and one is redundant, and only the source tells you which. Section 4.6 and the lifting lesson go deeper.

References: the dossier lists two incoming references. One is an UNCONDITIONAL_CALL from 0x1000006a0 inside the function Ghidra names entry at 0x1000005d0, 368 bytes long. The other is a DATA reference from 0x100008118 with no owning procedure, inside the __LINKEDIT segment that the overview lists from 0x100008000 (the capture). The one outgoing reference is the CONDITIONAL_JUMP of cbz. callers names entry, and callees is empty, because the function calls nothing. unresolved_calls is empty, and facet_availability marks all eight facets of the dossier as available.

Basic blocks: three, at 0x100000500, 0x100000504, and 0x100000514, with exclusive end values. The body range uses inclusive ends, as its contract name complete-inclusive-ranges-v1 says. Keep the two conventions apart when you compare addresses.

native_value_flow holds 13 p-code operations and 13 def-use edges with truncated: false. Every operation carries is_dead: true, and the limitation explains that the flag mirrors the Java AST and that block_membership reports the real membership. Read the limitations before the numbers, every time.

rea (rea-agents) with the Ghidra provider rea-agents 6.7.0, Ghidra 12.1.4 · 27dc42d · 2026-10-18 · Edition 2026.10