From 40d1a1939d6eb404e3723bc3284216db22526027 Mon Sep 17 00:00:00 2001 From: Nishant1600 Date: Mon, 9 Nov 2026 15:41:16 +0000 Subject: [PATCH 1/3] docs(cli): state that HTTP MCP endpoints have no client authentication The CLI guide shows `rea mcp --http --host 0.0.0.0` and the matching `environment-serve` example, and explains the Host/Origin 403 checks, but the warning that these endpoints have no TLS or client authentication lived only in the MCP contract reference. A reader could take `--allowed-hosts` or `MCP_ALLOWED_HOSTS` for access control; it only guards against DNS rebinding and cross-origin browser pages. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FZXWdmycjdemfBYLDTuc6r --- docs/getting-started/cli.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/getting-started/cli.md b/docs/getting-started/cli.md index 8d701d3e9ab..33d2fe988ce 100644 --- a/docs/getting-started/cli.md +++ b/docs/getting-started/cli.md @@ -1078,6 +1078,13 @@ For example: rea mcp --http --host 0.0.0.0 --allowed-hosts 127.0.0.1,rea.lan ``` +The endpoint uses plain HTTP without TLS or client authentication. The `Host` +and `Origin` checks guard against DNS rebinding and cross-origin browser pages; +they are not access control, because any client that can reach the port can +send an admitted `Host` header. A non-loopback bind makes the same tools and +active analysis state reachable by every such client, so bind outside loopback +only within a trusted or isolated network. + Requests with a different `Host` or browser `Origin` receive `403`; a missing `Origin` is accepted. The announced client URL uses the bound hostname when admitted, otherwise the first declared hostname, with the actual bound port. @@ -1122,7 +1129,9 @@ rea environment-serve manifest.json --evidence capture.json --port 8765 `MCP_PORT`, then `127.0.0.1` and an ephemeral port. A non-loopback bind requires `MCP_ALLOWED_HOSTS` with the same hostname rules and Host/Origin gates as above. `environment-serve` uses this environment setting; `--allowed-hosts` belongs to -`rea mcp --http`. For example: +`rea mcp --http`. It likewise has no TLS or client authentication: any client +that reaches the port can call the served tools, including write tools and the +`/agentenv` methods that change the served state. For example: ```sh MCP_ALLOWED_HOSTS=127.0.0.1,rea.lan rea environment-serve manifest.json --evidence capture.json --host 0.0.0.0 From 3a52fad41b381cc0e74e278d0c4dd0fa91ab8339 Mon Sep 17 00:00:00 2001 From: Nishant1600 Date: Mon, 9 Nov 2026 15:46:46 +0000 Subject: [PATCH 2/3] fix(ghidra): recover GCC's sign-extending AArch64 relative switch tables REA's AArch64 relative jump-table recovery matched only the shifted-register dispatch ADD that clang emits (`add xD, xN, xM, lsl #2`). GCC's aarch64_output_casesi loads the entry zero-extended with LDRB/LDRH and sign-extends it in an extended-register ADD (`add x1, x0, w1, sxtb #2` or `sxth #2`), so the matcher returned null and GCC-built tables kept their table source and case labels unknown. Accept the SXTB/SXTH extended-register form with the existing 0/2 scales. The extend must read exactly the loaded width and makes the entries signed; X-register extends, SXTW word tables and other scales are still refused. The recovered record keeps the extend so the jump-table evidence names the real dispatch instead of `LSL #2`. Add relative-switch-gcc.S, which shares relative-switch.S's symbol and case oracle, places one case before the ADR label so its entry is negative, and runs in the AArch64 jump-table lane for byte and halfword entries. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FZXWdmycjdemfBYLDTuc6r --- bridge/ghidra/ReaGhidraBridge.java | 45 ++++++++--- scripts/verify-real-ghidra.mjs | 57 +++++++------ tests/conformance/ghidra/README.md | 5 ++ .../conformance/ghidra/relative-switch-gcc.S | 79 +++++++++++++++++++ 4 files changed, 152 insertions(+), 34 deletions(-) create mode 100644 tests/conformance/ghidra/relative-switch-gcc.S diff --git a/bridge/ghidra/ReaGhidraBridge.java b/bridge/ghidra/ReaGhidraBridge.java index 55ef0d16763..eea4d76a753 100644 --- a/bridge/ghidra/ReaGhidraBridge.java +++ b/bridge/ghidra/ReaGhidraBridge.java @@ -2879,7 +2879,10 @@ private JsonObject inferredJumpTable( (recoveredAArch64.signedEntries ? "signed" : "unsigned") + " entries of size " + recoveredAArch64.entrySize + " bytes from " + canonicalAddress(recoveredAArch64.tableAddress) + "; the AArch64 ADRP/ADD table, indexed load, ADR base, ADD" + - (recoveredAArch64.offsetShift == 0 ? "" : " LSL #" + recoveredAArch64.offsetShift) + + (recoveredAArch64.dispatchExtend != null + ? " " + recoveredAArch64.dispatchExtend + + (recoveredAArch64.offsetShift == 0 ? "" : " #" + recoveredAArch64.offsetShift) + : recoveredAArch64.offsetShift == 0 ? "" : " LSL #" + recoveredAArch64.offsetShift) + "/BR chain uses a selector bounded by CMP #" + recoveredAArch64.compareImmediate + " and " + recoveredAArch64.guardMnemonic + " on one straight-line path" + (recoveredAArch64.nullTarget == null @@ -3229,6 +3232,8 @@ private static final class RecoveredAArch64RelativeTable { private final Address priorSelectorDefinition; private final boolean signedEntries; private final int offsetShift; + /** SXTB or SXTH for GCC's extended-register dispatch ADD; null for a shifted register. */ + private final String dispatchExtend; private final String guardMnemonic; private final long compareImmediate; private final Address nullTarget; @@ -3242,6 +3247,7 @@ private RecoveredAArch64RelativeTable( Address priorSelectorDefinition, boolean signedEntries, int offsetShift, + String dispatchExtend, String guardMnemonic, long compareImmediate, Address nullTarget, @@ -3254,6 +3260,7 @@ private RecoveredAArch64RelativeTable( this.priorSelectorDefinition = priorSelectorDefinition; this.signedEntries = signedEntries; this.offsetShift = offsetShift; + this.dispatchExtend = dispatchExtend; this.guardMnemonic = guardMnemonic; this.compareImmediate = compareImmediate; this.nullTarget = nullTarget; @@ -3262,8 +3269,13 @@ private RecoveredAArch64RelativeTable( } } + // Shifted register (clang): ADD Xd, Xn, Xm{, LSL #s}, shift in group 4. + // Extended register (GCC casesi): ADD Xd, Xn, Wm, SXTB|SXTH{ #s}, extend + // in group 5 and shift in group 6. Ghidra prints the extend with a + // trailing space, so a zero shift leaves trailing whitespace. private static final Pattern AARCH64_DISPATCH_ADD = Pattern.compile( - "(?i)^([wx]\\d+),\\s*([wx]\\d+),\\s*([wx]\\d+)(?:,\\s*LSL\\s*#(?:0x)?([0-9a-f]+))?$" + "(?i)^([wx]\\d+),\\s*([wx]\\d+),\\s*([wx]\\d+)" + + "(?:,\\s*(?:LSL\\s*#(?:0x)?([0-9a-f]+)|(SXTB|SXTH)(?:\\s*#(?:0x)?([0-9a-f]+))?))?\\s*$" ); private static final Pattern AARCH64_TABLE_LOAD = Pattern.compile( "(?i)^([wx]\\d+),\\s*\\[([wx]\\d+),\\s*([wx]\\d+)(?:,\\s*(LSL|UXTW)\\s*(?:#(?:0x)?([0-9a-f]+))?)?\\s*\\]$" @@ -3287,7 +3299,8 @@ private RecoveredAArch64RelativeTable( * Recover only directly verified AArch64 relative branch tables: * ADRP/ADD table base, LDRB/LDRH/LDRSB/LDRSH entry load with an exact * index scale, ADR branch base, optional CBZ on the loaded entry, and - * ADD (byte or LSL #2 scale)/BR, with a selector bounded by CMP #imm and an + * ADD (byte or LSL #2 scale, or GCC's SXTB/SXTH extended-register form + * with the same scales)/BR, with a selector bounded by CMP #imm and an * unsigned B.HI or B.CS/B.HS guard on one straight-line path. Any other * instruction sequence keeps its case labels unknown. */ @@ -3333,13 +3346,17 @@ private RecoveredAArch64RelativeTable matchAArch64RelativeDispatch( ) throws Exception { String branchRegister = normalizedRegister(operands(instructions.get(branchIndex))); - // ADD Xd, Xn, Xm{, LSL #s}; BR Xd. LSL applies to Xm only. + // ADD Xd, Xn, Xm{, LSL #s} or ADD Xd, Xn, Wm, SXTB|SXTH{ #s}; BR Xd. + // The shift or extend applies to the last source only. Instruction dispatchAdd = instructions.get(branchIndex - 1); if (!mnemonic(dispatchAdd).equals("ADD")) return null; java.util.regex.Matcher addMatcher = AARCH64_DISPATCH_ADD.matcher(operands(dispatchAdd)); if (!addMatcher.matches() || !addMatcher.group(1).toLowerCase(Locale.ROOT).startsWith("x")) return null; if (!normalizedRegister(addMatcher.group(1)).equals(branchRegister)) return null; - int offsetShift = addMatcher.group(4) == null ? 0 : Integer.parseInt(addMatcher.group(4), 16); + String dispatchExtend = addMatcher.group(5) == null ? null : addMatcher.group(5).toUpperCase(Locale.ROOT); + if (dispatchExtend != null && !addMatcher.group(3).toLowerCase(Locale.ROOT).startsWith("w")) return null; + String shiftText = dispatchExtend == null ? addMatcher.group(4) : addMatcher.group(6); + int offsetShift = shiftText == null ? 0 : Integer.parseInt(shiftText, 16); if (offsetShift != 0 && offsetShift != 2) return null; String firstSource = normalizedRegister(addMatcher.group(2)); String secondSource = normalizedRegister(addMatcher.group(3)); @@ -3350,7 +3367,8 @@ private RecoveredAArch64RelativeTable matchAArch64RelativeDispatch( int loadIndex = -1; String entryRegister = null; String baseRegister = null; - for (String candidate : offsetShift == 0 ? List.of(secondSource, firstSource) : List.of(secondSource)) { + boolean scaledOrExtended = offsetShift != 0 || dispatchExtend != null; + for (String candidate : scaledOrExtended ? List.of(secondSource) : List.of(secondSource, firstSource)) { int writer = latestWriter(instructions, branchIndex - 1, candidate); if (writer >= 0 && Set.of("LDRB", "LDRH", "LDRSB", "LDRSH").contains(mnemonic(instructions.get(writer)))) { loadIndex = writer; @@ -3367,9 +3385,17 @@ private RecoveredAArch64RelativeTable matchAArch64RelativeDispatch( boolean signedEntries = loadMnemonic.startsWith("LDRS"); java.util.regex.Matcher loadMatcher = AARCH64_TABLE_LOAD.matcher(operands(load)); if (!loadMatcher.matches()) return null; - // A signed load into a W register sign-extends only to 32 bits; the - // 64-bit dispatch ADD would then read a zero-extended value. - if (signedEntries && !loadMatcher.group(1).toLowerCase(Locale.ROOT).startsWith("x")) return null; + if (dispatchExtend != null) { + // GCC's casesi loads the entry with LDRB/LDRH and sign-extends its + // low byte or halfword in the dispatch ADD, so the extend decides + // the entries' sign. It must read exactly the loaded width. + if (entrySize != (dispatchExtend.equals("SXTB") ? 1 : 2)) return null; + signedEntries = true; + } else if (signedEntries && !loadMatcher.group(1).toLowerCase(Locale.ROOT).startsWith("x")) { + // A signed load into a W register sign-extends only to 32 bits; a + // 64-bit shifted-register dispatch ADD would then read a zero-extended value. + return null; + } String tableBaseRegister = normalizedRegister(loadMatcher.group(2)); String indexRegister = normalizedRegister(loadMatcher.group(3)); boolean wordIndex = loadMatcher.group(3).toLowerCase(Locale.ROOT).startsWith("w"); @@ -3547,6 +3573,7 @@ else if (mnemonic(instructions.get(indexWriter)).equals("MOV")) { selectorWriter < 0 ? null : instructions.get(selectorWriter).getAddress(), signedEntries, offsetShift, + dispatchExtend, guardMnemonic, compareImmediate, nullTarget, diff --git a/scripts/verify-real-ghidra.mjs b/scripts/verify-real-ghidra.mjs index 15323f9a1cb..dfd1ce0d864 100644 --- a/scripts/verify-real-ghidra.mjs +++ b/scripts/verify-real-ghidra.mjs @@ -150,31 +150,38 @@ try { await exec(compiler, [...jumpTableFlags, sourcePath, "-o", jumpTablePath]); const crossTargets = []; if (aarch64JumpTableOnly) { - const relativeSource = fileURLToPath( - new URL("../tests/conformance/ghidra/relative-switch.S", import.meta.url), - ); - for (const entrySize of [1, 2]) { - for (const format of [ - "elf", - ...(expectedNativeTarget.architecture === "arm64" ? ["mach-o"] : []), - ]) { - const targetPath = join( - fixtureRoot, - `relative-${entrySize}-${format}.o`, - ); - await exec(clang, [ - ...(format === "elf" ? ["--target=aarch64-linux-gnu"] : []), - `-DREA_ENTRY_BYTES=${entrySize}`, - "-c", - relativeSource, - "-o", - targetPath, - ]); - crossTargets.push([ - targetPath, - `relative-${entrySize}`, - { format, architecture: "arm64" }, - ]); + // relative-switch-gcc.S shares relative-switch.S's symbol and case oracle, + // with GCC's SXTB/SXTH extended-register dispatch instead of clang's LSL. + for (const [shape, fixture] of [ + ["", "relative-switch.S"], + ["gcc-", "relative-switch-gcc.S"], + ]) { + const relativeSource = fileURLToPath( + new URL(`../tests/conformance/ghidra/${fixture}`, import.meta.url), + ); + for (const entrySize of [1, 2]) { + for (const format of [ + "elf", + ...(expectedNativeTarget.architecture === "arm64" ? ["mach-o"] : []), + ]) { + const targetPath = join( + fixtureRoot, + `relative-${shape}${entrySize}-${format}.o`, + ); + await exec(clang, [ + ...(format === "elf" ? ["--target=aarch64-linux-gnu"] : []), + `-DREA_ENTRY_BYTES=${entrySize}`, + "-c", + relativeSource, + "-o", + targetPath, + ]); + crossTargets.push([ + targetPath, + `relative-${shape}${entrySize}`, + { format, architecture: "arm64" }, + ]); + } } } const signedRelativeSource = fileURLToPath( diff --git a/tests/conformance/ghidra/README.md b/tests/conformance/ghidra/README.md index 56ab857c13b..c49f01e2a15 100644 --- a/tests/conformance/ghidra/README.md +++ b/tests/conformance/ghidra/README.md @@ -100,6 +100,11 @@ callback fixture also proves that an unresolved targetless flow is not silently promoted to a direct callee. Every target runs in a separate owned project and must leave no process, socket, project, or runtime root after close. +`relative-switch-gcc.S` covers GCC's AArch64 dispatch, where LDRB/LDRH loads the +entry and the extended-register ADD (`SXTB`/`SXTH #2`) sign-extends it; one case +sits before the ADR label, so its entry is negative. It shares the +`relative-switch.S` symbol and case oracle for byte and halfword entries. + `relative-switch-signed.S` covers signed halfword offsets, the CBZ zero-entry route, and selector biases of 0, 2 and 10 in the AArch64 jump-table lane. The nonzero biases exercise overlapping and disjoint typed label/index ranges. REA diff --git a/tests/conformance/ghidra/relative-switch-gcc.S b/tests/conformance/ghidra/relative-switch-gcc.S new file mode 100644 index 00000000000..f4317d8c9fa --- /dev/null +++ b/tests/conformance/ghidra/relative-switch-gcc.S @@ -0,0 +1,79 @@ +// GCC-shaped AArch64 relative dispatch, as aarch64_output_casesi in +// gcc/config/aarch64/aarch64.cc emits it: the entry is loaded zero-extended +// with LDRB/LDRH and sign-extended by the extended-register dispatch ADD +// (SXTB/SXTH #2), so entries are signed instruction offsets from the label +// after the BR. Lgcc_case3 sits before that label, so its entry is negative. +// The source labels and the immediates below are the oracle shared with +// relative-switch.S; no REA or Ghidra result defines them. +// case 0 -> 100, 1 -> 101, 2 -> 102, 3 -> 103, selector > 3 -> default (-1). +#if defined(__APPLE__) +.section __TEXT,__text,regular,pure_instructions +.globl _rea_relative_switch +.p2align 2 +_rea_relative_switch: +#else +.text +.globl rea_relative_switch +.type rea_relative_switch,%function +.p2align 2 +rea_relative_switch: +#endif + b Lgcc_dispatch +Lgcc_case3: + mov w0, #103 + ret +Lgcc_dispatch: + cmp w0, #3 + b.hi Lgcc_default +#if defined(__APPLE__) + adrp x1, Lgcc_table@PAGE + add x1, x1, Lgcc_table@PAGEOFF +#else + adrp x1, Lgcc_table + add x1, x1, :lo12:Lgcc_table +#endif +#if REA_ENTRY_BYTES == 2 + ldrh w1, [x1, w0, uxtw #1] +#else + ldrb w1, [x1, w0, uxtw] +#endif + adr x0, Lgcc_rtx +#if REA_ENTRY_BYTES == 2 + add x1, x0, w1, sxth #2 +#else + add x1, x0, w1, sxtb #2 +#endif + br x1 +Lgcc_rtx: +Lgcc_case0: + mov w0, #100 + ret +#if REA_ENTRY_BYTES == 2 + // Halfword entries must exceed the signed byte range. + .space 1100 +#endif +Lgcc_case1: + mov w0, #101 + ret +Lgcc_case2: + mov w0, #102 + ret +Lgcc_default: + mov w0, #-1 + ret +#if !defined(__APPLE__) +.size rea_relative_switch, .-rea_relative_switch +#endif + +#if defined(__APPLE__) +.section __TEXT,__const +#else +.section .rodata +#endif +.p2align 1 +Lgcc_table: +#if REA_ENTRY_BYTES == 2 + .short (Lgcc_case0-Lgcc_rtx)/4, (Lgcc_case1-Lgcc_rtx)/4, (Lgcc_case2-Lgcc_rtx)/4, (Lgcc_case3-Lgcc_rtx)/4 +#else + .byte (Lgcc_case0-Lgcc_rtx)/4, (Lgcc_case1-Lgcc_rtx)/4, (Lgcc_case2-Lgcc_rtx)/4, (Lgcc_case3-Lgcc_rtx)/4 +#endif From c0ea5406ab99b55a9810f8f4d0a230cd84272b3f Mon Sep 17 00:00:00 2001 From: Nishant1600 Date: Mon, 9 Nov 2026 16:06:00 +0000 Subject: [PATCH 3/3] refactor(ghidra): simplify the AArch64 dispatch operator match Match LSL, SXTB and SXTH in one operator group and record that operator for the jump-table evidence, instead of separate shift and extend groups. Drop the W-register check on the extended source: SXTB/SXTH read only the low byte or halfword whatever the register is named, and Ghidra's ADD (extended register) constructor always prints Rm as a W register. Evidence text for clang's shifted-register forms is unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FZXWdmycjdemfBYLDTuc6r --- bridge/ghidra/ReaGhidraBridge.java | 53 +++++++++++------------------- 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/bridge/ghidra/ReaGhidraBridge.java b/bridge/ghidra/ReaGhidraBridge.java index eea4d76a753..c6e2fc0d266 100644 --- a/bridge/ghidra/ReaGhidraBridge.java +++ b/bridge/ghidra/ReaGhidraBridge.java @@ -2879,10 +2879,8 @@ private JsonObject inferredJumpTable( (recoveredAArch64.signedEntries ? "signed" : "unsigned") + " entries of size " + recoveredAArch64.entrySize + " bytes from " + canonicalAddress(recoveredAArch64.tableAddress) + "; the AArch64 ADRP/ADD table, indexed load, ADR base, ADD" + - (recoveredAArch64.dispatchExtend != null - ? " " + recoveredAArch64.dispatchExtend + - (recoveredAArch64.offsetShift == 0 ? "" : " #" + recoveredAArch64.offsetShift) - : recoveredAArch64.offsetShift == 0 ? "" : " LSL #" + recoveredAArch64.offsetShift) + + (recoveredAArch64.dispatchOperator == null ? "" : " " + recoveredAArch64.dispatchOperator) + + (recoveredAArch64.offsetShift == 0 ? "" : " #" + recoveredAArch64.offsetShift) + "/BR chain uses a selector bounded by CMP #" + recoveredAArch64.compareImmediate + " and " + recoveredAArch64.guardMnemonic + " on one straight-line path" + (recoveredAArch64.nullTarget == null @@ -3232,8 +3230,8 @@ private static final class RecoveredAArch64RelativeTable { private final Address priorSelectorDefinition; private final boolean signedEntries; private final int offsetShift; - /** SXTB or SXTH for GCC's extended-register dispatch ADD; null for a shifted register. */ - private final String dispatchExtend; + /** The dispatch ADD's LSL, SXTB or SXTH operator; null when it has none. */ + private final String dispatchOperator; private final String guardMnemonic; private final long compareImmediate; private final Address nullTarget; @@ -3247,7 +3245,7 @@ private RecoveredAArch64RelativeTable( Address priorSelectorDefinition, boolean signedEntries, int offsetShift, - String dispatchExtend, + String dispatchOperator, String guardMnemonic, long compareImmediate, Address nullTarget, @@ -3260,7 +3258,7 @@ private RecoveredAArch64RelativeTable( this.priorSelectorDefinition = priorSelectorDefinition; this.signedEntries = signedEntries; this.offsetShift = offsetShift; - this.dispatchExtend = dispatchExtend; + this.dispatchOperator = dispatchOperator; this.guardMnemonic = guardMnemonic; this.compareImmediate = compareImmediate; this.nullTarget = nullTarget; @@ -3269,13 +3267,9 @@ private RecoveredAArch64RelativeTable( } } - // Shifted register (clang): ADD Xd, Xn, Xm{, LSL #s}, shift in group 4. - // Extended register (GCC casesi): ADD Xd, Xn, Wm, SXTB|SXTH{ #s}, extend - // in group 5 and shift in group 6. Ghidra prints the extend with a - // trailing space, so a zero shift leaves trailing whitespace. + // Ghidra prints an extend with a trailing space, even without a shift. private static final Pattern AARCH64_DISPATCH_ADD = Pattern.compile( - "(?i)^([wx]\\d+),\\s*([wx]\\d+),\\s*([wx]\\d+)" + - "(?:,\\s*(?:LSL\\s*#(?:0x)?([0-9a-f]+)|(SXTB|SXTH)(?:\\s*#(?:0x)?([0-9a-f]+))?))?\\s*$" + "(?i)^([wx]\\d+),\\s*([wx]\\d+),\\s*([wx]\\d+)(?:,\\s*(LSL|SXTB|SXTH)(?:\\s*#(?:0x)?([0-9a-f]+))?)?\\s*$" ); private static final Pattern AARCH64_TABLE_LOAD = Pattern.compile( "(?i)^([wx]\\d+),\\s*\\[([wx]\\d+),\\s*([wx]\\d+)(?:,\\s*(LSL|UXTW)\\s*(?:#(?:0x)?([0-9a-f]+))?)?\\s*\\]$" @@ -3353,10 +3347,9 @@ private RecoveredAArch64RelativeTable matchAArch64RelativeDispatch( java.util.regex.Matcher addMatcher = AARCH64_DISPATCH_ADD.matcher(operands(dispatchAdd)); if (!addMatcher.matches() || !addMatcher.group(1).toLowerCase(Locale.ROOT).startsWith("x")) return null; if (!normalizedRegister(addMatcher.group(1)).equals(branchRegister)) return null; - String dispatchExtend = addMatcher.group(5) == null ? null : addMatcher.group(5).toUpperCase(Locale.ROOT); - if (dispatchExtend != null && !addMatcher.group(3).toLowerCase(Locale.ROOT).startsWith("w")) return null; - String shiftText = dispatchExtend == null ? addMatcher.group(4) : addMatcher.group(6); - int offsetShift = shiftText == null ? 0 : Integer.parseInt(shiftText, 16); + String dispatchOperator = addMatcher.group(4) == null ? null : addMatcher.group(4).toUpperCase(Locale.ROOT); + boolean extended = dispatchOperator != null && !dispatchOperator.equals("LSL"); + int offsetShift = addMatcher.group(5) == null ? 0 : Integer.parseInt(addMatcher.group(5), 16); if (offsetShift != 0 && offsetShift != 2) return null; String firstSource = normalizedRegister(addMatcher.group(2)); String secondSource = normalizedRegister(addMatcher.group(3)); @@ -3367,8 +3360,7 @@ private RecoveredAArch64RelativeTable matchAArch64RelativeDispatch( int loadIndex = -1; String entryRegister = null; String baseRegister = null; - boolean scaledOrExtended = offsetShift != 0 || dispatchExtend != null; - for (String candidate : scaledOrExtended ? List.of(secondSource) : List.of(secondSource, firstSource)) { + for (String candidate : offsetShift == 0 && !extended ? List.of(secondSource, firstSource) : List.of(secondSource)) { int writer = latestWriter(instructions, branchIndex - 1, candidate); if (writer >= 0 && Set.of("LDRB", "LDRH", "LDRSB", "LDRSH").contains(mnemonic(instructions.get(writer)))) { loadIndex = writer; @@ -3382,20 +3374,15 @@ private RecoveredAArch64RelativeTable matchAArch64RelativeDispatch( Instruction load = instructions.get(loadIndex); String loadMnemonic = mnemonic(load); int entrySize = loadMnemonic.endsWith("B") ? 1 : 2; - boolean signedEntries = loadMnemonic.startsWith("LDRS"); + // GCC's casesi sign-extends the loaded entry in the dispatch ADD; the + // extend must read exactly the loaded width. + boolean signedEntries = extended || loadMnemonic.startsWith("LDRS"); + if (extended && entrySize != (dispatchOperator.equals("SXTB") ? 1 : 2)) return null; java.util.regex.Matcher loadMatcher = AARCH64_TABLE_LOAD.matcher(operands(load)); if (!loadMatcher.matches()) return null; - if (dispatchExtend != null) { - // GCC's casesi loads the entry with LDRB/LDRH and sign-extends its - // low byte or halfword in the dispatch ADD, so the extend decides - // the entries' sign. It must read exactly the loaded width. - if (entrySize != (dispatchExtend.equals("SXTB") ? 1 : 2)) return null; - signedEntries = true; - } else if (signedEntries && !loadMatcher.group(1).toLowerCase(Locale.ROOT).startsWith("x")) { - // A signed load into a W register sign-extends only to 32 bits; a - // 64-bit shifted-register dispatch ADD would then read a zero-extended value. - return null; - } + // A signed load into a W register sign-extends only to 32 bits; the + // 64-bit dispatch ADD would then read a zero-extended value. + if (!extended && signedEntries && !loadMatcher.group(1).toLowerCase(Locale.ROOT).startsWith("x")) return null; String tableBaseRegister = normalizedRegister(loadMatcher.group(2)); String indexRegister = normalizedRegister(loadMatcher.group(3)); boolean wordIndex = loadMatcher.group(3).toLowerCase(Locale.ROOT).startsWith("w"); @@ -3573,7 +3560,7 @@ else if (mnemonic(instructions.get(indexWriter)).equals("MOV")) { selectorWriter < 0 ? null : instructions.get(selectorWriter).getAddress(), signedEntries, offsetShift, - dispatchExtend, + dispatchOperator, guardMnemonic, compareImmediate, nullTarget,