From c5c72cb90e9b62d143efb4e346654144efc2860d Mon Sep 17 00:00:00 2001 From: scizzydo <34661795+scizzydo@users.noreply.github.com> Date: Mon, 12 Oct 2026 13:27:45 -0700 Subject: [PATCH 1/2] Encode the remaining arm64 instructions with full wildcard support TBZ/TBNZ and exceptions, BTI and pointer authentication, FP/SIMD loads, stores and FMOV, signed/exclusive/acquire-release loads and LSE atomics, bitfield/shift/extend aliases, conditional select/compare, multiply/divide and the inverted-operand logical forms. /d now resolves TBZ/TBNZ, FP literal loads and FP loads after ADRP. A "?" shift or extend type now leaves the type open instead of pinning LSL. The mnemonic buffer grows to 15 chars (LDADDALB). Wildcard rules are documented in docs/arm64.md. Co-Authored-By: Claude Opus 5.5 --- docs/arm64.md | 40 +- include/PatternScanner/ArmPattern.hpp | 796 +++++++++++++++++++++++--- include/PatternScanner/Pattern.hpp | 29 +- tests/test_arm.cpp | 476 ++++++++++++++- 4 files changed, 1248 insertions(+), 93 deletions(-) diff --git a/docs/arm64.md b/docs/arm64.md index f995d62..b975054 100644 --- a/docs/arm64.md +++ b/docs/arm64.md @@ -6,31 +6,51 @@ One instruction per line; case, indentation and spacing don't matter, so lines pasted from IDA (`LDR W8, [X8,#0x18]`) or lldb (`ldr w8, [x8, #0x18]`) both work. lldb prints ADRP operands as page counts and branch targets as absolute addresses, so wildcard those (`#?`). -Wildcards: `?` for a register (`X?`), immediate (`#?`), shift (`LSL #?`), condition (`B.?`) or a whole instruction line. +## Wildcards -## Supported instructions +`?` works for any operand: a register (`?`, or `X?`/`W?`/`D?`/`Q?`... to keep its kind), an immediate (`#?`), a shift or extend (`LSL #?`, `?`), a condition (`B.?`, `CSEL X0, X1, X2, ?`), BTI targets (`BTI ?`) or a whole instruction line. + +- A `?` leaves open exactly the bits that operand decides. A field computed from a `?` and a known operand is left open too (`UBFX X0, X1, #?, #8`: imms is lsb + 7). +- A register letter pins the width; a bare `?` leaves it open, along with what depends on it (scaled offsets, bitfield immr/imms, TBZ's bit 5). Where only one width fits the other operands (`LSL ?, ?, #40`), that one is used. +- In loads and stores a bare `?` register is W or X; write `B?`/`H?`/`S?`/`D?`/`Q?` for floating-point/SIMD ones. +- Some wildcards can't be expressed with a mask and match more than the alias: `LSL Rd, Rn, #?` matches any `UBFM` (also `LSR`, `UBFX`...), and aliases that repeat a register (`CINC X?, X?, cond`, `ROR X?, X?, #n`) can't require the two to be the same. +- An operand that can't be encoded (out of range, the wrong register kind, an unknown condition) makes the line a wildcard for those 4 bytes, matching any instruction (the IDA plugin asks before searching with one). It never makes a pattern that can't match. Write `?` for a deliberate one. -`B`, `BL`, `B.cond` (`B.?` for any condition), `BR`, `BLR`, `RET`, `RETAA`, `RETAB`, `CBZ`, `CBNZ`, `ADR`, `ADRP`, `ADD(S)`, `SUB(S)`, `CMP`, `CMN`, `AND(S)`, `ORR`, `EOR`, `TST`, `LDR`, `LDRB`, `LDRH`, `LDRSW`, `STR`, `STRB`, `STRH`, `LDUR`/`STUR` (and their `B`/`H`/`SW` forms), `LDP`, `STP`, `MOV`, `MOVZ`, `MOVN`, `MOVK`, `NOP`, `PACIASP`, `PACIBSP`, `AUTIASP`, `AUTIBSP`. +## Supported instructions -- Loads and stores take W/X registers only; floating-point/SIMD ones (`LDR Q0, ...`, `STP D9, D8, ...`) are wildcards. -- Any other instruction becomes a wildcard for those 4 bytes, matching any instruction (the IDA plugin asks before searching with one); write `?` for a deliberate one. +- Branches: `B`, `BL`, `B.cond`, `BR`, `BLR`, `RET`, `CBZ`, `CBNZ`, `TBZ`, `TBNZ` +- Exceptions and hints: `SVC`, `HVC`, `SMC`, `BRK`, `HLT`, `UDF`, `NOP`, `BTI` +- Pointer authentication: `PACIASP`/`PACIBSP`/`AUTIASP`/`AUTIBSP` (and the `Z`/`1716` forms), `RETAA`, `RETAB`, `BRAA`/`BRAB`/`BLRAA`/`BLRAB` (and `Z` forms), `LDRAA`, `LDRAB`, `PACIA`/`PACIB`/`PACDA`/`PACDB`/`AUTIA`/`AUTIB`/`AUTDA`/`AUTDB` (and `Z` forms), `XPACI`, `XPACD`, `XPACLRI`, `PACGA` +- Addresses and moves: `ADR`, `ADRP`, `MOV`, `MOVZ`, `MOVN`, `MOVK`, `MVN` +- Arithmetic: `ADD(S)`, `SUB(S)`, `CMP`, `CMN`, `NEG(S)`, `ADC(S)`, `SBC(S)`, `NGC(S)`, `MADD`, `MSUB`, `MUL`, `MNEG`, `SMADDL`, `SMSUBL`, `UMADDL`, `UMSUBL`, `SMULL`, `UMULL`, `SMNEGL`, `UMNEGL`, `SMULH`, `UMULH`, `SDIV`, `UDIV` +- Logical: `AND(S)`, `ORR`, `EOR`, `TST`, `BIC(S)`, `ORN`, `EON` +- Shifts and bitfields: `LSL`, `LSR`, `ASR`, `ROR` (immediate or register), `LSLV`, `LSRV`, `ASRV`, `RORV`, `UBFM`, `SBFM`, `BFM`, `UBFX`, `SBFX`, `BFXIL`, `UBFIZ`, `SBFIZ`, `BFI`, `EXTR`, `SXTB`, `SXTH`, `SXTW`, `UXTB`, `UXTH` +- Conditionals: `CSEL`, `CSINC`, `CSINV`, `CSNEG`, `CSET`, `CSETM`, `CINC`, `CINV`, `CNEG`, `CCMP`, `CCMN` +- Loads and stores: `LDR`, `STR`, `LDUR`, `STUR` (W/X and B/H/S/D/Q registers), `LDRB`, `LDRH`, `LDRSB`, `LDRSH`, `LDRSW`, `STRB`, `STRH` and their `LDUR`/`STUR` forms, `LDP`, `STP` (W/X and S/D/Q), `LDPSW`, `LDNP`, `STNP` +- Exclusive and acquire/release: `LDXR`, `STXR`, `LDAXR`, `STLXR`, `LDAR`, `STLR`, `LDAPR` (and their `B`/`H` forms), `LDXP`, `STXP`, `LDAXP`, `STLXP` +- Atomics: `CAS`, `SWP`, `LD`, `ST` with `` `ADD`, `CLR`, `EOR`, `SET`, `SMAX`, `SMIN`, `UMAX`, `UMIN`, each with the `A`/`L`/`AL` and `B`/`H` suffixes +- Floating point: `FMOV` (register, to/from W/X, and immediates like `#1.0`) + +Any other instruction becomes a wildcard for those 4 bytes. ## Aliases Encoded the way assemblers pick them: - `MOV`: register, SP, or immediate via `MOVZ`/`MOVN`/`ORR`; `MOV Rd, #?` matches `MOVZ` or `MOVN`. -- `CMP`/`CMN`/`TST`: `SUBS`/`ADDS`/`ANDS` to the zero register. +- `CMP`/`CMN`/`TST`: `SUBS`/`ADDS`/`ANDS` to the zero register; `MVN`/`NEG(S)`/`NGC(S)`: `ORN`/`SUB(S)`/`SBC(S)` from it. - `LDR`/`STR` offsets the scaled form can't hold (negative, unaligned): `LDUR`/`STUR`. - `ADD`/`SUB`/`CMP` immediates that are negative or multiples of 0x1000: the opposite operation or `LSL #12`. - Register `ADD`/`SUB` with `SP`: the extended (`UXTX`) form. - -Other aliases aren't implemented. +- `CSET`/`CSETM`/`CINC`/`CINV`/`CNEG`: `CSINC`/`CSINV`/`CSNEG` with the inverted condition. +- `LSL`/`LSR`/`ASR` by an immediate, `UBFX`/`UBFIZ`/`BFI`/`SXTB`...: `UBFM`/`SBFM`/`BFM`; `ROR` by an immediate: `EXTR`. +- `MUL`/`MNEG`/`SMULL`...: the multiply-add forms with the zero register; `ST`: `LD` into the zero register. +- `FMOV Rd, #0.0`: `FMOV` from the zero register. ## `/d` resolution At the `X` instruction: -- `B`/`BL`, `B.cond`, `CBZ`/`CBNZ`, `LDR` (literal) and `ADR` give their target. -- `ADRP` gives the page plus the `ADD`/`SUB` and load/store offset that follow it through the same register (skipping NOPs and instructions that leave the register alone). +- `B`/`BL`, `B.cond`, `CBZ`/`CBNZ`, `TBZ`/`TBNZ`, `LDR` (literal) and `ADR` give their target. +- `ADRP` gives the page plus the `ADD`/`SUB` and load/store offset (including floating-point loads like `LDR D0`) that follow it through the same register (skipping NOPs and instructions that leave the register alone). - `MOVZ`/`MOVN`, `ADD`/`SUB` immediates and load/store offsets give the value. `/r` is not used on arm64. diff --git a/include/PatternScanner/ArmPattern.hpp b/include/PatternScanner/ArmPattern.hpp index dcb5bf6..f669443 100644 --- a/include/PatternScanner/ArmPattern.hpp +++ b/include/PatternScanner/ArmPattern.hpp @@ -35,6 +35,12 @@ namespace patterns { return str[i] == target ? i + 1 : i; } + constexpr size_t skip_mnemonic(const char* instr) { + size_t i = 0; + while (instr[i] && !isspace(instr[i])) ++i; + return i; + } + template constexpr size_t parse_token(const char* str, size_t i, char (&out)[N]) { size_t j = 0; @@ -85,7 +91,8 @@ namespace patterns { if (token[0] == '?' && token[1] == '\0') return 0xFFFFFFFF; - if ((token[0] == 'W' || token[0] == 'X') && token[1] >= '0' && token[1] <= '9') { + if ((token[0] == 'W' || token[0] == 'X' || token[0] == 'B' || token[0] == 'H' || token[0] == 'S' || + token[0] == 'D' || token[0] == 'Q') && token[1] >= '0' && token[1] <= '9') { uint32_t val = 0; for (int i = 1; token[i] >= '0' && token[i] <= '9'; ++i) { val = val * 10 + (token[i] - '0'); @@ -110,6 +117,58 @@ namespace patterns { return str_eq(token, "SP") || str_eq(token, "WSP"); } + // Floating-point/SIMD register: log2 of its size (B 0, H 1, S 2, D 3, Q 4), or -1 for any other token + constexpr int fp_size(const char* token) { + if (!((token[1] >= '0' && token[1] <= '9') || (token[1] == '?' && !token[2]))) return -1; + switch (token[0]) { + case 'B': return 0; + case 'H': return 1; + case 'S': return 2; + case 'D': return 3; + case 'Q': return 4; + default: return -1; + } + } + + // Comma-separated registers (brackets and '!' skipped), each into the 5-bit field at shifts[k] (-1: not + // encoded); widths[k] gets register_width. Returns the index past them + template + constexpr size_t parse_reg_list(const char* instr, size_t i, const int (&shifts)[N], ArmOpcode& base, int (&widths)[N]) { + for (size_t k = 0; k < N; ++k) { + while (instr[i] == ',' || instr[i] == '[' || isspace(instr[i])) ++i; + char token[5] = {}; + i = parse_token(instr, i, token); + widths[k] = register_width(token); + if (auto r = parse_register(token); r != 0xFFFFFFFF && shifts[k] >= 0) base.set_bits(r, 0x1F, shifts[k]); + while (instr[i] == ']' || instr[i] == '!' || isspace(instr[i])) ++i; + } + return i; + } + + // Instructions that are only registers. width_bit (sf at 31, or size bit 30 for loads/stores) is set from the + // first known width among the registers in the width_regs bitmask + template + constexpr ArmOpcode parse_regs(const char* instr, ArmOpcode base) { + constexpr int sh[] = { shifts... }; + int w[sizeof...(shifts)] = {}; + parse_reg_list(instr, skip_mnemonic(instr), sh, base, w); + for (size_t k = 0; k < sizeof...(shifts) && width_bit >= 0; ++k) + if ((width_regs >> k & 1) && w[k] >= 0) { base.set_bits(static_cast(w[k]), 1, width_bit); break; } + return base; + } + + constexpr const char* conditions[] = { "EQ", "NE", "CS", "CC", "MI", "PL", "VS", "VC", "HI", "LS", "GE", "LT", "GT", "LE", "AL", "NV" }; + + // Condition code: 0-15 (HS and LO too), 16 for "?", 17 if unknown + constexpr uint32_t parse_cond(const char* c) { + if (c[0] == '?' && !c[1]) return 16; + if (str_eq(c, "HS")) return 2; + if (str_eq(c, "LO")) return 3; + for (uint32_t k = 0; k < 16; ++k) + if (str_eq(c, conditions[k])) return k; + return 17; + } + template constexpr void parse_register(char(&buffer)[N], uint32_t mask, uint32_t shift, ArmOpcode& base) { if (auto val = parse_register(buffer); val != 0xFFFFFFFF) @@ -136,12 +195,6 @@ namespace patterns { return base; } - constexpr size_t skip_mnemonic(const char* instr) { - size_t i = 0; - while (instr[i] && !isspace(instr[i])) ++i; - return i; - } - constexpr std::pair parse_lsl_shift(const char* instr, size_t i) { i = skip_whitespace(instr, skip_char(instr, i, ',')); if (starts_with(&instr[i], "LSL")) { @@ -256,29 +309,43 @@ namespace patterns { return base; } - // imm19 (bits 5-23) from a byte offset, for CBZ/CBNZ, B.cond and LDR (literal) - constexpr void parse_imm19(const char* instr, size_t i, ArmOpcode& base) { + // A PC-relative word offset at bit 5 from a byte offset: imm19 for CBZ/CBNZ, B.cond and LDR (literal), imm14 for TBZ/TBNZ + template + constexpr void parse_branch_imm(const char* instr, size_t i, ArmOpcode& base) { char imm[24] = {}; parse_imm_token(instr, i, imm); - if (imm[0] != '?') base.set_bits(static_cast(stoi(imm) >> 2), 0x7FFFF, 5); + if (imm[0] != '?') base.set_bits(static_cast(stoi(imm) >> 2), (1u << bits) - 1, 5); } // Single-register loads/stores. op is the unsigned offset form; the unscaled (LDUR), pre/post-index and // register offset forms differ from it in fixed bits. scale is log2 of the access size, or -1 to take it - // from Rt (W or X, bit 30). An offset the unsigned form can't hold (negative, unaligned) is encoded unscaled, - // the way assemblers pick LDUR/STUR. Floating-point/SIMD registers aren't supported: the line is a wildcard - template + // from Rt: W or X (bit 30), or a floating-point/SIMD register B/H/S/D/Q (size, V bit 26, opc bit 23). A bare + // "?" Rt is W or X. sign_extend (LDRSB/LDRSH): opc is 11 for a W Rt and 10 for X, so bit 22 comes from Rt. + // An offset the unsigned form can't hold (negative, unaligned) is encoded unscaled, the way assemblers pick + // LDUR/STUR + template constexpr ArmOpcode parse_load_store(const char* instr, ArmOpcode) { ArmOpcode base{ 0, 0 }; size_t i = skip_whitespace(instr, skip_mnemonic(instr)); char rt[5] = {}, imm[24] = {}; i = parse_register(instr, parse_token(instr, i, rt), rt, 0x1F, 0, base, ','); - if (rt[0] != 'W' && rt[0] != 'X' && rt[0] != '?') return { 0, 0 }; + const int fp = scale < 0 ? fp_size(rt) : -1; + if (rt[0] != 'W' && rt[0] != 'X' && rt[0] != '?' && fp < 0) return { 0, 0 }; int size = scale; - if (scale < 0 && rt[0] != '?') { - size = rt[0] == 'W' ? 2 : 3; - base.set_bits(size - 2, 1, 30); + uint32_t op = op_int, width_mask = ~0u; // width_mask: Rt's width bit, set below when known + if (fp >= 0) { + size = fp; + op = (op & 0x3FFFFFFF) | static_cast(fp & 3) << 30 | 1u << 26 | static_cast(fp >> 2) << 23; + } else if (scale < 0) { + width_mask = ~(1u << 30); + if (rt[0] != '?') { + size = rt[0] == 'W' ? 2 : 3; + base.set_bits(size - 2, 1, 30); + } + } else if (sign_extend) { + width_mask = ~(1u << 22); + if (rt[0] != '?') base.set_bits(rt[0] == 'W' ? 1 : 0, 1, 22); } if (instr[i++] != '[') return { 0, 0 }; @@ -328,7 +395,6 @@ namespace patterns { if (is_unsigned_offset && known_imm && (val < 0 || (size > 0 && (val & ((1 << size) - 1))))) is_unsigned_offset = false; - const uint32_t width_mask = scale < 0 ? ~(1u << 30) : ~0u; // Rt's width bit, set above when known if (is_unsigned_offset) { base.set_bits(op, 0xFFC00000 & width_mask); if (known_imm && size >= 0) { @@ -355,9 +421,11 @@ namespace patterns { size_t i = skip_whitespace(instr, skip_mnemonic(instr)); i = parse_register(instr, parse_token(instr, i, rt), rt, 0x1F, 0, base, ','); if (instr[i] == '[') return parse_load_store<0xB9400000, -1>(instr, base); - if (rt[0] != 'W' && rt[0] != 'X' && rt[0] != '?') return { 0, 0 }; - if (rt[0] != '?') base.set_bits((rt[0] == 'W' ? 0 : 1), 1, 30); - parse_imm19(instr, i, base); + // opc (bits 30-31) is 00 for W, 01 for X; with V (bit 26) 00 for S, 01 for D, 10 for Q + if (const int fp = fp_size(rt); fp >= 2) base.set_bits(static_cast(fp - 2) << 4 | 1, 0x31, 26); + else if (rt[0] != 'W' && rt[0] != 'X' && rt[0] != '?') return { 0, 0 }; + else if (rt[0] != '?') base.set_bits((rt[0] == 'W' ? 0 : 1), 1, 30); + parse_branch_imm(instr, i, base); return base; } @@ -373,6 +441,7 @@ namespace patterns { if (width >= 0) base.set_bits(static_cast(width), 1, 31); if (instr[i] == '#') { + if (base.pattern >> 21 & 1) return { 0, 0 }; // BIC/ORN/EON (N set) have no immediate form base.set_bits((2 << 4), (3 << 4 | 1), 23); char imm[24] = {}; @@ -387,21 +456,25 @@ namespace patterns { } return base; } - base.set_bits((1 << 6), (3 << 6 | 1), 21); + // Shifted register; N (bit 21) comes from the table: set for BIC/ORN/EON + base.set_bits((1 << 6), (3 << 6), 21); + base.set_mask(1, 21); // Rm i = parse_register(instr, i, 0x1F, 16, base); if (instr[i] == ',') { i = skip_whitespace(instr, skip_char(instr, i, ',')); - if (starts_with(&instr[i], "LSL")) { base.set_bits(0, 3, 22); i += 3; } + const bool any = instr[i] == '?'; // any shift: type and amount + if (any) ++i; + else if (starts_with(&instr[i], "LSL")) { base.set_bits(0, 3, 22); i += 3; } else if (starts_with(&instr[i], "LSR")) { base.set_bits(1, 3, 22); i += 3; } else if (starts_with(&instr[i], "ASR")) { base.set_bits(2, 3, 22); i += 3; } else if (starts_with(&instr[i], "ROR")) { base.set_bits(3, 3, 22); i += 3; } else base.set_bits(0, 3, 22); i = skip_char(instr, skip_whitespace(instr, i), '#'); - if (instr[i] != '?') base.set_bits(stoi(&instr[i]), 0x3F, 10); + if (instr[i] != '?' && !(any && !instr[i])) base.set_bits(stoi(&instr[i]), 0x3F, 10); } else { base.set_mask((3 << 12 | 0x3F), 10); @@ -458,6 +531,12 @@ namespace patterns { if (instr[i] == ',') { i = skip_whitespace(instr, i + 1); bool shifted = false, extended = false; + // Any shift or extend: they share bits 10-15 and 21-23 (the table pins bit 23, which only ASR sets) + if (instr[i] == '?') { + base.pattern &= ~(7u << 21 | 0x3Fu << 10); + base.mask &= ~(7u << 21 | 0x3Fu << 10); + return base; + } if (starts_with(&instr[i], "LSL") && sp) { base.set_bits(sp_extend, 7, 13); extended = true; i += 3; } else if (starts_with(&instr[i], "LSL")) { base.set_bits(0, 7, 21); shifted = true; i += 3; } else if (starts_with(&instr[i], "LSR")) { base.set_bits(2, 7, 21); shifted = true; i += 3; } @@ -503,18 +582,33 @@ namespace patterns { return base; } + // LDP/STP and the forms sharing their layout; the table gives the load bit (22). opc (bits 30-31) is 00 for W + // and 10 for X, or with V (bit 26) 00 for S, 01 for D, 10 for Q; LDPSW passes it (01). no_allocate: LDNP/STNP, + // which only have the signed offset form + template constexpr ArmOpcode parse_ldp(const char* instr, ArmOpcode base) { size_t i = skip_whitespace(instr, skip_mnemonic(instr)); char rt1[5] = {}; i = parse_register(instr, parse_token(instr, i, rt1), rt1, 0x1F, 0, base, ','); - if (rt1[0] != 'W' && rt1[0] != 'X' && rt1[0] != '?') return { 0, 0 }; // FP/SIMD pairs aren't supported char rt2[5] = {}; i = parse_register(instr, parse_token(instr, i, rt2), rt2, 0x1F, 10, base, ','); // Width from Rt1, or from Rt2 when Rt1 is "?" const int width = register_width(rt1) >= 0 ? register_width(rt1) : register_width(rt2); - const bool know_size = width >= 0; - const uint32_t size_bits = know_size ? static_cast(width) : 0; - if (know_size) base.set_bits(size_bits, 1, 31); + const int fp = fp_size(rt1) >= 0 ? fp_size(rt1) : fp_size(rt2); + int scale = -1; // log2 of one register's size, when known + if (fp >= 2 && opc < 0) { + base.set_bits(static_cast(fp - 2) << 4 | 1, 0x31, 26); + scale = fp; + } else if (fp >= 0 || (rt1[0] != 'W' && rt1[0] != 'X' && rt1[0] != '?')) { + return { 0, 0 }; + } else if (opc >= 0) { + if (width == 0) return { 0, 0 }; // LDPSW loads into X + base.set_bits(static_cast(opc), 3, 30); + scale = 2; + } else if (width >= 0) { + base.set_bits(static_cast(width), 1, 31); + scale = 2 + width; + } if (instr[i] != '[') return base; i = skip_whitespace(instr, skip_char(instr, i, '[')); @@ -536,14 +630,13 @@ namespace patterns { i = parse_imm_token(instr, i, imm); } } - base.set_bits((post_index ? 0b0010100011 : - pre_index ? 0b0010100111 : - 0b0010100101), 0x1FF, 22); - if (imm[0] != '?' && know_size) { + if (no_allocate && (pre_index || post_index)) return { 0, 0 }; + // Bits 23-25: post-index 001, signed offset 010, pre-index 011, no-allocate offset 000 + base.set_bits(post_index ? 1 : pre_index ? 3 : no_allocate ? 0 : 2, 7, 23); + if (imm[0] != '?' && scale >= 0) { int32_t immVal = stoi(imm); - int32_t scale = (size_bits == 0) ? 4 : 8; - if ((immVal % scale) == 0) { - int32_t encoded = immVal / scale; + if ((immVal % (1 << scale)) == 0) { + int32_t encoded = immVal / (1 << scale); if (encoded >= -64 && encoded <= 63) { base.set_bits(encoded, 0x7F, 15); } @@ -552,13 +645,6 @@ namespace patterns { return base; } - // STP is LDP with the load bit (22) clear - constexpr ArmOpcode parse_stp(const char* instr, ArmOpcode base) { - base = parse_ldp(instr, base); - base.pattern &= ~(1u << 22); - return base; - } - // MOV is an alias; encoded the way assemblers pick: ADD #0 when SP is involved, ORR from the zero register for // registers, and MOVZ, then MOVN, then ORR (bitmask) for immediates. MOV Rd, #? matches MOVZ or MOVN constexpr ArmOpcode parse_mov(const char* instr, ArmOpcode) { @@ -620,39 +706,349 @@ namespace patterns { i = parse_register(instr, parse_token(instr, i, rt), rt, 0x1F, 0, base, ','); if (rt[0] != '?') base.set_bits((rt[0] == 'W' ? 0 : 1), 1, 31); - parse_imm19(instr, i, base); + parse_branch_imm(instr, i, base); return base; } - // CMP/CMN/TST Rn, ... are SUBS/ADDS/ANDS ZR, Rn, ...: parsed as that, with the zero register of Rn's width - template - constexpr ArmOpcode parse_zr_dest(const char* instr, ArmOpcode base) { + // Aliases that write the zero register (of the first operand's width) as operand pos, parsed as the full form: + // CMP/CMN/TST Rn, ... are SUBS/ADDS/ANDS ZR, Rn, ... (pos 0); MVN/NEG(S)/NGC(S) Rd, ... are + // ORN/SUB(S)/SBC(S) Rd, ZR, ... (pos 1) + template + constexpr ArmOpcode parse_zr(const char* instr, ArmOpcode base) { size_t i = skip_whitespace(instr, skip_mnemonic(instr)); const char* zr = instr[i] == 'W' ? "WZR, " : instr[i] == 'X' ? "XZR, " : "?, "; char line[max_line + 8] = { '_', ' ' }; size_t j = 2; + if (pos == 1) { + while (instr[i] && instr[i] != ',') line[j++] = instr[i++]; + line[j++] = ','; + line[j++] = ' '; + i = skip_whitespace(instr, skip_char(instr, i, ',')); + } while (*zr) line[j++] = *zr++; while (instr[i]) line[j++] = instr[i++]; ArmOpcode op = parse(line, base); - op.set_bits(31, 0x1F); + op.set_bits(31, 0x1F, pos == 0 ? 0 : 5); return op; } - constexpr const char* conditions[] = { "EQ", "NE", "CS", "CC", "MI", "PL", "VS", "VC", "HI", "LS", "GE", "LT", "GT", "LE", "AL", "NV" }; - // B.cond: the condition is part of the mnemonic (B.EQ, B.HS); B.? matches any constexpr ArmOpcode parse_bcond(const char* instr) { ArmOpcode base{ 0x54000000, 0xFF000010 }; char cond[4] = {}; for (size_t j = 0; j < 3 && instr[2 + j] && !isspace(instr[2 + j]); ++j) cond[j] = instr[2 + j]; - if (cond[0] != '?') { - uint32_t c = str_eq(cond, "HS") ? 2 : str_eq(cond, "LO") ? 3 : 16; - for (uint32_t k = 0; k < 16 && c == 16; ++k) - if (str_eq(cond, conditions[k])) c = k; - if (c == 16) return { 0, 0 }; - base.set_bits(c, 0xF); - } - parse_imm19(instr, skip_whitespace(instr, skip_mnemonic(instr)), base); + const uint32_t c = parse_cond(cond); + if (c == 17) return { 0, 0 }; + if (c < 16) base.set_bits(c, 0xF); + parse_branch_imm(instr, skip_whitespace(instr, skip_mnemonic(instr)), base); + return base; + } + + // TBZ/TBNZ Rt, #bit, #offset: b5 (bit 31) is the bit number's top bit, b40 (bits 19-23) the rest. A W register + // (or a bit number below 32) means b5 is 0 + constexpr ArmOpcode parse_tbz(const char* instr, ArmOpcode base) { + size_t i = skip_whitespace(instr, skip_mnemonic(instr)); + char rt[5] = {}, bit[24] = {}; + i = parse_register(instr, parse_token(instr, i, rt), rt, 0x1F, 0, base, ','); + i = skip_whitespace(instr, skip_char(instr, parse_imm_token(instr, i, bit), ',')); + const int width = register_width(rt); + if (bit[0] == '?') { + if (width == 0) base.set_bits(0, 1, 31); + } else { + const int32_t b = stoi(bit); + if (b < 0 || b >= (width == 0 ? 32 : 64)) return { 0, 0 }; + base.set_bits(static_cast(b), 0x1F, 19); + base.set_bits(static_cast(b) >> 5, 1, 31); + } + parse_branch_imm<14>(instr, i, base); + return base; + } + + // SVC/HVC/SMC/BRK/HLT #imm16 (bit 5), UDF #imm16 (bit 0) + template + constexpr ArmOpcode parse_imm16(const char* instr, ArmOpcode base) { + char imm[24] = {}; + parse_imm_token(instr, skip_whitespace(instr, skip_mnemonic(instr)), imm); + if (imm[0] != '?') { + const int64_t v = stoi(imm); + if (v < 0 || v > 0xFFFF) return { 0, 0 }; + base.set_bits(static_cast(v), 0xFFFF, shift); + } + return base; + } + + // BTI {C|J|JC}: the targets at bits 6-7, BTI ? for any + constexpr ArmOpcode parse_bti(const char* instr, ArmOpcode base) { + const char* t = &instr[skip_whitespace(instr, skip_mnemonic(instr))]; + const uint32_t targets = !*t ? 0 : str_eq(t, "C") ? 1 : str_eq(t, "J") ? 2 : str_eq(t, "JC") ? 3 : 4; + if (str_eq(t, "?")) base.mask &= ~(3u << 6); + else if (targets == 4) return { 0, 0 }; + else base.set_bits(targets, 3, 6); + return base; + } + + // LDRAA/LDRAB Xt, [Xn{,#imm}]{!}: imm10 scaled by 8 (sign at bit 22, the rest at 12-20), writeback at bit 11 + constexpr ArmOpcode parse_ldraa(const char* instr, ArmOpcode base) { + size_t i = skip_whitespace(instr, skip_mnemonic(instr)); + char rt[5] = {}, imm[24] = {}; + i = parse_register(instr, parse_token(instr, i, rt), rt, 0x1F, 0, base, ','); + if (rt[0] != 'X' && rt[0] != '?') return { 0, 0 }; + if (instr[i++] != '[') return { 0, 0 }; + i = parse_register(instr, i, 0x1F, 5, base); + if (instr[i] == ',') i = parse_imm_token(instr, skip_whitespace(instr, i + 1), imm); + i = skip_whitespace(instr, skip_past(instr, i, ']')); + base.set_bits(instr[i] == '!' ? 1 : 0, 1, 11); + if (imm[0] != '?') { + const int32_t val = stoi(imm); + if (val % 8 || val < -4096 || val > 4088) return { 0, 0 }; + const uint32_t enc = static_cast(val / 8) & 0x3FF; + base.set_bits(enc, 0x1FF, 12); + base.set_bits(enc >> 9, 1, 22); + } + return base; + } + + // Exact decimal (1, -2.5, 0.125) as num / den; false if it isn't one + constexpr bool parse_decimal(const char* s, bool& negative, uint64_t& num, uint64_t& den) { + negative = *s == '-'; + if (*s == '-' || *s == '+') ++s; + num = 0; den = 1; + bool digits = false, dot = false; + for (; *s; ++s) { + if (*s == '.' && !dot) { dot = true; continue; } + if (*s < '0' || *s > '9' || den > 1000000000000ULL || num > 1000000000000ULL) return false; + num = num * 10 + static_cast(*s - '0'); + if (dot) den *= 10; + digits = true; + } + return digits; + } + + // FMOV: register (Hd/Sd/Dd, same type), general-purpose to/from floating-point (Sd/Wn, Dd/Xn, Hd/Wn|Xn, and + // back), or an 8-bit immediate (#1.0, #-2.5: +-n/16 * 2^e, n 16-31, e -3..4; #0.0 is FMOV from the zero + // register). A bare "?" register is the same kind as the other one + constexpr ArmOpcode parse_fmov(const char* instr, ArmOpcode) { + size_t i = skip_whitespace(instr, skip_mnemonic(instr)); + char rd[5] = {}, rn[5] = {}; + i = skip_whitespace(instr, skip_char(instr, parse_token(instr, i, rd), ',')); + constexpr uint32_t ftype[] = { 4, 3, 0, 1, 4 }; // by fp_size: H 11, S 00, D 01 (B and Q: none) + const int fd = fp_size(rd); + ArmOpcode op{ 0, 0 }; + if (auto r = parse_register(rd); r != 0xFFFFFFFF) op.set_bits(r, 0x1F); + + char imm[24] = {}; + if (instr[i] == '#') { + parse_imm_token(instr, i, imm); + if (fd < 0 || ftype[fd] > 3) return { 0, 0 }; + bool negative = false; + uint64_t num = 0, den = 1; + if (imm[0] != '?' && !parse_decimal(imm, negative, num, den)) return { 0, 0 }; + if (imm[0] != '?' && num == 0) { + if (negative) return { 0, 0 }; + rn[0] = fd == 3 ? 'X' : 'W'; rn[1] = 'Z'; rn[2] = 'R'; // FMOV Rd, ZR + } else { + op.set_bits(0x1E201000 | ftype[fd] << 22, 0xFFE01FE0); + if (imm[0] == '?') return op; + for (uint32_t imm8 = 0; imm8 < 256; ++imm8) { + // (16 + n) / 16 * 2^e == num / den + const int e = (imm8 >> 6 & 1) ? static_cast(imm8 >> 4 & 3) - 3 : static_cast(imm8 >> 4 & 3) + 1; + const uint64_t m = 16 + (imm8 & 15); + const bool eq = e >= 0 ? m * den << e == num * 16 : m * den == num * 16 << -e; + if (eq && (imm8 >> 7) == (negative ? 1u : 0u)) { + op.set_bits(imm8, 0xFF, 13); + return op; + } + } + return { 0, 0 }; + } + } else { + parse_token(instr, i, rn); + } + if (auto r = parse_register(rn); r != 0xFFFFFFFF) op.set_bits(r, 0x1F, 5); + const int fn = fp_size(rn); + const int gd = register_width(rd), gn = register_width(rn); + const bool qd = str_eq(rd, "?"), qn = str_eq(rn, "?"); + if ((fd >= 0 || qd) && (fn >= 0 || qn)) { + // Between floating-point registers + const int f = fd >= 0 ? fd : fn; + if (f >= 0 && (ftype[f] > 3 || (fd >= 0 && fn >= 0 && fd != fn))) return { 0, 0 }; + op.set_bits(0x1E204000, 0xFF3FFC00); + if (f >= 0) op.set_bits(ftype[f], 3, 22); + return op; + } + // General-purpose <-> floating-point: sf, type, then rmode 00 and opcode 110 (to general) or 111 (from it) + const bool to_fp = fd >= 0; + const int f = to_fp ? fd : fn, g = to_fp ? gn : gd; + if (f < 0 || g < 0 || ftype[f] > 3 || (f == 2 && g != 0) || (f == 3 && g != 1)) return { 0, 0 }; + op.set_bits(0x1E260000 | static_cast(g) << 31 | ftype[f] << 22 | (to_fp ? 1u : 0u) << 16, 0xFFFFFC00); + return op; + } + + // CSEL/CSINC/CSINV/CSNEG Rd, Rn, Rm, cond. regs is how many registers are written: 1 for CSET/CSETM (Rn = Rm = + // ZR), 2 for CINC/CINV/CNEG (Rm = Rn). The aliases store the inverted condition, and can't take AL or NV + template + constexpr ArmOpcode parse_csel(const char* instr, ArmOpcode base) { + constexpr int all[] = { 0, 5, 16 }; + int shifts[regs] = {}, w[regs] = {}; + for (size_t k = 0; k < regs; ++k) shifts[k] = all[k]; + size_t i = parse_reg_list(instr, skip_mnemonic(instr), shifts, base, w); + for (size_t k = 0; k < regs; ++k) + if (w[k] >= 0) { base.set_bits(static_cast(w[k]), 1, 31); break; } + if (regs == 1) base.set_bits(31 << 11 | 31, 0x1F << 11 | 0x1F, 5); + if (regs == 2 && (base.mask >> 5 & 0x1F)) base.set_bits(base.pattern >> 5, 0x1F, 16); + char cond[4] = {}; + parse_token(instr, skip_whitespace(instr, skip_char(instr, i, ',')), cond); + uint32_t c = parse_cond(cond); + if (c == 17 || (regs < 3 && c >= 14 && c < 16)) return { 0, 0 }; + if (c < 16) base.set_bits(regs < 3 ? c ^ 1 : c, 0xF, 12); + return base; + } + + // CCMP/CCMN Rn, Rm|#imm5, #nzcv, cond: the immediate form sets bit 11 + constexpr ArmOpcode parse_ccmp(const char* instr, ArmOpcode base) { + size_t i = skip_whitespace(instr, skip_mnemonic(instr)); + char rn[5] = {}, imm[24] = {}, cond[4] = {}; + i = parse_register(instr, parse_token(instr, i, rn), rn, 0x1F, 5, base, ','); + if (register_width(rn) >= 0) base.set_bits(static_cast(register_width(rn)), 1, 31); + if (instr[i] == '#') { + i = parse_imm_token(instr, i, imm); + base.set_bits(1, 1, 11); + if (imm[0] != '?') { + const int32_t v = stoi(imm); + if (v < 0 || v > 31) return { 0, 0 }; + base.set_bits(static_cast(v), 0x1F, 16); + } + } else { + char rm[5] = {}; + i = parse_token(instr, i, rm); + parse_register(rm, 0x1F, 16, base); + base.set_bits(0, 1, 11); + } + i = parse_imm_token(instr, skip_whitespace(instr, skip_char(instr, i, ',')), imm); + if (imm[0] != '?') { + const int32_t v = stoi(imm); + if (v < 0 || v > 15) return { 0, 0 }; + base.set_bits(static_cast(v), 0xF); + } + parse_token(instr, skip_whitespace(instr, skip_char(instr, i, ',')), cond); + const uint32_t c = parse_cond(cond); + if (c == 17) return { 0, 0 }; + if (c < 16) base.set_bits(c, 0xF, 12); + return base; + } + + enum BitfieldKind { bf_raw, bf_lsl, bf_lsr, bf_extract, bf_insert, bf_extend, bf_ror, bf_extr }; + + // UBFM/SBFM/BFM and their aliases, and EXTR (ROR #imm is EXTR Rd, Rn, Rn): immr (bits 16-21) and imms (10-15) + // computed for the register size; N (bit 22) equals sf. With the width unknown ("?" registers) both sizes + // are encoded and only the bits they agree on are kept; a size the operands don't fit drops out. + // bf_extend (SXTB/SXTH/SXTW, UXTB/UXTH) has imms fixed to extend_imms + template + constexpr ArmOpcode parse_bitfield(const char* instr, ArmOpcode base) { + constexpr size_t nregs = kind == bf_extr ? 3 : 2; + constexpr int all[] = { 0, 5, 16 }; + int shifts[nregs] = {}, w[nregs] = {}; + for (size_t k = 0; k < nregs; ++k) shifts[k] = all[k]; + size_t i = parse_reg_list(instr, skip_mnemonic(instr), shifts, base, w); + if (kind == bf_ror && (base.mask >> 5 & 0x1F)) base.set_bits(base.pattern >> 5, 0x1F, 16); + // Width from Rd; SXT*/UXT* sources are always W, so only Rd says + int width = -1; + for (size_t k = 0; k < (kind == bf_extend ? 1 : nregs) && width < 0; ++k) width = w[k]; + + char a_text[24] = {}, b_text[24] = {}; + i = parse_imm_token(instr, skip_whitespace(instr, skip_char(instr, i, ',')), a_text); + parse_imm_token(instr, skip_whitespace(instr, skip_char(instr, i, ',')), b_text); + const bool ak = a_text[0] != '?', bk = b_text[0] != '?'; + const int32_t a = ak ? stoi(a_text) : 0, b = bk ? stoi(b_text) : 1; + const bool ubfm = (base.pattern >> 29 & 3) == 2 && kind != bf_ror && kind != bf_extr; + + // Only known values are checked; a field depending on a "?" is left open + auto encode = [&](int32_t size) -> ArmOpcode { + if (kind == bf_extend ? (ubfm && size == 64) || (size == 32 && extend_imms == 31) : ak && (a < 0 || a >= size)) + return { 0, 0 }; + if ((kind == bf_raw && bk && (b < 0 || b >= size)) || + ((kind == bf_extract || kind == bf_insert) && bk && (b < 1 || (ak ? a : 0) + b > size))) + return { 0, 0 }; + int32_t immr = 0, imms = 0; + bool immr_known = true, imms_known = true; + switch (kind) { + case bf_raw: immr = a; imms = b; immr_known = ak; imms_known = bk; break; + case bf_lsl: immr = (size - a) & (size - 1); imms = size - 1 - a; immr_known = imms_known = ak; break; + case bf_lsr: immr = a; imms = size - 1; immr_known = ak; break; + case bf_extract: immr = a; imms = a + b - 1; immr_known = ak; imms_known = ak && bk; break; + case bf_insert: immr = (size - a) & (size - 1); imms = b - 1; immr_known = ak; imms_known = bk; break; + case bf_extend: imms = static_cast(extend_imms); break; + case bf_ror: + case bf_extr: imms = a; immr_known = false; imms_known = ak; break; // EXTR has Rm at immr + } + ArmOpcode op{ 0, 0 }; + op.set_bits(size == 64 ? 0x80400000 : 0, 0x80400000); + if (immr_known) op.set_bits(static_cast(immr), 0x3F, 16); + if (imms_known) op.set_bits(static_cast(imms), 0x3F, 10); + return op; + }; + + // A valid encoding always has sf/N in its mask; mask 0 means the size doesn't fit + ArmOpcode e = encode(width == 0 ? 32 : 64); + if (width < 0) { + const ArmOpcode e32 = encode(32), e64 = encode(64); + const uint32_t m = e32.mask & e64.mask & ~(e32.pattern ^ e64.pattern); + if (!e32.mask && !e64.mask) return { 0, 0 }; + e = e32.mask && e64.mask ? ArmOpcode{ e32.pattern & m, m } : e32.mask ? e32 : e64; + } else if (!e.mask) { + return { 0, 0 }; + } + base.set_bits(e.pattern, e.mask); + return base; + } + + // LSL/LSR/ASR/ROR: by an immediate a bitfield (EXTR for ROR) alias, by a register LSLV/LSRV/ASRV/RORV (op2 at + // bits 10-11) + template + constexpr ArmOpcode parse_shift(const char* instr, ArmOpcode base) { + const size_t i = skip_whitespace(instr, skip_past(instr, skip_past(instr, 0, ','), ',')); + if (instr[i] == '#') return parse_bitfield(instr, base); + return parse_regs<31, 7, 0, 5, 16>(instr, { 0x1AC02000 | op2 << 10, 0x7FE0FC00 }); + } + + // LSE atomics and CAS, from the mnemonic: LD{A}{L}{B|H} Rs, Rt, [Rn]; ST{L}{B|H} Rs, [Rn] (LD into + // ZR); SWP{A}{L}{B|H} and CAS{A}{L}{B|H} Rs, Rt, [Rn]. op is ADD, CLR, EOR, SET, SMAX, SMIN, UMAX or UMIN. + // size (bits 30-31) from the B/H suffix, else W or X from Rs/Rt + constexpr ArmOpcode parse_atomic(const char* m, const char* instr) { + constexpr const char* ops[] = { "ADD", "CLR", "EOR", "SET", "SMAX", "SMIN", "UMAX", "UMIN" }; + ArmOpcode base{ 0x38200000, 0x3F200C00 }; + uint32_t a_bit = 23, l_bit = 22; // acquire, release + size_t j = 0; + const bool store = starts_with(m, "ST"); + if (starts_with(m, "CAS")) { base = { 0x08A07C00, 0x3FA07C00 }; a_bit = 22; l_bit = 15; j = 3; } + else if (starts_with(m, "SWP")) { base.set_bits(8, 0xF, 12); j = 3; } + else if (starts_with(m, "LD") || store) { + for (uint32_t k = 0; k < 8 && !j; ++k) + if (starts_with(m + 2, ops[k])) { base.set_bits(k, 0xF, 12); j = k < 4 ? 5 : 6; } + } + if (!j) return { 0, 0 }; + const bool acquire = m[j] == 'A' && !store; + if (acquire) ++j; + const bool release = m[j] == 'L'; + if (release) ++j; + const int size = m[j] == 'B' ? 0 : m[j] == 'H' ? 1 : -1; + if (size >= 0) ++j; + if (m[j]) return { 0, 0 }; + base.set_bits(acquire ? 1 : 0, 1, a_bit); + base.set_bits(release ? 1 : 0, 1, l_bit); + base.set_bits(size >= 0 ? static_cast(size) : 2, size >= 0 ? 3 : 2, 30); + + constexpr int load_shifts[] = { 16, 0, 5 }, store_shifts[] = { 16, 5 }; + int w[3] = { -1, -1, -1 }, ws[2] = {}; + if (store) { + parse_reg_list(instr, skip_mnemonic(instr), store_shifts, base, ws); + base.set_bits(31, 0x1F); + w[0] = ws[0]; + } else { + parse_reg_list(instr, skip_mnemonic(instr), load_shifts, base, w); + } + const int width = w[0] >= 0 ? w[0] : w[1]; + if (size < 0 && width >= 0) base.set_bits(static_cast(width), 1, 30); return base; } @@ -687,11 +1083,14 @@ namespace patterns { { "AND", parse_orr }, { "ANDS", parse_orr }, { "EOR", parse_orr }, - { "TST", parse_zr_dest }, - { "CMP", parse_zr_dest }, - { "CMN", parse_zr_dest }, + { "TST", parse_zr }, + { "CMP", parse_zr }, + { "CMN", parse_zr }, { "LDP", parse_ldp }, - { "STP", parse_stp }, + { "STP", parse_ldp }, + { "LDPSW", parse_ldp<1> }, + { "LDNP", parse_ldp<-1, true> }, + { "STNP", parse_ldp<-1, true> }, { "MOV", parse_mov }, { "ADD", parse_arithmetic }, { "ADDS", parse_arithmetic }, @@ -702,6 +1101,137 @@ namespace patterns { { "BR", parse_branch_reg }, { "BLR", parse_branch_reg }, { "RET", parse_branch_reg }, + { "TBZ", parse_tbz }, + { "TBNZ", parse_tbz }, + { "SVC", parse_imm16<5> }, + { "HVC", parse_imm16<5> }, + { "SMC", parse_imm16<5> }, + { "BRK", parse_imm16<5> }, + { "HLT", parse_imm16<5> }, + { "UDF", parse_imm16<0> }, + { "BTI", parse_bti }, + { "BRAA", parse_regs<-1, 0, 5, 0> }, + { "BRAB", parse_regs<-1, 0, 5, 0> }, + { "BLRAA", parse_regs<-1, 0, 5, 0> }, + { "BLRAB", parse_regs<-1, 0, 5, 0> }, + { "BRAAZ", parse_branch_reg }, + { "BRABZ", parse_branch_reg }, + { "BLRAAZ", parse_branch_reg }, + { "BLRABZ", parse_branch_reg }, + { "LDRAA", parse_ldraa }, + { "LDRAB", parse_ldraa }, + { "PACIA", parse_regs<-1, 0, 0, 5> }, + { "PACIB", parse_regs<-1, 0, 0, 5> }, + { "PACDA", parse_regs<-1, 0, 0, 5> }, + { "PACDB", parse_regs<-1, 0, 0, 5> }, + { "AUTIA", parse_regs<-1, 0, 0, 5> }, + { "AUTIB", parse_regs<-1, 0, 0, 5> }, + { "AUTDA", parse_regs<-1, 0, 0, 5> }, + { "AUTDB", parse_regs<-1, 0, 0, 5> }, + { "PACIZA", parse_regs<-1, 0, 0> }, + { "PACIZB", parse_regs<-1, 0, 0> }, + { "PACDZA", parse_regs<-1, 0, 0> }, + { "PACDZB", parse_regs<-1, 0, 0> }, + { "AUTIZA", parse_regs<-1, 0, 0> }, + { "AUTIZB", parse_regs<-1, 0, 0> }, + { "AUTDZA", parse_regs<-1, 0, 0> }, + { "AUTDZB", parse_regs<-1, 0, 0> }, + { "XPACI", parse_regs<-1, 0, 0> }, + { "XPACD", parse_regs<-1, 0, 0> }, + { "PACGA", parse_regs<-1, 0, 0, 5, 16> }, + { "LDRSB", parse_load_store<0x39800000, 0, false, true> }, + { "LDRSH", parse_load_store<0x79800000, 1, false, true> }, + { "LDURSB", parse_load_store<0x39800000, 0, true, true> }, + { "LDURSH", parse_load_store<0x79800000, 1, true, true> }, + { "LDXR", parse_regs<30, 1, 0, 5> }, + { "LDAXR", parse_regs<30, 1, 0, 5> }, + { "LDAR", parse_regs<30, 1, 0, 5> }, + { "LDAPR", parse_regs<30, 1, 0, 5> }, + { "STLR", parse_regs<30, 1, 0, 5> }, + { "STXR", parse_regs<30, 2, 16, 0, 5> }, + { "STLXR", parse_regs<30, 2, 16, 0, 5> }, + { "LDXRB", parse_regs<-1, 0, 0, 5> }, + { "LDXRH", parse_regs<-1, 0, 0, 5> }, + { "LDAXRB", parse_regs<-1, 0, 0, 5> }, + { "LDAXRH", parse_regs<-1, 0, 0, 5> }, + { "LDARB", parse_regs<-1, 0, 0, 5> }, + { "LDARH", parse_regs<-1, 0, 0, 5> }, + { "LDAPRB", parse_regs<-1, 0, 0, 5> }, + { "LDAPRH", parse_regs<-1, 0, 0, 5> }, + { "STLRB", parse_regs<-1, 0, 0, 5> }, + { "STLRH", parse_regs<-1, 0, 0, 5> }, + { "STXRB", parse_regs<-1, 0, 16, 0, 5> }, + { "STXRH", parse_regs<-1, 0, 16, 0, 5> }, + { "STLXRB", parse_regs<-1, 0, 16, 0, 5> }, + { "STLXRH", parse_regs<-1, 0, 16, 0, 5> }, + { "LDXP", parse_regs<30, 3, 0, 10, 5> }, + { "LDAXP", parse_regs<30, 3, 0, 10, 5> }, + { "STXP", parse_regs<30, 6, 16, 0, 10, 5> }, + { "STLXP", parse_regs<30, 6, 16, 0, 10, 5> }, + { "FMOV", parse_fmov }, + { "CSEL", parse_csel<3> }, + { "CSINC", parse_csel<3> }, + { "CSINV", parse_csel<3> }, + { "CSNEG", parse_csel<3> }, + { "CSET", parse_csel<1> }, + { "CSETM", parse_csel<1> }, + { "CINC", parse_csel<2> }, + { "CINV", parse_csel<2> }, + { "CNEG", parse_csel<2> }, + { "CCMP", parse_ccmp }, + { "CCMN", parse_ccmp }, + { "MADD", parse_regs<31, 15, 0, 5, 16, 10> }, + { "MSUB", parse_regs<31, 15, 0, 5, 16, 10> }, + { "MUL", parse_regs<31, 7, 0, 5, 16> }, + { "MNEG", parse_regs<31, 7, 0, 5, 16> }, + { "SMADDL", parse_regs<-1, 0, 0, 5, 16, 10> }, + { "SMSUBL", parse_regs<-1, 0, 0, 5, 16, 10> }, + { "UMADDL", parse_regs<-1, 0, 0, 5, 16, 10> }, + { "UMSUBL", parse_regs<-1, 0, 0, 5, 16, 10> }, + { "SMULL", parse_regs<-1, 0, 0, 5, 16> }, + { "SMNEGL", parse_regs<-1, 0, 0, 5, 16> }, + { "UMULL", parse_regs<-1, 0, 0, 5, 16> }, + { "UMNEGL", parse_regs<-1, 0, 0, 5, 16> }, + { "SMULH", parse_regs<-1, 0, 0, 5, 16> }, + { "UMULH", parse_regs<-1, 0, 0, 5, 16> }, + { "UDIV", parse_regs<31, 7, 0, 5, 16> }, + { "SDIV", parse_regs<31, 7, 0, 5, 16> }, + { "LSLV", parse_regs<31, 7, 0, 5, 16> }, + { "LSRV", parse_regs<31, 7, 0, 5, 16> }, + { "ASRV", parse_regs<31, 7, 0, 5, 16> }, + { "RORV", parse_regs<31, 7, 0, 5, 16> }, + { "ADC", parse_regs<31, 7, 0, 5, 16> }, + { "ADCS", parse_regs<31, 7, 0, 5, 16> }, + { "SBC", parse_regs<31, 7, 0, 5, 16> }, + { "SBCS", parse_regs<31, 7, 0, 5, 16> }, + { "NGC", parse_zr, 1> }, + { "NGCS", parse_zr, 1> }, + { "BIC", parse_orr }, + { "BICS", parse_orr }, + { "ORN", parse_orr }, + { "EON", parse_orr }, + { "MVN", parse_zr }, + { "NEG", parse_zr }, + { "NEGS", parse_zr }, + { "SBFM", parse_bitfield }, + { "BFM", parse_bitfield }, + { "UBFM", parse_bitfield }, + { "EXTR", parse_bitfield }, + { "LSL", parse_shift }, + { "LSR", parse_shift }, + { "ASR", parse_shift }, + { "ROR", parse_shift }, + { "UBFX", parse_bitfield }, + { "SBFX", parse_bitfield }, + { "BFXIL", parse_bitfield }, + { "UBFIZ", parse_bitfield }, + { "SBFIZ", parse_bitfield }, + { "BFI", parse_bitfield }, + { "SXTB", parse_bitfield }, + { "SXTH", parse_bitfield }, + { "SXTW", parse_bitfield }, + { "UXTB", parse_bitfield }, + { "UXTH", parse_bitfield }, }; constexpr InstructionDescriptor instructions[] = { @@ -738,6 +1268,144 @@ namespace patterns { { "PACIBSP", { 0xD503237F, 0xFFFFFFFF } }, { "AUTIASP", { 0xD50323BF, 0xFFFFFFFF } }, { "AUTIBSP", { 0xD50323FF, 0xFFFFFFFF } }, + { "PACIAZ", { 0xD503231F, 0xFFFFFFFF } }, + { "PACIBZ", { 0xD503235F, 0xFFFFFFFF } }, + { "AUTIAZ", { 0xD503239F, 0xFFFFFFFF } }, + { "AUTIBZ", { 0xD50323DF, 0xFFFFFFFF } }, + { "PACIA1716", { 0xD503211F, 0xFFFFFFFF } }, + { "PACIB1716", { 0xD503215F, 0xFFFFFFFF } }, + { "AUTIA1716", { 0xD503219F, 0xFFFFFFFF } }, + { "AUTIB1716", { 0xD50321DF, 0xFFFFFFFF } }, + { "XPACLRI", { 0xD50320FF, 0xFFFFFFFF } }, + { "BTI", { 0xD503241F, 0xFFFFFFFF } }, + { "TBZ", { 0x36000000, 0x7F000000 } }, + { "TBNZ", { 0x37000000, 0x7F000000 } }, + { "SVC", { 0xD4000001, 0xFFE0001F } }, + { "HVC", { 0xD4000002, 0xFFE0001F } }, + { "SMC", { 0xD4000003, 0xFFE0001F } }, + { "BRK", { 0xD4200000, 0xFFE0001F } }, + { "HLT", { 0xD4400000, 0xFFE0001F } }, + { "UDF", { 0x00000000, 0xFFFF0000 } }, + { "BRAA", { 0xD71F0800, 0xFFFFFC00 } }, + { "BRAB", { 0xD71F0C00, 0xFFFFFC00 } }, + { "BLRAA", { 0xD73F0800, 0xFFFFFC00 } }, + { "BLRAB", { 0xD73F0C00, 0xFFFFFC00 } }, + { "BRAAZ", { 0xD61F081F, 0xFFFFFC1F } }, + { "BRABZ", { 0xD61F0C1F, 0xFFFFFC1F } }, + { "BLRAAZ", { 0xD63F081F, 0xFFFFFC1F } }, + { "BLRABZ", { 0xD63F0C1F, 0xFFFFFC1F } }, + { "LDRAA", { 0xF8200400, 0xFFA00400 } }, + { "LDRAB", { 0xF8A00400, 0xFFA00400 } }, + { "PACIA", { 0xDAC10000, 0xFFFFFC00 } }, + { "PACIB", { 0xDAC10400, 0xFFFFFC00 } }, + { "PACDA", { 0xDAC10800, 0xFFFFFC00 } }, + { "PACDB", { 0xDAC10C00, 0xFFFFFC00 } }, + { "AUTIA", { 0xDAC11000, 0xFFFFFC00 } }, + { "AUTIB", { 0xDAC11400, 0xFFFFFC00 } }, + { "AUTDA", { 0xDAC11800, 0xFFFFFC00 } }, + { "AUTDB", { 0xDAC11C00, 0xFFFFFC00 } }, + { "PACIZA", { 0xDAC123E0, 0xFFFFFFE0 } }, + { "PACIZB", { 0xDAC127E0, 0xFFFFFFE0 } }, + { "PACDZA", { 0xDAC12BE0, 0xFFFFFFE0 } }, + { "PACDZB", { 0xDAC12FE0, 0xFFFFFFE0 } }, + { "AUTIZA", { 0xDAC133E0, 0xFFFFFFE0 } }, + { "AUTIZB", { 0xDAC137E0, 0xFFFFFFE0 } }, + { "AUTDZA", { 0xDAC13BE0, 0xFFFFFFE0 } }, + { "AUTDZB", { 0xDAC13FE0, 0xFFFFFFE0 } }, + { "XPACI", { 0xDAC143E0, 0xFFFFFFE0 } }, + { "XPACD", { 0xDAC147E0, 0xFFFFFFE0 } }, + { "PACGA", { 0x9AC03000, 0xFFE0FC00 } }, + { "LDXR", { 0x885F7C00, 0xBFFFFC00 } }, + { "LDAXR", { 0x885FFC00, 0xBFFFFC00 } }, + { "LDAR", { 0x88DFFC00, 0xBFFFFC00 } }, + { "LDAPR", { 0xB8BFC000, 0xBFFFFC00 } }, + { "STLR", { 0x889FFC00, 0xBFFFFC00 } }, + { "STXR", { 0x88007C00, 0xBFE0FC00 } }, + { "STLXR", { 0x8800FC00, 0xBFE0FC00 } }, + { "LDXRB", { 0x085F7C00, 0xFFFFFC00 } }, + { "LDXRH", { 0x485F7C00, 0xFFFFFC00 } }, + { "LDAXRB", { 0x085FFC00, 0xFFFFFC00 } }, + { "LDAXRH", { 0x485FFC00, 0xFFFFFC00 } }, + { "LDARB", { 0x08DFFC00, 0xFFFFFC00 } }, + { "LDARH", { 0x48DFFC00, 0xFFFFFC00 } }, + { "LDAPRB", { 0x38BFC000, 0xFFFFFC00 } }, + { "LDAPRH", { 0x78BFC000, 0xFFFFFC00 } }, + { "STLRB", { 0x089FFC00, 0xFFFFFC00 } }, + { "STLRH", { 0x489FFC00, 0xFFFFFC00 } }, + { "STXRB", { 0x08007C00, 0xFFE0FC00 } }, + { "STXRH", { 0x48007C00, 0xFFE0FC00 } }, + { "STLXRB", { 0x0800FC00, 0xFFE0FC00 } }, + { "STLXRH", { 0x4800FC00, 0xFFE0FC00 } }, + { "LDXP", { 0x887F0000, 0xBFFF8000 } }, + { "LDAXP", { 0x887F8000, 0xBFFF8000 } }, + { "STXP", { 0x88200000, 0xBFE08000 } }, + { "STLXP", { 0x88208000, 0xBFE08000 } }, + { "LDPSW", { 0x28400000, 0x7E400000 } }, + { "LDNP", { 0x28400000, 0x7E400000 } }, + { "STNP", { 0x28000000, 0x7E400000 } }, + { "CSEL", { 0x1A800000, 0x7FE00C00 } }, + { "CSINC", { 0x1A800400, 0x7FE00C00 } }, + { "CSINV", { 0x5A800000, 0x7FE00C00 } }, + { "CSNEG", { 0x5A800400, 0x7FE00C00 } }, + { "CSET", { 0x1A800400, 0x7FE00C00 } }, // CSINC Rd, ZR, ZR + { "CSETM", { 0x5A800000, 0x7FE00C00 } }, // CSINV Rd, ZR, ZR + { "CINC", { 0x1A800400, 0x7FE00C00 } }, // CSINC Rd, Rn, Rn + { "CINV", { 0x5A800000, 0x7FE00C00 } }, // CSINV Rd, Rn, Rn + { "CNEG", { 0x5A800400, 0x7FE00C00 } }, // CSNEG Rd, Rn, Rn + { "CCMP", { 0x7A400000, 0x7FE00410 } }, + { "CCMN", { 0x3A400000, 0x7FE00410 } }, + { "MADD", { 0x1B000000, 0x7FE08000 } }, + { "MSUB", { 0x1B008000, 0x7FE08000 } }, + { "MUL", { 0x1B007C00, 0x7FE0FC00 } }, // MADD Ra = ZR + { "MNEG", { 0x1B00FC00, 0x7FE0FC00 } }, // MSUB Ra = ZR + { "SMADDL", { 0x9B200000, 0xFFE08000 } }, + { "SMSUBL", { 0x9B208000, 0xFFE08000 } }, + { "UMADDL", { 0x9BA00000, 0xFFE08000 } }, + { "UMSUBL", { 0x9BA08000, 0xFFE08000 } }, + { "SMULL", { 0x9B207C00, 0xFFE0FC00 } }, + { "SMNEGL", { 0x9B20FC00, 0xFFE0FC00 } }, + { "UMULL", { 0x9BA07C00, 0xFFE0FC00 } }, + { "UMNEGL", { 0x9BA0FC00, 0xFFE0FC00 } }, + { "SMULH", { 0x9B407C00, 0xFFE0FC00 } }, + { "UMULH", { 0x9BC07C00, 0xFFE0FC00 } }, + { "UDIV", { 0x1AC00800, 0x7FE0FC00 } }, + { "SDIV", { 0x1AC00C00, 0x7FE0FC00 } }, + { "LSLV", { 0x1AC02000, 0x7FE0FC00 } }, + { "LSRV", { 0x1AC02400, 0x7FE0FC00 } }, + { "ASRV", { 0x1AC02800, 0x7FE0FC00 } }, + { "RORV", { 0x1AC02C00, 0x7FE0FC00 } }, + { "ADC", { 0x1A000000, 0x7FE0FC00 } }, + { "ADCS", { 0x3A000000, 0x7FE0FC00 } }, + { "SBC", { 0x5A000000, 0x7FE0FC00 } }, + { "SBCS", { 0x7A000000, 0x7FE0FC00 } }, + { "NGC", { 0x5A000000, 0x7FE0FC00 } }, // SBC Rd, ZR, Rm + { "NGCS", { 0x7A000000, 0x7FE0FC00 } }, // SBCS Rd, ZR, Rm + { "BIC", { 0x02200000, 0x67200000 } }, + { "BICS", { 0x62200000, 0x67200000 } }, + { "ORN", { 0x22200000, 0x67200000 } }, + { "EON", { 0x42200000, 0x67200000 } }, + { "MVN", { 0x22200000, 0x67200000 } }, // ORN Rd, ZR, Rm + { "NEG", { 0x41000000, 0x65000000 } }, // SUB Rd, ZR, Rm + { "NEGS", { 0x61000000, 0x65000000 } }, // SUBS Rd, ZR, Rm + { "SBFM", { 0x13000000, 0x7F800000 } }, + { "BFM", { 0x33000000, 0x7F800000 } }, + { "UBFM", { 0x53000000, 0x7F800000 } }, + { "EXTR", { 0x13800000, 0x7FA00000 } }, + { "LSL", { 0x53000000, 0x7F800000 } }, // UBFM + { "LSR", { 0x53000000, 0x7F800000 } }, // UBFM + { "ASR", { 0x13000000, 0x7F800000 } }, // SBFM + { "ROR", { 0x13800000, 0x7FA00000 } }, // EXTR Rd, Rn, Rn + { "UBFX", { 0x53000000, 0x7F800000 } }, + { "SBFX", { 0x13000000, 0x7F800000 } }, + { "BFXIL", { 0x33000000, 0x7F800000 } }, + { "UBFIZ", { 0x53000000, 0x7F800000 } }, + { "SBFIZ", { 0x13000000, 0x7F800000 } }, + { "BFI", { 0x33000000, 0x7F800000 } }, + { "SXTB", { 0x13000000, 0x7F800000 } }, + { "SXTH", { 0x13000000, 0x7F800000 } }, + { "SXTW", { 0x13000000, 0x7F800000 } }, + { "UXTB", { 0x53000000, 0x7F800000 } }, + { "UXTH", { 0x53000000, 0x7F800000 } }, }; constexpr ArmOpcode find_opcode(const char* mnemonic) { @@ -750,9 +1418,9 @@ namespace patterns { } constexpr ArmOpcode a64_encode_instruction(const char* instr) { - char mnemonic[8] = {}; + char mnemonic[16] = {}; size_t i = 0; - while (instr[i] && !isspace(instr[i]) && i < 7) { + while (instr[i] && !isspace(instr[i]) && i < 15) { mnemonic[i] = instr[i]; ++i; } @@ -767,7 +1435,7 @@ namespace patterns { } } - return base; + return base.mask ? base : parse_atomic(mnemonic, instr); } // Copies the line at str[i] into out, trimmed and uppercased (lldb/objdump print lowercase), so each diff --git a/include/PatternScanner/Pattern.hpp b/include/PatternScanner/Pattern.hpp index aa7ca9c..a2d3784 100644 --- a/include/PatternScanner/Pattern.hpp +++ b/include/PatternScanner/Pattern.hpp @@ -195,16 +195,18 @@ namespace patterns { } // https://developer.arm.com/documentation/ddi0602/2023-12/Index-by-Encoding/Loads-and-Stores - // Single general-purpose register loads/stores with an immediate offset (LDR/STR/LDRB/LDRH/LDRSW... and the - // LDUR/STUR forms). *offset is signed; *post_index is set for [Rn],#imm, which accesses Rn itself + // Single-register loads/stores with an immediate offset (LDR/STR/LDRB/LDRH/LDRSW... and the LDUR/STUR forms, + // general-purpose or floating-point/SIMD). *offset is signed; *post_index is set for [Rn],#imm, which accesses + // Rn itself inline bool a64_decode_load_store(uint32_t insn, unsigned* rn, unsigned* rt, int64_t* offset, bool* post_index = nullptr) { - // size 111 0 01 opc imm12 Rn Rt: unsigned offset, scaled by the access size - if (decode_masked_match(insn, 0x3F000000, 0x39000000)) { - *offset = static_cast(extract_bitfield(insn, 12, 10)) << (insn >> 30); + // size 111 V 01 opc imm12 Rn Rt: unsigned offset, scaled by the access size (a Q register is size 00, opc 1x) + if (decode_masked_match(insn, 0x3B000000, 0x39000000)) { + const unsigned scale = (insn >> 30) | ((insn >> 26) & (insn >> 23) & 1) << 2; + *offset = static_cast(extract_bitfield(insn, 12, 10)) << scale; if (post_index) *post_index = false; } - // size 111 0 00 opc 0 imm9 mode Rn Rt: unscaled (00), post-index (01), unprivileged (10), pre-index (11) - else if (decode_masked_match(insn, 0x3F200000, 0x38000000)) { + // size 111 V 00 opc 0 imm9 mode Rn Rt: unscaled (00), post-index (01), unprivileged (10), pre-index (11) + else if (decode_masked_match(insn, 0x3B200000, 0x38000000)) { *offset = extract_bitfield(insn, 9, 12); if (post_index) *post_index = ((insn >> 10) & 3) == 1; } @@ -214,13 +216,18 @@ namespace patterns { return true; } - // B.cond, CBZ/CBNZ and LDR (literal): a PC-relative imm19 - inline bool a64_decode_imm19(uint32_t insn, int64_t* offset) { + // B.cond, CBZ/CBNZ and LDR (literal, general-purpose or floating-point/SIMD): a PC-relative imm19. + // TBZ/TBNZ: imm14 + inline bool a64_decode_branch_imm(uint32_t insn, int64_t* offset) { if (decode_masked_match(insn, 0xFF000010, 0x54000000) || decode_masked_match(insn, 0x7E000000, 0x34000000) - || decode_masked_match(insn, 0x3F000000, 0x18000000)) { + || decode_masked_match(insn, 0x3B000000, 0x18000000)) { *offset = static_cast(extract_bitfield(insn, 19, 5)) * 4; return true; } + if (decode_masked_match(insn, 0x7E000000, 0x36000000)) { + *offset = static_cast(extract_bitfield(insn, 14, 5)) * 4; + return true; + } return false; } @@ -522,7 +529,7 @@ namespace patterns { const auto from_addr = is_adrp ? reinterpret_cast(insn) & ~0xfff : reinterpret_cast(insn); return reinterpret_cast(from_addr + saved_offset); } - else if (detail::a64_decode_imm19(*insn, &offset)) { + else if (detail::a64_decode_branch_imm(*insn, &offset)) { return reinterpret_cast(reinterpret_cast(insn) + offset); } else if (detail::a64_decode_load_store(*insn, nullptr, nullptr, &offset)) { diff --git a/tests/test_arm.cpp b/tests/test_arm.cpp index 82a9916..b53c3e2 100644 --- a/tests/test_arm.cpp +++ b/tests/test_arm.cpp @@ -160,7 +160,265 @@ X("ADD W0, WSP, W1", 0x0B2143E0) \ X("LDP X29, X30, [SP]", 0xA9407BFD) \ X("STP X29, X30, [SP]", 0xA9007BFD) \ - X("ADR X0, #-1", 0x70FFFFE0) + X("ADR X0, #-1", 0x70FFFFE0) \ + X("TBZ W0, #3, #8", 0x36180040) \ + X("TBNZ X1, #40, #-8", 0xB747FFC1) \ + X("TBZ X5, #3, #0x100", 0x36180805) \ + X("TBNZ W30, #31, #-0x8000", 0x37FC001E) \ + X("SVC #0x80", 0xD4001001) \ + X("SVC #0", 0xD4000001) \ + X("BRK #1", 0xD4200020) \ + X("BRK #0xF000", 0xD43E0000) \ + X("HLT #0", 0xD4400000) \ + X("UDF #0", 0x00000000) \ + X("UDF #0x1234", 0x00001234) \ + X("HVC #1", 0xD4000022) \ + X("BTI", 0xD503241F) \ + X("BTI C", 0xD503245F) \ + X("BTI J", 0xD503249F) \ + X("BTI JC", 0xD50324DF) \ + X("BRAA X16, X17", 0xD71F0A11) \ + X("BRAB X8, SP", 0xD71F0D1F) \ + X("BLRAA X8, X9", 0xD73F0909) \ + X("BLRAB X1, X2", 0xD73F0C22) \ + X("BRAAZ X16", 0xD61F0A1F) \ + X("BRABZ X3", 0xD61F0C7F) \ + X("BLRAAZ X8", 0xD63F091F) \ + X("BLRABZ X8", 0xD63F0D1F) \ + X("LDRAA X0, [X1,#8]", 0xF8201420) \ + X("LDRAB X0, [X1,#-8]!", 0xF8FFFC20) \ + X("LDRAA X16, [X17]", 0xF8200630) \ + X("LDRAA X1, [SP,#4088]", 0xF83FF7E1) \ + X("LDRAB X1, [X2,#-4096]", 0xF8E00441) \ + X("PACIA X0, X1", 0xDAC10020) \ + X("PACIB X16, SP", 0xDAC107F0) \ + X("PACDA X3, X4", 0xDAC10883) \ + X("PACDB X3, X4", 0xDAC10C83) \ + X("AUTIA X16, X17", 0xDAC11230) \ + X("AUTIB X5, SP", 0xDAC117E5) \ + X("AUTDA X1, X2", 0xDAC11841) \ + X("AUTDB X1, X2", 0xDAC11C41) \ + X("PACIZA X0", 0xDAC123E0) \ + X("PACIZB X1", 0xDAC127E1) \ + X("PACDZA X2", 0xDAC12BE2) \ + X("PACDZB X3", 0xDAC12FE3) \ + X("AUTIZA X4", 0xDAC133E4) \ + X("AUTIZB X5", 0xDAC137E5) \ + X("AUTDZA X6", 0xDAC13BE6) \ + X("AUTDZB X7", 0xDAC13FE7) \ + X("XPACI X0", 0xDAC143E0) \ + X("XPACD X1", 0xDAC147E1) \ + X("XPACLRI", 0xD50320FF) \ + X("PACGA X0, X1, X2", 0x9AC23020) \ + X("PACIAZ", 0xD503231F) \ + X("PACIBZ", 0xD503235F) \ + X("AUTIAZ", 0xD503239F) \ + X("AUTIBZ", 0xD50323DF) \ + X("PACIA1716", 0xD503211F) \ + X("PACIB1716", 0xD503215F) \ + X("AUTIA1716", 0xD503219F) \ + X("AUTIB1716", 0xD50321DF) \ + X("LDRSB W0, [X1,#1]", 0x39C00420) \ + X("LDRSB X0, [X1,#1]", 0x39800420) \ + X("LDRSH W0, [X1,#2]", 0x79C00420) \ + X("LDRSH X0, [X1,#0x7E]", 0x7980FC20) \ + X("LDRSB W0, [X1,#-1]", 0x38DFF020) \ + X("LDRSH X2, [X3,#2]!", 0x78802C62) \ + X("LDRSB W2, [X3],#1", 0x38C01462) \ + X("LDRSH W4, [X5,X6,LSL#1]", 0x78E678A4) \ + X("LDURSB W0, [X1,#-1]", 0x38DFF020) \ + X("LDURSH X0, [X1,#3]", 0x78803020) \ + X("LDPSW X0, X1, [X2,#8]", 0x69410440) \ + X("LDPSW X0, X1, [SP,#-8]!", 0x69FF07E0) \ + X("LDPSW X0, X1, [X2],#16", 0x68C20440) \ + X("LDNP X0, X1, [X2,#16]", 0xA8410440) \ + X("STNP W0, W1, [X2,#-8]", 0x283F0440) \ + X("LDNP Q0, Q1, [X2,#32]", 0xAC410440) \ + X("STNP D0, D1, [SP]", 0x6C0007E0) \ + X("LDXR X0, [X1]", 0xC85F7C20) \ + X("LDXR W0, [X1]", 0x885F7C20) \ + X("STXR W2, X0, [X1]", 0xC8027C20) \ + X("LDAXR W0, [X1]", 0x885FFC20) \ + X("STLXR W2, W0, [X1]", 0x8802FC20) \ + X("LDAR X0, [X1]", 0xC8DFFC20) \ + X("STLR W0, [X1]", 0x889FFC20) \ + X("LDAPR X0, [X1]", 0xF8BFC020) \ + X("LDAXRB W0, [X1]", 0x085FFC20) \ + X("LDXRH W0, [X1]", 0x485F7C20) \ + X("LDARB W3, [SP]", 0x08DFFFE3) \ + X("LDARH W3, [X4]", 0x48DFFC83) \ + X("STLRB W3, [X4]", 0x089FFC83) \ + X("STLRH W3, [X4]", 0x489FFC83) \ + X("LDAPRB W0, [X1]", 0x38BFC020) \ + X("LDAPRH W0, [X1]", 0x78BFC020) \ + X("STXRB W2, W0, [X1]", 0x08027C20) \ + X("STXRH W2, W0, [X1]", 0x48027C20) \ + X("STLXRB W2, W0, [X1]", 0x0802FC20) \ + X("STLXRH W2, W0, [X1]", 0x4802FC20) \ + X("LDXP X0, X1, [X2]", 0xC87F0440) \ + X("LDAXP W0, W1, [X2]", 0x887F8440) \ + X("STXP W3, X0, X1, [X2]", 0xC8230440) \ + X("STLXP W3, W0, W1, [X2]", 0x88238440) \ + X("CAS X0, X1, [X2]", 0xC8A07C41) \ + X("CASA W0, W1, [X2]", 0x88E07C41) \ + X("CASL X0, X1, [SP]", 0xC8A0FFE1) \ + X("CASAL W0, W1, [X2]", 0x88E0FC41) \ + X("CASB W0, W1, [X2]", 0x08A07C41) \ + X("CASALH W0, W1, [X2]", 0x48E0FC41) \ + X("LDADD X0, X1, [X2]", 0xF8200041) \ + X("LDADDA W0, W1, [X2]", 0xB8A00041) \ + X("LDADDL W0, W1, [X2]", 0xB8600041) \ + X("LDADDAL X0, X1, [X2]", 0xF8E00041) \ + X("LDADDALB W0, W1, [X2]", 0x38E00041) \ + X("LDADDH W0, W1, [X2]", 0x78200041) \ + X("LDCLR W0, W1, [X2]", 0xB8201041) \ + X("LDEOR X0, X1, [X2]", 0xF8202041) \ + X("LDSET W0, W1, [X2]", 0xB8203041) \ + X("LDSMAX X0, X1, [X2]", 0xF8204041) \ + X("LDSMINAL W0, W1, [X2]", 0xB8E05041) \ + X("LDUMAXB W0, W1, [X2]", 0x38206041) \ + X("LDUMINLH W0, W1, [X2]", 0x78607041) \ + X("SWP X0, X1, [X2]", 0xF8208041) \ + X("SWPAL W0, W1, [X2]", 0xB8E08041) \ + X("SWPB W0, W1, [X2]", 0x38208041) \ + X("STADD W0, [X2]", 0xB820005F) \ + X("STADDL X0, [X2]", 0xF860005F) \ + X("STCLRB W0, [X2]", 0x3820105F) \ + X("STSETLH W0, [X2]", 0x7860305F) \ + X("STSMAX X0, [SP]", 0xF82043FF) \ + X("LSL X0, X1, #3", 0xD37DF020) \ + X("LSL W0, W1, #3", 0x531D7020) \ + X("LSL X0, X1, #63", 0xD3410020) \ + X("LSL W0, W1, #0", 0x53007C20) \ + X("LSR X0, X1, #3", 0xD343FC20) \ + X("LSR W0, W1, #31", 0x531F7C20) \ + X("ASR W0, W1, #31", 0x131F7C20) \ + X("ASR X0, X1, #1", 0x9341FC20) \ + X("ROR X0, X1, #7", 0x93C11C20) \ + X("ROR W0, W1, #31", 0x13817C20) \ + X("LSL X0, X1, X2", 0x9AC22020) \ + X("LSR W0, W1, W2", 0x1AC22420) \ + X("ASR X0, X1, X2", 0x9AC22820) \ + X("ROR W0, W1, W2", 0x1AC22C20) \ + X("LSLV X0, X1, X2", 0x9AC22020) \ + X("RORV W0, W1, W2", 0x1AC22C20) \ + X("UBFX X0, X1, #4, #8", 0xD3442C20) \ + X("UBFX W0, W1, #31, #1", 0x531F7C20) \ + X("SBFX X0, X1, #0, #32", 0x93407C20) \ + X("BFXIL W0, W1, #3, #2", 0x33031020) \ + X("UBFIZ X0, X1, #4, #8", 0xD37C1C20) \ + X("SBFIZ W0, W1, #2, #5", 0x131E1020) \ + X("BFI X0, X1, #8, #4", 0xB3780C20) \ + X("BFI W0, W1, #0, #32", 0x33007C20) \ + X("SXTB X0, W1", 0x93401C20) \ + X("SXTB W0, W1", 0x13001C20) \ + X("SXTH X0, W1", 0x93403C20) \ + X("SXTH W0, W1", 0x13003C20) \ + X("SXTW X0, W1", 0x93407C20) \ + X("UXTB W0, W1", 0x53001C20) \ + X("UXTH W0, W1", 0x53003C20) \ + X("UBFM X0, X1, #4, #11", 0xD3442C20) \ + X("SBFM W0, W1, #1, #2", 0x13010820) \ + X("BFM X0, X1, #56, #3", 0xB3780C20) \ + X("EXTR X0, X1, X2, #8", 0x93C22020) \ + X("EXTR W0, W1, W2, #31", 0x13827C20) \ + X("CSEL X0, X1, X2, EQ", 0x9A820020) \ + X("CSEL W0, W1, W2, HS", 0x1A822020) \ + X("CSINC W0, W1, W2, NE", 0x1A821420) \ + X("CSINV X0, X1, XZR, LT", 0xDA9FB020) \ + X("CSNEG W0, W1, W2, AL", 0x5A82E420) \ + X("CSET W0, EQ", 0x1A9F17E0) \ + X("CSET X0, HI", 0x9A9F97E0) \ + X("CSETM X0, NE", 0xDA9F03E0) \ + X("CINC X0, X1, LT", 0x9A81A420) \ + X("CINV W0, W1, CC", 0x5A812020) \ + X("CNEG W0, W1, GE", 0x5A81B420) \ + X("CCMP X0, X1, #4, NE", 0xFA411004) \ + X("CCMP W0, #5, #0, EQ", 0x7A450800) \ + X("CCMN X0, #1, #2, GT", 0xBA41C802) \ + X("CCMN W0, W1, #15, LO", 0x3A41300F) \ + X("MADD X0, X1, X2, X3", 0x9B020C20) \ + X("MADD W0, W1, W2, W3", 0x1B020C20) \ + X("MSUB X0, X1, X2, X3", 0x9B028C20) \ + X("MUL W0, W1, W2", 0x1B027C20) \ + X("MUL X0, X1, X2", 0x9B027C20) \ + X("MNEG X0, X1, X2", 0x9B02FC20) \ + X("SMADDL X0, W1, W2, X3", 0x9B220C20) \ + X("UMADDL X0, W1, W2, X3", 0x9BA20C20) \ + X("SMSUBL X0, W1, W2, X3", 0x9B228C20) \ + X("UMSUBL X0, W1, W2, X3", 0x9BA28C20) \ + X("SMULL X0, W1, W2", 0x9B227C20) \ + X("UMULL X0, W1, W2", 0x9BA27C20) \ + X("SMNEGL X0, W1, W2", 0x9B22FC20) \ + X("UMNEGL X0, W1, W2", 0x9BA2FC20) \ + X("SMULH X0, X1, X2", 0x9B427C20) \ + X("UMULH X0, X1, X2", 0x9BC27C20) \ + X("UDIV W0, W1, W2", 0x1AC20820) \ + X("SDIV X0, X1, X2", 0x9AC20C20) \ + X("ADC X0, X1, X2", 0x9A020020) \ + X("ADCS W0, W1, W2", 0x3A020020) \ + X("SBC X0, X1, X2", 0xDA020020) \ + X("SBCS W0, W1, W2", 0x7A020020) \ + X("NGC X0, X1", 0xDA0103E0) \ + X("NGCS W0, W1", 0x7A0103E0) \ + X("BIC X0, X1, X2", 0x8A220020) \ + X("BICS W0, W1, W2, LSR #3", 0x6A620C20) \ + X("ORN W0, W1, W2", 0x2A220020) \ + X("EON X0, X1, X2, LSL #3", 0xCA220C20) \ + X("BIC X0, X1, X2, ROR #63", 0x8AE2FC20) \ + X("MVN X0, X1", 0xAA2103E0) \ + X("MVN W0, W1, LSL #4", 0x2A2113E0) \ + X("NEG X0, X1", 0xCB0103E0) \ + X("NEG W0, W1, LSL #2", 0x4B010BE0) \ + X("NEGS W0, W1", 0x6B0103E0) \ + X("NEGS X0, X1, ASR #3", 0xEB810FE0) \ + X("LDR S0, [X0,#8]", 0xBD400800) \ + X("LDR D0, [X0,#8]", 0xFD400400) \ + X("LDR Q0, [X0,#16]", 0x3DC00400) \ + X("LDR B0, [X0,#1]", 0x3D400400) \ + X("LDR H0, [X0,#2]", 0x7D400400) \ + X("STR D0, [SP,#8]", 0xFD0007E0) \ + X("STR Q1, [X2,#0x30]", 0x3D800C41) \ + X("STR S3, [X4,#-4]!", 0xBC1FCC83) \ + X("LDR D1, [X2],#8", 0xFC408441) \ + X("LDR Q0, [X1,X2,LSL#4]", 0x3CE27820) \ + X("LDR D0, [X1,X2]", 0xFC626820) \ + X("LDR S0, [X1,#-4]", 0xBC5FC020) \ + X("LDR D0, [X1,#3]", 0xFC403020) \ + X("LDUR Q0, [X0,#-16]", 0x3CDF0000) \ + X("STUR D0, [X29,#-8]", 0xFC1F83A0) \ + X("LDUR S0, [X0,#1]", 0xBC401000) \ + X("STP D9, D8, [SP,#-0x20]!", 0x6DBE23E9) \ + X("LDP D9, D8, [SP],#0x20", 0x6CC223E9) \ + X("LDP Q0, Q1, [X1]", 0xAD400420) \ + X("STP Q0, Q1, [X0,#0x20]", 0xAD010400) \ + X("LDP S0, S1, [X1,#8]", 0x2D410420) \ + X("STP D0, D1, [SP,#0x10]", 0x6D0107E0) \ + X("LDR D0, #0x100", 0x5C000800) \ + X("LDR Q0, #0x100", 0x9C000800) \ + X("LDR S1, #-8", 0x1CFFFFC1) \ + X("FMOV D0, D1", 0x1E604020) \ + X("FMOV S0, S1", 0x1E204020) \ + X("FMOV H0, H1", 0x1EE04020) \ + X("FMOV S0, W1", 0x1E270020) \ + X("FMOV W0, S1", 0x1E260020) \ + X("FMOV D0, X1", 0x9E670020) \ + X("FMOV X0, D1", 0x9E660020) \ + X("FMOV H0, W1", 0x1EE70020) \ + X("FMOV W0, H1", 0x1EE60020) \ + X("FMOV H0, X1", 0x9EE70020) \ + X("FMOV X0, H1", 0x9EE60020) \ + X("FMOV D0, XZR", 0x9E6703E0) \ + X("FMOV S0, WZR", 0x1E2703E0) \ + X("FMOV D0, #1.0", 0x1E6E1000) \ + X("FMOV S0, #-2.5", 0x1E309000) \ + X("FMOV D1, #0.125", 0x1E681001) \ + X("FMOV S2, #31.0", 0x1E27F002) \ + X("FMOV H3, #0.5", 0x1EEC1003) \ + X("FMOV D0, #-1", 0x1E7E1000) \ + X("FMOV D0, #0.0", 0x9E6703E0) \ + X("FMOV S0, #0.0", 0x1E2703E0) \ + X("FMOV D4, #1.0625", 0x1E6E3004) // Wildcard pattern, an instruction, its encoding, and whether the pattern must match it #define WILDCARD(X) \ @@ -290,7 +548,171 @@ X("MOV X0, #?", "MOVK X0, #5", 0xF28000A0, 0) \ X("LDP X29, X30, [SP]", "LDP X29, X30, [SP],#0x20", 0xA8C27BFD, 0) \ X("ADD ?, X1, #8", "ADD W0, W1, #8", 0x11002020, 0) \ - X("STP ?, X30, [SP,#-0x10]!", "STP W29, W30, [SP,#-8]!", 0x29BF7BFD, 0) + X("STP ?, X30, [SP,#-0x10]!", "STP W29, W30, [SP,#-8]!", 0x29BF7BFD, 0) \ + X("TBZ W?, #?, #?", "TBZ W5, #7, #0x40", 0x36380205, 1) \ + X("TBZ W?, #?, #?", "TBNZ W5, #7, #0x40", 0x37380205, 0) \ + X("TBZ W?, #?, #?", "TBZ X5, #33, #0x40", 0xB6080205, 0) \ + X("TBZ X?, #?, #?", "TBZ X5, #33, #0x40", 0xB6080205, 1) \ + X("TBZ ?, #?, #?", "TBZ X5, #33, #0x40", 0xB6080205, 1) \ + X("TBZ X0, #40, #?", "TBZ X0, #40, #-0x100", 0xB647F800, 1) \ + X("TBZ X0, #40, #?", "TBZ X0, #8, #-0x100", 0x3647F800, 0) \ + X("SVC #?", "SVC #0x80", 0xD4001001, 1) \ + X("SVC #?", "BRK #0x80", 0xD4201000, 0) \ + X("BRK #?", "BRK #1", 0xD4200020, 1) \ + X("UDF #?", "UDF #0x1234", 0x00001234, 1) \ + X("BTI ?", "BTI C", 0xD503245F, 1) \ + X("BTI ?", "BTI JC", 0xD50324DF, 1) \ + X("BTI ?", "NOP", 0xD503201F, 0) \ + X("BTI C", "BTI J", 0xD503249F, 0) \ + X("BRAA X?, X?", "BRAA X16, X17", 0xD71F0A11, 1) \ + X("BRAA X?, X?", "BRAB X16, X17", 0xD71F0E11, 0) \ + X("BLRAA X8, X?", "BLRAA X8, SP", 0xD73F091F, 1) \ + X("BLRAAZ X?", "BLRAAZ X8", 0xD63F091F, 1) \ + X("BLRAAZ X?", "BLRAA X8, X9", 0xD73F0909, 0) \ + X("LDRAA X?, [X?,#?]", "LDRAA X0, [X1,#-8]", 0xF87FF420, 1) \ + X("LDRAA X?, [X?,#?]", "LDRAA X0, [X1,#8]!", 0xF8201C20, 0) \ + X("LDRAA X?, [X?,#?]!", "LDRAA X0, [X1,#8]!", 0xF8201C20, 1) \ + X("LDRAA X?, [X?,#?]", "LDRAB X0, [X1,#8]", 0xF8A01420, 0) \ + X("PACIA X?, X?", "PACIA X16, SP", 0xDAC103F0, 1) \ + X("PACIA X?, X?", "PACIB X16, SP", 0xDAC107F0, 0) \ + X("AUTIZA X?", "AUTIZA X3", 0xDAC133E3, 1) \ + X("LDRSB W?, [X?,#?]", "LDRSB W0, [X1,#5]", 0x39C01420, 1) \ + X("LDRSB W?, [X?,#?]", "LDRSB X0, [X1,#5]", 0x39801420, 0) \ + X("LDRSB ?, [X?,#?]", "LDRSB X0, [X1,#5]", 0x39801420, 1) \ + X("LDRSB ?, [X?,#?]", "LDRB W0, [X1,#5]", 0x39401420, 0) \ + X("LDRSH X?, [X?,#?]!", "LDRSH X0, [X1,#-2]!", 0x789FEC20, 1) \ + X("LDURSH ?, [X?,#?]", "LDURSH W0, [X1,#-2]", 0x78DFE020, 1) \ + X("LDPSW X?, X?, [SP,#?]", "LDPSW X1, X2, [SP,#0x10]", 0x69420BE1, 1) \ + X("LDPSW X?, X?, [SP,#?]", "LDP X1, X2, [SP,#0x10]", 0xA9410BE1, 0) \ + X("LDNP X?, X?, [X?,#?]", "LDNP X1, X2, [X3,#0x10]", 0xA8410861, 1) \ + X("LDNP X?, X?, [X?,#?]", "LDP X1, X2, [X3,#0x10]", 0xA9410861, 0) \ + X("LDXR ?, [X?]", "LDXR X0, [X1]", 0xC85F7C20, 1) \ + X("LDXR ?, [X?]", "LDXR W0, [X1]", 0x885F7C20, 1) \ + X("LDXR ?, [X?]", "LDAXR W0, [X1]", 0x885FFC20, 0) \ + X("LDXR W?, [X?]", "LDXR X0, [X1]", 0xC85F7C20, 0) \ + X("STXR W?, X?, [X?]", "STXR W2, X0, [X1]", 0xC8027C20, 1) \ + X("STXR W?, X?, [X?]", "STXR W2, W0, [X1]", 0x88027C20, 0) \ + X("STXR ?, ?, [X?]", "STXR W2, W0, [X1]", 0x88027C20, 1) \ + X("STLR ?, [X?]", "STLR X0, [SP]", 0xC89FFFE0, 1) \ + X("LDARB W?, [X?]", "LDARB W0, [X1]", 0x08DFFC20, 1) \ + X("LDARB W?, [X?]", "LDAR W0, [X1]", 0x88DFFC20, 0) \ + X("CAS ?, ?, [X?]", "CAS X0, X1, [X2]", 0xC8A07C41, 1) \ + X("CAS ?, ?, [X?]", "CASA X0, X1, [X2]", 0xC8E07C41, 0) \ + X("CAS ?, ?, [X?]", "CASB W0, W1, [X2]", 0x08A07C41, 0) \ + X("CASAL W?, W?, [X?]", "CASAL W0, W1, [X2]", 0x88E0FC41, 1) \ + X("LDADDAL ?, ?, [X?]", "LDADDAL X0, X1, [X2]", 0xF8E00041, 1) \ + X("LDADDAL ?, ?, [X?]", "LDADDAL W0, W1, [X2]", 0xB8E00041, 1) \ + X("LDADDAL ?, ?, [X?]", "LDADD W0, W1, [X2]", 0xB8200041, 0) \ + X("LDADDAL ?, ?, [X?]", "LDCLRAL W0, W1, [X2]", 0xB8E01041, 0) \ + X("STADD W?, [X?]", "STADD W0, [X2]", 0xB820005F, 1) \ + X("STADD W?, [X?]", "LDADD W0, W1, [X2]", 0xB8200041, 0) \ + X("SWP X?, X?, [X?]", "SWP X0, X1, [X2]", 0xF8208041, 1) \ + X("SWP X?, X?, [X?]", "LDADD X0, X1, [X2]", 0xF8200041, 0) \ + X("LSL X?, X?, #?", "LSL X0, X1, #3", 0xD37DF020, 1) \ + X("LSL X?, X?, #?", "LSR X0, X1, #3", 0xD343FC20, 1) \ + X("LSL X?, X?, #?", "LSL W0, W1, #3", 0x531D7020, 0) \ + X("LSL X?, X?, #?", "ASR X0, X1, #3", 0x9343FC20, 0) \ + X("LSL ?, ?, #3", "LSL X0, X1, #3", 0xD37DF020, 1) \ + X("LSL ?, ?, #3", "LSL W0, W1, #3", 0x531D7020, 1) \ + X("LSL ?, ?, #3", "LSL X0, X1, #4", 0xD37CEC20, 0) \ + X("LSL ?, ?, #40", "LSL X0, X1, #40", 0xD3585C20, 1) \ + X("LSR X?, X?, #?", "LSR X0, X1, #3", 0xD343FC20, 1) \ + X("LSR X?, X?, #?", "LSL X0, X1, #3", 0xD37DF020, 0) \ + X("LSR X?, X?, #?", "UBFX X0, X1, #3, #8", 0xD3432820, 0) \ + X("LSR ?, ?, #?", "LSR W0, W1, #3", 0x53037C20, 1) \ + X("ASR W?, W?, #?", "ASR W0, W1, #31", 0x131F7C20, 1) \ + X("ASR W?, W?, #?", "LSR W0, W1, #31", 0x531F7C20, 0) \ + X("ROR X?, X?, #?", "ROR X0, X1, #7", 0x93C11C20, 1) \ + X("ROR X?, X?, #?", "EXTR X0, X1, X2, #7", 0x93C21C20, 1) \ + X("ROR X0, X1, #?", "EXTR X0, X1, X2, #7", 0x93C21C20, 0) \ + X("LSL X?, X?, X?", "LSL X0, X1, X2", 0x9AC22020, 1) \ + X("LSL X?, X?, X?", "LSR X0, X1, X2", 0x9AC22420, 0) \ + X("LSL X?, X?, X?", "LSL X0, X1, #2", 0xD37EF420, 0) \ + X("UBFX X?, X?, #?, #8", "UBFX X0, X1, #4, #8", 0xD3442C20, 1) \ + X("UBFX X?, X?, #4, #?", "UBFX X0, X1, #4, #9", 0xD3443020, 1) \ + X("UBFX X?, X?, #4, #?", "UBFX X0, X1, #5, #9", 0xD3453420, 0) \ + X("UBFX X?, X?, #?, #?", "SBFX X0, X1, #4, #9", 0x93443020, 0) \ + X("UBFIZ W?, W?, #?, #5", "UBFIZ W0, W1, #3, #5", 0x531D1020, 1) \ + X("UBFIZ W?, W?, #?, #5", "UBFIZ W0, W1, #3, #6", 0x531D1420, 0) \ + X("BFI X?, X?, #8, #?", "BFI X0, X1, #8, #4", 0xB3780C20, 1) \ + X("SXTB ?, W?", "SXTB X0, W1", 0x93401C20, 1) \ + X("SXTB ?, W?", "SXTB W0, W1", 0x13001C20, 1) \ + X("SXTB ?, W?", "SXTH W0, W1", 0x13003C20, 0) \ + X("SXTW ?, W?", "SXTW X0, W1", 0x93407C20, 1) \ + X("UXTB ?, W?", "UXTB W0, W1", 0x53001C20, 1) \ + X("UXTB ?, W?", "UXTH W0, W1", 0x53003C20, 0) \ + X("UBFM X?, X?, #?, #?", "UBFM X0, X1, #4, #11", 0xD3442C20, 1) \ + X("UBFM X?, X?, #?, #?", "SBFM X0, X1, #4, #11", 0x93442C20, 0) \ + X("CSEL X?, X?, X?, ?", "CSEL X0, X1, X2, LT", 0x9A82B020, 1) \ + X("CSEL X?, X?, X?, ?", "CSINC X0, X1, X2, LT", 0x9A82B420, 0) \ + X("CSEL X?, X?, X?, EQ", "CSEL X0, X1, X2, NE", 0x9A821020, 0) \ + X("CSET W?, ?", "CSET W0, EQ", 0x1A9F17E0, 1) \ + X("CSET W?, ?", "CSET W0, LE", 0x1A9FC7E0, 1) \ + X("CSET W?, ?", "CSINC W0, W1, W2, EQ", 0x1A820420, 0) \ + X("CSET W?, EQ", "CSET W0, NE", 0x1A9F07E0, 0) \ + X("CSET W?, EQ", "CSINC W0, WZR, WZR, NE", 0x1A9F17E0, 1) \ + X("CSETM ?, ?", "CSETM X0, NE", 0xDA9F03E0, 1) \ + X("CINC X?, X?, ?", "CINC X0, X1, LT", 0x9A81A420, 1) \ + X("CINC X0, X1, ?", "CSINC X0, X1, X2, LT", 0x9A82B420, 0) \ + X("CNEG W?, W?, GE", "CNEG W0, W1, GE", 0x5A81B420, 1) \ + X("CCMP X?, X?, #?, ?", "CCMP X0, X1, #4, NE", 0xFA411004, 1) \ + X("CCMP X?, X?, #?, ?", "CCMP X0, #1, #4, NE", 0xFA411804, 0) \ + X("CCMP X?, #?, #?, ?", "CCMP X0, #1, #4, NE", 0xFA411804, 1) \ + X("CCMP X?, #?, #?, ?", "CCMN X0, #1, #4, NE", 0xBA411804, 0) \ + X("CCMP W0, #5, #?, EQ", "CCMP W0, #5, #8, EQ", 0x7A450808, 1) \ + X("MADD X?, X?, X?, X?", "MADD X0, X1, X2, X3", 0x9B020C20, 1) \ + X("MADD X?, X?, X?, X?", "MSUB X0, X1, X2, X3", 0x9B028C20, 0) \ + X("MADD X?, X?, X?, X?", "MUL X0, X1, X2", 0x9B027C20, 1) \ + X("MUL ?, ?, ?", "MUL W0, W1, W2", 0x1B027C20, 1) \ + X("MUL ?, ?, ?", "MADD W0, W1, W2, W3", 0x1B020C20, 0) \ + X("SMULL X?, W?, W?", "SMULL X0, W1, W2", 0x9B227C20, 1) \ + X("SMULL X?, W?, W?", "UMULL X0, W1, W2", 0x9BA27C20, 0) \ + X("UDIV ?, ?, ?", "UDIV X0, X1, X2", 0x9AC20820, 1) \ + X("UDIV ?, ?, ?", "SDIV X0, X1, X2", 0x9AC20C20, 0) \ + X("BIC X?, X?, X?", "BIC X0, X1, X2", 0x8A220020, 1) \ + X("BIC X?, X?, X?", "AND X0, X1, X2", 0x8A020020, 0) \ + X("BIC X?, X?, X?, ?", "BIC X0, X1, X2, ROR #3", 0x8AE20C20, 1) \ + X("MVN X?, X?", "MVN X0, X1", 0xAA2103E0, 1) \ + X("MVN X?, X?", "MOV X0, X1", 0xAA0103E0, 0) \ + X("NEG X?, X?", "NEG X0, X1", 0xCB0103E0, 1) \ + X("NEG X?, X?", "SUB X0, X1, X2", 0xCB020020, 0) \ + X("NEGS ?, ?", "NEGS W0, W1", 0x6B0103E0, 1) \ + X("ADC ?, ?, ?", "ADC W0, W1, W2", 0x1A020020, 1) \ + X("ADC ?, ?, ?", "ADCS W0, W1, W2", 0x3A020020, 0) \ + X("ORR X?, X?, X?, ?", "ORR X0, X1, X2, ROR #7", 0xAAC21C20, 1) \ + X("ORR X?, X?, X?, ?", "ORR X0, X1, X2", 0xAA020020, 1) \ + X("ORR X?, X?, X?, ?", "ORN X0, X1, X2", 0xAA220020, 0) \ + X("ADD X?, X?, X?, ?", "ADD X0, X1, X2, ASR #3", 0x8B820C20, 1) \ + X("ADD X?, X?, X?, ?", "ADD X0, X1, W2, UXTW #2", 0x8B224820, 1) \ + X("LDR D?, [X?,#?]", "LDR D0, [X1,#8]", 0xFD400420, 1) \ + X("LDR D?, [X?,#?]", "LDR X0, [X1,#8]", 0xF9400420, 0) \ + X("LDR D?, [X?,#?]", "LDR S0, [X1,#8]", 0xBD400820, 0) \ + X("LDR D?, [X?,#?]", "STR D0, [X1,#8]", 0xFD000420, 0) \ + X("LDR Q?, [X?,#?]", "LDR Q0, [X1,#0x10]", 0x3DC00420, 1) \ + X("LDR Q?, [X?,#?]", "LDR B0, [X1,#0x10]", 0x3D404020, 0) \ + X("LDR ?, [X?,#?]", "LDR D0, [X1,#8]", 0xFD400420, 0) \ + X("LDR S0, [X1,#?]", "LDR S0, [X1,#0x40]", 0xBD404020, 1) \ + X("STR D?, [SP,#?]!", "STR D8, [SP,#-0x10]!", 0xFC1F0FE8, 1) \ + X("LDUR Q?, [X?,#?]", "LDUR Q0, [X0,#-16]", 0x3CDF0000, 1) \ + X("LDR D?, #?", "LDR D0, #0x100", 0x5C000800, 1) \ + X("LDR D?, #?", "LDR X0, #0x100", 0x58000800, 0) \ + X("LDR D?, #?", "LDR Q0, #0x100", 0x9C000800, 0) \ + X("STP D?, D?, [SP,#?]!", "STP D9, D8, [SP,#-0x20]!", 0x6DBE23E9, 1) \ + X("STP D?, D?, [SP,#?]!", "STP X9, X8, [SP,#-0x20]!", 0xA9BE23E9, 0) \ + X("STP D?, D?, [SP,#?]!", "STP Q9, Q8, [SP,#-0x20]!", 0xADBF23E9, 0) \ + X("LDP Q?, Q?, [X?]", "LDP Q0, Q1, [X1]", 0xAD400420, 1) \ + X("LDP Q?, Q?, [X?,#?]", "LDP Q0, Q1, [X1,#0x20]", 0xAD410420, 1) \ + X("LDP ?, ?, [X?,#?]", "LDP D0, D1, [X1,#0x20]", 0x6D420420, 0) \ + X("FMOV D?, D?", "FMOV D0, D1", 0x1E604020, 1) \ + X("FMOV D?, D?", "FMOV S0, S1", 0x1E204020, 0) \ + X("FMOV D?, ?", "FMOV D0, D1", 0x1E604020, 1) \ + X("FMOV ?, ?", "FMOV S0, S1", 0x1E204020, 1) \ + X("FMOV D?, X?", "FMOV D0, X1", 0x9E670020, 1) \ + X("FMOV D?, X?", "FMOV X0, D1", 0x9E660020, 0) \ + X("FMOV D?, #?", "FMOV D0, #1.0", 0x1E6E1000, 1) \ + X("FMOV D?, #?", "FMOV S0, #1.0", 0x1E2E1000, 0) \ + X("FMOV D?, #?", "FMOV D0, D1", 0x1E604020, 0) \ + X("FMOV D0, #1.0", "FMOV D0, #-1.0", 0x1E7E1000, 0) struct Word { uint32_t pat, mask; }; template static Word word(const P& p, size_t idx = 0) { @@ -327,12 +749,43 @@ static void wildcards() { // Forms the encoder doesn't support become a whole-word wildcard (mask 0), which the IDA plugin warns about, // rather than a pattern that can never match static void unsupported() { - CHECK(word(ARM_PATTERN("LDR Q0, [X1,#0x10]")).mask == 0); - CHECK(word(ARM_PATTERN("STR D0, [SP,#8]")).mask == 0); - CHECK(word(ARM_PATTERN("STP D9, D8, [SP,#-0x20]!")).mask == 0); CHECK(word(ARM_PATTERN("LDR W0, [X1,#0x4000]")).mask == 0); // past the unsigned offset's range CHECK(word(ARM_PATTERN("LDUR X0, [X1,#0x100]")).mask == 0); // past imm9 CHECK(word(ARM_PATTERN("B.XX #0x10")).mask == 0); + CHECK(word(ARM_PATTERN("TBZ W0, #32, #8")).mask == 0); + CHECK(word(ARM_PATTERN("TBZ X0, #64, #8")).mask == 0); + CHECK(word(ARM_PATTERN("SVC #0x10000")).mask == 0); + CHECK(word(ARM_PATTERN("BTI X")).mask == 0); + CHECK(word(ARM_PATTERN("LDRAA W0, [X1]")).mask == 0); + CHECK(word(ARM_PATTERN("LDRAA X0, [X1,#4]")).mask == 0); + CHECK(word(ARM_PATTERN("LDRAA X0, [X1,#4096]")).mask == 0); + CHECK(word(ARM_PATTERN("LDRSB D0, [X1]")).mask == 0); + CHECK(word(ARM_PATTERN("LDRB D0, [X1]")).mask == 0); + CHECK(word(ARM_PATTERN("LDPSW W0, W1, [X2]")).mask == 0); + CHECK(word(ARM_PATTERN("LDNP X0, X1, [X2,#16]!")).mask == 0); + CHECK(word(ARM_PATTERN("LDP B0, B1, [X2]")).mask == 0); + CHECK(word(ARM_PATTERN("LDR B0, #0x10")).mask == 0); + CHECK(word(ARM_PATTERN("CSET W0, AL")).mask == 0); + CHECK(word(ARM_PATTERN("CINC X0, X1, NV")).mask == 0); + CHECK(word(ARM_PATTERN("CSEL X0, X1, X2, XX")).mask == 0); + CHECK(word(ARM_PATTERN("CCMP X0, #32, #0, EQ")).mask == 0); + CHECK(word(ARM_PATTERN("CCMP X0, #1, #16, EQ")).mask == 0); + CHECK(word(ARM_PATTERN("LSL X0, X1, #64")).mask == 0); + CHECK(word(ARM_PATTERN("LSL W0, W1, #32")).mask == 0); + CHECK(word(ARM_PATTERN("UBFX W0, W1, #30, #4")).mask == 0); + CHECK(word(ARM_PATTERN("SXTW W0, W1")).mask == 0); + CHECK(word(ARM_PATTERN("UXTB X0, W1")).mask == 0); + CHECK(word(ARM_PATTERN("BIC X0, X1, #0xFF")).mask == 0); + CHECK(word(ARM_PATTERN("FMOV D0, #0.1")).mask == 0); + CHECK(word(ARM_PATTERN("FMOV D0, #32.0")).mask == 0); + CHECK(word(ARM_PATTERN("FMOV D0, #-0.0")).mask == 0); + CHECK(word(ARM_PATTERN("FMOV Q0, #1.0")).mask == 0); + CHECK(word(ARM_PATTERN("FMOV S0, X1")).mask == 0); + CHECK(word(ARM_PATTERN("FMOV D0, W1")).mask == 0); + CHECK(word(ARM_PATTERN("FMOV D0, S1")).mask == 0); + CHECK(word(ARM_PATTERN("LDADDX W0, W1, [X2]")).mask == 0); + CHECK(word(ARM_PATTERN("STADDA W0, [X2]")).mask == 0); + CHECK(word(ARM_PATTERN("CASP X0, X1, X2, X3, [X4]")).mask == 0); puts(" unsupported forms ok"); } @@ -460,8 +913,12 @@ static void deref_synthetic() { const auto at = reinterpret_cast(&vals[k]); CHECK(p.resolve(at, at + 4, &is_value) == reinterpret_cast(want[k]) && is_value); } - // Branches and literal loads with imm19 resolve to their target - static const uint32_t imm19[] = { 0x54000100, 0xB4000100, 0x58000100 }; // B.EQ, CBZ X0, LDR X0 (literal): #0x20 + // Branches and literal loads resolve to their target + static const uint32_t imm19[] = { + 0x54000100, 0xB4000100, 0x58000100, // B.EQ, CBZ X0, LDR X0 (literal): #0x20 + 0x5C000100, 0x9C000100, // LDR D0, LDR Q0 (literal): #0x20 + 0x36180100, 0xB7400101, // TBZ W0, #3 and TBNZ X1, #40: #0x20 + }; for (auto& w : imm19) { const auto at = reinterpret_cast(&w); CHECK(patterns::RuntimeArmPattern("X ?\n/d").resolve(at, at + 4) == at + 0x20); @@ -477,6 +934,9 @@ static void deref_synthetic() { { { 0x90000009, 0x91004108 }, 0x10 }, // ADRP X9, #0; ADD X8, X8, #0x10 { { 0xD503201F, 0x91004108 }, 0x10 }, // NOP; ADD X8, X8, #0x10 { { 0xD503201F, 0x39448D00 }, 0x123 }, // NOP; LDRB W0, [X8,#0x123] + { { 0xFD400D00, 0 }, 0x18 }, // LDR D0, [X8,#0x18] + { { 0x3DC00901, 0 }, 0x20 }, // LDR Q1, [X8,#0x20] + { { 0xBC5FC102, 0 }, -4 }, // LDUR S2, [X8,#-4] }; for (auto& f : follow) { alignas(4) uint32_t code[4] = { 0x528EEEE9, 0xB0000028, f.next[0], f.next[1] }; // MOVZ W9; ADRP X8, #0x5000 @@ -491,7 +951,7 @@ static void deref_synthetic() { #define SYM(x) "_" x #define ASM_LINE(text, ...) text "\n" #define ASM_WILD(ptext, itext, ...) itext "\n" -__asm__(".data\n.p2align 2\n.arch_extension pauth\n.globl " SYM("asm_concrete") "\n" SYM("asm_concrete") ":\n" CONCRETE(ASM_LINE) +__asm__(".data\n.p2align 2\n.arch armv8.5-a+fp16+lse+rcpc+pauth\n.globl " SYM("asm_concrete") "\n" SYM("asm_concrete") ":\n" CONCRETE(ASM_LINE) ".globl " SYM("asm_wildcard") "\n" SYM("asm_wildcard") ":\n" WILDCARD(ASM_WILD)); extern "C" const uint32_t asm_concrete[], asm_wildcard[]; From 0350b45b91a46c15bcc7193ac5d450fc8015a9a9 Mon Sep 17 00:00:00 2001 From: scizzydo <34661795+scizzydo@users.noreply.github.com> Date: Mon, 12 Oct 2026 13:42:26 -0700 Subject: [PATCH 2/2] Fix ? wildcards in register-offset and bare-? loads; /d chain past FP loads - A "?" extend in a register-offset load/store ([X1, X2, ?], ? #3) pinned LSL with the wrong S bit, so the pattern could never match. It now leaves the extend (and the amount unless one is given) open. - A bare "?" Rt with a known offset picked the scaled form from neither width, missing the LDUR the assembler emits (LDR ?, [X1,#0x81]) or the only width that fits (#0x4000 is X only). W and X are now both encoded and merged, like the bitfield aliases (shared either() helper). - /d: a load into an FP register or a store between ADRP and its use no longer ends the chain as if it overwrote the ADRP register. - docs: #? in LDR is the scaled form, FMOV #? doesn't cover #0.0. Co-Authored-By: Claude Opus 5.5 --- docs/arm64.md | 1 + include/PatternScanner/ArmPattern.hpp | 41 +++++++++++++++++++-------- include/PatternScanner/Pattern.hpp | 3 +- tests/test_arm.cpp | 12 ++++++++ 4 files changed, 44 insertions(+), 13 deletions(-) diff --git a/docs/arm64.md b/docs/arm64.md index b975054..cdbddae 100644 --- a/docs/arm64.md +++ b/docs/arm64.md @@ -14,6 +14,7 @@ One instruction per line; case, indentation and spacing don't matter, so lines p - A register letter pins the width; a bare `?` leaves it open, along with what depends on it (scaled offsets, bitfield immr/imms, TBZ's bit 5). Where only one width fits the other operands (`LSL ?, ?, #40`), that one is used. - In loads and stores a bare `?` register is W or X; write `B?`/`H?`/`S?`/`D?`/`Q?` for floating-point/SIMD ones. - Some wildcards can't be expressed with a mask and match more than the alias: `LSL Rd, Rn, #?` matches any `UBFM` (also `LSR`, `UBFX`...), and aliases that repeat a register (`CINC X?, X?, cond`, `ROR X?, X?, #n`) can't require the two to be the same. +- Others match less, as the encoding they name: `LDR Rt, [Rn,#?]` is the scaled form (write `LDUR` for negative or unaligned offsets, as IDA and lldb print them), and `FMOV Dd, #?` the 8-bit immediate form, not `#0.0` (`FMOV` from the zero register). - An operand that can't be encoded (out of range, the wrong register kind, an unknown condition) makes the line a wildcard for those 4 bytes, matching any instruction (the IDA plugin asks before searching with one). It never makes a pattern that can't match. Write `?` for a deliberate one. ## Supported instructions diff --git a/include/PatternScanner/ArmPattern.hpp b/include/PatternScanner/ArmPattern.hpp index f669443..c8bc75e 100644 --- a/include/PatternScanner/ArmPattern.hpp +++ b/include/PatternScanner/ArmPattern.hpp @@ -82,6 +82,13 @@ namespace patterns { } }; + // Matches what either encoding matches: the bits they share. One with mask 0 (the operands don't fit it) drops out + constexpr ArmOpcode either(ArmOpcode a, ArmOpcode b) { + if (!a.mask || !b.mask) return a.mask ? a : b; + const uint32_t m = a.mask & b.mask & ~(a.pattern ^ b.pattern); + return { a.pattern & m, m }; + } + struct InstructionDescriptor { const char* mnemonic; ArmOpcode opcode; @@ -330,6 +337,20 @@ namespace patterns { char rt[5] = {}, imm[24] = {}; i = parse_register(instr, parse_token(instr, i, rt), rt, 0x1F, 0, base, ','); + if (scale < 0 && str_eq(rt, "?")) { + // W or X: the offset's scaling and form (LDR or LDUR) depend on which, so encode both + const size_t at = skip_whitespace(instr, skip_mnemonic(instr)); + char w[max_line + 8] = {}, x[max_line + 8] = {}; + for (size_t s = 0, j = 0; instr[s]; ++s, ++j) { + w[j] = x[j] = instr[s]; + if (s == at) { w[j] = 'W'; x[j] = 'X'; ++j; w[j] = x[j] = '0'; } + } + ArmOpcode e = either(parse_load_store(w, {}), + parse_load_store(x, {})); + e.pattern &= ~0x1Fu; + e.mask &= ~0x1Fu; + return e; + } const int fp = scale < 0 ? fp_size(rt) : -1; if (rt[0] != 'W' && rt[0] != 'X' && rt[0] != '?' && fp < 0) return { 0, 0 }; int size = scale; @@ -353,7 +374,7 @@ namespace patterns { i = parse_register(instr, i, 0x1F, 5, base); bool is_register_offset = false, is_pre_index = false, is_post_index = false; - uint32_t option_val = 0b011, s_val = 0, s_mask = 1; + uint32_t option_val = 0b011, option_mask = 0b111, s_val = 0, s_mask = 1; if (instr[i] == ',') { i = skip_whitespace(instr, ++i); @@ -364,7 +385,9 @@ namespace patterns { i = parse_register(instr, i, 0x1F, 16, base, ','); is_register_offset = true; - if (starts_with(&instr[i], "LSL")) { option_val = 0b011; i += 3; } + // "?": any extend, and any amount unless one follows + if (instr[i] == '?') { option_mask = 0; s_mask = 0; ++i; } + else if (starts_with(&instr[i], "LSL")) { option_val = 0b011; i += 3; } else if (starts_with(&instr[i], "UXTW")) { option_val = 0b010; i += 4; } else if (starts_with(&instr[i], "SXTW")) { option_val = 0b110; i += 4; } else if (starts_with(&instr[i], "SXTX")) { option_val = 0b111; i += 4; } @@ -372,6 +395,7 @@ namespace patterns { i = skip_whitespace(instr, skip_char(instr, i, ',')); if (instr[i] == '#') { // For bytes an explicit #0 is the shifted form; for the rest the amount is 0 or the scale + s_mask = 1; if (instr[++i] == '?') s_mask = 0; else s_val = (size == 0 || stoi(&instr[i]) != 0) ? 1 : 0; } @@ -407,7 +431,7 @@ namespace patterns { const uint32_t form = is_register_offset ? 0x00200800 : is_pre_index ? 0xC00 : is_post_index ? 0x400 : 0; base.set_bits((op & ~0x01000000u) | form, 0xFFE00C00 & width_mask); if (is_register_offset) { - base.set_bits(option_val << 1 | s_val, 0xE | s_mask, 12); + base.set_bits(option_val << 1 | s_val, option_mask << 1 | s_mask, 12); } else if (known_imm) { if (val < -256 || val > 255) return { 0, 0 }; base.set_bits(val, 0x1FF, 12); @@ -989,15 +1013,8 @@ namespace patterns { }; // A valid encoding always has sf/N in its mask; mask 0 means the size doesn't fit - ArmOpcode e = encode(width == 0 ? 32 : 64); - if (width < 0) { - const ArmOpcode e32 = encode(32), e64 = encode(64); - const uint32_t m = e32.mask & e64.mask & ~(e32.pattern ^ e64.pattern); - if (!e32.mask && !e64.mask) return { 0, 0 }; - e = e32.mask && e64.mask ? ArmOpcode{ e32.pattern & m, m } : e32.mask ? e32 : e64; - } else if (!e.mask) { - return { 0, 0 }; - } + const ArmOpcode e = width < 0 ? either(encode(32), encode(64)) : encode(width == 0 ? 32 : 64); + if (!e.mask) return { 0, 0 }; base.set_bits(e.pattern, e.mask); return base; } diff --git a/include/PatternScanner/Pattern.hpp b/include/PatternScanner/Pattern.hpp index a2d3784..fa5cc99 100644 --- a/include/PatternScanner/Pattern.hpp +++ b/include/PatternScanner/Pattern.hpp @@ -516,7 +516,8 @@ namespace patterns { if (!post_index) saved_offset += offset; break; } - if (rt == saved_rd) break; + // Only a load into a general-purpose register (V clear, opc not 00) overwrites it + if (rt == saved_rd && !(*curr_insn >> 26 & 1) && (*curr_insn >> 22 & 3)) break; } else if (detail::a64_decode_adr(*curr_insn, nullptr, &rd, &offset) || detail::a64_decode_movz(*curr_insn, nullptr, &rd, &offset)) { diff --git a/tests/test_arm.cpp b/tests/test_arm.cpp index b53c3e2..843b45a 100644 --- a/tests/test_arm.cpp +++ b/tests/test_arm.cpp @@ -502,6 +502,16 @@ X("LDR X?, [X29,#-8]", "LDUR X3, [X29,#-8]", 0xF85F83A3, 1) \ X("LDR ?, [X29,#-8]", "LDUR W0, [X29,#-8]", 0xB85F83A0, 1) \ X("LDR ?, [X29,#-8]", "LDR X0, [X29,#8]", 0xF94007A0, 0) \ + X("LDR ?, [X1,#0x81]", "LDUR W0, [X1,#0x81]", 0xB8481020, 1) \ + X("LDR ?, [X1,#0x81]", "LDUR X0, [X1,#0x81]", 0xF8481020, 1) \ + X("LDR ?, [X1,#0xC]", "LDR W0, [X1,#0xC]", 0xB9400C20, 1) \ + X("LDR ?, [X1,#0xC]", "LDUR X0, [X1,#0xC]", 0xF840C020, 1) \ + X("LDR ?, [X1,#0xC]", "STR W0, [X1,#0xC]", 0xB9000C20, 0) \ + X("LDR ?, [X1,#0x4000]", "LDR X0, [X1,#0x4000]", 0xF9600020, 1) \ + X("LDR X?, [X?,X?,?]", "LDR X2, [X3,W4,SXTW]", 0xF864C862, 1) \ + X("LDR X?, [X?,X?,?]", "LDR X2, [X3,X4,LSL#3]", 0xF8647862, 1) \ + X("LDR X?, [X?,X?,? #3]", "LDR X2, [X3,W4,SXTW#3]", 0xF864D862, 1) \ + X("LDR X?, [X?,X?,? #3]", "LDR X2, [X3,X4]", 0xF8646862, 0) \ X("LDR X?, [X?,X?,LSL#?]", "LDR X2, [X3,X4]", 0xF8646862, 1) \ X("LDUR X?, [X?,#?]", "LDUR X0, [X1,#0x10]", 0xF8410020, 1) \ X("LDUR X?, [X?,#?]", "LDR X0, [X1,#0x10]", 0xF9400820, 0) \ @@ -937,6 +947,8 @@ static void deref_synthetic() { { { 0xFD400D00, 0 }, 0x18 }, // LDR D0, [X8,#0x18] { { 0x3DC00901, 0 }, 0x20 }, // LDR Q1, [X8,#0x20] { { 0xBC5FC102, 0 }, -4 }, // LDUR S2, [X8,#-4] + { { 0xFD400128, 0x39448D00 }, 0x123 }, // LDR D8, [X9] (not X8); LDRB W0, [X8,#0x123] + { { 0xF90003E8, 0x39448D00 }, 0x123 }, // STR X8, [SP]; LDRB W0, [X8,#0x123] }; for (auto& f : follow) { alignas(4) uint32_t code[4] = { 0x528EEEE9, 0xB0000028, f.next[0], f.next[1] }; // MOVZ W9; ADRP X8, #0x5000