[GH-ISSUE #1389] [Design] Add rsleigh as a headless static-analysis provider candidate #483

Open
opened 2026-10-15 18:25:26 +00:00 by bsakaguchi · 8 comments
Owner

Originally created by @ShaneBreazeale on GitHub (Oct 10, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/1389

Before submitting

  • I searched the open and closed issues and pull requests for a related discussion.
  • I removed credentials, tokens, private paths, and sensitive target data.

Decision needed

Should REA accept rsleigh as a deep binary-analysis provider candidate? If so, should the provider call the rsleigh CLI once per operation, or keep an rsleigh --mcp child process open for a target session?

rsleigh is a pure-Rust static analyzer. It lifts instructions with Ghidra SLEIGH specifications (P-code), recovers SSA, and decompiles. It needs no JVM, GUI, license, or network access. It supports x86-64, x86-32, AArch64, ARM32, MIPS32, and RISC-V 64 in PE, ELF, and Mach-O files. That would give REA a headless, read-only native provider that can run in CI or sandboxes where Ghidra, IDA, or Hopper are unavailable.

Context and evidence

Observed (rsleigh at main after #5/#6):

  • rsleigh --capabilities --json (rsleigh.capabilities/v1) is generated from the same typed contracts that validate every agent invocation. For each operation it lists the flag kinds, effects (reads_binary, writes_cache, ...), limits, examples, and support per frontend and architecture, with quality reported separately from availability.
  • Agent operations return versioned JSON (rsleigh.card/v2, rsleigh.ssa-slice/v3, rsleigh.agent-brief/v1, ...) with status ok/partial/failed, exit codes 0/2/1, and stable error_code values. Results that are partial or truncated, and boundaries that could not be resolved, are reported explicitly rather than dropped.
  • Evidence records (rsleigh.evidence/v1) are content-addressed and carry subject identity (binary SHA-256, format, architecture, image base) and producer identity (build, analysis profile). Bundles of them can be verified offline. Parts of this design were adapted from REA's evidence, unknown, and comparison models, as recorded in a design note.
  • rsleigh --mcp serves capabilities, agent-brief, card, ssa-slice, and verify-bundle over stdio. Its tool schemas are generated from the same catalog, and tests check that its results match the CLI.

Observed (REA, current main): AnalysisOperation is a closed union, and #740 lists "do not accept arbitrary unknown external tool catalogs" as an invariant. So rsleigh's MCP server cannot simply be mounted; an integration has to be a typed AnalysisProviderCandidate that maps onto REA's canonical contracts.

Derived: rsleigh's existing outputs could supply most of the provider interface:

  • inspectAvailability from --version --json.
  • inspectTargetSupport from the catalog's frontends and architectures.
  • resolveAnalysisProfile from the build and profile identity in evidence records.
  • capabilities() effects and limitations from the catalog.

Inference, unverified: a read-only subset of the official tools looks feasible:

REA tool rsleigh source
list_procedures, search_procedures function list, --search
procedure_info --card
procedure_assembly, read_function_instructions card instructions and P-code, paginated with cursors
procedure_pseudo_code --card --decompile
procedure_callers, procedure_callees --xrefs
list_strings string recovery

Mutation and UI-state tools (set_comment, set_address_name, bookmarks, goto_address/current_*) would be reported unavailable. I have not run REA's conformance or real-provider lanes against rsleigh.

Boundary and invariants

  • The provider is read-only: no artifact mutation, UI, network, or root, and no filesystem writes except an optional analysis cache directory that the provider owns. It is started as an owned process with REA's existing process primitives, and cancellation terminates that process.
  • Provider selection stays explicit. Adding a second usable deep candidate for the same target should not create silent priority over Ghidra, IDA, or Hopper (#740 notes that registry order is not provider priority).
  • rsleigh's partial status and unresolved boundaries map to REA limitations or unknowns and must not be upgraded to complete results. Its P-code-backed evidence would attach to REA's evidence ledger with rsleigh's provider identity and profile.
  • P-code and SSA-slice queries would need new canonical tool contracts. That is a separate decision; this issue covers only the provider and the existing tools.

Options and tradeoffs

  1. Provider over one-shot CLI calls (rsleigh FILE --card --json ... per operation). This is stateless, cleanup and cancellation are simple, and every call has a versioned JSON contract. The cost is parsing the binary on every call; rsleigh's --analysis-cache DIR avoids repeating SSA work.
  2. Provider over an owned rsleigh --mcp child process for each target session. This uses one connection, but today it gives no extra state: rsleigh MCP calls run one at a time, re-read the target, and do not act on cancellation notices (the process would have to be terminated). It would also put a second protocol layer inside a provider.
  3. No provider; users run both MCP servers side by side. This needs no REA changes, but the evidence never enters REA's ledger, routing, or comparisons, and the provenance stays split.

Option 1 looks like the smallest fit with REA's owned-process and contract model; option 2 becomes worthwhile only if rsleigh gains state that persists across calls in a session.

Success criteria and open questions

The decision is settled by maintainer agreement on whether an rsleigh provider is in scope and which transport to use. Implementation would then start with a PR containing an AnalysisProviderCandidate, its composition wiring, and the matching provider lane.

Open questions:

  1. Should providers without UI or mutation support declare those operations unavailable, or leave them out of capabilities()?
  2. Where should a new deep provider sit in the provider-selection policy relative to existing ones?
  3. Would you accept a provider lane that installs rsleigh from crates.io (cargo install rsleigh) or from release binaries?

References

Originally created by @ShaneBreazeale on GitHub (Oct 10, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/1389 ### Before submitting - [x] I searched the open and closed issues and pull requests for a related discussion. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Decision needed Should REA accept [rsleigh](https://github.com/ShaneBreazeale/rsleigh) as a deep binary-analysis provider candidate? If so, should the provider call the rsleigh CLI once per operation, or keep an `rsleigh --mcp` child process open for a target session? rsleigh is a pure-Rust static analyzer. It lifts instructions with Ghidra SLEIGH specifications (P-code), recovers SSA, and decompiles. It needs no JVM, GUI, license, or network access. It supports x86-64, x86-32, AArch64, ARM32, MIPS32, and RISC-V 64 in PE, ELF, and Mach-O files. That would give REA a headless, read-only native provider that can run in CI or sandboxes where Ghidra, IDA, or Hopper are unavailable. ### Context and evidence **Observed (rsleigh at main after [#5](https://github.com/ShaneBreazeale/rsleigh/pull/5)/[#6](https://github.com/ShaneBreazeale/rsleigh/pull/6)):** - `rsleigh --capabilities --json` (`rsleigh.capabilities/v1`) is generated from the same typed contracts that validate every agent invocation. For each operation it lists the flag kinds, effects (`reads_binary`, `writes_cache`, ...), limits, examples, and support per frontend and architecture, with quality reported separately from availability. - Agent operations return versioned JSON (`rsleigh.card/v2`, `rsleigh.ssa-slice/v3`, `rsleigh.agent-brief/v1`, ...) with `status` ok/partial/failed, exit codes 0/2/1, and stable `error_code` values. Results that are partial or truncated, and boundaries that could not be resolved, are reported explicitly rather than dropped. - Evidence records (`rsleigh.evidence/v1`) are content-addressed and carry subject identity (binary SHA-256, format, architecture, image base) and producer identity (build, analysis profile). Bundles of them can be verified offline. Parts of this design were adapted from REA's evidence, unknown, and comparison models, as recorded in a [design note](https://github.com/ShaneBreazeale/rsleigh/blob/master/docs/rea-adaptation-plan.md). - `rsleigh --mcp` serves `capabilities`, `agent-brief`, `card`, `ssa-slice`, and `verify-bundle` over stdio. Its tool schemas are generated from the same catalog, and tests check that its results match the CLI. **Observed (REA, current main):** `AnalysisOperation` is a closed union, and #740 lists "do not accept arbitrary unknown external tool catalogs" as an invariant. So rsleigh's MCP server cannot simply be mounted; an integration has to be a typed `AnalysisProviderCandidate` that maps onto REA's canonical contracts. **Derived:** rsleigh's existing outputs could supply most of the provider interface: - `inspectAvailability` from `--version --json`. - `inspectTargetSupport` from the catalog's frontends and architectures. - `resolveAnalysisProfile` from the build and profile identity in evidence records. - `capabilities()` effects and limitations from the catalog. **Inference, unverified:** a read-only subset of the official tools looks feasible: | REA tool | rsleigh source | |---|---| | `list_procedures`, `search_procedures` | function list, `--search` | | `procedure_info` | `--card` | | `procedure_assembly`, `read_function_instructions` | card instructions and P-code, paginated with cursors | | `procedure_pseudo_code` | `--card --decompile` | | `procedure_callers`, `procedure_callees` | `--xrefs` | | `list_strings` | string recovery | Mutation and UI-state tools (`set_comment`, `set_address_name`, bookmarks, `goto_address`/`current_*`) would be reported unavailable. I have not run REA's conformance or real-provider lanes against rsleigh. ### Boundary and invariants - The provider is read-only: no artifact mutation, UI, network, or root, and no filesystem writes except an optional analysis cache directory that the provider owns. It is started as an owned process with REA's existing process primitives, and cancellation terminates that process. - Provider selection stays explicit. Adding a second usable deep candidate for the same target should not create silent priority over Ghidra, IDA, or Hopper (#740 notes that registry order is not provider priority). - rsleigh's `partial` status and unresolved boundaries map to REA limitations or unknowns and must not be upgraded to complete results. Its P-code-backed evidence would attach to REA's evidence ledger with rsleigh's provider identity and profile. - P-code and SSA-slice queries would need new canonical tool contracts. That is a separate decision; this issue covers only the provider and the existing tools. ### Options and tradeoffs 1. **Provider over one-shot CLI calls** (`rsleigh FILE --card --json ...` per operation). This is stateless, cleanup and cancellation are simple, and every call has a versioned JSON contract. The cost is parsing the binary on every call; rsleigh's `--analysis-cache DIR` avoids repeating SSA work. 2. **Provider over an owned `rsleigh --mcp` child process for each target session.** This uses one connection, but today it gives no extra state: rsleigh MCP calls run one at a time, re-read the target, and do not act on cancellation notices (the process would have to be terminated). It would also put a second protocol layer inside a provider. 3. **No provider; users run both MCP servers side by side.** This needs no REA changes, but the evidence never enters REA's ledger, routing, or comparisons, and the provenance stays split. Option 1 looks like the smallest fit with REA's owned-process and contract model; option 2 becomes worthwhile only if rsleigh gains state that persists across calls in a session. ### Success criteria and open questions The decision is settled by maintainer agreement on whether an rsleigh provider is in scope and which transport to use. Implementation would then start with a PR containing an `AnalysisProviderCandidate`, its composition wiring, and the matching provider lane. Open questions: 1. Should providers without UI or mutation support declare those operations unavailable, or leave them out of `capabilities()`? 2. Where should a new deep provider sit in the provider-selection policy relative to existing ones? 3. Would you accept a provider lane that installs rsleigh from crates.io (`cargo install rsleigh`) or from release binaries? ### References - rsleigh capability catalog and error codes: [cli-reference.md#discover-supported-operations](https://github.com/ShaneBreazeale/rsleigh/blob/master/docs/cli-reference.md#discover-supported-operations) - rsleigh output contracts: [output-formats.md](https://github.com/ShaneBreazeale/rsleigh/blob/master/docs/output-formats.md) - rsleigh MCP server: [cli-reference.md#serve-operations-over-mcp](https://github.com/ShaneBreazeale/rsleigh/blob/master/docs/cli-reference.md#serve-operations-over-mcp) - REA: `src/application/AnalysisProvider.ts`, `src/contracts/officialToolContracts.ts`, #740
Author
Owner

@CassieFox23 commented on GitHub (Oct 10, 2026):

TL;DR: Of nine claims I checked on a Linux build of rsleigh, seven held, one held with a gap, and one did not hold in two cases. On an idle machine, rsleigh was far faster than REA with Ghidra, but its decompiler was wrong or short on 3 of 4 functions. I also re-read the REA provider code on main, and it matches the issue, with one detail to add about profile versions.

What I tested

I used rsleigh (Apache-2.0) at commit 0748bd6a7. That commit is the merge of ShaneBreazeale/rsleigh#6. Master still pointed there on 2026-10-10. I tested x86-64 ELF files only, against REA (MIT) 6.3.0 with Ghidra (Apache-2.0) 12.1.4. I read REA main at 6db97d516 for the code claims, and I ran nothing from REA against rsleigh.

Claims I checked

Claim in the issue Result
--capabilities --json is rsleigh.capabilities/v1 Held. The catalog lists 11 operations.
--version --json Held. The output gives the version and the ELF build ID.
Exit codes 0, 2 and 1 with status ok, partial, failed Held.
Stable error_code on failure Held, with one gap (below).
Evidence IDs are made from the content Held. The ID was the same cold, with a cache, and warm.
verify-bundle works offline Held. One changed byte fails.
--mcp serves 5 tools Held.
MCP results match the CLI Held, on one card.
Partial or unresolved parts are reported Did not hold, in two cases (see below).

The gap: I ran a card on the sqlite3 shell with --deadline-ms 1. The card exits 1 with status failed, but has no top-level error_code. The error text is "no decodable instructions at requested address", which is not the cause. The real cause sits in diagnostics, as execution_limit with reason deadline.

Speed and memory

These are medians of 5 runs on an idle machine, with nothing else of mine running. Load average is how many jobs were running or waiting for a CPU thread, on average, out of 24 threads. It was between 1.3 and 2.0 at the start of every run. The targets were copies of true and ls from coreutils 9.10 (GPL-3.0-or-later). I also used the sqlite3 shell from SQLite 3.51.2 (public domain) and my own small C program, rebuilt for this run. Each cell shows wall time, then peak memory of the largest process, in MiB.

Function rsleigh card rsleigh brief REA with Ghidra
my test program's main 0.08 s, 72 MiB 0.05 s, 30 MiB 7.7 s, 570 MiB
true stub 0.07 s, 75 MiB 0.10 s, 31 MiB 9.3 s, 617 MiB
ls function 0.12 s, 75 MiB 0.23 s, 34 MiB 13.2 s, 672 MiB
sqlite3_free 0.12 s, 77 MiB 1.86 s, 40 MiB 50.3 s, 1,082 MiB

This is not a fair race. Each REA call ran a full, fresh Ghidra import of the whole file. The rsleigh card looks at one function. The brief looks at the whole file, but it stops at 25 functions. For true, ls and sqlite3 it exited 2 with status partial on every run, with 25 of 143, 641 and 5,931 functions. All 20 rsleigh cards said ok, and all 20 REA runs returned JSON with a result.

Decompile output

Three of four functions came out wrong or short. My test program's main had the wrong structure. The ls function lost a cold block, and sqlite3_free came back empty. Both of those still said ok, with no warning. The true stub is a one-instruction jump into libc, so I do not count it. Both tools named it dcgettext. Ghidra showed the jump as a one-line call, and rsleigh showed nothing. The rsleigh text for my test program's main and for the ls function also changed a little from run to run on the same file. I opened ShaneBreazeale/rsleigh#7 for that. Details are in the "Decompile output" fold below.

REA code I read (main 6db97d516)

  • AnalysisOperation is a closed list of tool names, so rsleigh's MCP server cannot be mounted as it is. The issue says the same.
  • The Ghidra provider already runs a short Java probe with a 5 second limit. So --version --json fits inspectAvailability.
  • REA resolves a provider's profile before it starts the provider. A missing profile is rejected as version_unresolved. The profile must also carry the provider's version string. rsleigh gives its version and a build ID up front. I did not check if the rest of its profile can be built before a run.
  • Open question 1: REA already does both. Linux Ghidra leaves UI tools out of its list. Windows Ghidra marks its edit tool unavailable, with a reason. IDA headless does the same for procedure_callers.
  • Open question 2: when two providers can serve a target, auto returns an ambiguous error and picks neither.
  • The deep providers are listed by hand as hopper, ghidra and ida in src/composition/binary.ts, so a fourth needs an entry there. The provider SDK issues #45 and #371 were closed on 2026-07-20 as out of scope for now, with in-tree providers first.
  • #740 is still open. N0zoM1z0 says in a comment on #1065 that #740 "remains deferred". The migration guide that issue pointed to was removed from main in 7a0c80085.

Not run

  • REA conformance or provider lanes.
  • Any target that is not x86-64 ELF.
  • --xrefs, --search, --index, compare and the question tools.
  • MCP ssa-slice, verify-bundle and cancellation.
  • A P-code check against Ghidra.
  • An REA session that reuses one Ghidra import.
  • rsleigh's own test suite.

Thanks, ShaneBreazeale, for the clear post and for the design note behind it. I take no side on the design question.

Timing ranges (minimum to maximum of 5 runs)
Function rsleigh card rsleigh brief REA with Ghidra
my test program's main 0.08 to 0.09 s, 72 MiB 0.05 s, 29 to 30 MiB 7.60 to 7.91 s, 481 to 636 MiB
true stub 0.06 to 0.07 s, 75 MiB 0.10 to 0.11 s, 31 MiB 9.17 to 9.47 s, 503 to 654 MiB
ls function 0.12 s, 75 MiB 0.23 to 0.24 s, 33 to 34 MiB 12.78 to 13.33 s, 654 to 701 MiB
sqlite3_free 0.12 to 0.13 s, 77 MiB 1.85 to 1.96 s, 39 to 41 MiB 49.97 to 51.88 s, 1,035 to 1,163 MiB

I took 5 runs of each cell in turns, one process at a time. Before each run I waited while the one-minute load average was over 2.0. That happened before 7 of 60 runs, for 15 or 30 seconds each. No run started above 2.0. I ran one untimed warm-up of each command first. The REA warm-up took 8.3 s on my test program, about half a second over the timed runs. REA left no files in its temp folder between runs. No run was killed by a signal or timed out.

Decompile output
  • My test program's main: Ghidra matched the source. rsleigh had the right calls and strings. But rsleigh read argc as if it were argv, wrote a garbled hash loop, and lost two of the three return values. I rebuilt this program for this run, and it may differ from the one I used in my first check.
  • ls at 0x5580: the function jumps to a cold block at 0x598f. That block calls getenv("TERM"). rsleigh left it out of the body and the output. The card still said ok, with no warning.
  • sqlite3_free at 0x42960: rsleigh gave an empty body and status ok, with no warnings. Ghidra gave about 30 lines.
  • The true stub is a one-instruction jump into libc. Both tools named it dcgettext. Ghidra's body is one call to dcgettext. The rsleigh body is empty. I do not count this stub in the "3 of 4".
  • Over 5 runs on the same file, the rsleigh text for the true stub and for sqlite3_free never changed. The text for my test program's main took 3 different forms, and the ls text took 5. The structure stayed the same each time. A later check of 20 runs each gave 4 forms for main and 10 for ls.

The README says the decompiler is not stable yet. I did not compare P-code.

Build (relevant to open question 3)
  • No rsleigh crate has a build.rs. The generate step only writes into generated/*/out inside the clone.
  • On the idle machine, the generate step took 14.6 s, and the release build took 228 s (about 3.8 minutes). The biggest single process peaked at 5.15 GiB during the build.
  • The binary is 62.6 MiB.
Two small notes
  • The imagebase field held the address of the .init section. For ls the field said 0x33c, but the load base is 0. I don't know if that is on purpose.
  • The mapping table in the issue relies on --xrefs, --search and string recovery. Those modes are not in the capabilities catalog, and I did not test them.
REA source lines at 6db97d516
  • Closed list of operations: src/application/AnalysisProvider.ts lines 24 to 31.
  • Java probe: src/ghidra/GhidraInstallation.ts lines 729 to 740, with the 5 second limit at line 736. inspectAvailability is in src/ghidra/GhidraProvider.ts lines 88 to 120.
  • Profile before startup: src/application/AnalysisProvider.ts lines 130 to 133, and src/application/binary/AnalysisProviderEvaluation.ts lines 80 to 102. The version string must be non-empty: src/domain/analysisProfile.ts lines 10 to 14.
  • Linux Ghidra operation list: src/ghidra/GhidraProviderCapabilities.ts lines 20 to 23. Windows Ghidra marks edits unavailable at lines 192 to 199.
  • IDA headless procedure_callers: src/ida/IdaProviderCapabilities.ts lines 48 to 53.
  • ambiguous under auto: src/application/binary/AnalysisProviderRegistry.ts lines 318 to 319. The default selector is auto, in src/config/environment.ts line 12.
  • Provider list: src/composition/binary.ts lines 25 to 32.
Commands

Build, from the rsleigh clone. The fetch step needs the network. The other two run offline.

cargo fetch --locked
cargo run --locked --offline --release -p rsleigh-generate
cargo build --locked --offline --release

rsleigh card, and REA with Ghidra. For PIE files, the Ghidra address is the rsleigh address plus 0x100000. My test program is not a PIE, so it uses the same address in both tools.

rsleigh FILE ADDR --card --json --pcode --decompile
rsleigh FILE --agent-brief --json
export GHIDRA_INSTALL_DIR=<Ghidra 12.1.4 folder>
export JAVA_HOME=<JDK 21 folder> PATH=$JAVA_HOME/bin:$PATH
export REA_GHIDRA_STARTUP_TIMEOUT_MS=600000
export TMPDIR=<temp folder, no dot folder in the path>
export HOME=<empty scratch folder>
node <rea-agents 6.3.0>/scripts/rea.mjs decompile FILE GHIDRA_ADDR --provider ghidra --format json

Each timed run was wrapped as ( ulimit -c 0; /usr/bin/time -o FILE -f "%e %M %x" timeout N ... ). The %M value is in KiB, and the tables show it divided by 1024.

My test program was built with gcc -O1 -no-pie, not stripped. It checks argc, returns 2 with no argument, hashes argv[1] in a loop, and returns 0 or 1 on the hash.

Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.9-200.nobara.fc44.x86_64
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 32 GB DDR4-3600
  • rsleigh: version 0.5.0, built on 2026-10-10 with Rust 1.97.0 (MIT OR Apache-2.0). Commit 0748bd6a7 is the merge of ShaneBreazeale/rsleigh#6.
  • REA: 6.3.0 for the runs, main 6db97d516 for the code reading
  • Ghidra: 12.1.4 (Apache-2.0)
  • JDK: Temurin 21.0.12.1 is the JDK I used, and it is free to use under GPL-2.0-only WITH Classpath-exception-2.0.
  • Node: 24.18.0 (MIT) ran the REA command line. The system Node is 22.23.1, and I did not use it.
  • Targets: coreutils 9.10 and sqlite 3.51.2. I copied them from the system packages.
  • Test program: rebuilt for this run with GCC 16.2.1 (GPL-3.0-or-later). It may differ from the program in my first check.

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

<!-- gh-comment-id:6102433961 --> @CassieFox23 commented on GitHub (Oct 10, 2026): **TL;DR:** Of nine claims I checked on a Linux build of rsleigh, seven held, one held with a gap, and one did not hold in two cases. On an idle machine, rsleigh was far faster than REA with Ghidra, but its decompiler was wrong or short on 3 of 4 functions. I also re-read the REA provider code on main, and it matches the issue, with one detail to add about profile versions. ### What I tested I used rsleigh ([Apache-2.0](https://github.com/ShaneBreazeale/rsleigh/blob/master/LICENSE)) at commit [0748bd6a7](https://github.com/ShaneBreazeale/rsleigh/commit/0748bd6a7b10eb512e7cd909808b54ae6683663b). That commit is the merge of ShaneBreazeale/rsleigh#6. Master still pointed there on 2026-10-10. I tested x86-64 ELF files only, against REA ([MIT](https://github.com/morluto/rea/blob/main/LICENSE)) 6.3.0 with Ghidra ([Apache-2.0](https://github.com/NationalSecurityAgency/ghidra/blob/master/LICENSE)) 12.1.4. I read REA main at 6db97d516 for the code claims, and I ran nothing from REA against rsleigh. ### Claims I checked | Claim in the issue | Result | |---|---| | `--capabilities --json` is `rsleigh.capabilities/v1` | Held. The catalog lists 11 operations. | | `--version --json` | Held. The output gives the version and the ELF build ID. | | Exit codes 0, 2 and 1 with status ok, partial, failed | Held. | | Stable `error_code` on failure | Held, with one gap (below). | | Evidence IDs are made from the content | Held. The ID was the same cold, with a cache, and warm. | | `verify-bundle` works offline | Held. One changed byte fails. | | `--mcp` serves 5 tools | Held. | | MCP results match the CLI | Held, on one card. | | Partial or unresolved parts are reported | Did not hold, in two cases (see below). | The gap: I ran a card on the sqlite3 shell with `--deadline-ms 1`. The card exits 1 with status `failed`, but has no top-level `error_code`. The error text is "no decodable instructions at requested address", which is not the cause. The real cause sits in `diagnostics`, as `execution_limit` with reason `deadline`. ### Speed and memory These are medians of 5 runs on an idle machine, with nothing else of mine running. Load average is how many jobs were running or waiting for a CPU thread, on average, out of 24 threads. It was between 1.3 and 2.0 at the start of every run. The targets were copies of `true` and `ls` from coreutils 9.10 ([GPL-3.0-or-later](https://github.com/coreutils/coreutils/blob/master/COPYING)). I also used the `sqlite3` shell from SQLite 3.51.2 ([public domain](https://sqlite.org/copyright.html)) and my own small C program, rebuilt for this run. Each cell shows wall time, then peak memory of the largest process, in MiB. | Function | rsleigh card | rsleigh brief | REA with Ghidra | |---|---|---|---| | my test program's `main` | 0.08 s, 72 MiB | 0.05 s, 30 MiB | 7.7 s, 570 MiB | | `true` stub | 0.07 s, 75 MiB | 0.10 s, 31 MiB | 9.3 s, 617 MiB | | `ls` function | 0.12 s, 75 MiB | 0.23 s, 34 MiB | 13.2 s, 672 MiB | | `sqlite3_free` | 0.12 s, 77 MiB | 1.86 s, 40 MiB | 50.3 s, 1,082 MiB | This is not a fair race. Each REA call ran a full, fresh Ghidra import of the whole file. The rsleigh card looks at one function. The brief looks at the whole file, but it stops at 25 functions. For `true`, `ls` and `sqlite3` it exited 2 with status `partial` on every run, with 25 of 143, 641 and 5,931 functions. All 20 rsleigh cards said `ok`, and all 20 REA runs returned JSON with a result. ### Decompile output Three of four functions came out wrong or short. My test program's `main` had the wrong structure. The `ls` function lost a cold block, and `sqlite3_free` came back empty. Both of those still said `ok`, with no warning. The `true` stub is a one-instruction jump into libc, so I do not count it. Both tools named it `dcgettext`. Ghidra showed the jump as a one-line call, and rsleigh showed nothing. The rsleigh text for my test program's `main` and for the `ls` function also changed a little from run to run on the same file. I opened ShaneBreazeale/rsleigh#7 for that. Details are in the "Decompile output" fold below. ### REA code I read (main 6db97d516) - `AnalysisOperation` is a closed list of tool names, so rsleigh's MCP server cannot be mounted as it is. The issue says the same. - The Ghidra provider already runs a short Java probe with a 5 second limit. So `--version --json` fits `inspectAvailability`. - REA resolves a provider's profile before it starts the provider. A missing profile is rejected as `version_unresolved`. The profile must also carry the provider's version string. rsleigh gives its version and a build ID up front. I did not check if the rest of its profile can be built before a run. - Open question 1: REA already does both. Linux Ghidra leaves UI tools out of its list. Windows Ghidra marks its edit tool unavailable, with a reason. IDA headless does the same for `procedure_callers`. - Open question 2: when two providers can serve a target, `auto` returns an `ambiguous` error and picks neither. - The deep providers are listed by hand as hopper, ghidra and ida in `src/composition/binary.ts`, so a fourth needs an entry there. The provider SDK issues #45 and #371 were closed on 2026-07-20 as out of scope for now, with in-tree providers first. - #740 is still open. N0zoM1z0 says in [a comment on #1065](https://github.com/morluto/rea/issues/1065#issuecomment-6101160654) that #740 "remains deferred". The migration guide that issue pointed to was removed from main in 7a0c80085. ### Not run - REA conformance or provider lanes. - Any target that is not x86-64 ELF. - `--xrefs`, `--search`, `--index`, `compare` and the question tools. - MCP `ssa-slice`, `verify-bundle` and cancellation. - A P-code check against Ghidra. - An REA session that reuses one Ghidra import. - rsleigh's own test suite. Thanks, ShaneBreazeale, for the clear post and for the design note behind it. I take no side on the design question. <details> <summary>Timing ranges (minimum to maximum of 5 runs)</summary> | Function | rsleigh card | rsleigh brief | REA with Ghidra | |---|---|---|---| | my test program's `main` | 0.08 to 0.09 s, 72 MiB | 0.05 s, 29 to 30 MiB | 7.60 to 7.91 s, 481 to 636 MiB | | `true` stub | 0.06 to 0.07 s, 75 MiB | 0.10 to 0.11 s, 31 MiB | 9.17 to 9.47 s, 503 to 654 MiB | | `ls` function | 0.12 s, 75 MiB | 0.23 to 0.24 s, 33 to 34 MiB | 12.78 to 13.33 s, 654 to 701 MiB | | `sqlite3_free` | 0.12 to 0.13 s, 77 MiB | 1.85 to 1.96 s, 39 to 41 MiB | 49.97 to 51.88 s, 1,035 to 1,163 MiB | I took 5 runs of each cell in turns, one process at a time. Before each run I waited while the one-minute load average was over 2.0. That happened before 7 of 60 runs, for 15 or 30 seconds each. No run started above 2.0. I ran one untimed warm-up of each command first. The REA warm-up took 8.3 s on my test program, about half a second over the timed runs. REA left no files in its temp folder between runs. No run was killed by a signal or timed out. </details> <details> <summary>Decompile output</summary> - My test program's `main`: Ghidra matched the source. rsleigh had the right calls and strings. But rsleigh read `argc` as if it were `argv`, wrote a garbled hash loop, and lost two of the three return values. I rebuilt this program for this run, and it may differ from the one I used in my first check. - `ls` at `0x5580`: the function jumps to a cold block at `0x598f`. That block calls `getenv("TERM")`. rsleigh left it out of the body and the output. The card still said `ok`, with no warning. - `sqlite3_free` at `0x42960`: rsleigh gave an empty body and status `ok`, with no warnings. Ghidra gave about 30 lines. - The `true` stub is a one-instruction jump into libc. Both tools named it `dcgettext`. Ghidra's body is one call to `dcgettext`. The rsleigh body is empty. I do not count this stub in the "3 of 4". - Over 5 runs on the same file, the rsleigh text for the `true` stub and for `sqlite3_free` never changed. The text for my test program's `main` took 3 different forms, and the `ls` text took 5. The structure stayed the same each time. A later check of 20 runs each gave 4 forms for `main` and 10 for `ls`. The README says the decompiler is not stable yet. I did not compare P-code. </details> <details> <summary>Build (relevant to open question 3)</summary> - No rsleigh crate has a `build.rs`. The generate step only writes into `generated/*/out` inside the clone. - On the idle machine, the generate step took 14.6 s, and the release build took 228 s (about 3.8 minutes). The biggest single process peaked at 5.15 GiB during the build. - The binary is 62.6 MiB. </details> <details> <summary>Two small notes</summary> - The `imagebase` field held the address of the `.init` section. For `ls` the field said `0x33c`, but the load base is 0. I don't know if that is on purpose. - The mapping table in the issue relies on `--xrefs`, `--search` and string recovery. Those modes are not in the capabilities catalog, and I did not test them. </details> <details> <summary>REA source lines at 6db97d516</summary> - Closed list of operations: `src/application/AnalysisProvider.ts` lines 24 to 31. - Java probe: `src/ghidra/GhidraInstallation.ts` lines 729 to 740, with the 5 second limit at line 736. `inspectAvailability` is in `src/ghidra/GhidraProvider.ts` lines 88 to 120. - Profile before startup: `src/application/AnalysisProvider.ts` lines 130 to 133, and `src/application/binary/AnalysisProviderEvaluation.ts` lines 80 to 102. The version string must be non-empty: `src/domain/analysisProfile.ts` lines 10 to 14. - Linux Ghidra operation list: `src/ghidra/GhidraProviderCapabilities.ts` lines 20 to 23. Windows Ghidra marks edits unavailable at lines 192 to 199. - IDA headless `procedure_callers`: `src/ida/IdaProviderCapabilities.ts` lines 48 to 53. - `ambiguous` under `auto`: `src/application/binary/AnalysisProviderRegistry.ts` lines 318 to 319. The default selector is `auto`, in `src/config/environment.ts` line 12. - Provider list: `src/composition/binary.ts` lines 25 to 32. </details> <details> <summary>Commands</summary> Build, from the rsleigh clone. The fetch step needs the network. The other two run offline. ```sh cargo fetch --locked cargo run --locked --offline --release -p rsleigh-generate cargo build --locked --offline --release ``` rsleigh card, and REA with Ghidra. For PIE files, the Ghidra address is the rsleigh address plus 0x100000. My test program is not a PIE, so it uses the same address in both tools. ```sh rsleigh FILE ADDR --card --json --pcode --decompile rsleigh FILE --agent-brief --json export GHIDRA_INSTALL_DIR=<Ghidra 12.1.4 folder> export JAVA_HOME=<JDK 21 folder> PATH=$JAVA_HOME/bin:$PATH export REA_GHIDRA_STARTUP_TIMEOUT_MS=600000 export TMPDIR=<temp folder, no dot folder in the path> export HOME=<empty scratch folder> node <rea-agents 6.3.0>/scripts/rea.mjs decompile FILE GHIDRA_ADDR --provider ghidra --format json ``` Each timed run was wrapped as `( ulimit -c 0; /usr/bin/time -o FILE -f "%e %M %x" timeout N ... )`. The `%M` value is in KiB, and the tables show it divided by 1024. My test program was built with `gcc -O1 -no-pie`, not stripped. It checks `argc`, returns 2 with no argument, hashes `argv[1]` in a loop, and returns 0 or 1 on the hash. </details> <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.9-200.nobara.fc44.x86_64 - CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads - RAM: 32 GB DDR4-3600 - rsleigh: version 0.5.0, built on 2026-10-10 with Rust 1.97.0 (MIT OR Apache-2.0). Commit [0748bd6a7](https://github.com/ShaneBreazeale/rsleigh/commit/0748bd6a7b10eb512e7cd909808b54ae6683663b) is the merge of ShaneBreazeale/rsleigh#6. - REA: 6.3.0 for the runs, main 6db97d516 for the code reading - Ghidra: 12.1.4 (Apache-2.0) - JDK: Temurin 21.0.12.1 is the JDK I used, and it is free to use under [GPL-2.0-only WITH Classpath-exception-2.0](https://openjdk.org/legal/gplv2+ce.html). - Node: 24.18.0 (MIT) ran the REA command line. The system Node is 22.23.1, and I did not use it. - Targets: coreutils 9.10 and sqlite 3.51.2. I copied them from the system packages. - Test program: rebuilt for this run with GCC 16.2.1 (GPL-3.0-or-later). It may differ from the program in my first check. </details> 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
Author
Owner

@ShaneBreazeale commented on GitHub (Oct 11, 2026):

@CassieFox23, following up on your report. Here is where each point stands.

Fixed on master

  • Test main: the h*33 loop, % 7, and returns 2/1/0 now print correctly (#10, #11).
  • ls cold block and empty sqlite3_free: .cold parts are decoded into their function and tail calls are recovered. Partial status now reports unresolved control flow (#10).
  • true stub: recovered as a tail call (#10).
  • Run-to-run variation: fixed in #8, your commit 6c429ba, now on master.
  • Deadline failure missing error_code: fixed in #9.

Still open

  • imagebase for ls shows 0x33c instead of the load base. Fixing it changes evidence IDs for ELF and Mach-O, so we're waiting on a maintainer decision.
  • The mapping table relies on --xrefs and --search. Both work on our Debian builds, but they're legacy CLI modes and not in the capability catalog.

Not verified yet

  • We tested on Debian's coreutils 9.10 and libsqlite 3.53.4. We haven't checked your Fedora builds, so the addresses you quoted (ls 0x598f, sqlite3_free 0x42960) are unconfirmed. Could you rerun on your binaries and tell us what you see? That would settle it.

Thanks for the careful report and measurements.

<!-- gh-comment-id:6110354671 --> @ShaneBreazeale commented on GitHub (Oct 11, 2026): @CassieFox23, following up on your report. Here is where each point stands. **Fixed on master** - Test `main`: the h*33 loop, `% 7`, and returns 2/1/0 now print correctly (#10, #11). - `ls` cold block and empty `sqlite3_free`: `.cold` parts are decoded into their function and tail calls are recovered. Partial status now reports unresolved control flow (#10). - `true` stub: recovered as a tail call (#10). - Run-to-run variation: fixed in #8, your commit `6c429ba`, now on master. - Deadline failure missing `error_code`: fixed in #9. **Still open** - `imagebase` for `ls` shows 0x33c instead of the load base. Fixing it changes evidence IDs for ELF and Mach-O, so we're waiting on a maintainer decision. - The mapping table relies on `--xrefs` and `--search`. Both work on our Debian builds, but they're legacy CLI modes and not in the capability catalog. **Not verified yet** - We tested on Debian's coreutils 9.10 and libsqlite 3.53.4. We haven't checked your Fedora builds, so the addresses you quoted (`ls` 0x598f, `sqlite3_free` 0x42960) are unconfirmed. Could you rerun on your binaries and tell us what you see? That would settle it. Thanks for the careful report and measurements.
Author
Owner

@ShaneBreazeale commented on GitHub (Oct 11, 2026):

@CassieFox23, an update on the two items that were still open, and a correction to my last comment.

imagebase is fixed in ShaneBreazeale/rsleigh#13.

  • ELF now reports the lowest PT_LOAD page, and Mach-O reports the __TEXT address. These are the values Ghidra shows. PE is unchanged.
  • Before and after on our files:
    • Debian ls: 0x33c → 0x0
    • a non-PIE test ELF: 0x401254 → 0x400000
    • a clang arm64 Mach-O: 0x100000328 → 0x100000000
  • Evidence IDs for ELF and Mach-O records change with this. Records made by older builds still verify.

Correction about --xrefs. I said it works on our Debian builds. When I looked more closely, --xrefs sqlite3_free had three bugs:

  • It printed two decoder panics to stderr.
  • It listed func_c3b500000 as a callee. That is an internal temporary ID, not a function. The same bad name appeared in the normal decompile output.
  • It reported 0 callers. In the shared library, every call goes through the PLT stub, and that stub was never named.

All three are fixed in ShaneBreazeale/rsleigh#12. --xrefs sqlite3_free now prints nothing to stderr and finds 321 callers at 837 call sites. objdump counts 895 calls, and the rest are in functions longer than the 4 KiB scan window.

Still open

  • --xrefs and --search are still not in the capabilities catalog. The mapping table in the issue relies on modes that the agent contract does not describe yet.
  • Your Fedora addresses (ls 0x598f, sqlite3_free 0x42960) are still unconfirmed. A rerun on your binaries would settle it, if you have time.

One more fix: in my last comment, the bare numbers #8 to #11 point at REA issues. They mean ShaneBreazeale/rsleigh#8, ShaneBreazeale/rsleigh#9, ShaneBreazeale/rsleigh#10 and ShaneBreazeale/rsleigh#11.

<!-- gh-comment-id:6111110081 --> @ShaneBreazeale commented on GitHub (Oct 11, 2026): @CassieFox23, an update on the two items that were still open, and a correction to my last comment. **`imagebase` is fixed** in ShaneBreazeale/rsleigh#13. - ELF now reports the lowest `PT_LOAD` page, and Mach-O reports the `__TEXT` address. These are the values Ghidra shows. PE is unchanged. - Before and after on our files: - Debian `ls`: `0x33c` → `0x0` - a non-PIE test ELF: `0x401254` → `0x400000` - a clang arm64 Mach-O: `0x100000328` → `0x100000000` - Evidence IDs for ELF and Mach-O records change with this. Records made by older builds still verify. **Correction about `--xrefs`.** I said it works on our Debian builds. When I looked more closely, `--xrefs sqlite3_free` had three bugs: - It printed two decoder panics to stderr. - It listed `func_c3b500000` as a callee. That is an internal temporary ID, not a function. The same bad name appeared in the normal decompile output. - It reported 0 callers. In the shared library, every call goes through the PLT stub, and that stub was never named. All three are fixed in ShaneBreazeale/rsleigh#12. `--xrefs sqlite3_free` now prints nothing to stderr and finds 321 callers at 837 call sites. objdump counts 895 calls, and the rest are in functions longer than the 4 KiB scan window. **Still open** - `--xrefs` and `--search` are still not in the capabilities catalog. The mapping table in the issue relies on modes that the agent contract does not describe yet. - Your Fedora addresses (`ls` `0x598f`, `sqlite3_free` `0x42960`) are still unconfirmed. A rerun on your binaries would settle it, if you have time. One more fix: in my last comment, the bare numbers #8 to #11 point at REA issues. They mean ShaneBreazeale/rsleigh#8, ShaneBreazeale/rsleigh#9, ShaneBreazeale/rsleigh#10 and ShaneBreazeale/rsleigh#11.
Author
Owner

@CassieFox23 commented on GitHub (Oct 11, 2026):

TL;DR: I reran your list on my Fedora files. Five items are fixed. The loop test in my test main still prints wrong.

Thanks, Shane, for asking. I used master at 8f75176f1 (ShaneBreazeale/rsleigh#11). The ls address (0x598f) is real on my box. The sqlite3_free address (0x42960) is in the sqlite3 command-line program, not in the library. In Fedora's libsqlite3.so, sqlite3_free sits at 0x25f0. So your 3.53.4 library and my file are different files.

What I see on master

Item Result on my Fedora files
Test main Mostly fixed. The hash step, % 7 and returns 2, 1 and 0 all print. The loop ends on while (lVar1 % 7). The assembly loops until the next byte is zero.
ls cold block at 0x598f Fixed. The function grew from 179 to 280 instructions. getenv("TERM") is in the output.
sqlite3_free at 0x42960 Fixed. The body went from empty to 145 bytes. The card warns about an indirect call.
true stub at 0x680 Fixed. It prints return dcgettext();.
Same text each run Fixed. 30 runs of each function gave one text each.
Deadline error code Fixed. A 1 ms deadline gives exit 1 and execution_limit.
Partial status for unresolved control flow Not seen. All four cards say ok. I did not sweep other functions.

Before, ls gave 10 different texts in 20 runs.

Your second comment covers --xrefs. On Fedora, master showed the same problems:

  • --xrefs sqlite3_free on libsqlite3.so prints 4 panic lines on master. It finds 0 callers.
  • --search sqlite3_free on the same file crashes with a stack overflow (exit 134). That is a new problem, not on your list. It happens on master and on #12 too.
  • --search TERM on ls works.

I also tested both pull requests before they merged.

  • ShaneBreazeale/rsleigh#12 fixes the panics here. It finds 350 callers at 908 call sites. objdump counts 1,008 calls through the stub. The 100 missing calls are all in functions over 4 KiB, which fits your scan-window note.
  • One of its new tests fails on my build. My notes on that are on ShaneBreazeale/rsleigh#12.
  • ShaneBreazeale/rsleigh#13 gives the right imagebase on all 7 Fedora files I tried. For ls it goes from 0x33c to 0x0.

Not run

  • Debian binaries and libsqlite 3.53.4.
  • A sweep of all ls and sqlite3 functions for partial status.
  • REA wiring, Ghidra comparison and other file types.
Raw numbers
Function Runs Exit code Texts now Texts on the older build
test main 0x400480 30 0 1 4 in 20 runs
ls 0x5580 30 0 1 10 in 20 runs
sqlite3_free 0x42960 30 0 1 1
true 0x680 30 0 1 1
  • The older build is 0748bd6a7, the one I tested on 10-10.
  • The ls function now spans 0x5580 to 0x5a36. That is 1,208 bytes. Before it was 758 bytes.
  • The ls text is cut at 4,096 of 6,096 bytes. The card says so.
  • The // switch(...) comments in the ls text show a garbled dash. I did not trace the cause.
  • I used coreutils 9.10 (GPL-3.0-or-later) and SQLite 3.51.2 (public domain). Both came from Fedora packages.
  • The test main is the short program from my 10-10 run, built with gcc -O1 -no-pie. Its sha256 matches the 10-10 build.
ls                    54e936d37decdad9ecaf189b1de347605a0a99037ec87bd17679b6cbde871a0c
true                  b639fd49da0eeaffe498316c9788a081d41d2e009d343f8e75d7e5a219e82921
sqlite3               db1adf9d61f39a2a570e9418111f568cba3c8f5623f08b0c39e390addb1bebb8
libsqlite3.so.3.51.2  f5fc84b48c21889070e3f045fd3c984e51116ccc9765fb98c25a8b933bf3366f
test main             67113c19800f18135964a7b014052e23264cdc1e84fa562ccdc66b9e68eca79f
Commands
rsleigh FILE ADDR --card --json --pcode --decompile        # 30 runs each, hash of pseudocode.text
rsleigh FILE 0x42960 --card --json --deadline-ms 1
rsleigh FILE 0x42960 --card --json --max-decode-instructions 1
rsleigh libsqlite3.so.3.51.2 --xrefs sqlite3_free
rsleigh libsqlite3.so.3.51.2 --search sqlite3_free
objdump -d --start-address=0x5580 --stop-address=0x55b0 /usr/bin/ls

Each build: cargo run --locked --release -p rsleigh-generate, then cargo build --locked --release -p rsleigh-cli.

Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.9-200.nobara.fc44.x86_64
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 32 GB DDR4-3600
  • rsleigh: 0.5.0 (Apache-2.0), master as linked above, built with Rust 1.97.0
  • Other: GCC 16.2.1 (GPL-3.0-or-later) built the test program. Another test job shared the box.

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

<!-- gh-comment-id:6111506167 --> @CassieFox23 commented on GitHub (Oct 11, 2026): **TL;DR:** I reran your list on my Fedora files. Five items are fixed. The loop test in my test `main` still prints wrong. Thanks, Shane, for asking. I used master at [8f75176f1](https://github.com/ShaneBreazeale/rsleigh/commit/8f75176f1cb495808b1d98ffb75f03bcdf8252f3) (ShaneBreazeale/rsleigh#11). The `ls` address (0x598f) is real on my box. The `sqlite3_free` address (0x42960) is in the sqlite3 command-line program, not in the library. In Fedora's `libsqlite3.so`, `sqlite3_free` sits at 0x25f0. So your 3.53.4 library and my file are different files. ### What I see on master | Item | Result on my Fedora files | |---|---| | Test `main` | Mostly fixed. The hash step, `% 7` and returns 2, 1 and 0 all print. The loop ends on `while (lVar1 % 7)`. The assembly loops until the next byte is zero. | | `ls` cold block at 0x598f | Fixed. The function grew from 179 to 280 instructions. `getenv("TERM")` is in the output. | | `sqlite3_free` at 0x42960 | Fixed. The body went from empty to 145 bytes. The card warns about an indirect call. | | `true` stub at 0x680 | Fixed. It prints `return dcgettext();`. | | Same text each run | Fixed. 30 runs of each function gave one text each. | | Deadline error code | Fixed. A 1 ms deadline gives exit 1 and `execution_limit`. | | Partial status for unresolved control flow | Not seen. All four cards say `ok`. I did not sweep other functions. | Before, `ls` gave 10 different texts in 20 runs. Your second comment covers `--xrefs`. On Fedora, master showed the same problems: - `--xrefs sqlite3_free` on `libsqlite3.so` prints 4 panic lines on master. It finds 0 callers. - `--search sqlite3_free` on the same file crashes with a stack overflow (exit 134). That is a new problem, not on your list. It happens on master and on #12 too. - `--search TERM` on `ls` works. I also tested both pull requests before they merged. - ShaneBreazeale/rsleigh#12 fixes the panics here. It finds 350 callers at 908 call sites. objdump counts 1,008 calls through the stub. The 100 missing calls are all in functions over 4 KiB, which fits your scan-window note. - One of its new tests fails on my build. My notes on that are on ShaneBreazeale/rsleigh#12. - ShaneBreazeale/rsleigh#13 gives the right `imagebase` on all 7 Fedora files I tried. For `ls` it goes from 0x33c to 0x0. ### Not run - Debian binaries and libsqlite 3.53.4. - A sweep of all `ls` and `sqlite3` functions for partial status. - REA wiring, Ghidra comparison and other file types. <details> <summary>Raw numbers</summary> | Function | Runs | Exit code | Texts now | Texts on the older build | |---|---|---|---|---| | test `main` 0x400480 | 30 | 0 | 1 | 4 in 20 runs | | `ls` 0x5580 | 30 | 0 | 1 | 10 in 20 runs | | `sqlite3_free` 0x42960 | 30 | 0 | 1 | 1 | | `true` 0x680 | 30 | 0 | 1 | 1 | - The older build is [0748bd6a7](https://github.com/ShaneBreazeale/rsleigh/commit/0748bd6a7b10eb512e7cd909808b54ae6683663b), the one I tested on 10-10. - The `ls` function now spans 0x5580 to 0x5a36. That is 1,208 bytes. Before it was 758 bytes. - The `ls` text is cut at 4,096 of 6,096 bytes. The card says so. - The `// switch(...)` comments in the `ls` text show a garbled dash. I did not trace the cause. - I used coreutils 9.10 (GPL-3.0-or-later) and SQLite 3.51.2 (public domain). Both came from Fedora packages. - The test `main` is the short program from my 10-10 run, built with `gcc -O1 -no-pie`. Its sha256 matches the 10-10 build. ``` ls 54e936d37decdad9ecaf189b1de347605a0a99037ec87bd17679b6cbde871a0c true b639fd49da0eeaffe498316c9788a081d41d2e009d343f8e75d7e5a219e82921 sqlite3 db1adf9d61f39a2a570e9418111f568cba3c8f5623f08b0c39e390addb1bebb8 libsqlite3.so.3.51.2 f5fc84b48c21889070e3f045fd3c984e51116ccc9765fb98c25a8b933bf3366f test main 67113c19800f18135964a7b014052e23264cdc1e84fa562ccdc66b9e68eca79f ``` </details> <details> <summary>Commands</summary> ```sh rsleigh FILE ADDR --card --json --pcode --decompile # 30 runs each, hash of pseudocode.text rsleigh FILE 0x42960 --card --json --deadline-ms 1 rsleigh FILE 0x42960 --card --json --max-decode-instructions 1 rsleigh libsqlite3.so.3.51.2 --xrefs sqlite3_free rsleigh libsqlite3.so.3.51.2 --search sqlite3_free objdump -d --start-address=0x5580 --stop-address=0x55b0 /usr/bin/ls ``` Each build: `cargo run --locked --release -p rsleigh-generate`, then `cargo build --locked --release -p rsleigh-cli`. </details> <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.9-200.nobara.fc44.x86_64 - CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads - RAM: 32 GB DDR4-3600 - rsleigh: 0.5.0 (Apache-2.0), master as linked above, built with Rust 1.97.0 - Other: GCC 16.2.1 (GPL-3.0-or-later) built the test program. Another test job shared the box. </details> 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
Author
Owner

@ShaneBreazeale commented on GitHub (Oct 11, 2026):

@CassieFox23, a follow-up on the two items I left open.

--xrefs and --search are now in the capabilities catalog (ShaneBreazeale/rsleigh#15).

  • They print text as before. With --json they emit rsleigh.xrefs/v1 and rsleigh.search/v1.
  • Both are also MCP tools.
  • Each scans the first 4,096 bytes of every function. If any function is longer, the result is now partial (exit 2) and gives a truncated_scans count. Before, the missed calls were silent. On Debian libsqlite3, 28 functions are over the limit, which explains the gap to objdump.

I checked your Fedora addresses on the Fedora 44 packages:

  • ls 0x5580 → 0x598f matches coreutils-9.10-5.fc44 (the update). It does not match the -3 release build.
  • 0x42960 is in /usr/bin/sqlite3 from sqlite-3.51.2-1.fc44 and -2.fc44, not in libsqlite3. That binary is stripped. In libsqlite3.so, sqlite3_free is at 0x25f0.

On current rsleigh master, with your card command:

  • ls 0x5580: the cold block is now in the body, including getenv("TERM").
  • sqlite3 0x42960: the body is no longer empty. It shows the sqlite3_free shape: the null check, then the mutex, size accounting, and free hooks.
  • New bug from this check: those hooks are called through pointers in .data (call [0x1dd0a0]), but rsleigh prints them as direct calls such as func_1dd0a0(). They should read as calls through the pointer. I'll track that separately.
<!-- gh-comment-id:6113523529 --> @ShaneBreazeale commented on GitHub (Oct 11, 2026): @CassieFox23, a follow-up on the two items I left open. **`--xrefs` and `--search` are now in the capabilities catalog** (ShaneBreazeale/rsleigh#15). - They print text as before. With `--json` they emit `rsleigh.xrefs/v1` and `rsleigh.search/v1`. - Both are also MCP tools. - Each scans the first 4,096 bytes of every function. If any function is longer, the result is now `partial` (exit 2) and gives a `truncated_scans` count. Before, the missed calls were silent. On Debian `libsqlite3`, 28 functions are over the limit, which explains the gap to objdump. **I checked your Fedora addresses on the Fedora 44 packages:** - `ls` `0x5580` → `0x598f` matches `coreutils-9.10-5.fc44` (the update). It does not match the `-3` release build. - `0x42960` is in `/usr/bin/sqlite3` from `sqlite-3.51.2-1.fc44` and `-2.fc44`, not in `libsqlite3`. That binary is stripped. In `libsqlite3.so`, `sqlite3_free` is at `0x25f0`. On current rsleigh master, with your card command: - `ls` `0x5580`: the cold block is now in the body, including `getenv("TERM")`. - `sqlite3` `0x42960`: the body is no longer empty. It shows the `sqlite3_free` shape: the null check, then the mutex, size accounting, and free hooks. - **New bug from this check:** those hooks are called through pointers in `.data` (`call [0x1dd0a0]`), but rsleigh prints them as direct calls such as `func_1dd0a0()`. They should read as calls through the pointer. I'll track that separately.
Author
Owner

@CassieFox23 commented on GitHub (Oct 15, 2026):

TL;DR: I timed rsleigh (Apache-2.0) on three files, next to kuna (Apache-2.0) and Ghidra (Apache-2.0). It listed functions and decompiled one function in under 2.5 seconds on all three, even on the 10 MB file. Its decompile-everything run finished on a small file in 15 seconds, but did not finish on the 3.2 MB file in 40 minutes.

The files:

  • GNU coreutils ls (GPL-3.0-or-later), 153 KB.
  • CrashLoggerSSE 1.25.0 (MIT), 3.2 MB.
  • Skyrim A-Pose Fix 1.0.0.0 (GPL-3.0), 10.1 MB.

rsleigh was master at 13765bf06. Each time runs from process start to exit, so it includes loading the file. That is what one call per operation costs on these files.

Job ls, 153 KB CrashLogger, 3.2 MB APoseFix, 10 MB
One function 68 ms 485 ms 2.30 s
List functions 14 ms 249 ms 1.42 s
Every function to C 15.46 s none, 40 minute limit not tried
  • ls, every function: 372 of 373 functions had C, with 1 error. kuna took 5.3 s with 8 workers, and Ghidra took 15.3 s.
  • CrashLogger, every function: one run, stopped at 40 minutes (exit 124). kuna took 23.7 s with 8 workers, and Ghidra took 99.9 s.
  • On the PE files, rsleigh prints "possible scratch leak" warnings on stderr. I did not look into what they mean.
  • I did not read the one-function output. On CrashLogger it was 6 lines (176 bytes), so I cannot say whether it is C.
  • The tools find different function counts (CrashLogger: rsleigh 3,920, kuna 3,404, Ghidra 2,923). I have not reconciled them.

These are first results, and more tests are coming. The APoseFix rows are one pass each and ran under load. "Under load" means the 1-minute load went over 4, and load is the average number of programs running or waiting for a core. This box has 24 threads. The CrashLogger rsleigh run stayed under 3.

The full tables, commands and test setup are in the Show and tell post.

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

<!-- gh-comment-id:6185040111 --> @CassieFox23 commented on GitHub (Oct 15, 2026): **TL;DR:** I timed rsleigh (Apache-2.0) on three files, next to kuna (Apache-2.0) and Ghidra (Apache-2.0). It listed functions and decompiled one function in under 2.5 seconds on all three, even on the 10 MB file. Its decompile-everything run finished on a small file in 15 seconds, but did not finish on the 3.2 MB file in 40 minutes. The files: - GNU coreutils `ls` (GPL-3.0-or-later), 153 KB. - CrashLoggerSSE 1.25.0 (MIT), 3.2 MB. - Skyrim A-Pose Fix 1.0.0.0 (GPL-3.0), 10.1 MB. rsleigh was master at [13765bf06](https://github.com/ShaneBreazeale/rsleigh/commit/13765bf0696166b1cddb48c409e0257c4f13cfa1). Each time runs from process start to exit, so it includes loading the file. That is what one call per operation costs on these files. | Job | `ls`, 153 KB | CrashLogger, 3.2 MB | APoseFix, 10 MB | |---|---|---|---| | One function | 68 ms | 485 ms | 2.30 s | | List functions | 14 ms | 249 ms | 1.42 s | | Every function to C | 15.46 s | none, 40 minute limit | not tried | - `ls`, every function: 372 of 373 functions had C, with 1 error. kuna took 5.3 s with 8 workers, and Ghidra took 15.3 s. - CrashLogger, every function: one run, stopped at 40 minutes (exit 124). kuna took 23.7 s with 8 workers, and Ghidra took 99.9 s. - On the PE files, rsleigh prints "possible scratch leak" warnings on stderr. I did not look into what they mean. - I did not read the one-function output. On CrashLogger it was 6 lines (176 bytes), so I cannot say whether it is C. - The tools find different function counts (CrashLogger: rsleigh 3,920, kuna 3,404, Ghidra 2,923). I have not reconciled them. These are first results, and more tests are coming. The APoseFix rows are one pass each and ran under load. "Under load" means the 1-minute load went over 4, and load is the average number of programs running or waiting for a core. This box has 24 threads. The CrashLogger rsleigh run stayed under 3. The full tables, commands and test setup are in the [Show and tell post](https://github.com/morluto/rea/discussions/3858). 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
Author
Owner

@ShaneBreazeale commented on GitHub (Oct 16, 2026):

@CassieFox23, thanks for the timings and the C reads. They found real bugs. Fixes are in rsleigh v0.7.0 (cargo install rsleigh --version 0.7.0 --locked, or the release archives).

--all now finishes. I profiled CrashLoggerSSE 1.25.0 (same sha256 as your run). There were three causes:

  • One function, 0x18012e350, never finished. It is a double-to-decimal routine, and its conditions were printed in exponential time. With no per-function limit, it stalled the whole run. It now takes 0.12 s (ShaneBreazeale/rsleigh#30).
  • Work that depends only on the file was redone for every function: the import table (3 times per function, ~100 ms each) and the 32 MB CrashLogger.pdb that ships next to the DLL. This is now done once per file (ShaneBreazeale/rsleigh#31).
  • Everything ran on one thread. There is now --jobs N (default: one per core) and --fn-timeout SECS (default 10 s with --all). JSON is printed as functions finish, and the output is the same for any --jobs (ShaneBreazeale/rsleigh#32).

rsleigh FILE --all --json on CrashLogger now takes 41 s with 10 threads and 239 s with 1, on an Apple M4. That is not your Ryzen box, so it is not a head-to-head number. I have not rerun APoseFix.

The one-line CrashLogger body (your Update 3). Your diagnosis was right on all four points. MSVC/clang-cl tables with image-base offsets, a zero-extended entry, and a signed-byte index with an entry at −1 are now recovered (ShaneBreazeale/rsleigh#29). 0x18006a500 prints a switch with case -1, 2, 3 and default. Across the whole file, recovered jump tables went from 0 to 73.

Silent partial output. Plain output and --json now report a body that stops at a branch rsleigh could not follow. The text gets a // WARNING: line, each JSON function gets a status, and the exit code is 2 (ShaneBreazeale/rsleigh#28). The "possible scratch leak" messages were a check for one Python-extension packer that ran on every PE. It now runs only on files that import the Python C API.

Compiling the C (your Update 1). I reran your check (clang -fsyntax-only -std=gnu17 -w, extern stubs for callees and data labels) on 600 CrashLogger functions:

Compiles
v0.6.1 (your 5 functions) 0 of 5
v0.7.0 (600 functions) 34%

The fixes behind that:

Wrong C, not just invalid C. Three of these changed meaning, not only syntax:

  • Variables read before they are set. A text rule that hides register copies also deleted loads such as EAX = param_0->field_48;. Functions with a variable that is read but never set went from 2,772 to 2,138 of 3,889 (ShaneBreazeale/rsleigh#38).
  • MSVC /GS functions returned the cookie check's result instead of their own value. CrashLogger 0x18004e270 printed return func_18013d100(lVar2 ^ RSP - 128); it now returns param_0 (ShaneBreazeale/rsleigh#36).
  • Struct field names from the PDB could change from run to run, because ->field18 matched the name for offset 0x1 (ShaneBreazeale/rsleigh#31).

Still open:

  • Win32 field names put on unrelated structs by offset (lpDesktop on a std::string). This is now the most common remaining compile error.
  • Pointer/integer mixing in declarations.
  • The spurious float parameter.
  • The function counts are still not reconciled.

I'm working on the first and third next.

🤖 Generated with Claude Code (Opus 5.5)

<!-- gh-comment-id:6225104674 --> @ShaneBreazeale commented on GitHub (Oct 16, 2026): @CassieFox23, thanks for the timings and the C reads. They found real bugs. Fixes are in **rsleigh v0.7.0** (`cargo install rsleigh --version 0.7.0 --locked`, or the release archives). **`--all` now finishes.** I profiled CrashLoggerSSE 1.25.0 (same sha256 as your run). There were three causes: - One function, `0x18012e350`, never finished. It is a double-to-decimal routine, and its conditions were printed in exponential time. With no per-function limit, it stalled the whole run. It now takes 0.12 s (ShaneBreazeale/rsleigh#30). - Work that depends only on the file was redone for every function: the import table (3 times per function, ~100 ms each) and the 32 MB `CrashLogger.pdb` that ships next to the DLL. This is now done once per file (ShaneBreazeale/rsleigh#31). - Everything ran on one thread. There is now `--jobs N` (default: one per core) and `--fn-timeout SECS` (default 10 s with `--all`). JSON is printed as functions finish, and the output is the same for any `--jobs` (ShaneBreazeale/rsleigh#32). `rsleigh FILE --all --json` on CrashLogger now takes 41 s with 10 threads and 239 s with 1, on an Apple M4. That is not your Ryzen box, so it is not a head-to-head number. I have not rerun APoseFix. **The one-line CrashLogger body (your Update 3).** Your diagnosis was right on all four points. MSVC/clang-cl tables with image-base offsets, a zero-extended entry, and a signed-byte index with an entry at −1 are now recovered (ShaneBreazeale/rsleigh#29). `0x18006a500` prints a `switch` with `case -1`, `2`, `3` and `default`. Across the whole file, recovered jump tables went from 0 to 73. **Silent partial output.** Plain output and `--json` now report a body that stops at a branch rsleigh could not follow. The text gets a `// WARNING:` line, each JSON function gets a `status`, and the exit code is 2 (ShaneBreazeale/rsleigh#28). The "possible scratch leak" messages were a check for one Python-extension packer that ran on every PE. It now runs only on files that import the Python C API. **Compiling the C (your Update 1).** I reran your check (`clang -fsyntax-only -std=gnu17 -w`, extern stubs for callees and data labels) on 600 CrashLogger functions: | | Compiles | |---|---| | v0.6.1 (your 5 functions) | 0 of 5 | | v0.7.0 (600 functions) | 34% | The fixes behind that: - `->` on a `long`: a variable used only through consistent fields gets a packed struct. Any other access prints as `*(T *)(base + 0xN)` (ShaneBreazeale/rsleigh#33). - Undeclared names: stack slots, leftover registers and generated names are now declared (ShaneBreazeale/rsleigh#34, ShaneBreazeale/rsleigh#38). - The extra `)` in `ls` came from an `S_ISDIR` rewrite. Separately, conditions were printed without C precedence, so `x & 0xf000 != 16384` meant `x & (0xf000 != 16384)` (ShaneBreazeale/rsleigh#28, ShaneBreazeale/rsleigh#30). - `(*(void *)swi(0x29))(5)`-style calls print as `__fastfail(5)` (ShaneBreazeale/rsleigh#35). - C++ names print as C identifiers, e.g. `std__exception__vftable` (ShaneBreazeale/rsleigh#38). **Wrong C, not just invalid C.** Three of these changed meaning, not only syntax: - **Variables read before they are set.** A text rule that hides register copies also deleted loads such as `EAX = param_0->field_48;`. Functions with a variable that is read but never set went from 2,772 to 2,138 of 3,889 (ShaneBreazeale/rsleigh#38). - **MSVC `/GS` functions** returned the cookie check's result instead of their own value. CrashLogger `0x18004e270` printed `return func_18013d100(lVar2 ^ RSP - 128)`; it now returns `param_0` (ShaneBreazeale/rsleigh#36). - **Struct field names from the PDB** could change from run to run, because `->field18` matched the name for offset `0x1` (ShaneBreazeale/rsleigh#31). **Still open:** - Win32 field names put on unrelated structs by offset (`lpDesktop` on a `std::string`). This is now the most common remaining compile error. - Pointer/integer mixing in declarations. - The spurious `float` parameter. - The function counts are still not reconciled. I'm working on the first and third next. 🤖 Generated with Claude Code (Opus 5.5)
Author
Owner

@CassieFox23 commented on GitHub (Oct 18, 2026):

TL;DR: I reran my earlier rsleigh (Apache-2.0) checks on v0.7.0. Two fixes held on my files. --all on CrashLoggerSSE 1.25.0 (MIT) now finishes in 24 seconds. The jump-table function prints a switch. The C fix held in part: 1 of my 5 C files now compiles, up from 0, and I could not rerun the 10 MB file.

Thanks, ShaneBreazeale, for the fixes and the detailed reply. I built the v0.7.0 tag, commit 158488f45 (ShaneBreazeale/rsleigh#39). Master has moved on by 10 commits since, and I did not test those.

Your three fixes, on my files

Fix Before v0.7.0
--all finishes CrashLoggerSSE 1.25.0, --all --json: stopped at the 40 minute limit, exit 124, no JSON 24.42 s, exit 2, 590 MiB. 3,889 functions: 3,618 ok, 271 partial. No function hit the per-function time limit.
Jump table at 0x18006a500 One line, iVar1 = *(uint32_t*)(0x6a650 + DAT_180000000 + (char)param_1->field_48 * 4); switch (*(int8_t *)(param_1 + 0x48)) with case -1, case 2, case 3 and default
C that compiles 0 of 5 functions 1 of 5 (ls 0x3e10)

The C check used clang 22.1.8 and gcc 16.2.1 with -std=gnu17 -fsyntax-only -w, on the same five functions as my earlier check. I wrote my own stubs for callees and data labels, so they may differ from yours. Some of the four failures may come from my stubs.

What changed in the ls function at 0x3e10:

  • The extra ) is gone. It now reads if ((*(int32_t *)((char *)param_1 + 0xac) & 0xf000) != 0x4000 /* !S_ISDIR */) {.
  • iVar2 is now set before if (iVar2 == 3). lVar2 is still read without being set.
  • param_0->field_a8 on a const char * is now *(int32_t *)((char *)param_0 + 0xa8).

Still wrong in the CrashLogger function: float fparam_0 is still in the signature, and puVar1->lpDesktop = 0; puts a Win32 field name on a void *. You list that as still open. The --card --decompile call on that function still exits 2 and says partial, now with decode_failed at 0x18006a64e instead of unresolved_control_flow at 0x18006a520.

Old numbers next to new ones

Each cell is wall time, then peak memory of the process, in MiB. The old numbers come from three earlier commits, so I name each. The old posts said "MB" for the same field, which I believe is KiB divided by 1,024 (unverified for the 10-15 post).

  • A: 0748bd6a7, median of 5, quiet box (my 10-10 post).
  • B: 13765bf06, median of 3 (my 10-15 post, some rows under load).
  • New: v0.7.0, median of 3. The one-minute load average at the start was 5.1 to 6.8, which is above the quiet box. Load average is how many jobs were running or waiting for a CPU thread, out of 24.
Job Old v0.7.0
Card, test main A: 0.08 s, 72 0.08 s, 73
Card, true stub A: 0.07 s, 75 0.06 s, 76
Card, ls 0x5580 A: 0.12 s, 75 0.14 s, 76
Card, sqlite3_free A: 0.12 s, 77 0.13 s, 78
Brief, test main A: 0.05 s, 30 0.05 s, 31
Brief, true A: 0.10 s, 31 0.10 s, 33
Brief, ls A: 0.23 s, 34 0.20 s, 35
Brief, sqlite3 A: 1.86 s, 40 1.87 s, 42
One function, ls 0x3e10 B: 68 ms, 30 0.06 s, 30
List functions, ls B: 14 ms, 12 0.01 s, 12
One function, CrashLogger B: 485 ms, 34 0.30 s, 35
List functions, CrashLogger B: 249 ms, 11 0.14 s, 14
--all, ls B: 15.46 s, 92. 373 found, 372 with C, 1 error. 3.92 s, 372. 373 found, all ok, 372 with C text.
--all, CrashLogger B: no result at the 40 minute limit, exit 124, 641 24.42 s, 590. Exit 2. 3,889 found.

With one thread (--jobs 1), CrashLoggerSSE --all took 4 min 36 s, 72 MiB, exit 2 (load 7.8 at the start, so rough). The JSON was byte-identical to the 24-thread run, as the reply says. The single-thread run is the closest match to the earlier 40 minute run.

Your Update 3 case and my earlier error-code checks also hold or improved:

  • --deadline-ms 1 on 0x42960 still exits 1 with error_code execution_limit.
  • --xrefs sqlite3_free on the Fedora libsqlite3.so.3.51.2 no longer panics. It finds 387 callers at 978 call sites, with exit 2. I do not know why it exits 2.
  • --search sqlite3_free on the same file no longer crashes with a stack overflow. It finds 387 matches, exit 2, but takes 63 s.

Not run

  • Skyrim A-Pose Fix, the 10 MB file. My copy is gone, and the repository has no release to download. So I did not repeat the 90 minute --all row.
  • Your 600-function compile sample (the 34%) and your count of recovered jump tables (0 to 73).
  • Reading the new C against the disassembly. The C checks here only ask if the code compiles.
  • Medians of 5 on a quiet box. I used 3 runs, with other jobs on the machine.
  • REA, kuna and Ghidra columns. This run was rsleigh only.
Commands

Build, from the extracted v0.7.0 tag. The generate step must run with the clone as the working folder.

cargo fetch --locked
cargo run --locked --offline --release -p rsleigh-generate
cargo build --locked --offline --release

Timed runs. Each was /usr/bin/time -v timeout N rsleigh .... The --all runs were inside systemd-run --user --scope -p MemoryMax=8G -p MemorySwapMax=0, with timeout 1800.

rsleigh CrashLogger.dll --all --json
rsleigh CrashLogger.dll --all --json --jobs 1
rsleigh ls --all --json
rsleigh FILE ADDR --card --json --pcode --decompile      # hello 0x400480, true 0x680, ls 0x5580, sqlite3 0x42960
rsleigh FILE --agent-brief --json
rsleigh ls 0x3e10                                        # and 0x3ee0, 0x4940
rsleigh ls                                               # list functions
rsleigh CrashLogger.dll 0x18006a500                      # and 0x180075030
rsleigh CrashLogger.dll                                  # list functions
rsleigh CrashLogger.dll 0x18006a500 --card --json --decompile
rsleigh sqlite3 0x42960 --card --json --deadline-ms 1
rsleigh sqlite3 0x42960 --card --json --max-decode-instructions 1
rsleigh libsqlite3.so.3.51.2 --xrefs sqlite3_free
rsleigh libsqlite3.so.3.51.2 --search sqlite3_free
rsleigh ls --search TERM

C check, for each output file after adding the stubs:

clang -fsyntax-only -std=gnu17 -w -x c FILE
gcc   -fsyntax-only -std=gnu17 -w -x c FILE
Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.9-200.nobara.fc44.x86_64
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 32 GB DDR4-3600
  • rsleigh: 0.7.0 (Apache-2.0), tag v0.7.0, commit 158488f45, build id 8f6de438e9d8717f692801f4ea78bece9305c8f2
  • Rust: rustc 1.97.0 (MIT OR Apache-2.0). The generate step took 14.7 s and the release build 4 min 15 s.
  • Compilers for the C check: clang 22.1.8 (Apache-2.0 WITH LLVM-exception) and GCC 16.2.1 (GPL-3.0-or-later). GCC also built the test main with -O1 -no-pie.
  • Files: CrashLoggerSSE 1.25.0 (MIT), 3,209,728 bytes, sha256 c5ef54156f4eff3d94769dd839ca465ad5c302293d397fd452fb805f1b328122 (same as my 10-15 posts). The release archive had no PDB beside the DLL in my main runs.
  • Files: coreutils 9.10 ls and true (GPL-3.0-or-later), SQLite 3.51.2 (public domain), from Fedora packages. Same sha256 as my earlier posts.
  • Other: the one-minute load average at the start of each run was 1.7 to 6.8. Other jobs shared the box, and one of my own --jobs 1 runs ran in the background during the small runs.

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

<!-- gh-comment-id:6254646368 --> @CassieFox23 commented on GitHub (Oct 18, 2026): **TL;DR:** I reran my earlier rsleigh (Apache-2.0) checks on v0.7.0. Two fixes held on my files. `--all` on CrashLoggerSSE 1.25.0 (MIT) now finishes in 24 seconds. The jump-table function prints a `switch`. The C fix held in part: 1 of my 5 C files now compiles, up from 0, and I could not rerun the 10 MB file. Thanks, ShaneBreazeale, for the fixes and the detailed reply. I built the v0.7.0 tag, commit [158488f45](https://github.com/ShaneBreazeale/rsleigh/commit/158488f45d72d1c15ecb53d8fcaff1ab8e39646e) (ShaneBreazeale/rsleigh#39). Master has moved on by 10 commits since, and I did not test those. ### Your three fixes, on my files | Fix | Before | v0.7.0 | |---|---|---| | `--all` finishes | CrashLoggerSSE 1.25.0, `--all --json`: stopped at the 40 minute limit, exit 124, no JSON | 24.42 s, exit 2, 590 MiB. 3,889 functions: 3,618 `ok`, 271 `partial`. No function hit the per-function time limit. | | Jump table at `0x18006a500` | One line, `iVar1 = *(uint32_t*)(0x6a650 + DAT_180000000 + (char)param_1->field_48 * 4);` | `switch (*(int8_t *)(param_1 + 0x48))` with `case -1`, `case 2`, `case 3` and `default` | | C that compiles | 0 of 5 functions | 1 of 5 (`ls` `0x3e10`) | The C check used clang 22.1.8 and gcc 16.2.1 with `-std=gnu17 -fsyntax-only -w`, on the same five functions as my earlier check. I wrote my own stubs for callees and data labels, so they may differ from yours. Some of the four failures may come from my stubs. What changed in the `ls` function at `0x3e10`: - The extra `)` is gone. It now reads `if ((*(int32_t *)((char *)param_1 + 0xac) & 0xf000) != 0x4000 /* !S_ISDIR */) {`. - `iVar2` is now set before `if (iVar2 == 3)`. `lVar2` is still read without being set. - `param_0->field_a8` on a `const char *` is now `*(int32_t *)((char *)param_0 + 0xa8)`. Still wrong in the CrashLogger function: `float fparam_0` is still in the signature, and `puVar1->lpDesktop = 0;` puts a Win32 field name on a `void *`. You list that as still open. The `--card --decompile` call on that function still exits 2 and says `partial`, now with `decode_failed` at `0x18006a64e` instead of `unresolved_control_flow` at `0x18006a520`. ### Old numbers next to new ones Each cell is wall time, then peak memory of the process, in MiB. The old numbers come from three earlier commits, so I name each. The old posts said "MB" for the same field, which I believe is KiB divided by 1,024 (unverified for the 10-15 post). - A: 0748bd6a7, median of 5, quiet box (my 10-10 post). - B: 13765bf06, median of 3 (my 10-15 post, some rows under load). - New: v0.7.0, median of 3. The one-minute load average at the start was 5.1 to 6.8, which is above the quiet box. Load average is how many jobs were running or waiting for a CPU thread, out of 24. | Job | Old | v0.7.0 | |---|---|---| | Card, test `main` | A: 0.08 s, 72 | 0.08 s, 73 | | Card, `true` stub | A: 0.07 s, 75 | 0.06 s, 76 | | Card, `ls` `0x5580` | A: 0.12 s, 75 | 0.14 s, 76 | | Card, `sqlite3_free` | A: 0.12 s, 77 | 0.13 s, 78 | | Brief, test `main` | A: 0.05 s, 30 | 0.05 s, 31 | | Brief, `true` | A: 0.10 s, 31 | 0.10 s, 33 | | Brief, `ls` | A: 0.23 s, 34 | 0.20 s, 35 | | Brief, `sqlite3` | A: 1.86 s, 40 | 1.87 s, 42 | | One function, `ls` `0x3e10` | B: 68 ms, 30 | 0.06 s, 30 | | List functions, `ls` | B: 14 ms, 12 | 0.01 s, 12 | | One function, CrashLogger | B: 485 ms, 34 | 0.30 s, 35 | | List functions, CrashLogger | B: 249 ms, 11 | 0.14 s, 14 | | `--all`, `ls` | B: 15.46 s, 92. 373 found, 372 with C, 1 error. | 3.92 s, 372. 373 found, all `ok`, 372 with C text. | | `--all`, CrashLogger | B: no result at the 40 minute limit, exit 124, 641 | 24.42 s, 590. Exit 2. 3,889 found. | With one thread (`--jobs 1`), CrashLoggerSSE `--all` took 4 min 36 s, 72 MiB, exit 2 (load 7.8 at the start, so rough). The JSON was byte-identical to the 24-thread run, as the reply says. The single-thread run is the closest match to the earlier 40 minute run. Your Update 3 case and my earlier error-code checks also hold or improved: - `--deadline-ms 1` on `0x42960` still exits 1 with `error_code` `execution_limit`. - `--xrefs sqlite3_free` on the Fedora `libsqlite3.so.3.51.2` no longer panics. It finds 387 callers at 978 call sites, with exit 2. I do not know why it exits 2. - `--search sqlite3_free` on the same file no longer crashes with a stack overflow. It finds 387 matches, exit 2, but takes 63 s. ### Not run - Skyrim A-Pose Fix, the 10 MB file. My copy is gone, and the repository has no release to download. So I did not repeat the 90 minute `--all` row. - Your 600-function compile sample (the 34%) and your count of recovered jump tables (0 to 73). - Reading the new C against the disassembly. The C checks here only ask if the code compiles. - Medians of 5 on a quiet box. I used 3 runs, with other jobs on the machine. - REA, kuna and Ghidra columns. This run was rsleigh only. <details> <summary>Commands</summary> Build, from the extracted v0.7.0 tag. The generate step must run with the clone as the working folder. ```sh cargo fetch --locked cargo run --locked --offline --release -p rsleigh-generate cargo build --locked --offline --release ``` Timed runs. Each was `/usr/bin/time -v timeout N rsleigh ...`. The `--all` runs were inside `systemd-run --user --scope -p MemoryMax=8G -p MemorySwapMax=0`, with `timeout 1800`. ```sh rsleigh CrashLogger.dll --all --json rsleigh CrashLogger.dll --all --json --jobs 1 rsleigh ls --all --json rsleigh FILE ADDR --card --json --pcode --decompile # hello 0x400480, true 0x680, ls 0x5580, sqlite3 0x42960 rsleigh FILE --agent-brief --json rsleigh ls 0x3e10 # and 0x3ee0, 0x4940 rsleigh ls # list functions rsleigh CrashLogger.dll 0x18006a500 # and 0x180075030 rsleigh CrashLogger.dll # list functions rsleigh CrashLogger.dll 0x18006a500 --card --json --decompile rsleigh sqlite3 0x42960 --card --json --deadline-ms 1 rsleigh sqlite3 0x42960 --card --json --max-decode-instructions 1 rsleigh libsqlite3.so.3.51.2 --xrefs sqlite3_free rsleigh libsqlite3.so.3.51.2 --search sqlite3_free rsleigh ls --search TERM ``` C check, for each output file after adding the stubs: ```sh clang -fsyntax-only -std=gnu17 -w -x c FILE gcc -fsyntax-only -std=gnu17 -w -x c FILE ``` </details> <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.9-200.nobara.fc44.x86_64 - CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads - RAM: 32 GB DDR4-3600 - rsleigh: 0.7.0 (Apache-2.0), tag v0.7.0, commit [158488f45](https://github.com/ShaneBreazeale/rsleigh/commit/158488f45d72d1c15ecb53d8fcaff1ab8e39646e), build id 8f6de438e9d8717f692801f4ea78bece9305c8f2 - Rust: rustc 1.97.0 (MIT OR Apache-2.0). The generate step took 14.7 s and the release build 4 min 15 s. - Compilers for the C check: clang 22.1.8 (Apache-2.0 WITH LLVM-exception) and GCC 16.2.1 (GPL-3.0-or-later). GCC also built the test `main` with `-O1 -no-pie`. - Files: CrashLoggerSSE 1.25.0 (MIT), 3,209,728 bytes, sha256 c5ef54156f4eff3d94769dd839ca465ad5c302293d397fd452fb805f1b328122 (same as my 10-15 posts). The release archive had no PDB beside the DLL in my main runs. - Files: coreutils 9.10 `ls` and `true` (GPL-3.0-or-later), SQLite 3.51.2 (public domain), from Fedora packages. Same sha256 as my earlier posts. - Other: the one-minute load average at the start of each run was 1.7 to 6.8. Other jobs shared the box, and one of my own `--jobs 1` runs ran in the background during the small runs. </details> 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
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#483
No description provided.