diff --git a/README.md b/README.md index 7a1fca1..a0f2274 100644 --- a/README.md +++ b/README.md @@ -48,11 +48,12 @@ Include the header with the same name, e.g. ``. - `4?` / `?8`: any low / high nibble - arm64: `?` for a register (`X?`), immediate (`#?`), shift (`LSL #?`), condition (`B.?`) or a whole instruction -**Capture marker**: `X` marks the operand you care about; `X1`/`X2`/`X4`/`X8` give its size. +**Capture marker**: `X` marks the operand you care about. `X` is how many bytes from it to the end of the instruction, which `/d` targets are relative to (`E8 X4 ? ? ? ?`: 4). **Flags** (at the end of the pattern) - `/d`: dereference: return the address the operand points to (e.g. a call or RIP-relative target) - `/r`: read the value at the operand +- `/d1`, `/r2`, `/r8`...: the operand's size in bytes (1, 2, 4 or 8 on x64; default 4) - `/a`: only match at aligned addresses (4 bytes on x86/arm64, 8 on x64) ## Examples @@ -65,7 +66,7 @@ constexpr auto a = "AB CC 11 22 33 44 AB 6D X4 EF BE AD DE /r"_ctpattern; constexpr auto b = "FE ED FA CE E8 X4 ? ? ? ? EF BE AD DE /da"_xorpattern; // Follow a short jump (1-byte operand), pattern built at runtime -auto c = "BA BE CA FE 72 X1 ? 11 22 /d"_rtpattern; +auto c = "BA BE CA FE 72 X1 ? 11 22 /d1"_rtpattern; // arm64: resolve ADRP + ADD to the full address constexpr auto d = diff --git a/docs/arm64.md b/docs/arm64.md index cdbddae..35e1ba3 100644 --- a/docs/arm64.md +++ b/docs/arm64.md @@ -4,7 +4,7 @@ ## Writing patterns -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 (`#?`). +One instruction per line; case, indentation, spacing and `;` comments 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, and IDA prints symbols (`BL _printf`, `#off_1000@PAGE`), so wildcard those (`#?`). ## Wildcards @@ -15,7 +15,10 @@ One instruction per line; case, indentation and spacing don't matter, so lines p - 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. + +## Errors + +A bad operand is an error: a compile error for `ArmPattern`/`ArmXORPattern`, a `std::logic_error` naming the line for `RuntimeArmPattern` (a trap without exceptions). That's a typo, a register that doesn't exist (`X31`) or doesn't fit (`SP` where the encoding reads the zero register, `W` mixed with `X`, `D0` in an integer instruction), a value out of range or misaligned for its field, a symbol instead of a number, an unknown shift, extend or condition, and a missing or extra operand. ## Supported instructions @@ -32,7 +35,7 @@ One instruction per line; case, indentation and spacing don't matter, so lines p - 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. +Any other mnemonic becomes a wildcard for those 4 bytes, matching any instruction (the IDA plugin asks before searching with one). ## Aliases @@ -41,6 +44,7 @@ Encoded the way assemblers pick them: - `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`. +- `BIC`/`BICS`/`ORN`/`EON` immediates: `AND`/`ANDS`/`ORR`/`EOR` with the inverted value. - Register `ADD`/`SUB` with `SP`: the extended (`UXTX`) form. - `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`. @@ -53,5 +57,6 @@ At the `X` instruction: - `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. +- Any other instruction: `find()` returns `nullptr`, as for no match. `/r` is not used on arm64. diff --git a/docs/usage.md b/docs/usage.md index a8a7d79..d153976 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -78,3 +78,7 @@ Each `_xorpattern` and `_armxorpattern` gets its own random keys from where it's ## SIMD Scanning uses SSE2 (x86/x64) or NEON (arm64) when available. Define `PATTERNS_NO_SIMD` before including to use a portable 64-bit integer scan instead (e.g. kernel code). + +## Without exceptions + +Pattern errors (bad hex, `/d` with `/r`, a bad arm64 operand...) throw `std::logic_error`, a compile error for compile-time patterns. Built without exceptions (`-fno-exceptions`, kernel code) they are still compile errors there, and trap at runtime (`RuntimePattern` with bad text). diff --git a/include/PatternScanner/ArmPattern.hpp b/include/PatternScanner/ArmPattern.hpp index c8bc75e..178ae3a 100644 --- a/include/PatternScanner/ArmPattern.hpp +++ b/include/PatternScanner/ArmPattern.hpp @@ -41,35 +41,98 @@ namespace patterns { return i; } + constexpr bool starts_with(const char* a, const char* prefix) { + while (*prefix) { + if (*a != *prefix) return false; + ++a; ++prefix; + } + return true; + } + + // Operand errors (a typo, a value out of range, a missing or extra operand) fail: a compile error for + // compile-time patterns. Only an unknown mnemonic becomes a wildcard + + // An operand up to the next separator; fails if it doesn't fit out (no operand is that long) template - constexpr size_t parse_token(const char* str, size_t i, char (&out)[N]) { + constexpr size_t parse_token(const char* str, size_t i, char (&out)[N]) { size_t j = 0; - while (str[i] && str[i] != ',' && str[i] != ' ' && str[i] != ']' && j + 1 < N) + while (str[i] && str[i] != ',' && str[i] != ']' && !isspace(str[i])) { + if (j + 1 >= N) PATTERNS_FAIL("Invalid operand!"); out[j++] = str[i++]; + } out[j] = '\0'; return i; } - template + // Decimal or 0x hex, optionally negative, nothing else, within 64 bits (-2^63 for negatives) + constexpr bool is_number(const char* s) { + const bool negative = *s == '-'; + if (negative) ++s; + const bool hex = s[0] == '0' && (s[1] == 'x' || s[1] == 'X'); + if (hex) s += 2; + const uint64_t base = hex ? 16 : 10; + uint64_t v = 0; + size_t digits = 0; + for (; hex ? is_hex_digit(*s) : is_digit(*s); ++s, ++digits) { + const uint64_t d = static_cast(get_bits(*s)); + if (v > (~0ULL - d) / base) return false; + v = v * base + d; + } + return digits && !*s && (!negative || v <= 1ULL << 63); + } + + // An immediate ('#' optional): a number or "?". numeric false leaves the text to the caller (FMOV's decimals) + template constexpr size_t parse_imm_token(const char* str, size_t i, char (&out)[N]) { + i = skip_whitespace(str, i); if (str[i] == '#') ++i; - size_t j = 0; - while (str[i] && !isspace(str[i]) && str[i] != ',' && str[i] != ']' && j + 1 < N) - out[j++] = str[i++]; - out[j] = '\0'; + i = parse_token(str, i, out); + if (numeric && !str_eq(out, "?") && !is_number(out)) PATTERNS_FAIL("Invalid immediate!"); return i; } - constexpr size_t max_line = 128; + // A known immediate (checked by parse_imm_token) as signed: fails past INT64_MAX, which would wrap negative + constexpr int64_t signed_imm(const char* token) { + const int64_t v = stoi(token); + if (v < 0 && token[0] != '-') PATTERNS_FAIL("Immediate out of range!"); + return v; + } - constexpr bool starts_with(const char* a, const char* prefix) { - while (*prefix) { - if (*a != *prefix) return false; - ++a; ++prefix; - } + // A known immediate (checked by parse_imm_token) in [lo, hi] and a multiple of align + constexpr int64_t imm_in(const char* token, int64_t lo, int64_t hi, int64_t align = 1) { + const int64_t v = signed_imm(token); + if (v < lo || v > hi || v % align) PATTERNS_FAIL("Immediate out of range!"); + return v; + } + + // Past ch and the whitespace around it; fails if ch isn't next (a missing operand or bracket) + constexpr size_t expect(const char* instr, size_t i, char ch) { + i = skip_whitespace(instr, i); + if (instr[i] != ch) PATTERNS_FAIL("Malformed operands!"); + return skip_whitespace(instr, i + 1); + } + + // Past ch and the whitespace around it, if ch is next + constexpr bool accept(const char* instr, size_t& i, char ch) { + const size_t j = skip_whitespace(instr, i); + if (instr[j] != ch) return false; + i = skip_whitespace(instr, j + 1); + return true; + } + + // Past word (a shift or extend) and the whitespace around it, if word is next + constexpr bool keyword(const char* instr, size_t& i, const char* word) { + const size_t j = skip_whitespace(instr, i); + if (!starts_with(&instr[j], word)) return false; + size_t n = 0; + while (word[n]) ++n; + i = skip_whitespace(instr, j + n); return true; } + + constexpr size_t max_line = 128; + struct ArmOpcode { uint32_t pattern; uint32_t mask; @@ -89,30 +152,40 @@ namespace patterns { return { a.pattern & m, m }; } + // After the last operand: anything left is a typo or an operand the instruction doesn't take. op by reference: + // callers pass base next to a call that fills it in, and argument order is unspecified (MSVC copies first) + constexpr ArmOpcode done(const char* instr, size_t i, const ArmOpcode& op) { + if (instr[skip_whitespace(instr, i)]) PATTERNS_FAIL("Unexpected text after the operands!"); + return op; + } + struct InstructionDescriptor { const char* mnemonic; ArmOpcode opcode; }; - constexpr uint32_t parse_register(const char* token) { - if (token[0] == '?' && token[1] == '\0') - return 0xFFFFFFFF; - - 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') { + // What a register operand may be, as a mask: a general-purpose register where the encoding reads 31 as the + // zero register, one where it reads 31 as SP (bases, ADD/SUB and logical immediate Rd), or floating-point/SIMD + enum RegKind : unsigned { reg_zr = 1, reg_sp = 2, reg_fp = 4 }; + + // The number of the register in token, or 0xFFFFFFFF for a wildcard ("?", or "X?", "W?", "D?"... of an + // allowed kind). Fails on anything else: not a register, out of range (X31, D32) or a kind not allowed + constexpr uint32_t parse_register(const char* token, unsigned kinds) { + if (str_eq(token, "?")) return 0xFFFFFFFF; + const char c = token[0]; + const bool gp = c == 'W' || c == 'X'; + const bool fp = c == 'B' || c == 'H' || c == 'S' || c == 'D' || c == 'Q'; + if ((gp && (kinds & (reg_zr | reg_sp))) || (fp && (kinds & reg_fp))) { + if (token[1] == '?' && !token[2]) return 0xFFFFFFFF; uint32_t val = 0; - for (int i = 1; token[i] >= '0' && token[i] <= '9'; ++i) { - val = val * 10 + (token[i] - '0'); - } - return val; + size_t i = 1; + for (; i < 3 && token[i] >= '0' && token[i] <= '9'; ++i) val = val * 10 + static_cast(token[i] - '0'); + if (i > 1 && !token[i] && val < (gp ? 31u : 32u)) return val; } - - if (str_eq(token, "SP") || str_eq(token, "WSP") || str_eq(token, "XSP") || - str_eq(token, "ZR") || str_eq(token, "WZR") || str_eq(token, "XZR")) { + if (((kinds & reg_zr) && (str_eq(token, "XZR") || str_eq(token, "WZR"))) || + ((kinds & reg_sp) && (str_eq(token, "SP") || str_eq(token, "WSP")))) return 31; - } - - return 0xFFFFFFFF; + PATTERNS_FAIL("Invalid register!"); } // 1 for a 64-bit register (X, SP, XZR), 0 for 32-bit (W, WSP, WZR), -1 when unknown ("?") @@ -124,6 +197,12 @@ namespace patterns { return str_eq(token, "SP") || str_eq(token, "WSP"); } + // Two register sizes that must match (register_width): fails on W with X. Returns the known one, or -1 + constexpr int same_width(int a, int b) { + if (a >= 0 && b >= 0 && a != b) PATTERNS_FAIL("Register size doesn't fit the instruction!"); + return a >= 0 ? a : b; + } + // 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; @@ -137,34 +216,52 @@ namespace patterns { } } - // 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 + // A register operand of the allowed kinds into the 5-bit field at shift, its text into out. Returns past it + template + constexpr size_t read_register(const char* instr, size_t i, char (&out)[N], unsigned kinds, uint32_t shift, ArmOpcode& base) { + i = parse_token(instr, skip_whitespace(instr, i), out); + if (const uint32_t r = parse_register(out, kinds); r != 0xFFFFFFFF) base.set_bits(r, 0x1F, shift); + return i; + } + + // parse_reg_list operands: the field's shift, plus what the register may be. Without r_w or r_x it's W or X, + // the same size as the other such operands + constexpr int r_w = 1 << 8; // always W + constexpr int r_x = 1 << 9; // always X + constexpr int r_sp = 1 << 10; // 31 is SP, not the zero register + constexpr int r_mem = 1 << 11; // in brackets + constexpr int r_base = r_x | r_sp | r_mem; // [Xn|SP] + + // Comma-separated registers as specs say. width gets the size of the ones without r_w or r_x (-1 if + // unknown). Returns past them template - constexpr size_t parse_reg_list(const char* instr, size_t i, const int (&shifts)[N], ArmOpcode& base, int (&widths)[N]) { + constexpr size_t parse_reg_list(const char* instr, size_t i, const int (&specs)[N], ArmOpcode& base, int& width) { + width = -1; for (size_t k = 0; k < N; ++k) { - while (instr[i] == ',' || instr[i] == '[' || isspace(instr[i])) ++i; + const int s = specs[k]; + if (k) i = expect(instr, i, ','); + if (s & r_mem) i = expect(instr, 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; + i = read_register(instr, i, token, s & r_sp ? reg_sp : reg_zr, static_cast(s & 0x1F), base); + if (s & (r_w | r_x)) same_width(register_width(token), s & r_x ? 1 : 0); + else width = same_width(width, register_width(token)); + if (s & r_mem) i = expect(instr, 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 + // Instructions that are only registers (specs as parse_reg_list). width_bit (sf at 31, or size bit 30 for + // loads/stores) is set from the registers without r_w or r_x + 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 int s[] = { specs... }; + int width = -1; + const size_t i = parse_reg_list(instr, skip_mnemonic(instr), s, base, width); + if (width_bit >= 0 && width >= 0) base.set_bits(static_cast(width), 1, width_bit); + return done(instr, i, base); } - constexpr const char* conditions[] = { "EQ", "NE", "CS", "CC", "MI", "PL", "VS", "VC", "HI", "LS", "GE", "LT", "GT", "LE", "AL", "NV" }; + inline 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) { @@ -176,25 +273,6 @@ namespace patterns { 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) - base.set_bits(val, mask, shift); - } - - template - constexpr size_t parse_register(const char* instr, size_t i, char(&buffer)[N], uint32_t mask, uint32_t shift, ArmOpcode& base, char skipchar = '\0') { - if (skipchar) i = skip_char(instr, i, skipchar); - i = skip_whitespace(instr, i); - parse_register(buffer, mask, shift, base); - return i; - } - - constexpr size_t parse_register(const char* instr, size_t i, uint32_t mask, uint32_t shift, ArmOpcode& base, char skipchar = '\0') { - char buffer[5] = {}; - return parse_register(instr, parse_token(instr, i, buffer), buffer, mask, shift, base, skipchar); - } - using ParseFunc = ArmOpcode(*)(const char* instr, ArmOpcode base); // Passthrough parser (no field parsing) @@ -202,19 +280,6 @@ namespace patterns { return base; } - 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")) { - i = skip_char(instr, skip_whitespace(instr, i + 3), '#'); - if (instr[i] == '?') { - return { false, 0 }; - } else { - return { true, static_cast(stoi(&instr[i]) / 16) }; - } - } - return { true, 0 }; - } - constexpr uint64_t rotate_right(uint64_t v, int n) { return (v >> (n & 63)) | (v << (-n & 63)); } @@ -262,97 +327,79 @@ namespace patterns { } constexpr ArmOpcode parse_movX(const char* instr, ArmOpcode base) { - size_t i = skip_whitespace(instr, skip_mnemonic(instr)); - - char rd[5] = {}; - i = parse_register(instr, parse_token(instr, i, rd), rd, 0x1F, 0, base, ','); - if (rd[0] != '?') base.set_bits((rd[0] == 'W' ? 0 : 1), 1, 31); - - char imm[24] = {}; - i = parse_imm_token(instr, skip_char(instr, i, '#'), imm); - if (imm[0] != '?') { - if (int32_t val = stoi(imm); val >= 0 && val <= 0xFFFF) base.set_bits(val, 0xFFFF, 5); + char rd[5] = {}, imm[24] = {}; + size_t i = read_register(instr, skip_mnemonic(instr), rd, reg_zr, 0, base); + const int width = register_width(rd); + if (width >= 0) base.set_bits(static_cast(width), 1, 31); + i = parse_imm_token(instr, expect(instr, i, ','), imm); + if (imm[0] != '?') base.set_bits(static_cast(imm_in(imm, 0, 0xFFFF)), 0xFFFF, 5); + // LSL #0, #16, #32 or #48 (#0 or #16 for W); #0 when not written + int64_t hw = 0; + if (accept(instr, i, ',')) { + if (!keyword(instr, i, "LSL")) PATTERNS_FAIL("Invalid shift or extend!"); + char amount[24] = {}; + i = parse_imm_token(instr, i, amount); + hw = amount[0] == '?' ? -1 : imm_in(amount, 0, width == 0 ? 16 : 48, 16) / 16; } - - auto [has_hw, hw] = parse_lsl_shift(instr, i); - if (has_hw) base.set_bits(hw, 3, 21); - - return base; + if (hw >= 0) base.set_bits(static_cast(hw), 3, 21); + return done(instr, i, base); } constexpr ArmOpcode parse_branch_imm26(const char* instr, ArmOpcode base) { - size_t i = skip_char(instr, skip_whitespace(instr, skip_mnemonic(instr)), '#'); - - if (instr[i] == '?') { - base.pattern &= 0xFC000000; - base.mask &= 0xFC000000; - } else { - base.set_bits(static_cast(stoi(&instr[i]) >> 2), 0x3FFFFFF); - } - - return base; + char imm[24] = {}; + const size_t i = parse_imm_token(instr, skip_mnemonic(instr), imm); + if (imm[0] != '?') base.set_bits(static_cast(imm_in(imm, -(1 << 27), (1 << 27) - 4, 4) / 4), 0x3FFFFFF); + return done(instr, i, base); } - // BR/BLR/RET Xn (a bare RET returns through X30) + // BR/BLR/RET Xn and the PAC forms with one register (a bare RET returns through X30) constexpr ArmOpcode parse_branch_reg(const char* instr, ArmOpcode base) { - size_t i = skip_whitespace(instr, skip_mnemonic(instr)); - if (instr[i]) parse_register(instr, i, 0x1F, 5, base); - else base.set_bits(30, 0x1F, 5); - return base; + if (base.pattern == 0xD65F0000 && !instr[skip_whitespace(instr, skip_mnemonic(instr))]) { + base.set_bits(30, 0x1F, 5); + return base; + } + return parse_regs<-1, 5 | r_x>(instr, base); } constexpr ArmOpcode parse_adr(const char* instr, ArmOpcode base) { - size_t i = skip_whitespace(instr, skip_mnemonic(instr)); - // Rd - i = parse_register(instr, i, 0x1F, 0, base, ','); - - char imm[24] = {}; - i = parse_imm_token(instr, skip_char(instr, i, '#'), imm); - if (!(imm[0] == '?' && imm[1] == '\0')) { - int64_t offset = stoi(imm); - if ((base.pattern >> 31) & 1) offset >>= 12; - base.set_bits(static_cast((offset << 24) | ((offset >> 2) & 0x7FFFF)), (3 << 24 | 0x7FFFF), 5); + char rd[5] = {}, imm[24] = {}; + size_t i = read_register(instr, skip_mnemonic(instr), rd, reg_zr, 0, base); + same_width(register_width(rd), 1); + i = parse_imm_token(instr, expect(instr, i, ','), imm); + if (imm[0] != '?') { + // ADR: +-1MB. ADRP: the distance to the target's 4KB page (a multiple of 0x1000), +-4GB + const bool page = (base.pattern >> 31) & 1; + const int64_t range = int64_t(1) << (page ? 32 : 20), unit = page ? 0x1000 : 1; + int64_t offset = imm_in(imm, -range, range - unit, unit); + if (page) offset >>= 12; + base.set_bits(static_cast((static_cast(offset) << 24) | ((offset >> 2) & 0x7FFFF)), (3 << 24 | 0x7FFFF), 5); } - return base; + return done(instr, i, 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 + // 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. Returns past it template - constexpr void parse_branch_imm(const char* instr, size_t i, ArmOpcode& base) { + constexpr size_t 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), (1u << bits) - 1, 5); + i = parse_imm_token(instr, i, imm); + constexpr int64_t range = int64_t(1) << (bits + 1); // in bytes + if (imm[0] != '?') base.set_bits(static_cast(imm_in(imm, -range, range - 4, 4) / 4), (1u << bits) - 1, 5); + return i; } // 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), 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) { + // 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). + // 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. Mask 0 + // if the offset or shift doesn't fit Rt's size, so parse_load_store can try W and X for a bare "?" Rt + template + constexpr ArmOpcode encode_load_store(const char* instr) { 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 (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 }; + char rt[5] = {}, rn[5] = {}, imm[24] = {}; + size_t i = read_register(instr, skip_mnemonic(instr), rt, scale < 0 ? reg_zr | reg_fp : reg_zr, 0, base); + const int fp = fp_size(rt), rw = register_width(rt); int size = scale; uint32_t op = op_int, width_mask = ~0u; // width_mask: Rt's width bit, set below when known if (fp >= 0) { @@ -360,61 +407,69 @@ namespace patterns { 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); + if (rw >= 0) { + size = 2 + rw; + base.set_bits(static_cast(rw), 1, 30); } } else if (sign_extend) { width_mask = ~(1u << 22); - if (rt[0] != '?') base.set_bits(rt[0] == 'W' ? 1 : 0, 1, 22); + if (rw >= 0) base.set_bits(rw == 0 ? 1 : 0, 1, 22); + } else { + same_width(rw, scale == 2 ? 1 : 0); // LDRSW loads into X; the byte and halfword ones use W } - if (instr[i++] != '[') return { 0, 0 }; - // Rn - i = parse_register(instr, i, 0x1F, 5, base); + // [Rn + i = read_register(instr, expect(instr, expect(instr, i, ','), '['), rn, reg_sp, 5, base); + same_width(register_width(rn), 1); - bool is_register_offset = false, is_pre_index = false, is_post_index = false; + bool is_register_offset = false, is_pre_index = false, is_post_index = false, has_offset = false; uint32_t option_val = 0b011, option_mask = 0b111, s_val = 0, s_mask = 1; - - if (instr[i] == ',') { - i = skip_whitespace(instr, ++i); + if (accept(instr, i, ',')) { if (instr[i] == '#') { i = parse_imm_token(instr, i, imm); + has_offset = true; } else { - // Rm - i = parse_register(instr, i, 0x1F, 16, base, ','); + // Rm, then LSL #amount, UXTW, SXTW or SXTX (amount optional), or "?" for any extend and amount + char rm[5] = {}; + i = read_register(instr, i, rm, reg_zr, 16, base); is_register_offset = true; - - // "?": 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; } - - 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; + if (accept(instr, i, ',')) { + bool shift = false; + if (accept(instr, i, '?')) { option_mask = 0; s_mask = 0; } + else if (keyword(instr, i, "LSL")) { option_val = 0b011; shift = true; } + else if (keyword(instr, i, "UXTW")) option_val = 0b010; + else if (keyword(instr, i, "SXTW")) option_val = 0b110; + else if (keyword(instr, i, "SXTX")) option_val = 0b111; + else PATTERNS_FAIL("Invalid shift or extend!"); + if (shift || instr[i] == '#') { + char amount[24] = {}; + i = parse_imm_token(instr, i, amount); + s_mask = 1; + if (amount[0] == '?') s_mask = 0; + else { + // 0 or log2 of the access size; for bytes an explicit #0 is the shifted form + const int64_t a = signed_imm(amount); + if (a != 0 && a != size) return { 0, 0 }; + s_val = (size == 0 || a != 0) ? 1 : 0; + } + } } + // X for LSL and SXTX, W for UXTW and SXTW + if (option_mask) same_width(register_width(rm), static_cast(option_val & 1)); } } - - i = skip_whitespace(instr, skip_past(instr, i, ']')); - if (instr[i] == '!') { is_pre_index = true; ++i; } - i = skip_whitespace(instr, i); - if (instr[i] == ',') { - i = skip_whitespace(instr, i + 1); - if (instr[i] == '#') { - i = parse_imm_token(instr, i + 1, imm); - is_post_index = true; - } + i = expect(instr, i, ']'); + if (accept(instr, i, '!')) is_pre_index = true; + else if (accept(instr, i, ',')) { + i = parse_imm_token(instr, i, imm); + is_post_index = true; } + if ((is_pre_index && !has_offset) || (is_post_index && (has_offset || is_register_offset)) || + (unscaled && (is_register_offset || is_pre_index || is_post_index))) + PATTERNS_FAIL("Malformed operands!"); const bool known_imm = imm[0] != '?'; - const int32_t val = known_imm ? stoi(imm) : 0; + const int64_t val = known_imm && imm[0] ? signed_imm(imm) : 0; bool is_unsigned_offset = !unscaled && !is_register_offset && !is_pre_index && !is_post_index; if (is_unsigned_offset && known_imm && (val < 0 || (size > 0 && (val & ((1 << size) - 1))))) is_unsigned_offset = false; @@ -423,9 +478,9 @@ namespace patterns { base.set_bits(op, 0xFFC00000 & width_mask); if (known_imm && size >= 0) { if ((val >> size) > 0xFFF) return { 0, 0 }; - base.set_bits(val >> size, 0xFFF, 10); + base.set_bits(static_cast(val >> size), 0xFFF, 10); } - return base; + return done(instr, i, base); } const uint32_t form = is_register_offset ? 0x00200800 : is_pre_index ? 0xC00 : is_post_index ? 0x400 : 0; @@ -434,114 +489,153 @@ namespace patterns { 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); + base.set_bits(static_cast(val), 0x1FF, 12); } - return base; + return done(instr, i, base); + } + + // encode_load_store, failing on an offset or shift that doesn't fit. A bare "?" Rt is W or X + template + constexpr ArmOpcode parse_load_store(const char* instr, ArmOpcode) { + const size_t at = skip_whitespace(instr, skip_mnemonic(instr)); + char rt[5] = {}; + parse_token(instr, at, rt); + ArmOpcode e{ 0, 0 }; + if (scale < 0 && str_eq(rt, "?")) { + // W or X: the offset's scaling and form (LDR or LDUR) depend on which, so encode both + 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'; } + } + e = either(encode_load_store(w), + encode_load_store(x)); + e.pattern &= ~0x1Fu; + e.mask &= ~0x1Fu; + } else { + e = encode_load_store(instr); + } + if (!e.mask) PATTERNS_FAIL("Immediate out of range!"); + return e; } // LDR is a load/store, or a PC-relative literal load (LDR Rt, #offset) constexpr ArmOpcode parse_ldr(const char* instr, ArmOpcode base) { char rt[5] = {}; - size_t i = skip_whitespace(instr, skip_mnemonic(instr)); - i = parse_register(instr, parse_token(instr, i, rt), rt, 0x1F, 0, base, ','); + const size_t i = expect(instr, read_register(instr, skip_mnemonic(instr), rt, reg_zr | reg_fp, 0, base), ','); if (instr[i] == '[') return parse_load_store<0xB9400000, -1>(instr, 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; + else if (fp >= 0) PATTERNS_FAIL("Invalid register!"); + else if (register_width(rt) >= 0) base.set_bits(static_cast(register_width(rt)), 1, 30); + return done(instr, parse_branch_imm(instr, i, base), base); } constexpr ArmOpcode parse_orr(const char* instr, ArmOpcode base) { - size_t i = skip_whitespace(instr, skip_mnemonic(instr)); - - char rd[5] = {}, rn[5] = {}; - i = parse_register(instr, parse_token(instr, i, rd), rd, 0x1F, 0, base, ','); - // Rn - i = parse_register(instr, parse_token(instr, i, rn), rn, 0x1F, 5, base, ','); - // Width from Rd, or from Rn when Rd is "?" - const int width = register_width(rd) >= 0 ? register_width(rd) : register_width(rn); - if (width >= 0) base.set_bits(static_cast(width), 1, 31); + char rd[5] = {}, rn[5] = {}, rm[5] = {}; + size_t i = read_register(instr, skip_mnemonic(instr), rd, reg_zr | reg_sp, 0, base); + i = expect(instr, read_register(instr, expect(instr, i, ','), rn, reg_zr, 5, base), ','); + int width = same_width(register_width(rd), register_width(rn)); if (instr[i] == '#') { - if (base.pattern >> 21 & 1) return { 0, 0 }; // BIC/ORN/EON (N set) have no immediate form + // BIC/BICS/ORN/EON (N set) have no immediate form: as assemblers do, AND/ANDS/ORR/EOR the inverse + const bool invert = base.pattern >> 21 & 1; + base.pattern &= ~(1u << 21); + base.mask &= ~(1u << 21); // immr's top bit here, set by the immediate + parse_register(rd, (base.pattern >> 29 & 3) == 3 ? reg_zr : reg_sp); // Rd 31 is SP, ZR for ANDS + if (width >= 0) base.set_bits(static_cast(width), 1, 31); base.set_bits((2 << 4), (3 << 4 | 1), 23); char imm[24] = {}; i = parse_imm_token(instr, i, imm); if (imm[0] != '?') { - uint64_t immVal = stoi(imm); - // Unknown width (Rd is "?"): a value that fits in 32 bits can be either, and the two only differ in N - const bool is64 = width == 1 || (width < 0 && immVal > 0xFFFFFFFFULL); - const uint32_t n_mask = (width >= 0 || is64) ? 1 : 0; - auto [ ok, N, imms, immr ] = encode_bitmask_immediate(immVal, is64); - if (ok) base.set_bits((N << 12 | immr << 6 | imms), (n_mask << 12 | 0x3F << 6 | 0x3F), 10); + const uint64_t immVal = stoi(imm); + // sf, N, immr and imms for one width; mask 0 if the value doesn't fit it. W also takes negative + // values that sign-extend from 32 bits + auto encode = [&](bool is64) -> ArmOpcode { + const uint64_t ones = is64 ? ~0ULL : 0xFFFFFFFFULL; + uint64_t v = !is64 && immVal >= 0xFFFFFFFF80000000ULL ? immVal & ones : immVal; + if (invert && v <= ones) v = ~v & ones; + auto [ ok, N, imms, immr ] = encode_bitmask_immediate(v, is64); + if (!ok) return { 0, 0 }; + ArmOpcode e{ 0, 0 }; + e.set_bits((is64 ? 1u : 0u) << 21 | N << 12 | immr << 6 | imms, 1u << 21 | 1 << 12 | 0x3F << 6 | 0x3F, 10); + return e; + }; + // Unknown width (Rd and Rn "?"): either, keeping the bits both encodings share + const ArmOpcode e = width >= 0 ? encode(width == 1) : either(encode(false), encode(true)); + if (!e.mask) PATTERNS_FAIL("Operands can't be encoded!"); + base.set_bits(e.pattern, e.mask); } - return base; + return done(instr, i, base); } // Shifted register; N (bit 21) comes from the table: set for BIC/ORN/EON + parse_register(rd, reg_zr); + i = read_register(instr, i, rm, reg_zr, 16, base); + width = same_width(width, register_width(rm)); + if (width >= 0) base.set_bits(static_cast(width), 1, 31); 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, ',')); - 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] != '?' && !(any && !instr[i])) base.set_bits(stoi(&instr[i]), 0x3F, 10); - + if (accept(instr, i, ',')) { + const bool any = accept(instr, i, '?'); // any shift: type and amount + if (!any) { + uint32_t type = 0; + if (keyword(instr, i, "LSL")) type = 0; + else if (keyword(instr, i, "LSR")) type = 1; + else if (keyword(instr, i, "ASR")) type = 2; + else if (keyword(instr, i, "ROR")) type = 3; + else PATTERNS_FAIL("Invalid shift or extend!"); + base.set_bits(type, 3, 22); + } + if (!any || instr[i]) { + char amount[24] = {}; + i = parse_imm_token(instr, i, amount); + if (amount[0] != '?') base.set_bits(static_cast(imm_in(amount, 0, width == 0 ? 31 : 63)), 0x3F, 10); + } } else { base.set_mask((3 << 12 | 0x3F), 10); } - - return base; + return done(instr, i, base); } constexpr ArmOpcode parse_arithmetic(const char* instr, ArmOpcode base) { - size_t i = skip_whitespace(instr, skip_mnemonic(instr)); - - char rd[5] = {}, rn[5] = {}; - i = parse_register(instr, parse_token(instr, i, rd), rd, 0x1F, 0, base, ','); - // Rn - i = parse_register(instr, parse_token(instr, i, rn), rn, 0x1F, 5, base, ','); - // Width from Rd, or from Rn when Rd is "?" - const int width = register_width(rd) >= 0 ? register_width(rd) : register_width(rn); + char rd[5] = {}, rn[5] = {}, rm[5] = {}; + size_t i = read_register(instr, skip_mnemonic(instr), rd, reg_zr | reg_sp, 0, base); + i = expect(instr, read_register(instr, expect(instr, i, ','), rn, reg_zr | reg_sp, 5, base), ','); + int width = same_width(register_width(rd), register_width(rn)); if (width >= 0) base.set_bits(static_cast(width), 1, 31); + // The immediate and extended register forms read Rn 31 as SP, and Rd 31 as SP too unless setting flags; + // the shifted register form reads both as ZR + const unsigned rd_kind = (base.pattern >> 29 & 1) ? reg_zr : reg_sp; if (instr[i] == '#') { + parse_register(rd, rd_kind); + parse_register(rn, reg_sp); base.set_bits((2 << 4 | 2), (3 << 4 | 7), 23); char imm[24] = {}; - i = skip_whitespace(instr, parse_imm_token(instr, i, imm)); + i = parse_imm_token(instr, i, imm); int sh = -1; // LSL #12 (1) or #0 (0) when written, -1 if not or #? - if (instr[i] == ',') { - i = skip_whitespace(instr, i + 1); - if (starts_with(&instr[i], "LSL")) { - i = skip_char(instr, skip_whitespace(instr, i + 3), '#'); - if (instr[i] != '?') sh = stoi(&instr[i]) == 12 ? 1 : 0; - } + bool sh_any = false; // LSL #?: either, so the immediate is the field as written + if (accept(instr, i, ',')) { + if (!keyword(instr, i, "LSL")) PATTERNS_FAIL("Invalid shift or extend!"); + char amount[24] = {}; + i = parse_imm_token(instr, i, amount); + if (amount[0] == '?') sh_any = true; + else sh = imm_in(amount, 0, 12, 12) ? 1 : 0; } if (imm[0] != '?') { - int64_t val = stoi(imm); + int64_t val = imm_in(imm, -0xFFF000, 0xFFF000); // As assemblers do: a negative immediate is the opposite operation (ADD #-8 is SUB #8, CMP #-1 // is CMN #1), and a multiple of 0x1000 the shifted form (#0x1000 is #1, LSL #12) if (val < 0) { base.pattern ^= 1u << 30; val = -val; } - if (sh < 0 && val > 0xFFF && !(val & 0xFFF)) { sh = 1; val >>= 12; } - if (val > 0xFFF) return { 0, 0 }; + if (sh < 0 && !sh_any && val > 0xFFF && !(val & 0xFFF)) { sh = 1; val >>= 12; } + if (val > 0xFFF) PATTERNS_FAIL("Immediate out of range!"); base.set_bits(static_cast(val), 0xFFF, 10); - if (sh < 0) sh = 0; + if (sh < 0 && !sh_any) sh = 0; } if (sh >= 0) base.set_bits(static_cast(sh), 1, 22); - return base; + return done(instr, i, base); } // With SP as Rd or Rn only the extended register form exists: assemblers write LSL as UXTX (UXTW for W) @@ -549,61 +643,50 @@ namespace patterns { const uint32_t sp_extend = str_eq(rd, "WSP") || str_eq(rn, "WSP") ? 2 : 3; base.set_bits(5, 0xF, 25); - // Rm - i = parse_register(instr, i, 0x1F, 16, base); + i = read_register(instr, i, rm, reg_zr, 16, base); + + // Shifted (LSL, LSR, ASR, by an amount), extended (UXTB...SXTX, optional amount), or "?" for any + enum { any, shifted, extended } form = sp ? extended : shifted; + uint32_t type = sp ? sp_extend : 0; // shift type, or extend option + char amount[24] = {}; + if (accept(instr, i, ',')) { + constexpr const char* extends[] = { "UXTB", "UXTH", "UXTW", "UXTX", "SXTB", "SXTH", "SXTW", "SXTX" }; + bool shift = true; + if (accept(instr, i, '?')) { form = any; shift = false; } + else if (keyword(instr, i, "LSL")) {} + else if (!sp && keyword(instr, i, "LSR")) type = 1; + else if (!sp && keyword(instr, i, "ASR")) type = 2; + else { + for (type = 0; type < 8 && !keyword(instr, i, extends[type]);) ++type; + if (type == 8) PATTERNS_FAIL("Invalid shift or extend!"); + form = extended; + shift = false; + } + if (shift || (form == extended && instr[i])) i = parse_imm_token(instr, i, amount); + } - if (instr[i] == ',') { - i = skip_whitespace(instr, i + 1); - bool shifted = false, extended = false; + if (form == any) { // 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; } - else if (starts_with(&instr[i], "ASR")) { base.set_bits(4, 7, 21); shifted = true; i += 3; } - else if (starts_with(&instr[i], "UXTB")) { base.set_bits(0, 7, 13); extended = true; i += 4; } - else if (starts_with(&instr[i], "UXTH")) { base.set_bits(1, 7, 13); extended = true; i += 4; } - else if (starts_with(&instr[i], "UXTW")) { base.set_bits(2, 7, 13); extended = true; i += 4; } - else if (starts_with(&instr[i], "UXTX")) { base.set_bits(3, 7, 13); extended = true; i += 4; } - else if (starts_with(&instr[i], "SXTB")) { base.set_bits(4, 7, 13); extended = true; i += 4; } - else if (starts_with(&instr[i], "SXTH")) { base.set_bits(5, 7, 13); extended = true; i += 4; } - else if (starts_with(&instr[i], "SXTW")) { base.set_bits(6, 7, 13); extended = true; i += 4; } - else if (starts_with(&instr[i], "SXTX")) { base.set_bits(7, 7, 13); extended = true; i += 4; } - i = skip_whitespace(instr, i); - if (instr[i] == '#') { - char amount[12] = {}; - i = parse_imm_token(instr, i, amount); - if (amount[0] != '?') { - auto val = stoi(amount); - if (shifted) { - base.set_bits(val, 0x3F, 10); - } else if (extended) { - base.set_bits(val, 7, 10); - } - } - if (shifted) { - base.set_bits(0, 1, 21); - } else if (extended) { - base.set_bits(1, 7, 21); - } - } else { - if (shifted) { - base.set_bits(0, (1 << 11 | 0x3F), 10); - } else if (extended) { - base.set_bits((1 << 11), (7 << 11 | 7), 10); - } - } - } else if (sp) { - base.set_bits((1 << 11 | sp_extend << 3), (7 << 11 | 0x3F), 10); + base.pattern &= ~(7u << 21 | 0x3Fu << 10); + base.mask &= ~(7u << 21 | 0x3Fu << 10); + } else if (form == shifted) { + parse_register(rd, reg_zr); + parse_register(rn, reg_zr); + width = same_width(width, register_width(rm)); + if (width >= 0) base.set_bits(static_cast(width), 1, 31); + base.set_bits(type << 1, 7, 21); + if (amount[0] != '?') base.set_bits(static_cast(amount[0] ? imm_in(amount, 0, width == 0 ? 31 : 63) : 0), 0x3F, 10); } else { - base.set_bits(0, (7 << 11 | 0x3F), 10); + parse_register(rd, rd_kind); + parse_register(rn, reg_sp); + // Rm is X for UXTX/SXTX in an X instruction, else W + if ((type & 3) == 3) same_width(width, register_width(rm)); + else same_width(register_width(rm), 0); + base.set_bits(type, 7, 13); + base.set_bits(1, 7, 21); + if (amount[0] != '?') base.set_bits(static_cast(amount[0] ? imm_in(amount, 0, 4) : 0), 7, 10); } - - return base; + return done(instr, i, base); } // LDP/STP and the forms sharing their layout; the table gives the load bit (22). opc (bits 30-31) is 00 for W @@ -611,22 +694,22 @@ namespace patterns { // 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, ','); - 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 int fp = fp_size(rt1) >= 0 ? fp_size(rt1) : fp_size(rt2); + char rt1[5] = {}, rt2[5] = {}, rn[5] = {}, imm[24] = {}; + size_t i = read_register(instr, skip_mnemonic(instr), rt1, reg_zr | reg_fp, 0, base); + i = read_register(instr, expect(instr, i, ','), rt2, reg_zr | reg_fp, 10, base); + const int width = same_width(register_width(rt1), register_width(rt2)); + // Both general-purpose, or floating-point of one size + const int f1 = fp_size(rt1), f2 = fp_size(rt2); + if ((f1 >= 0 && register_width(rt2) >= 0) || (f2 >= 0 && register_width(rt1) >= 0) || (f1 >= 0 && f2 >= 0 && f1 != f2)) + PATTERNS_FAIL("Invalid register!"); + const int fp = f1 >= 0 ? f1 : f2; int scale = -1; // log2 of one register's size, when known - if (fp >= 2 && opc < 0) { + if (fp >= 0) { + if (fp < 2 || opc >= 0) PATTERNS_FAIL("Invalid register!"); // S, D or Q; LDPSW loads into X 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 + same_width(width, 1); base.set_bits(static_cast(opc), 3, 30); scale = 2; } else if (width >= 0) { @@ -634,104 +717,108 @@ namespace patterns { scale = 2 + width; } - if (instr[i] != '[') return base; - i = skip_whitespace(instr, skip_char(instr, i, '[')); - - // Rn - i = parse_register(instr, i, 0x1F, 5, base, ','); - - // [Rn] alone is the signed offset form with offset 0 (imm stays empty, which reads as 0) + // [Rn{, #imm}]{!} or [Rn], #imm. [Rn] alone is the signed offset form with offset 0 + i = read_register(instr, expect(instr, expect(instr, i, ','), '['), rn, reg_sp, 5, base); + same_width(register_width(rn), 1); + const bool offset = accept(instr, i, ','); + if (offset) i = parse_imm_token(instr, i, imm); + i = expect(instr, i, ']'); bool post_index = false, pre_index = false; - char imm[24] = {}; - if (instr[i] == '#') - i = skip_whitespace(instr, parse_imm_token(instr, i, imm)); - if (instr[i] == ']') { - i = skip_whitespace(instr, skip_char(instr, skip_char(instr, i, ']'), ',')); - if (instr[i] == '!') { - pre_index = true; - } else if (instr[i] == '#') { - post_index = true; - i = parse_imm_token(instr, i, imm); - } + if (offset && accept(instr, i, '!')) { + pre_index = true; + } else if (!offset && accept(instr, i, ',')) { + post_index = true; + i = parse_imm_token(instr, i, imm); } - if (no_allocate && (pre_index || post_index)) return { 0, 0 }; + if (no_allocate && (pre_index || post_index)) PATTERNS_FAIL("Malformed operands!"); // 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); - if ((immVal % (1 << scale)) == 0) { - int32_t encoded = immVal / (1 << scale); - if (encoded >= -64 && encoded <= 63) { - base.set_bits(encoded, 0x7F, 15); - } - } + if (imm[0] != '?') { + // imm7, scaled by the register size. With the size unknown ("?" registers) the field stays open, but + // the offset must fit one of W and X + const int64_t v = imm[0] ? signed_imm(imm) : 0; + auto fits = [&](int s) { return v % (1 << s) == 0 && v / (1 << s) >= -64 && v / (1 << s) <= 63; }; + if (scale >= 0 ? !fits(scale) : !fits(2) && !fits(3)) PATTERNS_FAIL("Immediate out of range!"); + if (scale >= 0) base.set_bits(static_cast(v / (1 << scale)), 0x7F, 15); } - return base; + return done(instr, i, 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) { - size_t i = skip_whitespace(instr, skip_mnemonic(instr)); - char rd[5] = {}; - i = skip_whitespace(instr, skip_char(instr, parse_token(instr, i, rd), ',')); - const bool rd_sp = str_eq(rd, "SP") || str_eq(rd, "WSP"); - const bool known_width = rd[0] == 'W' || rd[0] == 'X' || rd_sp; - const bool is64 = rd[0] == 'X' || str_eq(rd, "SP"); + char rd[5] = {}, rm[5] = {}; ArmOpcode op{ 0, 0 }; + size_t i = expect(instr, read_register(instr, skip_mnemonic(instr), rd, reg_zr | reg_sp, 0, op), ','); + const bool rd_sp = is_sp(rd), rd_zr = str_eq(rd, "XZR") || str_eq(rd, "WZR"); + const bool known_width = register_width(rd) >= 0; + const bool is64 = register_width(rd) == 1; if (known_width) op.set_bits(is64 ? 1 : 0, 1, 31); - if (auto r = parse_register(rd); r != 0xFFFFFFFF) op.set_bits(r, 0x1F); if (instr[i] == '#') { char imm[24] = {}; - parse_imm_token(instr, i, imm); + i = parse_imm_token(instr, i, imm); if (imm[0] == '?') { + parse_register(rd, reg_zr); // MOVZ and MOVN can't write SP op.set_bits(0x12800000, 0x3F800000); // MOVZ or MOVN: bit 30 of opc left open, bit 29 clear (not MOVK) - return op; + return done(instr, i, op); } - const unsigned width = is64 ? 64 : 32; - const uint64_t ones = is64 ? ~0ULL : 0xFFFFFFFFULL; - const uint64_t v = stoi(imm) & ones; // unsigned: 64-bit hex fits, and negatives wrap - for (uint32_t hw = 0; hw < width / 16; ++hw) { - if ((v & ~(0xFFFFULL << (hw * 16))) == 0) { - op.set_bits(0x52800000 | hw << 21 | static_cast(v >> (hw * 16)) << 5, 0x7FFFFFE0); - return op; + const uint64_t raw = stoi(imm); // unsigned: 64-bit hex fits, and negatives wrap + // The encoding for one width, sf included; mask 0 if the value doesn't fit it. MOVZ and MOVN read Rd + // 31 as ZR, ORR as SP + auto encode = [&](bool wide) -> ArmOpcode { + const unsigned width = wide ? 64 : 32; + const uint64_t ones = wide ? ~0ULL : 0xFFFFFFFFULL; + // W takes 32-bit values, or negative ones that sign-extend from 32 bits + if (!wide && raw > ones && raw < 0xFFFFFFFF80000000ULL) return { 0, 0 }; + const uint64_t v = raw & ones; + const uint32_t sf = wide ? 1u << 31 : 0; + ArmOpcode e{ 0, 0 }; + for (uint32_t hw = 0; hw < width / 16 && !rd_sp; ++hw) { + if ((v & ~(0xFFFFULL << (hw * 16))) == 0) { + e.set_bits(sf | 0x52800000 | hw << 21 | static_cast(v >> (hw * 16)) << 5, 0xFFFFFFE0); + return e; + } } - } - for (uint32_t hw = 0; hw < width / 16; ++hw) { - if ((~v & ones & ~(0xFFFFULL << (hw * 16))) == 0) { - op.set_bits(0x12800000 | hw << 21 | static_cast((~v & ones) >> (hw * 16)) << 5, 0x7FFFFFE0); - return op; + for (uint32_t hw = 0; hw < width / 16 && !rd_sp; ++hw) { + if ((~v & ones & ~(0xFFFFULL << (hw * 16))) == 0) { + e.set_bits(sf | 0x12800000 | hw << 21 | static_cast((~v & ones) >> (hw * 16)) << 5, 0xFFFFFFE0); + return e; + } } - } - auto [ ok, N, imms, immr ] = encode_bitmask_immediate(v, is64); - if (!ok) return { 0, 0 }; - op.set_bits(0x320003E0 | N << 22 | immr << 16 | imms << 10, 0x7FFFFFE0); - return op; + auto [ ok, N, imms, immr ] = encode_bitmask_immediate(v, wide); + if (!ok || rd_zr) return { 0, 0 }; + e.set_bits(sf | 0x320003E0 | N << 22 | immr << 16 | imms << 10, 0xFFFFFFE0); + return e; + }; + // A bare "?" Rd: W or X, keeping the bits both encodings share + const ArmOpcode e = known_width ? encode(is64) : either(encode(false), encode(true)); + if (!e.mask) PATTERNS_FAIL("Operands can't be encoded!"); + op.set_bits(e.pattern, e.mask); + return done(instr, i, op); } - char rm[5] = {}; - parse_token(instr, i, rm); - const auto rm_val = parse_register(rm); - if (rd_sp || str_eq(rm, "SP") || str_eq(rm, "WSP")) { - op.set_bits(0x11000000, 0x7FFFFC00); // ADD Rd, Rn, #0 + i = parse_token(instr, i, rm); + const auto rm_val = parse_register(rm, reg_zr | reg_sp); + same_width(register_width(rd), register_width(rm)); + if (rd_sp || is_sp(rm)) { + // ADD Rd, Rn, #0: 31 is SP in both + parse_register(rd, reg_sp); + parse_register(rm, reg_sp); + op.set_bits(0x11000000, 0x7FFFFC00); if (rm_val != 0xFFFFFFFF) op.set_bits(rm_val, 0x1F, 5); } else { op.set_bits(0x2A0003E0, 0x7FE0FFE0); // ORR Rd, ZR, Rm if (rm_val != 0xFFFFFFFF) op.set_bits(rm_val, 0x1F, 16); } - return op; + return done(instr, i, op); } constexpr ArmOpcode parse_cbz(const char* instr, ArmOpcode base) { - size_t i = skip_whitespace(instr, skip_mnemonic(instr)); - char rt[5] = {}; - 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_branch_imm(instr, i, base); - return base; + const size_t i = read_register(instr, skip_mnemonic(instr), rt, reg_zr, 0, base); + if (register_width(rt) >= 0) base.set_bits(static_cast(register_width(rt)), 1, 31); + return done(instr, parse_branch_imm(instr, expect(instr, i, ','), base), base); } // Aliases that write the zero register (of the first operand's width) as operand pos, parsed as the full form: @@ -740,7 +827,10 @@ namespace patterns { 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 first[5] = {}; + parse_token(instr, i, first); + const int width = register_width(first); + const char* zr = width == 0 ? "WZR, " : width == 1 ? "XZR, " : "?, "; char line[max_line + 8] = { '_', ' ' }; size_t j = 2; if (pos == 1) { @@ -762,43 +852,35 @@ namespace patterns { char cond[4] = {}; for (size_t j = 0; j < 3 && instr[2 + j] && !isspace(instr[2 + j]); ++j) cond[j] = instr[2 + j]; const uint32_t c = parse_cond(cond); - if (c == 17) return { 0, 0 }; + if (c == 17) PATTERNS_FAIL("Invalid condition!"); if (c < 16) base.set_bits(c, 0xF); - parse_branch_imm(instr, skip_whitespace(instr, skip_mnemonic(instr)), base); - return base; + return done(instr, parse_branch_imm(instr, skip_mnemonic(instr), base), 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), ',')); + size_t i = read_register(instr, skip_mnemonic(instr), rt, reg_zr, 0, base); + i = parse_imm_token(instr, expect(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); + const uint32_t b = static_cast(imm_in(bit, 0, width == 0 ? 31 : 63)); + base.set_bits(b, 0x1F, 19); + base.set_bits(b >> 5, 1, 31); } - parse_branch_imm<14>(instr, i, base); - return base; + return done(instr, parse_branch_imm<14>(instr, expect(instr, i, ','), base), 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; + const size_t i = parse_imm_token(instr, skip_mnemonic(instr), imm); + if (imm[0] != '?') base.set_bits(static_cast(imm_in(imm, 0, 0xFFFF)), 0xFFFF, shift); + return done(instr, i, base); } // BTI {C|J|JC}: the targets at bits 6-7, BTI ? for any @@ -806,30 +888,27 @@ namespace patterns { 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 if (targets == 4) PATTERNS_FAIL("Invalid operand!"); 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); + char rt[5] = {}, rn[5] = {}, imm[24] = {}; + size_t i = read_register(instr, skip_mnemonic(instr), rt, reg_zr, 0, base); + same_width(register_width(rt), 1); + i = read_register(instr, expect(instr, expect(instr, i, ','), '['), rn, reg_sp, 5, base); + same_width(register_width(rn), 1); + if (accept(instr, i, ',')) i = parse_imm_token(instr, i, imm); + i = expect(instr, i, ']'); + base.set_bits(accept(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; + const uint32_t enc = static_cast((imm[0] ? imm_in(imm, -4096, 4088, 8) : 0) / 8) & 0x3FF; base.set_bits(enc, 0x1FF, 12); base.set_bits(enc >> 9, 1, 22); } - return base; + return done(instr, i, base); } // Exact decimal (1, -2.5, 0.125) as num / den; false if it isn't one @@ -852,27 +931,24 @@ namespace patterns { // 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), ',')); + char rd[5] = {}, rn[5] = {}, imm[24] = {}; 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); + size_t i = expect(instr, read_register(instr, skip_mnemonic(instr), rd, reg_zr | reg_fp, 0, op), ','); + const int fd = fp_size(rd); - char imm[24] = {}; if (instr[i] == '#') { - parse_imm_token(instr, i, imm); - if (fd < 0 || ftype[fd] > 3) return { 0, 0 }; + i = parse_imm_token(instr, i, imm); + if (fd < 0 || ftype[fd] > 3) PATTERNS_FAIL("Invalid register!"); bool negative = false; uint64_t num = 0, den = 1; - if (imm[0] != '?' && !parse_decimal(imm, negative, num, den)) return { 0, 0 }; + if (imm[0] != '?' && !parse_decimal(imm, negative, num, den)) PATTERNS_FAIL("Invalid immediate!"); if (imm[0] != '?' && num == 0) { - if (negative) return { 0, 0 }; + if (negative) PATTERNS_FAIL("Operands can't be encoded!"); 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; + if (imm[0] == '?') return done(instr, i, 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; @@ -880,32 +956,32 @@ namespace patterns { 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 done(instr, i, op); } } - return { 0, 0 }; + PATTERNS_FAIL("Operands can't be encoded!"); } } else { - parse_token(instr, i, rn); + i = parse_token(instr, i, rn); } - if (auto r = parse_register(rn); r != 0xFFFFFFFF) op.set_bits(r, 0x1F, 5); + if (auto r = parse_register(rn, reg_zr | reg_fp); 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 }; + if (f >= 0 && (ftype[f] > 3 || (fd >= 0 && fn >= 0 && fd != fn))) PATTERNS_FAIL("Invalid register!"); op.set_bits(0x1E204000, 0xFF3FFC00); if (f >= 0) op.set_bits(ftype[f], 3, 22); - return op; + return done(instr, i, 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 }; + if (f < 0 || g < 0 || ftype[f] > 3 || (f == 2 && g != 0) || (f == 3 && g != 1)) PATTERNS_FAIL("Invalid register!"); op.set_bits(0x1E260000 | static_cast(g) << 31 | ftype[f] << 22 | (to_fp ? 1u : 0u) << 16, 0xFFFFFC00); - return op; + return done(instr, i, op); } // CSEL/CSINC/CSINV/CSNEG Rd, Rn, Rm, cond. regs is how many registers are written: 1 for CSET/CSETM (Rn = Rm = @@ -913,52 +989,43 @@ namespace patterns { 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; } + int specs[regs] = {}; + for (size_t k = 0; k < regs; ++k) specs[k] = all[k]; + int width = -1; + size_t i = parse_reg_list(instr, skip_mnemonic(instr), specs, base, width); + if (width >= 0) base.set_bits(static_cast(width), 1, 31); 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 }; + i = parse_token(instr, expect(instr, i, ','), cond); + const uint32_t c = parse_cond(cond); + if (c == 17 || (regs < 3 && c >= 14 && c < 16)) PATTERNS_FAIL("Invalid condition!"); if (c < 16) base.set_bits(regs < 3 ? c ^ 1 : c, 0xF, 12); - return base; + return done(instr, i, 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); + char rn[5] = {}, rm[5] = {}, imm[24] = {}, cond[4] = {}; + size_t i = expect(instr, read_register(instr, skip_mnemonic(instr), rn, reg_zr, 5, base), ','); + int width = register_width(rn); 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); - } + if (imm[0] != '?') base.set_bits(static_cast(imm_in(imm, 0, 31)), 0x1F, 16); } else { - char rm[5] = {}; - i = parse_token(instr, i, rm); - parse_register(rm, 0x1F, 16, base); + i = read_register(instr, i, rm, reg_zr, 16, base); + width = same_width(width, register_width(rm)); 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); + if (width >= 0) base.set_bits(static_cast(width), 1, 31); + i = parse_imm_token(instr, expect(instr, i, ','), imm); + if (imm[0] != '?') base.set_bits(static_cast(imm_in(imm, 0, 15)), 0xF); + i = parse_token(instr, expect(instr, i, ','), cond); const uint32_t c = parse_cond(cond); - if (c == 17) return { 0, 0 }; + if (c == 17) PATTERNS_FAIL("Invalid condition!"); if (c < 16) base.set_bits(c, 0xF, 12); - return base; + return done(instr, i, base); } enum BitfieldKind { bf_raw, bf_lsl, bf_lsr, bf_extract, bf_insert, bf_extend, bf_ror, bf_extr }; @@ -971,29 +1038,30 @@ namespace patterns { 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 specs[nregs] = {}; + for (size_t k = 0; k < nregs; ++k) specs[k] = all[k]; + if (kind == bf_extend) specs[1] |= r_w; // SXT*/UXT* sources are always W, so only Rd gives the width int width = -1; - for (size_t k = 0; k < (kind == bf_extend ? 1 : nregs) && width < 0; ++k) width = w[k]; + size_t i = parse_reg_list(instr, skip_mnemonic(instr), specs, base, width); + if (kind == bf_ror && (base.mask >> 5 & 0x1F)) base.set_bits(base.pattern >> 5, 0x1F, 16); + // No immediates for the extends, two for the raw, extract and insert forms, else one + constexpr int nimm = kind == bf_extend ? 0 : kind == bf_raw || kind == bf_extract || kind == bf_insert ? 2 : 1; 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); + if (nimm >= 1) i = parse_imm_token(instr, expect(instr, i, ','), a_text); + if (nimm >= 2) i = parse_imm_token(instr, expect(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 int64_t a = ak && a_text[0] ? signed_imm(a_text) : 0, b = bk && b_text[0] ? signed_imm(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 { + auto encode = [&](int64_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))) + ((kind == bf_extract || kind == bf_insert) && bk && (b < 1 || b > size - (ak ? a : 0)))) return { 0, 0 }; - int32_t immr = 0, imms = 0; + int64_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; @@ -1001,7 +1069,7 @@ namespace patterns { 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_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 } @@ -1014,9 +1082,9 @@ namespace patterns { // A valid encoding always has sf/N in its mask; mask 0 means the size doesn't fit const ArmOpcode e = width < 0 ? either(encode(32), encode(64)) : encode(width == 0 ? 32 : 64); - if (!e.mask) return { 0, 0 }; + if (!e.mask) PATTERNS_FAIL("Operands can't be encoded!"); base.set_bits(e.pattern, e.mask); - return base; + return done(instr, i, base); } // LSL/LSR/ASR/ROR: by an immediate a bitfield (EXTR for ROR) alias, by a register LSLV/LSRV/ASRV/RORV (op2 at @@ -1025,12 +1093,12 @@ namespace patterns { 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 }); + return parse_regs<31, 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 + // size (bits 30-31) from the B/H suffix (W registers), else W or X from Rs/Rt. Mask 0 for an unknown mnemonic 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 }; @@ -1055,18 +1123,18 @@ namespace patterns { 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] = {}; + constexpr int load_specs[] = { 16, 0, 5 | r_base }, store_specs[] = { 16, 5 | r_base }; + int width = -1; + size_t i = 0; if (store) { - parse_reg_list(instr, skip_mnemonic(instr), store_shifts, base, ws); + i = parse_reg_list(instr, skip_mnemonic(instr), store_specs, base, width); base.set_bits(31, 0x1F); - w[0] = ws[0]; } else { - parse_reg_list(instr, skip_mnemonic(instr), load_shifts, base, w); + i = parse_reg_list(instr, skip_mnemonic(instr), load_specs, base, width); } - 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; + if (size >= 0) same_width(width, 0); + else if (width >= 0) base.set_bits(static_cast(width), 1, 30); + return done(instr, i, base); } struct ParseDescriptor { @@ -1074,7 +1142,9 @@ namespace patterns { ParseFunc func; }; - constexpr ParseDescriptor parseTable[] = { + // The tables are inline: a namespace-scope constant is emitted unreferenced at GCC -O0 (-fkeep-static-consts), + // which would put every parser and mnemonic in binaries that only use compile-time patterns + inline constexpr ParseDescriptor parseTable[] = { { "B", parse_branch_imm26 }, { "BL", parse_branch_imm26 }, { "ADR", parse_adr }, @@ -1127,64 +1197,64 @@ namespace patterns { { "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> }, + { "BRAA", parse_regs<-1, 5 | r_x, 0 | r_x | r_sp> }, + { "BRAB", parse_regs<-1, 5 | r_x, 0 | r_x | r_sp> }, + { "BLRAA", parse_regs<-1, 5 | r_x, 0 | r_x | r_sp> }, + { "BLRAB", parse_regs<-1, 5 | r_x, 0 | r_x | r_sp> }, { "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> }, + { "PACIA", parse_regs<-1, 0 | r_x, 5 | r_x | r_sp> }, + { "PACIB", parse_regs<-1, 0 | r_x, 5 | r_x | r_sp> }, + { "PACDA", parse_regs<-1, 0 | r_x, 5 | r_x | r_sp> }, + { "PACDB", parse_regs<-1, 0 | r_x, 5 | r_x | r_sp> }, + { "AUTIA", parse_regs<-1, 0 | r_x, 5 | r_x | r_sp> }, + { "AUTIB", parse_regs<-1, 0 | r_x, 5 | r_x | r_sp> }, + { "AUTDA", parse_regs<-1, 0 | r_x, 5 | r_x | r_sp> }, + { "AUTDB", parse_regs<-1, 0 | r_x, 5 | r_x | r_sp> }, + { "PACIZA", parse_regs<-1, 0 | r_x> }, + { "PACIZB", parse_regs<-1, 0 | r_x> }, + { "PACDZA", parse_regs<-1, 0 | r_x> }, + { "PACDZB", parse_regs<-1, 0 | r_x> }, + { "AUTIZA", parse_regs<-1, 0 | r_x> }, + { "AUTIZB", parse_regs<-1, 0 | r_x> }, + { "AUTDZA", parse_regs<-1, 0 | r_x> }, + { "AUTDZB", parse_regs<-1, 0 | r_x> }, + { "XPACI", parse_regs<-1, 0 | r_x> }, + { "XPACD", parse_regs<-1, 0 | r_x> }, + { "PACGA", parse_regs<-1, 0 | r_x, 5 | r_x, 16 | r_x | r_sp> }, { "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> }, + { "LDXR", parse_regs<30, 0, 5 | r_base> }, + { "LDAXR", parse_regs<30, 0, 5 | r_base> }, + { "LDAR", parse_regs<30, 0, 5 | r_base> }, + { "LDAPR", parse_regs<30, 0, 5 | r_base> }, + { "STLR", parse_regs<30, 0, 5 | r_base> }, + { "STXR", parse_regs<30, 16 | r_w, 0, 5 | r_base> }, + { "STLXR", parse_regs<30, 16 | r_w, 0, 5 | r_base> }, + { "LDXRB", parse_regs<-1, 0 | r_w, 5 | r_base> }, + { "LDXRH", parse_regs<-1, 0 | r_w, 5 | r_base> }, + { "LDAXRB", parse_regs<-1, 0 | r_w, 5 | r_base> }, + { "LDAXRH", parse_regs<-1, 0 | r_w, 5 | r_base> }, + { "LDARB", parse_regs<-1, 0 | r_w, 5 | r_base> }, + { "LDARH", parse_regs<-1, 0 | r_w, 5 | r_base> }, + { "LDAPRB", parse_regs<-1, 0 | r_w, 5 | r_base> }, + { "LDAPRH", parse_regs<-1, 0 | r_w, 5 | r_base> }, + { "STLRB", parse_regs<-1, 0 | r_w, 5 | r_base> }, + { "STLRH", parse_regs<-1, 0 | r_w, 5 | r_base> }, + { "STXRB", parse_regs<-1, 16 | r_w, 0 | r_w, 5 | r_base> }, + { "STXRH", parse_regs<-1, 16 | r_w, 0 | r_w, 5 | r_base> }, + { "STLXRB", parse_regs<-1, 16 | r_w, 0 | r_w, 5 | r_base> }, + { "STLXRH", parse_regs<-1, 16 | r_w, 0 | r_w, 5 | r_base> }, + { "LDXP", parse_regs<30, 0, 10, 5 | r_base> }, + { "LDAXP", parse_regs<30, 0, 10, 5 | r_base> }, + { "STXP", parse_regs<30, 16 | r_w, 0, 10, 5 | r_base> }, + { "STLXP", parse_regs<30, 16 | r_w, 0, 10, 5 | r_base> }, { "FMOV", parse_fmov }, { "CSEL", parse_csel<3> }, { "CSINC", parse_csel<3> }, @@ -1197,32 +1267,32 @@ namespace patterns { { "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> }, + { "MADD", parse_regs<31, 0, 5, 16, 10> }, + { "MSUB", parse_regs<31, 0, 5, 16, 10> }, + { "MUL", parse_regs<31, 0, 5, 16> }, + { "MNEG", parse_regs<31, 0, 5, 16> }, + { "SMADDL", parse_regs<-1, 0 | r_x, 5 | r_w, 16 | r_w, 10 | r_x> }, + { "SMSUBL", parse_regs<-1, 0 | r_x, 5 | r_w, 16 | r_w, 10 | r_x> }, + { "UMADDL", parse_regs<-1, 0 | r_x, 5 | r_w, 16 | r_w, 10 | r_x> }, + { "UMSUBL", parse_regs<-1, 0 | r_x, 5 | r_w, 16 | r_w, 10 | r_x> }, + { "SMULL", parse_regs<-1, 0 | r_x, 5 | r_w, 16 | r_w> }, + { "SMNEGL", parse_regs<-1, 0 | r_x, 5 | r_w, 16 | r_w> }, + { "UMULL", parse_regs<-1, 0 | r_x, 5 | r_w, 16 | r_w> }, + { "UMNEGL", parse_regs<-1, 0 | r_x, 5 | r_w, 16 | r_w> }, + { "SMULH", parse_regs<-1, 0 | r_x, 5 | r_x, 16 | r_x> }, + { "UMULH", parse_regs<-1, 0 | r_x, 5 | r_x, 16 | r_x> }, + { "UDIV", parse_regs<31, 0, 5, 16> }, + { "SDIV", parse_regs<31, 0, 5, 16> }, + { "LSLV", parse_regs<31, 0, 5, 16> }, + { "LSRV", parse_regs<31, 0, 5, 16> }, + { "ASRV", parse_regs<31, 0, 5, 16> }, + { "RORV", parse_regs<31, 0, 5, 16> }, + { "ADC", parse_regs<31, 0, 5, 16> }, + { "ADCS", parse_regs<31, 0, 5, 16> }, + { "SBC", parse_regs<31, 0, 5, 16> }, + { "SBCS", parse_regs<31, 0, 5, 16> }, + { "NGC", parse_zr, 1> }, + { "NGCS", parse_zr, 1> }, { "BIC", parse_orr }, { "BICS", parse_orr }, { "ORN", parse_orr }, @@ -1251,7 +1321,7 @@ namespace patterns { { "UXTH", parse_bitfield }, }; - constexpr InstructionDescriptor instructions[] = { + inline constexpr InstructionDescriptor instructions[] = { { "B", { 0x14000000, 0xFC000000 } }, { "BL", { 0x94000000, 0xFC000000 } }, { "ADR", { 0x10000000, 0x9F000000 } }, @@ -1452,17 +1522,22 @@ namespace patterns { } } - return base.mask ? base : parse_atomic(mnemonic, instr); + // Instructions without operands, else an atomic or an unknown mnemonic + return base.mask ? done(instr, i, base) : parse_atomic(mnemonic, instr); } // Copies the line at str[i] into out, trimmed and uppercased (lldb/objdump print lowercase), so each - // instruction is parsed on its own and can't run into the next line. Returns where the next line starts + // instruction is parsed on its own and can't run into the next line. A comment (';', as IDA and lldb print + // them) is left out. Returns where the next line starts template constexpr size_t read_line(const char* str, size_t i, char (&out)[N]) { while (str[i] == ' ' || str[i] == '\t') ++i; size_t j = 0; + bool comment = false; for (; str[i] && str[i] != '\n'; ++i) { - if (j + 1 >= N) throw std::logic_error("Instruction line is too long!"); + comment = comment || str[i] == ';'; + if (comment) continue; + if (j + 1 >= N) PATTERNS_FAIL("Instruction line is too long!"); out[j++] = (str[i] >= 'a' && str[i] <= 'z') ? static_cast(str[i] - 'a' + 'A') : str[i]; } while (j && isspace(out[j - 1])) --j; @@ -1475,13 +1550,13 @@ namespace patterns { // flags(text) for the text after '/'. Reads up to n chars or a terminator. Returns the number of instructions template constexpr size_t parse_arm(const char* p, size_t n, Line&& line, X&& x, Flags&& flags) { - const size_t options = skip_past(p, 0, '/'); - if (options && p[options - 1] == '/') flags(&p[options]); size_t count = 0; for (size_t i = 0; i < n && p[i];) { char text[max_line] = {}; + const size_t line_start = i; i = read_line(p, i, text); - if (text[0] == '/') break; + // Flags start the last line; a '/' anywhere else (a ; comment) isn't one + if (text[0] == '/') { flags(&p[skip_past(p, line_start, '/')]); break; } if (!text[0]) continue; size_t start = 0; if (text[0] == 'X' && (!text[1] || isspace(text[1]))) { x(count); start = skip_whitespace(text, 1); } diff --git a/include/PatternScanner/CompileTimePattern.hpp b/include/PatternScanner/CompileTimePattern.hpp index c92d336..ea6d6f4 100644 --- a/include/PatternScanner/CompileTimePattern.hpp +++ b/include/PatternScanner/CompileTimePattern.hpp @@ -6,7 +6,7 @@ namespace patterns { namespace detail { constexpr size_t pattern_length(const char* s, size_t nstr) { - return parse_bytes(s, nstr - 1, [](size_t, char, char) {}, [](size_t, const char*) {}, [](const char*) {}); + return parse_bytes(s, nstr - 1, [](size_t, char, char) {}, [](size_t, const char*, size_t) {}, [](const char*, size_t) {}); } template diff --git a/include/PatternScanner/Pattern.hpp b/include/PatternScanner/Pattern.hpp index fa5cc99..a344f01 100644 --- a/include/PatternScanner/Pattern.hpp +++ b/include/PatternScanner/Pattern.hpp @@ -25,6 +25,28 @@ #define __forceinline inline __attribute__((always_inline)) #endif +// Pattern errors throw std::logic_error, which in a compile-time pattern is a compile error. Without exceptions +// (-fno-exceptions, kernel code) they call a non-constexpr function instead: still a compile error at compile time, +// a trap at runtime +#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) +#define PATTERNS_EXCEPTIONS 1 +#define PATTERNS_FAIL(msg) throw std::logic_error(msg) +#else +#ifdef _MSC_VER +#include +#endif +namespace patterns::detail { + [[noreturn]] inline void fail() { +#ifdef _MSC_VER + __fastfail(7); +#else + __builtin_trap(); +#endif + } +} +#define PATTERNS_FAIL(msg) patterns::detail::fail() +#endif + // Compile-time patterns must never be built at runtime: that would put the pattern text in the binary. // consteval enforces it (C++20); before that the macros force it with a constexpr local. #ifdef __cpp_consteval @@ -115,21 +137,22 @@ namespace patterns { } // The byte pattern syntax ("48 8B ? X4 ? ? ? ? /d"). Calls byte(index, hi, lo) for each byte (its two characters: - // hex digits or '?'; lo is '\0' past the end), x(index, len_text) for the X marker (len_text at the digits after - // X, or nullptr) and flags(text) for the text after '/'. Returns the number of bytes + // hex digits or '?'; lo is '\0' past the end), x(index, text, n) for the X marker (the n characters after X) + // and flags(text, n) for the n characters after '/'. Never reads past len. Returns the number of bytes template constexpr size_t parse_bytes(const char* p, size_t len, Byte&& byte, X&& x, Flags&& flags) { size_t n = 0; for (size_t i = 0; i < len; i += 2) { const char c = p[i]; if (c == 'X' || c == 'x') { - x(n, i + 1 < len && p[i + 1] != ' ' ? &p[i + 1] : nullptr); + const size_t start = i + 1; while (i + 1 < len && p[i + 1] != ' ') ++i; + x(n, &p[start], i + 1 - start); } // The flags end the pattern else if (c == '/') { - flags(&p[i + 1]); + flags(&p[i + 1], len - i - 1); break; } else if (c != ' ') @@ -142,7 +165,9 @@ namespace patterns { template T extract_bitfield(uint32_t insn, unsigned width, unsigned offset) { constexpr size_t int_width = sizeof(int32_t) * 8; - return (static_cast(insn) << (int_width - (offset + width))) >> (int_width - width); + // Shifted left unsigned (a negative signed value can't be before C++20), then back down as T: signed T + // sign-extends the field + return static_cast(insn << (int_width - (offset + width))) >> (int_width - width); } bool __forceinline decode_masked_match(uint32_t insn, uint32_t mask, uint32_t pattern) { @@ -164,7 +189,7 @@ namespace patterns { // 0 - B if (decode_masked_match(insn, 0b0111'1100'0000'0000'0000'0000'0000'0000, 0b0001'0100'0000'0000'0000'0000'0000'0000)) { if (is_bl) *is_bl = (insn >> 31) & 0x1; - *offset = extract_bitfield(insn, 26, 0) << 2; + *offset = extract_bitfield(insn, 26, 0) * 4; return true; } return false; @@ -183,7 +208,7 @@ namespace patterns { if (decode_masked_match(insn, 0b0001'1111'0000'0000'0000'0000'0000'0000, 0b0001'0000'0000'0000'0000'0000'0000'0000)) { if (rd) *rd = insn & 0x1f; uint32_t immlo = (insn >> 29) & 0x3; - int32_t immhi = extract_bitfield(insn, 19, 5) << 2; + int32_t immhi = extract_bitfield(insn, 19, 5) * 4; auto adrp = (insn >> 31) & 0x1; if (is_adrp) *is_adrp = adrp; // Back to signed (immlo is unsigned) before widening, and a 64-bit multiply: ADRP reaches +-4GB @@ -357,12 +382,12 @@ namespace patterns { if (hi == '?' && lo != '?' && !detail::is_hex_digit(lo)) lo = '?'; emit(n, uint8_t(nibble(hi) << 4 | nibble(lo)), uint8_t((hi == '?' ? 0 : 0xF0) | (lo == '?' ? 0 : 0x0F))); }, - [&](size_t n, const char* len_text) { + [&](size_t n, const char* len_text, size_t len_chars) { offset_ = static_cast(n); - if (len_text && arch_ != arch::arm64) - insn_len_ = get_inst_len_opt(len_text); + if (len_chars && arch_ != arch::arm64) + insn_len_ = get_inst_len_opt(len_text, len_chars); }, - [&](const char* flags) { handle_options(flags); }); + [&](const char* flags, size_t n) { handle_options(flags, n); }); } // First match of pat/msk in [bytes, bytes + size). Two anchor bytes are checked first // (16 positions at a time with SIMD), and only positions passing both get a full compare. @@ -454,18 +479,19 @@ namespace patterns { static constexpr uint8_t nibble(char c) { if (c == '?') return 0; if (!detail::is_hex_digit(c)) - throw std::logic_error("Invalid hex byte in pattern!"); + PATTERNS_FAIL("Invalid hex byte in pattern!"); return static_cast(detail::get_bits(c)); } - constexpr void handle_options(const char* ptr) { + // The flags in ptr, up to n characters, a terminator or the end of the line (an arm64 ';' comment ends it too) + constexpr void handle_options(const char* ptr, size_t n = SIZE_MAX) { const bool arm64 = arch_ == arch::arm64; - while (*ptr) { + for (; n && *ptr && *ptr != '\n' && !(arm64 && *ptr == ';'); --n) { if (*ptr == 'd') { - if (rel_) throw std::logic_error("Cannot use relative and deref together!"); + if (rel_) PATTERNS_FAIL("Cannot use relative and deref together!"); deref_ = true; } else if (*ptr == 'r' && !arm64) { - if (deref_) throw std::logic_error("Cannot use relative and deref together!"); + if (deref_) PATTERNS_FAIL("Cannot use relative and deref together!"); rel_ = true; } else if (*ptr == 'a') @@ -473,7 +499,7 @@ namespace patterns { // Check the next character to see what size we're reading at this relative address else if (!arm64 && *ptr > '0' && (arch_ == arch::x64 ? *ptr < '9' : *ptr < '5')) { size_ = *ptr - '0'; - if ((size_ & (size_ - 1)) != 0) throw std::logic_error("Size is not a valid data type size!"); + if ((size_ & (size_ - 1)) != 0) PATTERNS_FAIL("Size is not a valid data type size!"); } ++ptr; } @@ -542,8 +568,9 @@ namespace patterns { if (is_value) *is_value = true; return reinterpret_cast((sf ? offset : static_cast(offset))); } - else - throw std::logic_error("Failed to decode instruction with defined functions"); + // Not an instruction /d resolves: no result, as for no match. find() runs where an exception can't + // (TLS callbacks, DllMain, kernel code) + return nullptr; } else if (arch_ != arch::arm64 && (deref_ || rel_)) { const auto relative_address = relative_value(address + offset_); @@ -584,10 +611,14 @@ namespace patterns { // Should never hit here return 0; } - constexpr int32_t get_inst_len_opt(const char* ptr) const { - if (*ptr > '9' || *ptr < '0') - throw std::logic_error("Invalid data for calculating remaining instruction size!"); - return detail::stoi(ptr); + // The digits in the first n characters at ptr + constexpr uint32_t get_inst_len_opt(const char* ptr, size_t n) const { + if (!detail::is_digit(*ptr)) + PATTERNS_FAIL("Invalid data for calculating remaining instruction size!"); + uint32_t value = 0; + for (size_t k = 0; k < n && detail::is_digit(ptr[k]); ++k) + value = value * 10 + static_cast(ptr[k] - '0'); + return value; } }; diff --git a/include/PatternScanner/RuntimeArmPattern.hpp b/include/PatternScanner/RuntimeArmPattern.hpp index c7650f1..e006701 100644 --- a/include/PatternScanner/RuntimeArmPattern.hpp +++ b/include/PatternScanner/RuntimeArmPattern.hpp @@ -18,7 +18,14 @@ namespace patterns { const std::string s(text, len); const auto count = detail::parse_arm(s.c_str(), len, [&](size_t, const char* instruction) { +#ifdef PATTERNS_EXCEPTIONS + detail::ArmOpcode encoded{ 0, 0 }; + // Name the line: a pattern is often many + try { encoded = detail::a64_encode_instruction(instruction); } + catch (const std::logic_error& e) { throw std::logic_error(std::string(e.what()) + " (" + instruction + ")"); } +#else const auto encoded = detail::a64_encode_instruction(instruction); +#endif m_instructions.push_back(encoded.pattern); m_masks.push_back(encoded.mask); }, diff --git a/plugins/ida/search.hpp b/plugins/ida/search.hpp index 14da5a6..f8a7b6d 100644 --- a/plugins/ida/search.hpp +++ b/plugins/ida/search.hpp @@ -86,14 +86,14 @@ namespace pscan { hit_t h; h.match = lo + (m - buf); if (h.match >= end) return true; - try { - const auto r = reinterpret_cast(p.resolve(m, run_end, &h.is_value)); + const auto r = reinterpret_cast(p.resolve(m, run_end, &h.is_value)); + // nullptr (not a value): /d at an instruction it can't decode + if (r || h.is_value) { // Addresses come back in buffer terms; the arithmetic wraps, so targets outside it work too h.result = h.is_value ? ea_t(r) : ea_t(lo + (r - reinterpret_cast(buf))); if (!inf_is_64bit()) h.result &= 0xFFFFFFFF; h.resolved = true; } - catch (const std::logic_error&) {} if (!cb(h)) return true; cur = m + 1; } diff --git a/plugins/ida/test/test_search.cpp b/plugins/ida/test/test_search.cpp index 1d76fee..d8e9807 100644 --- a/plugins/ida/test/test_search.cpp +++ b/plugins/ida/test/test_search.cpp @@ -103,6 +103,9 @@ static void arm64(const char* path) { CHECK(pscan::unsupported_lines(prologue).empty()); const auto fn = all(prologue, pscan::arm64_pattern); CHECK(fn.size() == 1 && fn[0].match == sym("wanted")); + // /d on an instruction it can't decode (STP): a hit, but unresolved + const auto stp = next("X STP X20, X19, [SP,#?]!\n/d", pscan::arm64_pattern, 0, false); + CHECK(stp.match == sym("wanted") && !stp.resolved); // Unknown instructions are reported (they'd match anything); a deliberate "?" line isn't CHECK(pscan::unsupported_lines("NOP\nFOO X1, X2\n ? \nstp x29, x30, [sp,#?]\nBAR\n/d") == "FOO X1, X2\nBAR\n"); @@ -110,6 +113,11 @@ static void arm64(const char* path) { bool threw = false; try { pscan::parse("48 8G", pscan::bytes_pattern); } catch (const std::logic_error&) { threw = true; } CHECK(threw); + // A bad operand is an error naming its line, not a wildcard + threw = false; + try { pscan::parse("NOP\nADD X0, X1, X32", pscan::arm64_pattern); } + catch (const std::logic_error& e) { threw = strstr(e.what(), "(ADD X0, X1, X32)") != nullptr; } + CHECK(threw); close_database(false); puts(" arm64 ok"); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 348b9db..9bad63e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -33,7 +33,28 @@ endforeach() add_test(NAME test_patterns COMMAND test_patterns) add_test(NAME test_patterns_nosimd COMMAND test_patterns_nosimd) add_test(NAME test_cpp17 COMMAND test_cpp17) +if(NOT MSVC) + # The headers build without exceptions (kernel code) + add_executable(test_cpp17_noexcept test_cpp17.cpp) + set_target_properties(test_cpp17_noexcept PROPERTIES CXX_STANDARD 17) + target_compile_options(test_cpp17_noexcept PRIVATE -fno-exceptions) + add_test(NAME test_cpp17_noexcept COMMAND test_cpp17_noexcept) +endif() add_test(NAME test_arm COMMAND test_arm) +# Compile-time arm64 patterns: a bad operand must be a compile error (the bad build fails), and the encoder must stay +# out of the binary even unoptimized +foreach(std 17 20) + set(t test_arm_compile_time_cpp${std}) + add_executable(${t} test_arm_compile_time.cpp) + add_executable(${t}_bad EXCLUDE_FROM_ALL test_arm_compile_time.cpp) + set_target_properties(${t} ${t}_bad PROPERTIES CXX_STANDARD ${std}) + target_compile_options(${t} PRIVATE $,/Od,-O0>) + target_compile_definitions(${t}_bad PRIVATE BAD_OPERAND) + add_test(NAME ${t} COMMAND ${t}) + add_test(NAME ${t}_bad COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target ${t}_bad --config $) + # Both builds run in this build directory: one at a time (two ninjas there at once can fail, which would pass) + set_tests_properties(${t}_bad PROPERTIES WILL_FAIL TRUE RESOURCE_LOCK build_dir) +endforeach() add_test(NAME example_usage COMMAND example_usage) if(WIN32) add_executable(test_tls test_tls.cpp) diff --git a/tests/test_arm.cpp b/tests/test_arm.cpp index 843b45a..f75972d 100644 --- a/tests/test_arm.cpp +++ b/tests/test_arm.cpp @@ -131,6 +131,11 @@ X("TST X0, X1", 0xEA01001F) \ X("TST X0, #0x8000000000000000", 0xF241001F) \ X("AND X0, X1, #0xFF", 0x92401C20) \ + X("AND W0, W1, #-0x1000", 0x12144C20) \ + X("BIC X0, X1, #0xFF", 0x9278DC20) \ + X("BICS W2, W3, #0xF0", 0x72186C62) \ + X("ORN X4, X5, #0xFFFF0000FFFF0000", 0xB2003CA4) \ + X("EON W6, W7, #1", 0x521F78E6) \ X("AND W0, W1, W2", 0x0A020020) \ X("ANDS X0, X1, X2, LSR#3", 0xEA420C20) \ X("EOR W0, W1, #0x80000000", 0x52010020) \ @@ -682,6 +687,7 @@ 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("BIC X?, X?, #?", "AND X0, X1, #0xFFFFFFFF00000000", 0x92607C20, 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) \ @@ -722,6 +728,17 @@ 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("ADD X?, X?, #0x123, LSL #?", "ADD X0, X1, #0x123, LSL #12", 0x91448C20, 1) \ + X("ADD X?, X?, #0x123, LSL #?", "ADD X0, X1, #0x123", 0x91048C20, 1) \ + X("SUB ?, ?, #0x123, LSL #?", "SUB W2, W3, #0x123, LSL #12", 0x51448C62, 1) \ + X("TST ?, #0x3FFFC0", "TST X0, #0x3FFFC0", 0xF27A3C1F, 1) \ + X("TST ?, #0x3FFFC0", "TST W0, #0x3FFFC0", 0x721A3C1F, 1) \ + X("MOV ?, #0xFFFFFFFF00000000", "MOV X0, #0xFFFFFFFF00000000", 0xB2607FE0, 1) \ + X("MOV ?, #0xFFFFFFFF00000000", "MOV X0, #0", 0xD2800000, 0) \ + X("MOV ?, #0x1FFFE00", "MOV X1, #0x1FFFE00", 0xB2773FE1, 1) \ + X("MOV ?, #0x1FFFE00", "MOV W1, #0x1FFFE00", 0x32173FE1, 1) \ + X("MOV ?, #0xFFFFFFFF", "MOV W0, #-1", 0x12800000, 1) \ + X("MOV ?, #0xFFFFFFFF", "MOV X0, #0xFFFFFFFF", 0xB2407FE0, 1) \ X("FMOV D0, #1.0", "FMOV D0, #-1.0", 0x1E7E1000, 0) struct Word { uint32_t pat, mask; }; @@ -756,47 +773,54 @@ static void wildcards() { printf(" wildcard matching ok (%d)\n", n); } -// 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 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); +// A bad operand (out of range, the wrong register, missing or extra) is an error: a compile error for ArmPattern +// (tests/CMakeLists.txt checks one), a throw for RuntimeArmPattern. Only an unknown mnemonic becomes a whole-word +// wildcard (mask 0), which the IDA plugin warns about +static void bad_operands() { + static const char* const bad[] = { + // Registers: out of range, not one, the wrong kind or size, SP where 31 is the zero register and back + "ADD X0, X1, X32", "ADD X0, X1, X31", "ADD X0, X1, FOO", "ADD X0, W1, X2", "ADD XZR, X1, #1", "ADDS SP, X1, #1", + "ADD X0, XZR, X1, UXTX", "ORR SP, X1, X2", "ORR X0, SP, #1", "MOV SP, XZR", "MOV X0, W1", "LDR SP, [X1]", + "LDR X0, [XZR]", "LDR X0, [W1]", "LDR X0, [X1, W2]", "LDR X0, [X1, X2, UXTW]", "LDRB X0, [X1]", "LDRSW W0, [X1]", + "LDRB D0, [X1]", "LDRSB D0, [X1]", "LDRAA W0, [X1]", "LDR B0, #0x10", "STP X0, W1, [SP]", "STP X0, D1, [SP]", + "LDP B0, B1, [X2]", "LDPSW W0, W1, [X2]", "ADR W0, #8", "BR W0", "PACIA X0, XZR", "CSEL X0, X1, W2, EQ", + "CCMP X0, W1, #0, EQ", "SXTB X0, X1", "SXTW W0, W1", "UXTB X0, W1", "SMADDL X0, X1, W2, X3", + "STXR X0, X1, [X2]", "LDADD W0, X1, [X2]", "LDADDB X0, X1, [X2]", "SWP W0, W1, [XZR]", "FMOV D0, SP", + "FMOV S0, X1", "FMOV D0, W1", "FMOV D0, S1", + // Immediates: not a number, out of range, unaligned + "BL _printf", "B #2", "B #0x8000000", "B.EQ #1", "CBZ X0, #0x100000", "TBZ X0, #3, #0x8000", "LDR X0, #3", + "ADR X0, #0x100000", "ADRP X0, #0x100000000", "ADD X0, X1, #0x1001", "ADD X0, X1, #1, LSL #3", + "ORR X0, X1, #0", "BIC X0, X1, #-1", "MOV X0, #0x123456789", "MOVZ X0, #0x10000", "MOVZ W0, #1, LSL #32", + "MOVZ X0, #1, LSL #8", "LDR W0, [X1,#0x4000]", "LDUR X0, [X1,#0x100]", "LDR X0, [X1, X2, LSL #2]", + "STP X0, X1, [SP, #4]", "STP X0, X1, [SP, #0x200]", "LDRAA X0, [X1,#4]", "LDRAA X0, [X1,#4096]", + "TBZ W0, #32, #8", "TBZ X0, #64, #8", "TBZ X0, #4294967299, #8", "SVC #0x10000", "SVC #-1", + "CCMP X0, #32, #0, EQ", "CCMP X0, #1, #16, EQ", "LSL X0, X1, #64", "LSL W0, W1, #32", "UBFX W0, W1, #30, #4", + "FMOV D0, #0.1", "FMOV D0, #32.0", "FMOV D0, #-0.0", "FMOV Q0, #1.0", + "ADRP X0, #0x1234", "ADD X0, X1, #0xFFFFFFFFFFFFFFFF", "LDR X0, [X1, #0xFFFFFFFFFFFFFFF8]", + "STP X0, X1, [SP, #0x8000000000000000]", "MOV X0, #-0xFFFFFFFFFFFFFFFF", + // Shifts, extends and conditions + "ADD X0, X1, X2, ROR #3", "ADD X0, X1, X2, LSL", "CMP SP, X5, LSL", "LDR X0, [X1, X2, ROR #3]", "B.XX #0x10", + "CSET W0, AL", "CINC X0, X1, NV", "CSEL X0, X1, X2, XX", "BTI X", + // Missing or extra operands, brackets + "ADD X0, X1", "ADD X0, X1, X2, X3", "MOV X0", "LDR X0, X1", "LDR X0, [X1, #8", "LDR X0, [X1, #8] junk", + "LDUR X0, [X1, #8]!", "LDP X0, X1, [SP]!", "LDNP X0, X1, [X2,#16]!", "STP X0, X1", "BR", "RET X0, X1", + "NOP X0", "SVC", "CSEL X0, X1, X2", "LSL X0, X1", "UBFX X0, X1, #3", "MADD X0, X1, X2", "LDXR X0, X1", + "LDXR X0, [X1", + }; + int n = 0; + for (const char* text : bad) { + bool threw = false; + try { patterns::RuntimeArmPattern p(text); } catch (const std::logic_error&) { threw = true; } + if (!threw) { printf("FAIL %s: accepted\n", text); exit(1); } + ++n; + } + // The error names the line + try { patterns::RuntimeArmPattern p("NOP\nADD X0, X1, X32"); } catch (const std::logic_error& e) { CHECK(strstr(e.what(), "(ADD X0, X1, X32)")); } + CHECK(word(ARM_PATTERN("FOO X0, X1")).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"); + printf(" bad operands ok (%d)\n", n); } template static bool same(const A& a, const B& b) { @@ -816,6 +840,11 @@ static void line_formats() { CHECK(same(ARM_PATTERN("ADD X0, X1, #1, LSL#12"), ARM_PATTERN("ADD X0, X1, #1, LSL #12"))); CHECK(same(ARM_PATTERN("BL #?"), ARM_PATTERN("BL ?"))); CHECK(same(ARM_PATTERN("NOP\nRET"), ARM_PATTERN("NOP\nRET\n"))); + CHECK(same(ARM_PATTERN("NOP\nRET"), ARM_PATTERN("NOP ; comment\nRET ; as IDA and lldb print them"))); + // A '/' in a comment isn't the flags line: "add" mustn't read as /a /d + CHECK(same(ARM_PATTERN("NOP\nadd x0, x1, x2"), ARM_PATTERN("NOP ; a/b\nadd x0, x1, x2"))); + CHECK(same(ARM_PATTERN("NOP\nRET\n/d"), patterns::RuntimeArmPattern("NOP ; a/b\nRET\n /d"))); + CHECK(same(ARM_PATTERN("NOP\nRET\n/a"), ARM_PATTERN("NOP\nRET\n/a ; done"))); // a comment's 'd' isn't /d CHECK(ARM_PATTERN("NOP\nRET\n/a").length() == 8); puts(" line formats ok"); } @@ -956,6 +985,9 @@ static void deref_synthetic() { const auto pg = (reinterpret_cast(&code[1]) & ~uintptr_t(0xFFF)) + 0x5000; CHECK(adrl.find(b, sizeof code) == pg + f.add); } + // An instruction /d can't resolve (STP X29, X30, [SP,#-0x10]!): no result, not an exception + alignas(4) const uint32_t stp[] = { 0xA9BF7BFD }; + CHECK(patterns::RuntimeArmPattern("X ?\n/d").find(reinterpret_cast(stp), 4) == nullptr); puts(" /d on synthetic code ok"); } @@ -1019,7 +1051,7 @@ static void deref_relocated() { int main() { concrete(); wildcards(); - unsupported(); + bad_operands(); line_formats(); prologue(); runtime_patterns(); diff --git a/tests/test_arm_compile_time.cpp b/tests/test_arm_compile_time.cpp new file mode 100644 index 0000000..5d563e6 --- /dev/null +++ b/tests/test_arm_compile_time.cpp @@ -0,0 +1,38 @@ +// Compile-time arm64 patterns. A bad operand is a compile error: tests/CMakeLists.txt also builds this with BAD_OPERAND, +// which must fail. And the encoder stays out of the binary: no parser, error message or mnemonic table, checked at -O0 +// (where GCC emits unreferenced namespace-scope constants). Exit code 0 = pass +#include +#include +#include +#include +#include + +#ifdef BAD_OPERAND +#define LINE "ADD X0, X1, X32" +#else +#define LINE "ADD X0, X1, X30" +#endif + +int main(int, char** argv) { +#ifdef PATTERNS_LITERALS + constexpr auto p = LINE ""_armpattern; +#else + constexpr auto p = ARM_PATTERN(LINE); +#endif + FILE* f = fopen(argv[0], "rb"); + if (!f) { printf("FAIL: can't open %s\n", argv[0]); return 1; } + std::vector bin; + for (int c; (c = fgetc(f)) != EOF;) bin.push_back(static_cast(c)); + fclose(f); + // Reversed and turned around at runtime (through volatile, so the optimizer can't), to keep them out of this binary + for (const char* reversed : { "!retsiger dilavnI", "LDDAMS" }) { + const volatile char* v = reversed; + std::string needle; + for (size_t i = 0; v[i]; ++i) needle.insert(needle.begin(), v[i]); + if (std::search(bin.begin(), bin.end(), needle.begin(), needle.end()) != bin.end()) { + printf("FAIL: \"%s\" in the binary\n", needle.c_str()); + return 1; + } + } + return p.length() == 4 ? 0 : 1; +} diff --git a/tests/test_cpp17.cpp b/tests/test_cpp17.cpp index ec073dd..41d4eab 100644 --- a/tests/test_cpp17.cpp +++ b/tests/test_cpp17.cpp @@ -17,6 +17,9 @@ int main() { auto arm = ARM_PATTERN("NOP\nRET"); auto arm_xor = ARM_XOR_PATTERN("NOP\nRET"); patterns::RuntimeArmPattern arm_rt("NOP\nRET"); + // Negative PC-relative offsets in a constant expression (no left shift of a negative value) + constexpr auto adr = ARM_PATTERN("ADR X3, #-0x4\nADRP X1, #-0x2000"); + const auto adr_words = reinterpret_cast(adr.pattern()); const bool ok = ct.find(buf, 8) == buf + 2 && x1.find(buf, 8) == buf + 2 && x2.find(buf, 8) == buf + 2 @@ -24,6 +27,7 @@ int main() { && arm.find(code_bytes, 8) == code_bytes && arm_xor.find(code_bytes, 8) == code_bytes && arm_rt.find(code_bytes, 8) == code_bytes + && adr_words[0] == 0x10FFFFE3 && adr_words[1] == 0xD0FFFFE1 && memcmp(x1.pattern(), x2.pattern(), 5) != 0; // per-definition keys puts(ok ? "c++17 macros ok" : "FAIL c++17 macros"); return !ok; diff --git a/tests/test_patterns.cpp b/tests/test_patterns.cpp index 4837b67..b35629d 100644 --- a/tests/test_patterns.cpp +++ b/tests/test_patterns.cpp @@ -91,6 +91,12 @@ static void edge_cases() { threw = false; try { patterns::RuntimePattern("E8 X4 ? ? ? ? /r8", patterns::arch::x86); } catch (const std::logic_error&) { threw = true; } CHECK(!threw); // 8 isn't a size on x86, so it's ignored like any other unknown flag + // X is the bytes left in the instruction, /d1 the operand size: jb short (72 rel8), as in the README + const uint8_t jb[] = { 0xBA, 0xBE, 0xCA, 0xFE, 0x72, 0x05, 0x11, 0x22 }; + CHECK(patterns::RuntimePattern("BA BE CA FE 72 X1 ? 11 22 /d1", patterns::arch::x64).find(jb, 8) == jb + 6 + 5); + // Flags are read only up to the given length: the "1" past it isn't a size + const patterns::RuntimePattern bounded("E8 X4 ? ? ? ? /d1", 16, patterns::arch::x64); + CHECK(bounded.find(code, 16) == code + 6 + 0x10); puts(" edge cases ok"); }