Thomas Piacentini
← Atelier

Chapitre

rea function, end to end

Texte original · anglais

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 source
$ 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.
Original source
title: the recorded command
source: capture/out/00-commands.txt
lang: text
note: The time is cut, because it changes with every recording and stays between 7 and 9 s, as [the kit notes](manuals/reverse-engineering-101/capture/README.md) record.
---
$ rea function fixtures/ledger-arm64-O1 _percent_change --provider ghidra --format json
[exit 0] (… s) -> 06-function-percent-change.json

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 {{cli Output and exit status}}. Figure follows the call through the system.

Fig. 1.2one rea function run, step by stepsequence
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.
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.

Original SVG

Original source
id: fig-1-2
kind: sequence
title: one rea function run, step by step
claim: 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.
caption: 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 source
{
  "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.
Original source
title: the head of the record
source: capture/out/06-function-percent-change.json
lang: json
note: Cut after the provider. The analysis profile and the results follow in the next listings.
---
{
  "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"
  },
…

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 source
  "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.
Original source
title: the analysis profile
source: capture/out/06-function-percent-change.json
lang: json
note: Cut to five of the 25 parameters and the digest.
---
  "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"
  },

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 lays the 16 fields out.

Fig. 1.3the 16 fields of one Evidence recordstructure
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.
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. From capture/out/06-function-percent-change.json, with the field list from src/domain/evidence.ts at the tag.

Original SVG

Original source
id: fig-1-3
kind: structure
title: the 16 fields of one Evidence record
claim: 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.
caption: Read the field, its value in this record, and the note. From capture/out/06-function-percent-change.json, with the field list from src/domain/evidence.ts at the tag.

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 source
    "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.
Original source
title: the six instructions
source: capture/out/06-function-percent-change.json
lang: json
note: Nothing is cut from the assembly array.
---
    "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"
    ],

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 source
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.
Original source
title: the pseudocode, with its newlines expanded
source: capture/out/06-function-percent-change.json
lang: text
note: The pseudocode field is one JSON string. This listing shows it line by line, and every line is a substring of that string.
---
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 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.

Original source
- Run `rea function PATH NAME --provider ghidra --format json` and save the whole record.
- Check `subject.digest.sha256` against your own `shasum -a 256` before you trust anything else.
- Read `confidence`, `authority`, and `limitations` before `normalized_result`.
- Compare the pseudocode with the assembly, and expect decompiler checks that the source never had.

Sources: cli Output and exit status (rea docs/cli.md); rea at 27dc42d: src/cli/coreBinaryCommands.ts, src/cliCommandNames.ts, src/cliOutput.ts, src/domain/binaryTarget.ts, src/application/BinaryTargetResolver.ts, src/application/binary/AnalysisProviderRegistry.ts, src/ghidra/GhidraAnalysisProfile.ts, src/ghidra/GhidraClient.ts, src/ghidra/GhidraTargetSnapshot.ts, src/ghidra/GhidraJavaLaunch.ts, src/ghidra/GhidraLauncher.ts, src/config/providerStartupTimeout.ts, src/ghidra/GhidraClientStartup.ts, src/ghidra/GhidraSessionValues.ts, src/ghidra/GhidraClientWire.ts, bridge/ghidra/ReaGhidraBridge.java, src/ghidra/GhidraProviderClient.ts, src/server/registerEvidenceTools.ts, src/domain/evidence.ts, src/domain/analysisProfile.ts, src/domain/relationshipCoverage.ts; capture/README.md; capture/fixtures/ledger.c; capture/out/00-commands.txt, 00-fixtures.txt, 02-capabilities.json, 05-analyze-arm64-O1.json, 06-function-percent-change.json

Code et livrables 1
Ce que vous en retenez

Enregistré dans votre carnet sur cet appareil.

Sources et limites

Les repères de lecture sont déclaratifs. Les résultats de tests sont enregistrés séparément ; ils valent pour les cas exécutés.

AI Engineering from Scratch · 3f9715c · MIT

Télécharger ce fichier ↓