[GH-ISSUE #487] [Feature] Improve Ghidra AArch64 jump-table recovery #261

Open
opened 2026-10-15 18:23:07 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @morluto on GitHub (Sep 29, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/487

Before submitting

  • I searched open and closed issues for related proposals.
  • I removed credentials, private paths, and sensitive target data.

Area

Native analysis or decompilation

Current limitation

REA's Ghidra native API boundary can report candidate jump-table mappings, but AArch64 analysis may leave table bounds and case-value-to-target relationships unresolved. In a real Ghidra 12.1.4 run on a source-built ARM64 Mach-O switch fixture, the result contained 41 mappings and five residual unknowns; two of those unknowns concerned exact table entry size/count and case-value mapping. This is a coverage observation, not evidence that the returned mappings are incorrect.

Use cases

  • Explain how a compiled ARM64 switch maps selector values to branch targets.
  • Distinguish fully recovered mappings from candidate targets and unresolved table structure.
  • Keep Ghidra results useful across Mach-O and AArch64 ELF targets without treating compiler-specific lowering as a universal pattern.

Desired outcome and success criteria

Improve REA's Ghidra AArch64 jump-table extraction and reporting where the binary and Ghidra evidence support it. The result should:

  • recover table data sources, dispatch instructions, entry sizes/counts, and case-value-to-target mappings when those facts are directly supported;
  • preserve confidence and evidence for each recovered mapping;
  • report ambiguous bounds, missing case values, unsupported instruction patterns, and truncation as explicit residual unknowns;
  • never fill missing values by assuming a compiler's usual layout or by extrapolating from neighboring targets.

Add source-owned fixtures for ARM64 Mach-O and AArch64 ELF switch layouts, including dense, sparse, stripped, and optimized variants where the supported toolchain can produce them. Verify exact mappings for recoverable cases and explicit unknowns for cases that remain ambiguous. Keep this coverage in the host-native or cross-format Ghidra lanes according to the fixture's toolchain requirements.

Authority boundaries and non-goals

  • Static analysis only; do not execute the target or infer runtime behavior.
  • Do not claim complete recovery when case values or bounds remain unknown.
  • This issue concerns REA's normalization and evidence contract around Ghidra output; it does not request changes to Ghidra itself.
  • #481 Add a read-only Ghidra provider for macOS
  • #482 Trace native state and value dependencies across functions
Originally created by @morluto on GitHub (Sep 29, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/487 ## Before submitting - [x] I searched open and closed issues for related proposals. - [x] I removed credentials, private paths, and sensitive target data. ## Area Native analysis or decompilation ## Current limitation REA's Ghidra native API boundary can report candidate jump-table mappings, but AArch64 analysis may leave table bounds and case-value-to-target relationships unresolved. In a real Ghidra 12.1.4 run on a source-built ARM64 Mach-O switch fixture, the result contained 41 mappings and five residual unknowns; two of those unknowns concerned exact table entry size/count and case-value mapping. This is a coverage observation, not evidence that the returned mappings are incorrect. ## Use cases - Explain how a compiled ARM64 switch maps selector values to branch targets. - Distinguish fully recovered mappings from candidate targets and unresolved table structure. - Keep Ghidra results useful across Mach-O and AArch64 ELF targets without treating compiler-specific lowering as a universal pattern. ## Desired outcome and success criteria Improve REA's Ghidra AArch64 jump-table extraction and reporting where the binary and Ghidra evidence support it. The result should: - recover table data sources, dispatch instructions, entry sizes/counts, and case-value-to-target mappings when those facts are directly supported; - preserve confidence and evidence for each recovered mapping; - report ambiguous bounds, missing case values, unsupported instruction patterns, and truncation as explicit residual unknowns; - never fill missing values by assuming a compiler's usual layout or by extrapolating from neighboring targets. Add source-owned fixtures for ARM64 Mach-O and AArch64 ELF switch layouts, including dense, sparse, stripped, and optimized variants where the supported toolchain can produce them. Verify exact mappings for recoverable cases and explicit unknowns for cases that remain ambiguous. Keep this coverage in the host-native or cross-format Ghidra lanes according to the fixture's toolchain requirements. ## Authority boundaries and non-goals - Static analysis only; do not execute the target or infer runtime behavior. - Do not claim complete recovery when case values or bounds remain unknown. - This issue concerns REA's normalization and evidence contract around Ghidra output; it does not request changes to Ghidra itself. ## Related work - #481 Add a read-only Ghidra provider for macOS - #482 Trace native state and value dependencies across functions
Author
Owner

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

TL;DR: I ran this on two real AArch64 ELF libraries from open-source Android apps. Switches the compiler made came back fully mapped. Hand-written assembly with a computed branch came back empty, and REA did not flag that gap as an unknown. One hand-written jump table failed with a decompiler error dressed up as "retry or run doctor".

Data point on real AArch64 ELF targets with the published rea-agents@6.2.0, through rea inspect-native-api <so> <address> --provider ghidra. Targets:

  • librenderscript-toolkit.so (387 KB, NDK r21e). It is the Apache-2.0 RenderScript toolkit from AOSP as shipped in Lawnchair 14, itself Apache-2.0.
  • libjni_latinime.so (1.3 MB, NDK r28). It is the Apache-2.0 AOSP LatinIME engine as shipped in HeliBoard, a GPL-3.0 app.

I found dispatch candidates by scanning .text for br Xn (428 and 147 sites) and sampled from them.

Switches the compiler made came back clean:

function br sites tables data source mappings null cases defaults
BlendTask::blend 1 1 ghidra-decompiler-load-table, 2-byte entries, 15 15 0 1
ColorMatrixTask::kernel 2 2 same, 1-byte entries, 4 each 4 + 4 0 0, with the explicit "no typed default label" limitation

Hand-written NEON assembly (the blur and blend kernels) is where the gaps are:

  • Take the br x8 at 0x1262d8. Ghidra folds it into rsdIntrinsicBlurU1_K (entry 0x12c440) as a second body block. The same pattern is at 0x12b298 in the unnamed kernel at 0x12adc0. Here x8 is an adrp/add code address adjusted by x5 shifted left 6 and 3. That is a computed jump into unrolled code. Then jump_tables is empty, unsupported_branches is empty, and no residual unknown mentions the dispatch. The dossier reads as complete.
  • rsdIntrinsicBlend_K (0x1255d0, 15 instructions) is a textbook relative halfword table: adrp/add x5 to the table, cmp w2, #0xf with b.cs to the default, ldrsh x6, [x5, w2, UXTW #1], cbz x6, adr x5 to the dispatch base, add x6, x5, x6, br x6. The call fails with execution_failure, cause Ghidra decompilation failed: Recoverable Error: Unable to find unique hash for varnode, and the remediation says retry once or run rea doctor. A decompiler failure on one function reads as an environment problem.
  • 291 of the 428 br sites sit in a 10 KB symbol-less stretch between rsdIntrinsicBlurU4_K and rsdIntrinsicColorMatrix_int_K. Ghidra's auto-analysis created no procedure there: none of the 1,032 addressed procedures in list_procedures fall in 0x12c564-0x12edf0. The neighbours are 0x12c4d0 and 0x12edf0. Those sites return No procedure exists at the requested address, which is honest, but there is no way to ask about them.
More detail

On the silence: in 6.2.0 unsupported_branches is either empty (boundary available) or the fixed pair structured-boundary-types / jump-table-data-mapping (boundary unavailable). And the "did not verify a supported byte/halfword relative dispatch chain" limitation only attaches to a table Ghidra already found. So an unresolved computed br inside an analyzed function has no field that could carry it today.

Ten other sampled sites across both libraries reported no table. The seven I looked at were indirect tail calls (the vtable shape ldr x8, [x0], ldr x16, [x8, #0x28], frame restore, br x16) or a register trampoline. So no table is right. None of the eight sampled LatinIME sites turned out to be a switch, so that library only contributed negatives.

Each CLI call re-imported the library. That took about 19 s for the 387 KB one and 29 s for the 1.3 MB one.

Test setup
  • Box: Linux x86-64
  • Other: Ghidra 12.1.4, JDK 21

🤖 Generated with Claude Code (Fable 5.1) 🦊

<!-- gh-comment-id:6087737312 --> @CassieFox23 commented on GitHub (Oct 9, 2026): **TL;DR:** I ran this on two real AArch64 ELF libraries from open-source Android apps. Switches the compiler made came back fully mapped. Hand-written assembly with a computed branch came back empty, and REA did not flag that gap as an unknown. One hand-written jump table failed with a decompiler error dressed up as "retry or run doctor". Data point on real AArch64 ELF targets with the published `rea-agents@6.2.0`, through `rea inspect-native-api <so> <address> --provider ghidra`. Targets: - `librenderscript-toolkit.so` (387 KB, NDK r21e). It is the Apache-2.0 RenderScript toolkit from AOSP as shipped in Lawnchair 14, itself Apache-2.0. - `libjni_latinime.so` (1.3 MB, NDK r28). It is the Apache-2.0 AOSP LatinIME engine as shipped in HeliBoard, a GPL-3.0 app. I found dispatch candidates by scanning `.text` for `br Xn` (428 and 147 sites) and sampled from them. Switches the compiler made came back clean: | function | `br` sites | tables | data source | mappings | null cases | defaults | | --- | --- | --- | --- | --- | --- | --- | | `BlendTask::blend` | 1 | 1 | `ghidra-decompiler-load-table`, 2-byte entries, 15 | 15 | 0 | 1 | | `ColorMatrixTask::kernel` | 2 | 2 | same, 1-byte entries, 4 each | 4 + 4 | 0 | 0, with the explicit "no typed default label" limitation | Hand-written NEON assembly (the blur and blend kernels) is where the gaps are: - Take the `br x8` at `0x1262d8`. Ghidra folds it into `rsdIntrinsicBlurU1_K` (entry `0x12c440`) as a second body block. The same pattern is at `0x12b298` in the unnamed kernel at `0x12adc0`. Here `x8` is an `adrp`/`add` code address adjusted by `x5` shifted left 6 and 3. That is a computed jump into unrolled code. Then `jump_tables` is empty, `unsupported_branches` is empty, and no residual unknown mentions the dispatch. The dossier reads as complete. - `rsdIntrinsicBlend_K` (`0x1255d0`, 15 instructions) is a textbook relative halfword table: `adrp`/`add x5` to the table, `cmp w2, #0xf` with `b.cs` to the default, `ldrsh x6, [x5, w2, UXTW #1]`, `cbz x6`, `adr x5` to the dispatch base, `add x6, x5, x6`, `br x6`. The call fails with `execution_failure`, cause `Ghidra decompilation failed: Recoverable Error: Unable to find unique hash for varnode`, and the remediation says retry once or run `rea doctor`. A decompiler failure on one function reads as an environment problem. - 291 of the 428 `br` sites sit in a 10 KB symbol-less stretch between `rsdIntrinsicBlurU4_K` and `rsdIntrinsicColorMatrix_int_K`. Ghidra's auto-analysis created no procedure there: none of the 1,032 addressed procedures in `list_procedures` fall in `0x12c564`-`0x12edf0`. The neighbours are `0x12c4d0` and `0x12edf0`. Those sites return `No procedure exists at the requested address`, which is honest, but there is no way to ask about them. <details> <summary>More detail</summary> On the silence: in 6.2.0 `unsupported_branches` is either empty (boundary available) or the fixed pair `structured-boundary-types` / `jump-table-data-mapping` (boundary unavailable). And the "did not verify a supported byte/halfword relative dispatch chain" limitation only attaches to a table Ghidra already found. So an unresolved computed `br` inside an analyzed function has no field that could carry it today. Ten other sampled sites across both libraries reported no table. The seven I looked at were indirect tail calls (the vtable shape `ldr x8, [x0]`, `ldr x16, [x8, #0x28]`, frame restore, `br x16`) or a register trampoline. So no table is right. None of the eight sampled LatinIME sites turned out to be a switch, so that library only contributed negatives. Each CLI call re-imported the library. That took about 19 s for the 387 KB one and 29 s for the 1.3 MB one. </details> <details> <summary>Test setup</summary> - Box: Linux x86-64 - Other: Ghidra 12.1.4, JDK 21 </details> 🤖 Generated with Claude Code (Fable 5.1) 🦊
Author
Owner

@RishTheFish21 commented on GitHub (Oct 9, 2026):

I'd like to take a stab at this.Going off @CassieFox23's results, I think there are three separate problems:

  • Computed brs that Ghidra can't turn into a jump table just vanish from the output. Nothing in jump_tables, unsupported_branches or the unknowns, so it looks complete when it isn't. I'm planning to scan the p-code for leftover BRANCHINDs and report them in a new unresolved_dispatches field (empty for other providers).
  • When the decompiler chokes on a function (the varnode hash error on rsdIntrinsicBlend_K), the whole call fails and tells you to run rea doctor, which won't help. I'd rather return what the listing still knows and mark the native API part as unavailable.
  • The AArch64 table matcher only handles the exact unsigned ldrb/ldrh + lsl #2 + b.hi shape. Blend_K uses ldrsh, b.cs, a cbz on the entry and no shift, so I'd widen it to cover those, still only when bounds and targets check out exactly.

Plus new fixtures in relative-switch.S for those shapes, and ARM64 versions of the C switch fixtures.

Plan is to do it as a few smaller PRs, starting with the first two. If you'd rather not add a new field to the output, or would prefer a proper error over a partial result when decompiling fails, let me know

<!-- gh-comment-id:6089479262 --> @RishTheFish21 commented on GitHub (Oct 9, 2026): I'd like to take a stab at this.Going off @CassieFox23's results, I think there are three separate problems: - Computed `br`s that Ghidra can't turn into a jump table just vanish from the output. Nothing in `jump_tables`, `unsupported_branches` or the unknowns, so it looks complete when it isn't. I'm planning to scan the p-code for leftover `BRANCHIND`s and report them in a new `unresolved_dispatches` field (empty for other providers). - When the decompiler chokes on a function (the varnode hash error on `rsdIntrinsicBlend_K`), the whole call fails and tells you to run `rea doctor`, which won't help. I'd rather return what the listing still knows and mark the native API part as unavailable. - The AArch64 table matcher only handles the exact unsigned ldrb/ldrh + lsl #2 + b.hi shape. Blend_K uses ldrsh, b.cs, a cbz on the entry and no shift, so I'd widen it to cover those, still only when bounds and targets check out exactly. Plus new fixtures in `relative-switch.S` for those shapes, and ARM64 versions of the C switch fixtures. Plan is to do it as a few smaller PRs, starting with the first two. If you'd rather not add a new field to the output, or would prefer a proper error over a partial result when decompiling fails, let me know
Author
Owner

@CassieFox23 commented on GitHub (Nov 3, 2026):

TL;DR: 3749ccec4 (#1592) did not finish this issue. Main still misses two computed branches on my October library, and rsdIntrinsicBlend_K still has no table. Only #2977 at 063df5093 reports those branches.

Results

I ran rea inspect-native-api <so> <address> --provider ghidra --json on librenderscript-toolkit.so. It is RenderScript code (Apache-2.0). It ships in Lawnchair 14 (Apache-2.0). It is the same file as in my October comment. I built rea from main (c3647ade1) and from #2977 (063df5093) in a Fedora 44 VM. All six calls exited 0 and took 21 to 38 seconds.

Address, name main c3647ade1 #2977 at 063df5093
0x1255d0 rsdIntrinsicBlend_K No jump table. Native API marked unavailable: "Ghidra decompilation failed: Recoverable Error: Unable to find unique hash for varnode". Unknown: "Jump-table mappings are unavailable." Same as main, plus an unknown that no indirect-branch inventory was reported.
0x12c440 rsdIntrinsicBlurU1_K jump_tables and unsupported_branches empty. Only unknowns: six inferred types to confirm. Nothing names the dispatch. Reports 0x1262d8 (the br x8 from my October comment). Also reports 0x126828, which was not in that comment. One unknown per branch.
0x12adc0 (unnamed kernel) Same as main for 0x12c440. Reports 0x12b298 (from my October comment). Also reports 0x12c188, which was not in that comment. One unknown per branch.

For 0x12c440 and 0x12adc0 on #2977, unsupported_branches is indirect-branch-target-recovery and every branch is jump_table_recovery_failed with no listing targets.

How this maps onto the three problems in RishTheFish21's comment:

  • Still open on main: a computed br vanishes. Both hand-written kernels give no unknown. With #2977, each branch is reported. It "may be a dispatch inside this function or an indirect tail call". It recovers no targets, so the dispatch stays unknown there.
  • Changed on main, not by 3749ccec4: a decompiler failure reads as "retry or run doctor". The call for 0x1255d0 no longer fails, and the output has no "retry" or "doctor" text. git log -S points at c374c2fe7 (#4650, 20 October), which added the partial-result path. I did not build an older tree to confirm that this commit caused the change. The varnode error itself still happens, and no table or case list comes back for this function.
  • Widened by 3749ccec4, not reached on the real library: the AArch64 matcher. I read the code at main: signed and unsigned byte or halfword entries, UXTW or LSL #2, B.HI or B.CS or B.HS, and the CBZ route. It only receives a jump table that Ghidra already reported. 0x1255d0 is the case that motivated it. There, the result gives the decompiler failure above as its reason. No table or case list comes back. I did not run the lane that tests the matcher on its own fixture.

Not run

  • The verify:ghidra:* lanes, including verify:ghidra:aarch64-jump-table. On 10 October that lane took about 380 seconds here. RishTheFish21 reports it stops early on main's host checks.
  • Any ARM64 Mach-O target. Mach-O cannot be checked for real on Linux. So I did not re-check the issue's 41-mapping Mach-O result.
  • Tests: no test:focused or other test run.
  • libjni_latinime.so, and the 291 branches in the symbol-less stretch from my October comment.
  • A build from before c374c2fe7, so no before and after for 0x1255d0 on one build.
  • What the branches at 0x126828 and 0x12c188 are. I only have #2977's own record for them.

Thanks, RishTheFish21, for #1592 and #2977.

Static read against the issue's success criteria (main c3647ade1)

File and line are at main. "Not done" means I found no code or fixture for it.

Criterion Status Where
Recover data sources, dispatch instructions, entry sizes and counts, and case-to-target mappings when directly supported Done for the shapes the matcher names. Not reached for 0x1255d0 bridge/ghidra/ReaGhidraBridge.java 3174-3179 (shapes), 2750-2784 (data source with entry size and count), 2880 (mappings)
Keep confidence and evidence per mapping Done same file, 2857-2883
Report ambiguous bounds, missing case values, unsupported patterns and truncation as explicit unknowns Partly src/application/native/NativeApiInspection.ts 70-107 (missing default, empty mappings, missing data source, unknown entry size or count, null case value); bridge 2963-2964 (unsupported encodings, only when Ghidra already found a table). Not done: a computed branch with no table (runs above). Not done: jump-table truncation. A search for "truncat" in the bridge finds only type layouts, instruction and value-flow scans, and address offsets
Never fill missing values from a usual compiler layout Done bridge 3178-3179 ("Any other instruction sequence keeps its case labels unknown"), 2819 (extra labels stay unknown); scripts/verify-real-ghidra-function.mjs 700-735 checks it
Source-owned fixtures: AArch64 ELF Done, optimized and dense tests/conformance/ghidra/cross-format.c built with --target=aarch64-linux-gnu -O2 (scripts/verify-real-ghidra.mjs 209-224); relative-switch.S with 1 and 2 byte entries (151-177); relative-switch-signed.S with selector bias 0, 2, 10 (178-207)
Source-owned fixtures: ARM64 Mach-O Built only on an ARM64 macOS host scripts/verify-real-ghidra.mjs 157 and 187. Not checkable on Linux
Sparse variant Partly relative-switch-signed.S is hand-written with one empty entry (index 3, routed by CBZ). No compiler-made sparse AArch64 switch: the sparse holes_shared switch is in tests/conformance/ghidra/switch-fixture.mjs 11, and its lane asserts Linux x64 (scripts/verify-real-ghidra-switch.mjs 49-50)
Stripped variant Not done for AArch64 The stripped copy is built in scripts/verify-real-ghidra-switch.mjs (Linux x64 only) and for the host-native fixture (scripts/verify-real-ghidra.mjs 140)
A fixture for a computed branch that Ghidra cannot turn into a table Not done at main #2977 adds tests/conformance/ghidra/indirect-branches.S and a probe. I read its diff and did not run it
Test setup
  • Box: Fedora 44 VM (4 vCPUs, 8 GB) on my Nobara 44 machine, Linux 7.2.11 in the guest
  • Node: 24.22.1
  • Ghidra 12.1.4 (Apache-2.0), JDK 21.0.12.1
  • Target: librenderscript-toolkit.so, SHA-256 6f60acc69e73c6a1a0fa66f375d9c2cf2704d19079c89c3211a93ab5b52bddc2, checked against the saved sums in the VM
  • Head / base: 063df5093 (#2977) / c3647ade1 (main). Each tree was built from its own sources, not from an installed rea-agents.
  • Timing: the first two main runs (38 and 23 seconds) overlapped with the build of the #2977 tree in the same VM. The third main run (22 seconds) and the three #2977 runs (23, 21 and 23 seconds) ran alone.
Commands
npm --prefix ~/i487-main/src ci --offline                       # exit 0, 62 s
npm --prefix ~/i487-main/src run build:cached                   # exit 0, 12 s
npm --prefix ~/pr-2977/src ci --offline                         # exit 0, 22 s
npm --prefix ~/pr-2977/src run build:cached                     # exit 0, 13 s

# each with timeout 300, GHIDRA_INSTALL_DIR and JAVA_HOME set, one at a time
node ~/<tree>/src/scripts/rea.mjs inspect-native-api ~/targets/librenderscript-toolkit.so 0x1255d0 --provider ghidra --json
node ~/<tree>/src/scripts/rea.mjs inspect-native-api ~/targets/librenderscript-toolkit.so 0x12c440 --provider ghidra --json
node ~/<tree>/src/scripts/rea.mjs inspect-native-api ~/targets/librenderscript-toolkit.so 0x12adc0 --provider ghidra --json
# <tree> = i487-main (exit 0, 0, 0) and pr-2977 (exit 0, 0, 0)

🤖 Written and tested with Claude Code (Sonnet 5.5, verified by Opus 5.5), posted with my OK 🦊

<!-- gh-comment-id:6613683089 --> @CassieFox23 commented on GitHub (Nov 3, 2026): **TL;DR:** 3749ccec4 (#1592) did not finish this issue. Main still misses two computed branches on my [October library](https://github.com/morluto/rea/issues/487#issuecomment-6087737312), and `rsdIntrinsicBlend_K` still has no table. Only #2977 at 063df5093 reports those branches. ### Results I ran `rea inspect-native-api <so> <address> --provider ghidra --json` on `librenderscript-toolkit.so`. It is RenderScript code (Apache-2.0). It ships in Lawnchair 14 (Apache-2.0). It is the same file as in my October comment. I built rea from main (c3647ade1) and from #2977 (063df5093) in a Fedora 44 VM. All six calls exited 0 and took 21 to 38 seconds. | Address, name | main c3647ade1 | #2977 at 063df5093 | |---|---|---| | 0x1255d0 `rsdIntrinsicBlend_K` | No jump table. Native API marked unavailable: "Ghidra decompilation failed: Recoverable Error: Unable to find unique hash for varnode". Unknown: "Jump-table mappings are unavailable." | Same as main, plus an unknown that no indirect-branch inventory was reported. | | 0x12c440 `rsdIntrinsicBlurU1_K` | `jump_tables` and `unsupported_branches` empty. Only unknowns: six inferred types to confirm. Nothing names the dispatch. | Reports 0x1262d8 (the `br x8` from my October comment). Also reports 0x126828, which was not in that comment. One unknown per branch. | | 0x12adc0 (unnamed kernel) | Same as main for 0x12c440. | Reports 0x12b298 (from my October comment). Also reports 0x12c188, which was not in that comment. One unknown per branch. | For 0x12c440 and 0x12adc0 on #2977, `unsupported_branches` is `indirect-branch-target-recovery` and every branch is `jump_table_recovery_failed` with no listing targets. How this maps onto the three problems in RishTheFish21's comment: - **Still open on main: a computed `br` vanishes.** Both hand-written kernels give no unknown. With #2977, each branch is reported. It "may be a dispatch inside this function or an indirect tail call". It recovers no targets, so the dispatch stays unknown there. - **Changed on main, not by 3749ccec4: a decompiler failure reads as "retry or run doctor".** The call for 0x1255d0 no longer fails, and the output has no "retry" or "doctor" text. `git log -S` points at c374c2fe7 (#4650, 20 October), which added the partial-result path. I did not build an older tree to confirm that this commit caused the change. The varnode error itself still happens, and no table or case list comes back for this function. - **Widened by 3749ccec4, not reached on the real library: the AArch64 matcher.** I read the code at main: signed and unsigned byte or halfword entries, UXTW or LSL #2, B.HI or B.CS or B.HS, and the CBZ route. It only receives a jump table that Ghidra already reported. 0x1255d0 is the case that motivated it. There, the result gives the decompiler failure above as its reason. No table or case list comes back. I did not run the lane that tests the matcher on its own fixture. ### Not run - The `verify:ghidra:*` lanes, including `verify:ghidra:aarch64-jump-table`. On 10 October that lane took about 380 seconds here. RishTheFish21 reports it stops early on main's host checks. - Any ARM64 Mach-O target. Mach-O cannot be checked for real on Linux. So I did not re-check the issue's 41-mapping Mach-O result. - Tests: no `test:focused` or other test run. - `libjni_latinime.so`, and the 291 branches in the symbol-less stretch from my October comment. - A build from before c374c2fe7, so no before and after for 0x1255d0 on one build. - What the branches at 0x126828 and 0x12c188 are. I only have #2977's own record for them. Thanks, RishTheFish21, for #1592 and #2977. <details> <summary>Static read against the issue's success criteria (main c3647ade1)</summary> File and line are at main. "Not done" means I found no code or fixture for it. | Criterion | Status | Where | |---|---|---| | Recover data sources, dispatch instructions, entry sizes and counts, and case-to-target mappings when directly supported | Done for the shapes the matcher names. Not reached for 0x1255d0 | `bridge/ghidra/ReaGhidraBridge.java` 3174-3179 (shapes), 2750-2784 (data source with entry size and count), 2880 (mappings) | | Keep confidence and evidence per mapping | Done | same file, 2857-2883 | | Report ambiguous bounds, missing case values, unsupported patterns and truncation as explicit unknowns | Partly | `src/application/native/NativeApiInspection.ts` 70-107 (missing default, empty mappings, missing data source, unknown entry size or count, null case value); bridge 2963-2964 (unsupported encodings, only when Ghidra already found a table). Not done: a computed branch with no table (runs above). Not done: jump-table truncation. A search for "truncat" in the bridge finds only type layouts, instruction and value-flow scans, and address offsets | | Never fill missing values from a usual compiler layout | Done | bridge 3178-3179 ("Any other instruction sequence keeps its case labels unknown"), 2819 (extra labels stay unknown); `scripts/verify-real-ghidra-function.mjs` 700-735 checks it | | Source-owned fixtures: AArch64 ELF | Done, optimized and dense | `tests/conformance/ghidra/cross-format.c` built with `--target=aarch64-linux-gnu -O2` (`scripts/verify-real-ghidra.mjs` 209-224); `relative-switch.S` with 1 and 2 byte entries (151-177); `relative-switch-signed.S` with selector bias 0, 2, 10 (178-207) | | Source-owned fixtures: ARM64 Mach-O | Built only on an ARM64 macOS host | `scripts/verify-real-ghidra.mjs` 157 and 187. Not checkable on Linux | | Sparse variant | Partly | `relative-switch-signed.S` is hand-written with one empty entry (index 3, routed by CBZ). No compiler-made sparse AArch64 switch: the sparse `holes_shared` switch is in `tests/conformance/ghidra/switch-fixture.mjs` 11, and its lane asserts Linux x64 (`scripts/verify-real-ghidra-switch.mjs` 49-50) | | Stripped variant | Not done for AArch64 | The stripped copy is built in `scripts/verify-real-ghidra-switch.mjs` (Linux x64 only) and for the host-native fixture (`scripts/verify-real-ghidra.mjs` 140) | | A fixture for a computed branch that Ghidra cannot turn into a table | Not done at main | #2977 adds `tests/conformance/ghidra/indirect-branches.S` and a probe. I read its diff and did not run it | </details> <details> <summary>Test setup</summary> - Box: Fedora 44 VM (4 vCPUs, 8 GB) on my Nobara 44 machine, Linux 7.2.11 in the guest - Node: 24.22.1 - Ghidra 12.1.4 (Apache-2.0), JDK 21.0.12.1 - Target: `librenderscript-toolkit.so`, SHA-256 6f60acc69e73c6a1a0fa66f375d9c2cf2704d19079c89c3211a93ab5b52bddc2, checked against the saved sums in the VM - Head / base: 063df5093 (#2977) / c3647ade1 (main). Each tree was built from its own sources, not from an installed rea-agents. - Timing: the first two main runs (38 and 23 seconds) overlapped with the build of the #2977 tree in the same VM. The third main run (22 seconds) and the three #2977 runs (23, 21 and 23 seconds) ran alone. </details> <details> <summary>Commands</summary> ``` npm --prefix ~/i487-main/src ci --offline # exit 0, 62 s npm --prefix ~/i487-main/src run build:cached # exit 0, 12 s npm --prefix ~/pr-2977/src ci --offline # exit 0, 22 s npm --prefix ~/pr-2977/src run build:cached # exit 0, 13 s # each with timeout 300, GHIDRA_INSTALL_DIR and JAVA_HOME set, one at a time node ~/<tree>/src/scripts/rea.mjs inspect-native-api ~/targets/librenderscript-toolkit.so 0x1255d0 --provider ghidra --json node ~/<tree>/src/scripts/rea.mjs inspect-native-api ~/targets/librenderscript-toolkit.so 0x12c440 --provider ghidra --json node ~/<tree>/src/scripts/rea.mjs inspect-native-api ~/targets/librenderscript-toolkit.so 0x12adc0 --provider ghidra --json # <tree> = i487-main (exit 0, 0, 0) and pr-2977 (exit 0, 0, 0) ``` </details> 🤖 Written and tested with Claude Code (Sonnet 5.5, verified by Opus 5.5), posted with my OK 🦊
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#261
No description provided.