Files
reverse-engineering-anything/docs/providers/jeb-analysis.md
T
morluto 607a7872bb refactor(jeb): classify JEB failures through the shared provider classifier (#10831)
* fix(pcap): report a refused tool launch as an unavailable tool

When a capture read's tool launch was refused with EPERM, pcap reported it
as unreadable tool output (AnalysisProtocolError) instead of an unavailable
tool. Pcap also never told "not executable" apart from "missing".

Add classifyLaunchFailure in src/process as the one owner of which spawn
errnos mean missing (ENOENT, ENOTDIR) and which mean present but not
executable (EACCES, EPERM, ENOEXEC, EISDIR). Pcap, apktool and gdb used three
different errno lists; all three now use it.

* refactor(process): classify owned command outcomes through one owner

Add ProviderOutcome, read once from the supervisor's facts, and classifyProviderOutcome, the one mapping from an outcome to a typed error. Migrate pcap and the shared artifact producer command failure (TShark, sigrok). Add the rea/provider-failure-classifier lint rule with a shrink-only baseline of per-site (cause: unknown) => AnalysisError functions.

* refactor(process): classify Java and adb launch refusals with the shared owner (#10598)

The Ghidra and JADX Java probes and adb's version probe now take
missing-versus-not-executable from classifyLaunchFailure instead of their
own errno checks. ELOOP and ENAMETOOLONG join the owner's missing set, so
adb keeps its existing classification and both Java probes now treat a
non-directory path component as a missing runtime.

* fix(pcap): preserve command launch attribution

* test(pcap): expect an unknown failing stage when a limited capture tool exits unsuccessfully

* refactor(process): classify owned command outcomes through one owner

Add ProviderOutcome, read once from the supervisor's facts, and classifyProviderOutcome, the one mapping from an outcome to a typed error. Migrate pcap and the shared artifact producer command failure (TShark, sigrok). Add the rea/provider-failure-classifier lint rule with a shrink-only baseline of per-site (cause: unknown) => AnalysisError functions.

* test(lint): cover baselined files in the provider-failure classifier rule

* fix(apktool): report the decoder JVM's heap exhaustion as memory, not a target refusal

The supervisor now watches for a managed runtime's out-of-memory report while output streams (JVM java.lang.OutOfMemoryError, V8 'JavaScript heap out of memory'), so a retention budget cannot hide it. ProviderOutcome gains a runtime-exhausted kind and classifyProviderOutcome maps it once. Apktool decode opts in and moves to the shared classifier; Ghidra's single-purpose output token becomes the JVM runtime option and its area matching reuses the shared table.

* fix(process): keep the runtime's out-of-memory report on the memory error it supports

* fix(pcap): attribute a limited capture's exit through the prlimit wrapper

* fix(process): keep a shortened out-of-memory line marked as shortened

* fix(process): identify the refused executable for every launcher shape

Name the executable on errnos Node throws synchronously from spawn
(ENOEXEC, ENOTDIR, ...) and read the Windows native launcher's
executable-launch constraint, so pcap attributes those refusals to the
selected tool or limiter instead of an unknown stage.

* test(pcap): prove synchronous launch naming with ENOTDIR on every POSIX host

* fix(apktool): advise the JVM limit that governs the reported memory area

* fix(ghidra): read the supervisor's parsed OOM area instead of reparsing lines

* fix(sqlite,capture): read V8 heap exhaustion from streamed output, not retained stderr (#10724)

The SQLite worker and the Node capture decoders now declare runtime: "v8", so the supervisor observes V8's heap report as output streams. Both classifiers read the runtime-exhausted outcome instead of matching retained stderr, so a report beyond the retention budget, or one whose process then overflowed its output budget, is still reported as memory exhaustion.

* refactor(evm): classify EVMole worker outcomes through the shared provider classifier (#10716)

* refactor(evm): classify EVMole worker outcomes through the shared provider classifier

The EVMole worker's cancellation, deadline, output budget and CPU/file-size signals now go through classifyProviderOutcome; only the prlimit launch refusal and the reserved EFBIG exit remain in its dialect. The worker is opted into V8 exhaustion scanning so a heap report names its fixed 256 MiB heap. providerOutcomeOf now reads a launch refusal only from a spawn errno, so a private-storage or carrier read errno is no longer reported as a limiter launch failure.

* fix(evm): keep a stream failure's exit status and update Linux worker expectations

* refactor(process): read launch refusals through the launcher identity owner

* refactor(wasm): classify WABT command outcomes through the shared provider classifier (#10727)

* refactor(wasm): classify WABT command outcomes through the shared provider classifier

WABT's cancellation, command deadline, output budget, and inherited-limit signals now go through classifyProviderOutcome; its dialect keeps only the configuration-probe and launch-refusal unavailability and the unclassified-exit output error. A tool ended by SIGXCPU/SIGXFSZ or the memory limit is now a resource constraint instead of an output error, and a refused launch in the producer phase is reported as unavailable. The shared timeout context gains the analysis setting that selected the deadline, and the output-limit context may name its exceeded budget.

* fix(wasm): keep setup guidance when a WABT version probe's output cannot be collected

* refactor(providers): classify raw provider failures through one owner

* refactor(process): build classified cancellations with cancelledBy and the carried caller reason

* chore(lint): count runOwned's close-failure mapper from main in the classifier baseline

* fix(process): carry the runtime's exhaustion report on every memory error that claims it

SQLite and historical-capture memory errors recognised a streamed V8 heap report but dropped it, so a report outside retained output reached callers without its source line or omission flags. One projection, runtimeExhaustionReport, now owns that shape for the shared classifier and both consumers. Also lowers the EVMole and WABT cancellation-constructor baselines.

* fix(process): keep the provider's recovery for a native-thread exhaustion report

The shared classifier replaced the provider-supplied recovery with generic capacity advice whenever the JVM reported it could not create a native thread, dropping Apktool's launcher guidance and flipping retryable to true. The provider's recovery now wins; the capacity advice remains the fallback for providers that supply none.

* refactor(pwntools): classify pwntools failures through the shared provider classifier (#10782)

* refactor(pwntools): classify pwntools failures through the shared provider classifier

pwntools' per-site classifier is replaced by pwntoolsFailureDialect for classifyProviderFailure. Command outcomes go through classifyProviderOutcome, launch refusals through launcherRefusalOf instead of a 'spawn ' syscall prefix check, the input limit through the file dialect's fileSize, and the cleanup report through commandCleanup. The shared read cancellation now carries the reader's partial observation, and resource-signal reports accept provider detail.

* fix(pwntools): report a failed command's output once and expect resource_constraint for overflow

The shared outcome owner already attaches the command's output as capturedOutput, so the pwntools adapter fallback no longer copies the same text into diagnostics.stdout, diagnostics.stderr and diagnostics.captured_output; diagnostics keep the exit facts. Update the overflow expectations the dialect migration missed (layout provider cleanup and over-budget rows, pwntools cleanup) to resource_constraint.

* refactor(wasm): leave WABT cancellation to the shared owners

WabtFailures matched any Error named AbortError as a cancellation. No WABT path produces one: reads, path checks and owned commands report cancellation as ArtifactReaderFailure or OwnedCommandFailure, which classifyProviderFailure and classifyProviderOutcome already classify with the caller's reason. The provider's two signal checks now build the cancellation with cancelledBy. Lowers the AnalysisCancelledError constructor baseline for both WABT files to zero.

* fix(wasm): report a cancelled snapshot write as the caller's cancellation

The private snapshot write passed the caller's signal to Node's writeFile, whose abort envelope reached the dialect's unclassified output failure once the name-based AbortError guess was removed. The write now turns the envelope carrying its own signal's reason into cancelledBy at the producer.

* refactor(firmware): classify firmware failures through the shared provider classifier (#10810)

Firmware producers now report their own deadline expiry: the start check, the output inventory and launcher admission throw the phase timeout where they observe the abort, and a read the phase signal cancelled reports it through the dialect's interruption. The classifier no longer rereads a cancellation's reason as a timeout. A new readCleanup hook keeps firmware's cleanup report shape.

* refactor(captures): classify TShark, sigrok and pcap failures through the shared provider classifier

TsharkCaptureProvider and SigrokRecordingProvider replace #failure with a dialect, and PcapProvider replaces analysisFailure and the tool-identity classifier. ArtifactAnalysisResources exposes its command context and a readCleanupFailure composer so the readCleanup hook keeps each provider's cleanup report shape.

* refactor(jeb): classify JEB failures through the shared provider classifier

JEB's raw MCP failures go through classifyProviderFailure with a JEB dialect;
a typed attribution step reports a shared phase's failure under the REA
operation. Each MCP phase deadline now reports its own interruption when it
aborts, deciding a cancellation's owner from the caller's own signal instead
of comparing abort reasons. A refusal carrying the JVM's OutOfMemoryError
report becomes a resource constraint naming JEB's Java heap.

* fix(jeb): advise the JVM limit that governs the reported memory area

* refactor(ghidra): classify Ghidra failures through the shared provider classifier (#10836)

* refactor(ghidra): classify Ghidra failures through the shared provider classifier

PSP extension inspection reports through classifyProviderFailure with a
Ghidra dialect. The startup deadline decides at abort time whether the shared
startup owner closed it, so Ghidra and Hopper no longer compare the abort
reason with the owner's sentinel, and Ghidra's startup no longer compares a
producer cancellation's reason with the deadline's. Three helpers that only
construct one error kind now name that kind instead of AnalysisError.

* refactor(hopper): classify original-source read failures through the shared provider classifier (#10838)

* test(ghidra): pin the aborted startup deadline over a producer cancellation

* refactor(kuna): classify Kuna failures through the shared provider classifier (#10843)

Session operations, the workspace copy and the project export report raw
failures through classifyProviderFailure with Kuna dialects; the copy and
export keep their cleanup report shapes through the readCleanup hook.

* chore(guards): shrink the cancellation construction baseline after merging the base
2026-11-15 19:37:46 +08:00

8.9 KiB

JEB instrumentation and analysis

REA exposes JEB-backed project, unit, and decompilation inspection through the CLI and MCP, served by JEB's own MCP interface. JEB is a bring-your-own engine: REA never installs it, launches it, or opens its project databases itself.

Bring-your-own engine

Start a JEB client that serves MCP and point REA at it:

  • Default endpoint: http://127.0.0.1:8425/mcp. Override with REA_JEB_MCP_URL (absolute http/https URL with literal host 127.0.0.1 or [::1]; redirects, remote hosts, credentials, query strings, and fragments are rejected rather than silently dropped).

  • In the JEB GUI client, start the MCP server from the File menu (or via the VIBRE assistant).

  • Headless, the official Linux CE build 5.48.0.202610071539 supports -c --mcp. From the JEB installation directory, select JDK 21 or newer through JAVA_HOME, choose your target and bind explicitly to loopback:

    "$JAVA_HOME/bin/java" -jar bin/app/jeb.jar -c --mcp --interface=127.0.0.1 \
      --port=8425 --infile=/absolute/path/to/target
    

    Complete the vendor's first-use EULA and license-key activation when prompted. The Linux audit used an isolated installation, a 1536 MiB Java heap and one active processor inside a separately bounded process family; JVM heap limits alone do not bound the whole family.

  • JEB's documented programmatic route (JebMcpServerInstance from the JEB API) remains an alternative. The earlier licensed 5.48 headless verification used that route because its CLI did not accept --mcp. Check your installed build's -c --help and live tool catalog; the version number alone does not establish the launcher options or supported operations.

For setup registration, set REA_JEB_MCP_URL in the REA process environment before running rea setup. The approved setup plan preserves the exact selected URL after validating its syntax. That validation does not establish that the JEB service is reachable or that a project is open; use the existing inspect-jeb-client command for a read-only service query.

MCP binary_session probes the live JEB service and its advertised tool catalog. Availability is reported per operation: a reachable headless client can support inspection and decompilation while refusing open_jeb_project. Catalog discovery is one deadline across connection and pagination; a responsive server cannot keep discovery running indefinitely by returning more pages.

Catalog discovery and each tools/call share REA_JEB_MCP_TIMEOUT_MS. Set it in REA's launch environment to an integer from 1 to 2147483647 milliseconds, for example REA_JEB_MCP_TIMEOUT_MS=300000 for a five-minute budget. Omitted means the pinned MCP client SDK default of 60000 ms (@modelcontextprotocol/client 2.3.1). Change the budget for subsequent operations with update_analysis_settings using jeb.mcp_timeout_ms. To change the launch value, update REA's environment and restart. Raising the setting raises both phases. It does not make one end-to-end deadline across a whole CLI or MCP operation: discovery and each tools/call each get that budget. A request that reaches its budget returns a typed timeout naming the phase and the effective milliseconds, while caller cancellation remains a cancellation. Allow execution, cleanup, and delivery headroom in the outer MCP client deadline.

Earlier verification used JEB 5.48.0 (Linux x64, licensed build) serving jeb-mcp-server 1.3.0 over streamable HTTP. The official Linux x64 CE build 5.48.0.202610071539 was also verified through real CLI and production stdio MCP native-code workflows; see the Linux CE audit for measurements and limits. CE verification does not establish licensed/Demo Android decompilation or GUI behavior.

Operations

MCP tool / CLI command Purpose
inspect_jeb_client / inspect-jeb-client Engine version, GUI or headless mode, startup time
open_jeb_project / open-jeb-project Open or create a project from an artifact or .jdb2 on the engine host; returns input-file SHA-256 digests and top-level units
list_jeb_units / list-jeb-units Page units by stable path and type (filter, parent-unit-path, index, count)
decompile_jeb_item / decompile-jeb-item Decompile one type or method to engine pseudo-code

All four preserve target-file bytes. open_jeb_project selects or creates an engine-side project; analysis queries can populate computed analysis within it. Evidence records the endpoint, caller-selected parameters, and available engine-reported digests. Mutation workflows the engine also offers (renames, comments, save_project, close_project) are intentionally not exposed yet.

Engine coverage note: The earlier licensed 5.48.0 headless verification did not advertise open_project; it selected the target when the engine client started. For that build, use --infile on the programmatic launcher or open the file in the GUI. The audited CE headless catalog also omitted open_project; the verified CLI route above selects its input at startup. Use binary_session to inspect the current engine's advertised operations. REA preserves an unavailable tool's refusal; open_jeb_project requires an engine that advertises open_project. The CE audit does not establish GUI project-opening support.

Semantics

  • Paths in open_jeb_project are resolved by the JEB process, not by REA; REA does not require the artifact to exist locally.
  • The engine caps list_units pages at 100; a full page is reported as coverage: "partial" because more units may exist. An empty remainder is complete only when fewer units than requested were returned.
  • When decompile_jeb_item omits unit_path, the engine implicitly selects the project's first code unit; REA reports unit_path: null and a limitation instead of guessing the effective unit.
  • item_address is a JEB code-item reference, such as a value returned by the engine's list_code_items. Preserve that representation and select its code unit explicitly. For the audited CE SQLite unit, the exported method reference was sqlite3_libversion_number; using its raw ELF symbol address did not produce a successful decompilation. File offsets, ELF virtual addresses and engine item references have different semantics. REA currently exposes unit listing, while code-item discovery remains an upstream JEB operation.
  • Valid engine refusals (success: false envelopes) return execution_failure with the engine's message when available. Diagnostics preserve the original envelope, including any recovery advice, and the inputs of a refused primary request. If client metadata fails after a primary reply, that reply and the caller's inputs remain in partial Evidence. REA does not infer an unsupported target from a refusal, invoke a recovery tool, or retry the request. A refusal whose message carries the JVM's own java.lang.OutOfMemoryError report is a resource constraint instead (capacity for native-thread exhaustion), with recovery naming the JEB client's Java heap; the envelope stays in diagnostics.
  • Successfully parsed client information is reused within the current MCP session, including when the engine omits its version. Explicit client inspection and availability probes refresh it; reconnecting invalidates the cached information.
  • An unreachable endpoint is a provider-availability failure with recovery guidance; REA does not spawn the engine or automatically replay failed operations.
  • After a transport or RPC failure (including a request or catalog timeout), the next explicit call attempts a new MCP connection after cleanup succeeds. Engine version and GUI mode are refreshed for that connection. Caller cancellation and decoded engine refusal envelopes preserve the existing connection.

Real-engine verification lives in the JEB lane described in testing.md; mock transport tests do not establish real-engine compatibility.

A project-open refusal retains the selected engine-host artifact path separately from the service endpoint. Discovery of an unreachable service includes the same start/configuration recovery as a direct request. If a primary reply completes before a later client-information lookup fails or is cancelled, the typed failure retains that reply as partial Evidence. Engine metadata stays unknown; this is not a claim that the full operation completed.