isa.c (44678B)
1 /* x86_64 instruction descriptor table + operand print/decode dispatch. 2 * 3 * The table mirrors every encoding `src/arch/x64/emit.c` produces, plus a 4 * handful that show up via direct byte writes in arch/x64/{alloc,link,ops}.c 5 * (CALL/JMP rel32, PUSH/POP r64, multi-byte NOP, atomic prefixes). Each 6 * row pins down (leg_pfx, opcode bytes, /digit) so the disassembler can 7 * identify a raw byte stream with one linear pass and then dispatch on 8 * the format to render operands. 9 * 10 * Row ordering: first-match wins. Aliases (rows with X64_ASMFL_ALIAS) 11 * sit BEFORE the canonical row they alias so the disassembler prefers 12 * the alias spelling on output. We keep aliases narrow today (e.g., 13 * SSE-prefixed forms naturally precede their no-prefix neighbours) — we 14 * can add `xor %eax,%eax` zeroing-idiom aliases later if disasm output 15 * needs them. */ 16 17 #include "arch/x64/isa.h" 18 19 #include <stddef.h> 20 #include <string.h> 21 22 #include "core/bytes.h" 23 24 /* ==================================================================== 25 * Table. Mnemonics are AT&T-style, lower-case, no size suffix; the 26 * printer derives the size letter (b/w/l/q) from the fmt + REX.W where 27 * appropriate. 28 * ==================================================================== */ 29 30 #define ROW(mn, lp, ol, b0, b1, b2, lm, mr, wr, f, fl) \ 31 {{{(mn)}, sizeof(mn) - 1}, lp, ol, {b0, b1, b2}, lm, mr, wr, f, fl} 32 #define NO_MODRM 0xFFu 33 34 const X64InsnDesc x64_insn_table[] = { 35 /* ---- single-byte nullary ---- */ 36 ROW("nop", X64_PFX_NONE, 1, 0x90, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 37 X64_FMT_NULLARY, 0), 38 ROW("ret", X64_PFX_NONE, 1, 0xC3, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 39 X64_FMT_NULLARY, 0), 40 ROW("leave", X64_PFX_NONE, 1, 0xC9, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 41 X64_FMT_NULLARY, 0), 42 ROW("cltd", X64_PFX_NONE, 1, 0x99, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_0, 43 X64_FMT_NULLARY, 0), 44 ROW("cqto", X64_PFX_NONE, 1, 0x99, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_1, 45 X64_FMT_NULLARY, 0), 46 47 /* ---- two-byte UD2 ---- */ 48 ROW("ud2", X64_PFX_NONE, 2, 0x0F, 0x0B, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 49 X64_FMT_NULLARY, 0), 50 /* ---- SYSCALL (0F 05): fast system call ---- */ 51 ROW("syscall", X64_PFX_NONE, 2, 0x0F, 0x05, 0, 0xFF, NO_MODRM, 52 X64_W_REQ_ANY, X64_FMT_NULLARY, 0), 53 /* ---- RDTSC (0F 31): read timestamp counter into edx:eax ---- */ 54 ROW("rdtsc", X64_PFX_NONE, 2, 0x0F, 0x31, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 55 X64_FMT_NULLARY, 0), 56 ROW("mfence", X64_PFX_NONE, 3, 0x0F, 0xAE, 0xF0, 0xFF, NO_MODRM, 57 X64_W_REQ_ANY, X64_FMT_NULLARY, 0), 58 59 /* ---- multi-byte NOP: 66 0F 1F /0 ---- 60 * Matches the 6-byte canonical "NOPW 0(%rax,%rax,1)" kit emits to pad 61 * the IPLT stub. The mod/rm bytes (and any disp) are consumed by the 62 * NOP_MULTI printer. */ 63 ROW("nopw", X64_PFX_66, 2, 0x0F, 0x1F, 0, 0xFF, 0, X64_W_REQ_ANY, 64 X64_FMT_NOP_MULTI, 0), 65 ROW("nopl", X64_PFX_NONE, 2, 0x0F, 0x1F, 0, 0xFF, 0, X64_W_REQ_ANY, 66 X64_FMT_NOP_MULTI, 0), 67 68 /* ---- PUSH/POP r64 (embed-reg in low 3 bits) ---- */ 69 ROW("push", X64_PFX_NONE, 1, 0x50, 0, 0, 0xF8, NO_MODRM, X64_W_REQ_ANY, 70 X64_FMT_PUSH_POP, X64_ASMFL_FORCE_W64), 71 ROW("pop", X64_PFX_NONE, 1, 0x58, 0, 0, 0xF8, NO_MODRM, X64_W_REQ_ANY, 72 X64_FMT_PUSH_POP, X64_ASMFL_FORCE_W64), 73 74 /* ---- MOV r, imm — B8+rd; width via REX.W ---- 75 * imm32 form (no REX.W) and imm64 movabs form (REX.W=1) share the 76 * same row; the printer reads ctx->rex_w to pick the imm width. */ 77 ROW("mov", X64_PFX_NONE, 1, 0xB8, 0, 0, 0xF8, NO_MODRM, X64_W_REQ_ANY, 78 X64_FMT_MOV_RI, X64_ASMFL_W_FROM_REX), 79 80 /* ---- ALU r/m, r — opcode picks op ---- */ 81 ROW("add", X64_PFX_NONE, 1, 0x01, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 82 X64_FMT_ALU_RR, X64_ASMFL_W_FROM_REX), 83 ROW("or", X64_PFX_NONE, 1, 0x09, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 84 X64_FMT_ALU_RR, X64_ASMFL_W_FROM_REX), 85 ROW("and", X64_PFX_NONE, 1, 0x21, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 86 X64_FMT_ALU_RR, X64_ASMFL_W_FROM_REX), 87 ROW("sub", X64_PFX_NONE, 1, 0x29, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 88 X64_FMT_ALU_RR, X64_ASMFL_W_FROM_REX), 89 ROW("xor", X64_PFX_NONE, 1, 0x31, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 90 X64_FMT_ALU_RR, X64_ASMFL_W_FROM_REX), 91 ROW("cmp", X64_PFX_NONE, 1, 0x39, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 92 X64_FMT_ALU_RR, X64_ASMFL_W_FROM_REX), 93 ROW("test", X64_PFX_NONE, 1, 0x85, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 94 X64_FMT_ALU_RR, X64_ASMFL_W_FROM_REX), 95 ROW("mov", X64_PFX_NONE, 1, 0x89, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 96 X64_FMT_ALU_RR, X64_ASMFL_W_FROM_REX), 97 /* Byte form: MOV r/m8, r8 — opcode 88 forces 1-byte operands. */ 98 ROW("mov", X64_PFX_NONE, 1, 0x88, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 99 X64_FMT_ALU_RR, X64_ASMFL_BYTE), 100 /* 16-bit form: 0x66 prefix forces 2-byte operands. */ 101 ROW("mov", X64_PFX_66, 1, 0x89, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 102 X64_FMT_ALU_RR, X64_ASMFL_W16), 103 104 /* ---- MOV r, r/m (load and reg-reg share opcode 8B) ---- 105 * 8B /r matches both r,r and r,[base+disp]; the printer dispatches on 106 * ModR/M.mod. LEA is 8D /r — register-only ModR/M.mod=11 is illegal, 107 * so we use a separate row keyed on the opcode. */ 108 ROW("mov", X64_PFX_NONE, 1, 0x8B, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 109 X64_FMT_MOV_RM_LOAD, X64_ASMFL_W_FROM_REX), 110 /* 16-bit r←r/m via 0x66 prefix. */ 111 ROW("mov", X64_PFX_66, 1, 0x8B, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 112 X64_FMT_MOV_RM_LOAD, X64_ASMFL_W16), 113 ROW("lea", X64_PFX_NONE, 1, 0x8D, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 114 X64_FMT_MOV_RM_LOAD, X64_ASMFL_W_FROM_REX), 115 116 /* ---- MOVZX / MOVSX r{32,64}, r/m{8,16} ---- 117 * The destination width is the *l* (32-bit) form without REX.W and the *q* 118 * (64-bit) form with it; split by W so the disassembler emits a mnemonic 119 * whose size letter matches the printed register width (clang rejects a 120 * `movsbl` with a 64-bit destination). Same opcodes; W disambiguates, 121 * exactly like cltd/cqto (0x99). */ 122 ROW("movzbl", X64_PFX_NONE, 2, 0x0F, 0xB6, 0, 0xFF, NO_MODRM, X64_W_REQ_0, 123 X64_FMT_MOVZX_MOVSX, 0), 124 ROW("movzbq", X64_PFX_NONE, 2, 0x0F, 0xB6, 0, 0xFF, NO_MODRM, X64_W_REQ_1, 125 X64_FMT_MOVZX_MOVSX, 0), 126 ROW("movzwl", X64_PFX_NONE, 2, 0x0F, 0xB7, 0, 0xFF, NO_MODRM, X64_W_REQ_0, 127 X64_FMT_MOVZX_MOVSX, 0), 128 ROW("movzwq", X64_PFX_NONE, 2, 0x0F, 0xB7, 0, 0xFF, NO_MODRM, X64_W_REQ_1, 129 X64_FMT_MOVZX_MOVSX, 0), 130 ROW("movsbl", X64_PFX_NONE, 2, 0x0F, 0xBE, 0, 0xFF, NO_MODRM, X64_W_REQ_0, 131 X64_FMT_MOVZX_MOVSX, 0), 132 ROW("movsbq", X64_PFX_NONE, 2, 0x0F, 0xBE, 0, 0xFF, NO_MODRM, X64_W_REQ_1, 133 X64_FMT_MOVZX_MOVSX, 0), 134 ROW("movswl", X64_PFX_NONE, 2, 0x0F, 0xBF, 0, 0xFF, NO_MODRM, X64_W_REQ_0, 135 X64_FMT_MOVZX_MOVSX, 0), 136 ROW("movswq", X64_PFX_NONE, 2, 0x0F, 0xBF, 0, 0xFF, NO_MODRM, X64_W_REQ_1, 137 X64_FMT_MOVZX_MOVSX, 0), 138 139 /* ---- MOVSXD r64, r/m32 ---- */ 140 ROW("movslq", X64_PFX_NONE, 1, 0x63, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_1, 141 X64_FMT_MOVSXD, 0), 142 143 /* ---- ALU r/m, imm — /digit picks operation ---- 144 * 83 (imm8 sign-extended), 81 (imm32 sign-extended). One row per 145 * (opcode, /digit) pair. 146 * /0 ADD /1 OR /4 AND /5 SUB /6 XOR /7 CMP 147 * (/2 ADC and /3 SBB are also valid in the Intel manual but kit 148 * doesn't emit them; they can land later as additional rows.) */ 149 ROW("add", X64_PFX_NONE, 1, 0x83, 0, 0, 0xFF, 0, X64_W_REQ_ANY, 150 X64_FMT_ALU_RM_IMM8, X64_ASMFL_W_FROM_REX), 151 ROW("or", X64_PFX_NONE, 1, 0x83, 0, 0, 0xFF, 1, X64_W_REQ_ANY, 152 X64_FMT_ALU_RM_IMM8, X64_ASMFL_W_FROM_REX), 153 ROW("and", X64_PFX_NONE, 1, 0x83, 0, 0, 0xFF, 4, X64_W_REQ_ANY, 154 X64_FMT_ALU_RM_IMM8, X64_ASMFL_W_FROM_REX), 155 ROW("sub", X64_PFX_NONE, 1, 0x83, 0, 0, 0xFF, 5, X64_W_REQ_ANY, 156 X64_FMT_ALU_RM_IMM8, X64_ASMFL_W_FROM_REX), 157 ROW("xor", X64_PFX_NONE, 1, 0x83, 0, 0, 0xFF, 6, X64_W_REQ_ANY, 158 X64_FMT_ALU_RM_IMM8, X64_ASMFL_W_FROM_REX), 159 ROW("cmp", X64_PFX_NONE, 1, 0x83, 0, 0, 0xFF, 7, X64_W_REQ_ANY, 160 X64_FMT_ALU_RM_IMM8, X64_ASMFL_W_FROM_REX), 161 ROW("add", X64_PFX_NONE, 1, 0x81, 0, 0, 0xFF, 0, X64_W_REQ_ANY, 162 X64_FMT_ALU_RM_IMM32, X64_ASMFL_W_FROM_REX), 163 ROW("or", X64_PFX_NONE, 1, 0x81, 0, 0, 0xFF, 1, X64_W_REQ_ANY, 164 X64_FMT_ALU_RM_IMM32, X64_ASMFL_W_FROM_REX), 165 ROW("and", X64_PFX_NONE, 1, 0x81, 0, 0, 0xFF, 4, X64_W_REQ_ANY, 166 X64_FMT_ALU_RM_IMM32, X64_ASMFL_W_FROM_REX), 167 ROW("sub", X64_PFX_NONE, 1, 0x81, 0, 0, 0xFF, 5, X64_W_REQ_ANY, 168 X64_FMT_ALU_RM_IMM32, X64_ASMFL_W_FROM_REX), 169 ROW("xor", X64_PFX_NONE, 1, 0x81, 0, 0, 0xFF, 6, X64_W_REQ_ANY, 170 X64_FMT_ALU_RM_IMM32, X64_ASMFL_W_FROM_REX), 171 ROW("cmp", X64_PFX_NONE, 1, 0x81, 0, 0, 0xFF, 7, X64_W_REQ_ANY, 172 X64_FMT_ALU_RM_IMM32, X64_ASMFL_W_FROM_REX), 173 174 /* ---- IMUL r, r/m (0F AF) / IMUL r, r/m, imm (69 / 6B) ---- */ 175 ROW("imul", X64_PFX_NONE, 2, 0x0F, 0xAF, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 176 X64_FMT_IMUL_RR, X64_ASMFL_W_FROM_REX), 177 ROW("imul", X64_PFX_NONE, 1, 0x6B, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 178 X64_FMT_IMUL_RRI, X64_ASMFL_W_FROM_REX), 179 ROW("imul", X64_PFX_NONE, 1, 0x69, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 180 X64_FMT_IMUL_RRI, X64_ASMFL_W_FROM_REX | 0x80u /* imm32 */), 181 182 /* ---- F7 /sub family (no immediate read except for /0 /1 which we 183 * don't emit) ---- */ 184 ROW("not", X64_PFX_NONE, 1, 0xF7, 0, 0, 0xFF, 2, X64_W_REQ_ANY, 185 X64_FMT_F7_RM, X64_ASMFL_W_FROM_REX), 186 ROW("neg", X64_PFX_NONE, 1, 0xF7, 0, 0, 0xFF, 3, X64_W_REQ_ANY, 187 X64_FMT_F7_RM, X64_ASMFL_W_FROM_REX), 188 ROW("mul", X64_PFX_NONE, 1, 0xF7, 0, 0, 0xFF, 4, X64_W_REQ_ANY, 189 X64_FMT_F7_RM, X64_ASMFL_W_FROM_REX), 190 ROW("imul", X64_PFX_NONE, 1, 0xF7, 0, 0, 0xFF, 5, X64_W_REQ_ANY, 191 X64_FMT_F7_RM, X64_ASMFL_W_FROM_REX), 192 ROW("div", X64_PFX_NONE, 1, 0xF7, 0, 0, 0xFF, 6, X64_W_REQ_ANY, 193 X64_FMT_F7_RM, X64_ASMFL_W_FROM_REX), 194 ROW("idiv", X64_PFX_NONE, 1, 0xF7, 0, 0, 0xFF, 7, X64_W_REQ_ANY, 195 X64_FMT_F7_RM, X64_ASMFL_W_FROM_REX), 196 197 /* ---- Shifts ---- */ 198 ROW("rol", X64_PFX_NONE, 1, 0xC1, 0, 0, 0xFF, 0, X64_W_REQ_ANY, 199 X64_FMT_SHIFT_IMM, X64_ASMFL_W_FROM_REX), 200 ROW("ror", X64_PFX_NONE, 1, 0xC1, 0, 0, 0xFF, 1, X64_W_REQ_ANY, 201 X64_FMT_SHIFT_IMM, X64_ASMFL_W_FROM_REX), 202 ROW("shl", X64_PFX_NONE, 1, 0xC1, 0, 0, 0xFF, 4, X64_W_REQ_ANY, 203 X64_FMT_SHIFT_IMM, X64_ASMFL_W_FROM_REX), 204 ROW("shr", X64_PFX_NONE, 1, 0xC1, 0, 0, 0xFF, 5, X64_W_REQ_ANY, 205 X64_FMT_SHIFT_IMM, X64_ASMFL_W_FROM_REX), 206 ROW("sar", X64_PFX_NONE, 1, 0xC1, 0, 0, 0xFF, 7, X64_W_REQ_ANY, 207 X64_FMT_SHIFT_IMM, X64_ASMFL_W_FROM_REX), 208 /* 16-bit ROL imm8 via 0x66 + C1 /0 — used by emit_rol16_imm8. */ 209 ROW("rol", X64_PFX_66, 1, 0xC1, 0, 0, 0xFF, 0, X64_W_REQ_ANY, 210 X64_FMT_SHIFT_IMM, X64_ASMFL_W16), 211 ROW("shl", X64_PFX_NONE, 1, 0xD3, 0, 0, 0xFF, 4, X64_W_REQ_ANY, 212 X64_FMT_SHIFT_CL, X64_ASMFL_W_FROM_REX), 213 ROW("shr", X64_PFX_NONE, 1, 0xD3, 0, 0, 0xFF, 5, X64_W_REQ_ANY, 214 X64_FMT_SHIFT_CL, X64_ASMFL_W_FROM_REX), 215 ROW("sar", X64_PFX_NONE, 1, 0xD3, 0, 0, 0xFF, 7, X64_W_REQ_ANY, 216 X64_FMT_SHIFT_CL, X64_ASMFL_W_FROM_REX), 217 218 /* ---- Branches ---- */ 219 /* Jcc near: 0F 80..8F rel32; condition in low 4 bits. The printer 220 * picks the mnemonic from a per-condition table. */ 221 ROW("j", X64_PFX_NONE, 2, 0x0F, 0x80, 0, 0xF0, NO_MODRM, X64_W_REQ_ANY, 222 X64_FMT_JCC_REL32, 0), 223 ROW("jmp", X64_PFX_NONE, 1, 0xE9, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 224 X64_FMT_JMP_REL32, 0), 225 ROW("callq", X64_PFX_NONE, 1, 0xE8, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 226 X64_FMT_CALL_REL32, 0), 227 /* Indirect jmp / call via FF /4 or /2. */ 228 ROW("callq", X64_PFX_NONE, 1, 0xFF, 0, 0, 0xFF, 2, X64_W_REQ_ANY, 229 X64_FMT_BR_RM, 0), 230 ROW("jmpq", X64_PFX_NONE, 1, 0xFF, 0, 0, 0xFF, 4, X64_W_REQ_ANY, 231 X64_FMT_BR_RM, 0), 232 233 /* ---- SETcc / CMOVcc ---- 234 * SETcc condition in low 4 bits of 2nd opcode byte (0F 90..9F). 235 * CMOVcc same encoding around 0F 40..4F. */ 236 ROW("set", X64_PFX_NONE, 2, 0x0F, 0x90, 0, 0xF0, 0, X64_W_REQ_ANY, 237 X64_FMT_SETCC_RM, 0), 238 ROW("cmov", X64_PFX_NONE, 2, 0x0F, 0x40, 0, 0xF0, NO_MODRM, X64_W_REQ_ANY, 239 X64_FMT_CMOVCC_RR, X64_ASMFL_W_FROM_REX), 240 241 /* ---- BSWAP r — 0F C8+rd ---- */ 242 ROW("bswap", X64_PFX_NONE, 2, 0x0F, 0xC8, 0, 0xF8, NO_MODRM, X64_W_REQ_ANY, 243 X64_FMT_BSWAP, X64_ASMFL_W_FROM_REX), 244 245 /* ---- Bit scan: BSF / BSR ---- */ 246 ROW("bsf", X64_PFX_NONE, 2, 0x0F, 0xBC, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 247 X64_FMT_BS, X64_ASMFL_W_FROM_REX), 248 ROW("bsr", X64_PFX_NONE, 2, 0x0F, 0xBD, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 249 X64_FMT_BS, X64_ASMFL_W_FROM_REX), 250 251 /* ---- POPCNT — F3 0F B8 /r (note: F3 prefix is REQUIRED) ---- */ 252 ROW("popcnt", X64_PFX_F3, 2, 0x0F, 0xB8, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 253 X64_FMT_POPCNT, X64_ASMFL_W_FROM_REX), 254 255 /* ---- Atomic primitives ---- */ 256 /* XADD m, r — 0F C1 /r (LOCK prefix is decoded separately) */ 257 ROW("xadd", X64_PFX_NONE, 2, 0x0F, 0xC1, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 258 X64_FMT_XADD_MEM, X64_ASMFL_W_FROM_REX), 259 /* XCHG r, r/m — 0x87 /r */ 260 ROW("xchg", X64_PFX_NONE, 1, 0x87, 0, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 261 X64_FMT_XCHG_MEM, X64_ASMFL_W_FROM_REX), 262 /* CMPXCHG m, r — 0F B1 /r */ 263 ROW("cmpxchg", X64_PFX_NONE, 2, 0x0F, 0xB1, 0, 0xFF, NO_MODRM, 264 X64_W_REQ_ANY, X64_FMT_CMPXCHG_MEM, X64_ASMFL_W_FROM_REX), 265 266 /* ---- SSE scalar FP — F2/F3 0F xx /r ---- 267 * Three opcodes per (sd, ss) pair: arith / mov / cmp. Each row pairs 268 * the legacy prefix (selects sd vs ss) with the 0F xx /r opcode. */ 269 /* MOVSS / MOVSD */ 270 ROW("movsd", X64_PFX_F2, 2, 0x0F, 0x10, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 271 X64_FMT_SSE_RR, 0), 272 ROW("movsd", X64_PFX_F2, 2, 0x0F, 0x11, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 273 X64_FMT_SSE_RR, X64_ASMFL_ALIAS), 274 ROW("movss", X64_PFX_F3, 2, 0x0F, 0x10, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 275 X64_FMT_SSE_RR, 0), 276 ROW("movss", X64_PFX_F3, 2, 0x0F, 0x11, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 277 X64_FMT_SSE_RR, X64_ASMFL_ALIAS), 278 /* MOVAPS */ 279 ROW("movaps", X64_PFX_NONE, 2, 0x0F, 0x28, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 280 X64_FMT_SSE_RR, 0), 281 ROW("movaps", X64_PFX_NONE, 2, 0x0F, 0x29, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 282 X64_FMT_SSE_RR, X64_ASMFL_ALIAS), 283 /* ADD/SUB/MUL/DIV — opcodes 58/5C/59/5E (same byte for ss and sd; 284 * prefix picks). */ 285 ROW("addsd", X64_PFX_F2, 2, 0x0F, 0x58, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 286 X64_FMT_SSE_RR, 0), 287 ROW("addss", X64_PFX_F3, 2, 0x0F, 0x58, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 288 X64_FMT_SSE_RR, 0), 289 ROW("mulsd", X64_PFX_F2, 2, 0x0F, 0x59, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 290 X64_FMT_SSE_RR, 0), 291 ROW("mulss", X64_PFX_F3, 2, 0x0F, 0x59, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 292 X64_FMT_SSE_RR, 0), 293 ROW("subsd", X64_PFX_F2, 2, 0x0F, 0x5C, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 294 X64_FMT_SSE_RR, 0), 295 ROW("subss", X64_PFX_F3, 2, 0x0F, 0x5C, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 296 X64_FMT_SSE_RR, 0), 297 ROW("divsd", X64_PFX_F2, 2, 0x0F, 0x5E, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 298 X64_FMT_SSE_RR, 0), 299 ROW("divss", X64_PFX_F3, 2, 0x0F, 0x5E, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 300 X64_FMT_SSE_RR, 0), 301 /* Compare scalar (UCOMISS / UCOMISD) */ 302 ROW("ucomisd", X64_PFX_66, 2, 0x0F, 0x2E, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 303 X64_FMT_SSE_RR, 0), 304 ROW("ucomiss", X64_PFX_NONE, 2, 0x0F, 0x2E, 0, 0xFF, NO_MODRM, 305 X64_W_REQ_ANY, X64_FMT_SSE_RR, 0), 306 /* Conversions touched by FP↔int paths: CVTSI2SS/SD, CVTTSS/SD2SI. */ 307 ROW("cvtsi2sd", X64_PFX_F2, 2, 0x0F, 0x2A, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 308 X64_FMT_SSE_RR, X64_ASMFL_W_FROM_REX), 309 ROW("cvtsi2ss", X64_PFX_F3, 2, 0x0F, 0x2A, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 310 X64_FMT_SSE_RR, X64_ASMFL_W_FROM_REX), 311 ROW("cvttsd2si", X64_PFX_F2, 2, 0x0F, 0x2C, 0, 0xFF, NO_MODRM, 312 X64_W_REQ_ANY, X64_FMT_SSE_RR, X64_ASMFL_W_FROM_REX), 313 ROW("cvttss2si", X64_PFX_F3, 2, 0x0F, 0x2C, 0, 0xFF, NO_MODRM, 314 X64_W_REQ_ANY, X64_FMT_SSE_RR, X64_ASMFL_W_FROM_REX), 315 ROW("cvtsd2ss", X64_PFX_F2, 2, 0x0F, 0x5A, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 316 X64_FMT_SSE_RR, 0), 317 ROW("cvtss2sd", X64_PFX_F3, 2, 0x0F, 0x5A, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 318 X64_FMT_SSE_RR, 0), 319 /* MOVD/MOVQ between GPR and XMM. 66 0F 6E /r is gpr->xmm, 66 0F 7E /r is 320 * xmm->gpr (note the reversed operand order, handled in print_xmm_rr). 321 * REX.W picks movq (64-bit GPR) vs movd (32-bit), and since the *mnemonic* 322 * itself changes we split into W_REQ_0 / W_REQ_1 rows rather than a width 323 * suffix. The backend emits these for int<->FP bitcasts (emit_sse_rr_w). */ 324 ROW("movd", X64_PFX_66, 2, 0x0F, 0x6E, 0, 0xFF, NO_MODRM, X64_W_REQ_0, 325 X64_FMT_SSE_RR, 0), 326 ROW("movq", X64_PFX_66, 2, 0x0F, 0x6E, 0, 0xFF, NO_MODRM, X64_W_REQ_1, 327 X64_FMT_SSE_RR, 0), 328 ROW("movd", X64_PFX_66, 2, 0x0F, 0x7E, 0, 0xFF, NO_MODRM, X64_W_REQ_0, 329 X64_FMT_SSE_RR, 0), 330 ROW("movq", X64_PFX_66, 2, 0x0F, 0x7E, 0, 0xFF, NO_MODRM, X64_W_REQ_1, 331 X64_FMT_SSE_RR, 0), 332 /* XORPS / XORPD (0F 57, prefix selects packed-single vs -double). The 333 * backend uses these to clear/negate FP registers. Both operands xmm. */ 334 ROW("xorps", X64_PFX_NONE, 2, 0x0F, 0x57, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 335 X64_FMT_SSE_RR, 0), 336 ROW("xorpd", X64_PFX_66, 2, 0x0F, 0x57, 0, 0xFF, NO_MODRM, X64_W_REQ_ANY, 337 X64_FMT_SSE_RR, 0), 338 }; 339 340 const u32 x64_insn_table_n = 341 (u32)(sizeof x64_insn_table / sizeof x64_insn_table[0]); 342 343 /* ==================================================================== 344 * Prefix decode. 345 * ==================================================================== */ 346 347 u32 x64_decode_prefixes(const u8* bytes, u32 len, X64DecodeCtx* ctx) { 348 u32 off = 0; 349 memset(ctx, 0, sizeof *ctx); 350 while (off < len) { 351 u8 b = bytes[off]; 352 if (b == 0x66u || b == 0xF2u || b == 0xF3u) { 353 ctx->leg_pfx = b; 354 ++off; 355 continue; 356 } 357 if (b == 0xF0u) { 358 /* LOCK — ignored for opcode lookup but consumed so the 359 * subsequent opcode aligns. The printer adds a "lock " prefix 360 * separately when annotating, but kit's emit.c currently emits 361 * LOCK only before XADD / XCHG / CMPXCHG. */ 362 ctx->has_lock = 1; 363 ++off; 364 continue; 365 } 366 break; 367 } 368 if (off < len && bytes[off] >= 0x40u && bytes[off] <= 0x4Fu) { 369 u8 r = bytes[off]; 370 ctx->has_rex = 1; 371 ctx->rex_w = (r >> 3) & 1u; 372 ctx->rex_r = (r >> 2) & 1u; 373 ctx->rex_x = (r >> 1) & 1u; 374 ctx->rex_b = r & 1u; 375 ++off; 376 } 377 ctx->opc_off = off; 378 return off; 379 } 380 381 /* ==================================================================== 382 * Disassembler row lookup. 383 * ==================================================================== */ 384 385 const X64InsnDesc* x64_disasm_find(const u8* bytes, u32 len, 386 X64DecodeCtx* ctx) { 387 if (ctx->opc_off >= len) return NULL; 388 for (u32 i = 0; i < x64_insn_table_n; ++i) { 389 const X64InsnDesc* d = &x64_insn_table[i]; 390 if (d->leg_pfx != ctx->leg_pfx) continue; 391 if (d->rex_w_req == X64_W_REQ_1 && !ctx->rex_w) continue; 392 if (d->rex_w_req == X64_W_REQ_0 && ctx->rex_w) continue; 393 if (ctx->opc_off + d->opc_len > len) continue; 394 /* Opcode bytes match exactly except the LAST byte, which may use 395 * a low-bit mask (embed-reg or condition nibble). */ 396 int ok = 1; 397 for (u32 j = 0; j + 1u < d->opc_len; ++j) { 398 if (bytes[ctx->opc_off + j] != d->opc[j]) { 399 ok = 0; 400 break; 401 } 402 } 403 if (!ok) continue; 404 { 405 u8 last_act = bytes[ctx->opc_off + d->opc_len - 1u] & d->opc_last_mask; 406 u8 last_exp = d->opc[d->opc_len - 1u] & d->opc_last_mask; 407 if (last_act != last_exp) continue; 408 } 409 /* /digit constraint reads ModR/M.reg. */ 410 if (d->modrm_reg != NO_MODRM) { 411 u32 mrm_off = ctx->opc_off + d->opc_len; 412 if (mrm_off >= len) continue; 413 u8 mrm = bytes[mrm_off]; 414 if (((mrm >> 3) & 7u) != d->modrm_reg) continue; 415 } 416 return d; 417 } 418 return NULL; 419 } 420 421 /* ==================================================================== 422 * Operand printers. 423 * ==================================================================== */ 424 425 #define X64_REG_RIP 16u 426 427 static const char* g_cc_name[16] = { 428 "o", "no", "b", "ae", "e", "ne", "be", "a", 429 "s", "ns", "p", "np", "l", "ge", "le", "g", 430 }; 431 432 /* AT&T register names by width. Index 0..15 covers RAX..R15. */ 433 static const char* reg_name(u32 reg, u32 width_bytes, int has_rex) { 434 static const char* r64[16] = { 435 "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", 436 "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", 437 }; 438 static const char* r32[16] = { 439 "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi", 440 "r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d", 441 }; 442 static const char* r16[16] = { 443 "ax", "cx", "dx", "bx", "sp", "bp", "si", "di", 444 "r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w", 445 }; 446 static const char* r8[16] = { 447 "al", "cl", "dl", "bl", "spl", "bpl", "sil", "dil", 448 "r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b", 449 }; 450 static const char* rh8[4] = {"ah", "ch", "dh", "bh"}; 451 reg &= 15u; 452 if (width_bytes == 8) return r64[reg]; 453 if (width_bytes == 4) return r32[reg]; 454 if (width_bytes == 2) return r16[reg]; 455 if (!has_rex && reg >= 4u && reg <= 7u) return rh8[reg - 4u]; 456 return r8[reg]; 457 } 458 459 static const char* xmm_name(u32 reg) { 460 static const char* x[16] = { 461 "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", 462 "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", 463 }; 464 return x[reg & 15u]; 465 } 466 467 static void put_reg(StrBuf* sb, u32 reg, u32 width) { 468 strbuf_putc(sb, '%'); 469 strbuf_puts(sb, reg_name(reg, width, 1)); 470 } 471 static void put_reg_ctx(StrBuf* sb, u32 reg, u32 width, int has_rex) { 472 strbuf_putc(sb, '%'); 473 strbuf_puts(sb, reg_name(reg, width, has_rex)); 474 } 475 static void put_xmm(StrBuf* sb, u32 reg) { 476 strbuf_putc(sb, '%'); 477 strbuf_puts(sb, xmm_name(reg)); 478 } 479 static void put_imm(StrBuf* sb, i64 imm) { 480 strbuf_putc(sb, '$'); 481 strbuf_put_i64(sb, imm); 482 } 483 484 /* Read a signed displacement of n bytes (1 or 4). Returns 1 on success. */ 485 static int read_disp(const u8* bytes, u32 len, u32 off, u32 n, i32* out) { 486 if (off + n > len) return 0; 487 if (n == 1u) { 488 *out = (i32)(i8)bytes[off]; 489 } else if (n == 4u) { 490 *out = (i32)rd_u32_le(bytes + off); 491 } else { 492 *out = 0; 493 } 494 return 1; 495 } 496 497 /* Decode a ModR/M memory operand starting at bytes[off]. Returns number of 498 * extra bytes consumed (ModR/M + SIB? + disp?), or (u32)-1 on truncation. 499 * The ModR/M byte itself is bytes[off]; caller has already read mod/reg/rm. 500 * `disp_out` and `base_out` describe what to print. */ 501 typedef struct DecodedMem { 502 u32 base; 503 u32 index; /* SIB index register (valid when has_index) */ 504 u32 scale; /* SIB scale as the literal 1/2/4/8 (valid when has_index) */ 505 i32 disp; 506 int has_base; 507 int has_index; /* a SIB index register is present */ 508 int rip_relative; 509 u32 bytes_used; 510 } DecodedMem; 511 512 static u32 decode_mem(const u8* bytes, u32 len, u32 off, X64DecodeCtx ctx, 513 u32 mod, u32 rm_low, DecodedMem* out) { 514 out->base = 0; 515 out->index = 0; 516 out->scale = 1; 517 out->disp = 0; 518 out->has_base = 1; 519 out->has_index = 0; 520 out->rip_relative = 0; 521 out->bytes_used = 0; 522 if (mod == 3u) return 0; /* caller handles reg-form */ 523 /* SIB-required form: r/m=100. */ 524 if (rm_low == 4u) { 525 if (off >= len) return (u32)-1; 526 u8 s = bytes[off]; 527 u32 sib_base = (s & 7u) | ((u32)ctx.rex_b << 3); 528 u32 sib_index = ((s >> 3) & 7u) | ((u32)ctx.rex_x << 3); 529 u32 used = 1; 530 /* SIB index = 4 (RSP) with REX.X=0 encodes "no index". */ 531 if (sib_index != 4u) { 532 out->has_index = 1; 533 out->index = sib_index; 534 out->scale = 1u << (s >> 6); 535 } 536 if (mod == 0u && (s & 7u) == 5u) { 537 /* mod=00, base=101: disp32 with no base — either a label-table 538 * disp32 (no index) or an indexed `[index*scale + disp32]`. */ 539 i32 d = 0; 540 if (!read_disp(bytes, len, off + used, 4, &d)) return (u32)-1; 541 used += 4; 542 out->disp = d; 543 out->has_base = 0; 544 out->bytes_used = used; 545 return used; 546 } 547 if (mod == 1u) { 548 i32 d = 0; 549 if (!read_disp(bytes, len, off + used, 1, &d)) return (u32)-1; 550 used += 1; 551 out->disp = d; 552 } else if (mod == 2u) { 553 i32 d = 0; 554 if (!read_disp(bytes, len, off + used, 4, &d)) return (u32)-1; 555 used += 4; 556 out->disp = d; 557 } 558 out->base = sib_base; 559 out->bytes_used = used; 560 return used; 561 } 562 /* Non-SIB form. */ 563 if (mod == 0u && rm_low == 5u) { 564 /* RIP-relative disp32. */ 565 i32 d = 0; 566 if (!read_disp(bytes, len, off, 4, &d)) return (u32)-1; 567 out->disp = d; 568 out->rip_relative = 1; 569 out->bytes_used = 4; 570 return 4; 571 } 572 u32 base = rm_low | ((u32)ctx.rex_b << 3); 573 out->base = base; 574 if (mod == 1u) { 575 i32 d = 0; 576 if (!read_disp(bytes, len, off, 1, &d)) return (u32)-1; 577 out->disp = d; 578 out->bytes_used = 1; 579 return 1; 580 } 581 if (mod == 2u) { 582 i32 d = 0; 583 if (!read_disp(bytes, len, off, 4, &d)) return (u32)-1; 584 out->disp = d; 585 out->bytes_used = 4; 586 return 4; 587 } 588 /* mod == 0u with rm != 5,4 → [reg], no disp. */ 589 return 0; 590 } 591 592 static void put_mem(StrBuf* sb, const DecodedMem* m) { 593 if (m->disp != 0 || (!m->has_base && !m->rip_relative)) { 594 strbuf_put_i64(sb, (i64)m->disp); 595 } 596 if (m->rip_relative) { 597 strbuf_puts(sb, "(%rip)"); 598 } else if (m->has_base || m->has_index) { 599 /* `(base)`, `(base,index,scale)`, or the base-less `(,index,scale)`. */ 600 strbuf_putc(sb, '('); 601 if (m->has_base) put_reg(sb, m->base, 8); 602 if (m->has_index) { 603 strbuf_putc(sb, ','); 604 put_reg(sb, m->index, 8); 605 strbuf_putc(sb, ','); 606 strbuf_put_i64(sb, (i64)m->scale); 607 } 608 strbuf_putc(sb, ')'); 609 } 610 } 611 612 /* ==================================================================== 613 * Width derivation. 614 * ==================================================================== */ 615 616 static u32 width_for(const X64InsnDesc* d, const X64DecodeCtx* ctx) { 617 if (d->flags & X64_ASMFL_FORCE_W64) return 8u; 618 if (d->flags & X64_ASMFL_BYTE) return 1u; 619 if (d->flags & X64_ASMFL_W16) return 2u; 620 if (d->flags & X64_ASMFL_W_FROM_REX) return ctx->rex_w ? 8u : 4u; 621 if (d->leg_pfx == X64_PFX_66) return 2u; 622 return 4u; 623 } 624 625 char x64_size_suffix_for(const X64InsnDesc* desc, const X64DecodeCtx* ctx) { 626 switch ((X64Format)desc->fmt) { 627 case X64_FMT_ALU_RR: 628 case X64_FMT_MOV_RM_LOAD: 629 case X64_FMT_ALU_RM_IMM8: 630 case X64_FMT_ALU_RM_IMM32: 631 case X64_FMT_IMUL_RR: 632 case X64_FMT_IMUL_RRI: 633 case X64_FMT_F7_RM: 634 case X64_FMT_SHIFT_IMM: 635 case X64_FMT_SHIFT_CL: 636 case X64_FMT_BSWAP: 637 case X64_FMT_BS: 638 case X64_FMT_POPCNT: 639 case X64_FMT_XADD_MEM: 640 case X64_FMT_XCHG_MEM: 641 case X64_FMT_CMPXCHG_MEM: 642 case X64_FMT_MOV_RI: 643 switch (width_for(desc, ctx)) { 644 case 1: 645 return 'b'; 646 case 2: 647 return 'w'; 648 case 4: 649 return 'l'; 650 case 8: 651 return 'q'; 652 } 653 return 0; 654 default: 655 return 0; 656 } 657 } 658 659 /* ==================================================================== 660 * Per-format printers. 661 * ==================================================================== */ 662 663 /* Decode a ModR/M with reg+r/m. Returns total bytes consumed by the 664 * ModR/M + any SIB/disp. */ 665 typedef struct RegRm { 666 u32 reg; /* high bit from REX.R */ 667 u32 rm_low; /* low 3 bits */ 668 u32 mod; 669 u32 bytes_after_modrm; /* SIB/disp bytes */ 670 DecodedMem mem; /* valid iff mod != 3 */ 671 } RegRm; 672 673 static int read_modrm(const u8* bytes, u32 len, u32 off, X64DecodeCtx ctx, 674 RegRm* rr) { 675 if (off >= len) return 0; 676 u8 mr = bytes[off]; 677 rr->mod = (mr >> 6) & 3u; 678 rr->reg = ((mr >> 3) & 7u) | ((u32)ctx.rex_r << 3); 679 rr->rm_low = mr & 7u; 680 if (rr->mod == 3u) { 681 rr->bytes_after_modrm = 0; 682 memset(&rr->mem, 0, sizeof rr->mem); 683 return 1; 684 } 685 u32 used = 686 decode_mem(bytes, len, off + 1u, ctx, rr->mod, rr->rm_low, &rr->mem); 687 if (used == (u32)-1) return 0; 688 rr->bytes_after_modrm = used; 689 return 1; 690 } 691 692 /* Print a ModR/M r/m operand at width `w`. */ 693 static void put_rm(StrBuf* sb, const RegRm* rr, X64DecodeCtx ctx, u32 w) { 694 if (rr->mod == 3u) { 695 u32 rm = rr->rm_low | ((u32)ctx.rex_b << 3); 696 put_reg_ctx(sb, rm, w, ctx.has_rex); 697 } else { 698 put_mem(sb, &rr->mem); 699 } 700 } 701 static void put_rm_xmm(StrBuf* sb, const RegRm* rr, X64DecodeCtx ctx) { 702 if (rr->mod == 3u) { 703 u32 rm = rr->rm_low | ((u32)ctx.rex_b << 3); 704 put_xmm(sb, rm); 705 } else { 706 put_mem(sb, &rr->mem); 707 } 708 } 709 710 static u32 print_nullary(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 711 u32 len, const X64DecodeCtx* ctx) { 712 (void)sb; 713 (void)d; 714 (void)bytes; 715 (void)len; 716 return ctx->opc_off + d->opc_len; 717 } 718 719 static u32 print_push_pop(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 720 u32 len, const X64DecodeCtx* ctx) { 721 (void)len; 722 u32 reg = (bytes[ctx->opc_off] & 7u) | ((u32)ctx->rex_b << 3); 723 put_reg(sb, reg, 8); 724 (void)d; 725 return ctx->opc_off + 1u; 726 } 727 728 static u32 print_mov_ri(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 729 u32 len, const X64DecodeCtx* ctx) { 730 (void)d; 731 u32 reg = (bytes[ctx->opc_off] & 7u) | ((u32)ctx->rex_b << 3); 732 u32 off = ctx->opc_off + 1u; 733 if (ctx->rex_w) { 734 if (off + 8u > len) return 0; 735 put_imm(sb, (i64)rd_u64_le(bytes + off)); 736 off += 8u; 737 strbuf_puts(sb, ", "); 738 put_reg(sb, reg, 8); 739 } else { 740 if (off + 4u > len) return 0; 741 put_imm(sb, (i64)(i32)rd_u32_le(bytes + off)); 742 off += 4u; 743 strbuf_puts(sb, ", "); 744 put_reg(sb, reg, 4); 745 } 746 return off; 747 } 748 749 static u32 print_alu_rr(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 750 u32 len, const X64DecodeCtx* ctx) { 751 /* op r/m, r (reg is the source). Width comes from width_for, which 752 * honours the BYTE / W16 / W_FROM_REX flags on the descriptor. */ 753 u32 off = ctx->opc_off + d->opc_len; 754 RegRm rr; 755 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 756 u32 w = width_for(d, ctx); 757 put_reg_ctx(sb, rr.reg, w, ctx->has_rex); 758 strbuf_puts(sb, ", "); 759 put_rm(sb, &rr, *ctx, w); 760 return off + 1u + rr.bytes_after_modrm; 761 } 762 763 static u32 print_mov_rm_load(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 764 u32 len, const X64DecodeCtx* ctx) { 765 /* op r, r/m. */ 766 u32 off = ctx->opc_off + d->opc_len; 767 RegRm rr; 768 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 769 u32 w = width_for(d, ctx); 770 if (d->opc[0] == 0x8Du) w = 8u; /* LEA always loads a 64-bit address */ 771 put_rm(sb, &rr, *ctx, w); 772 strbuf_puts(sb, ", "); 773 put_reg_ctx(sb, rr.reg, w, ctx->has_rex); 774 return off + 1u + rr.bytes_after_modrm; 775 } 776 777 static u32 print_movzx_movsx(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 778 u32 len, const X64DecodeCtx* ctx) { 779 u32 off = ctx->opc_off + d->opc_len; 780 RegRm rr; 781 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 782 /* Source width = 1 for B6/BE, 2 for B7/BF. Destination width = 4 unless 783 * REX.W (then 8). */ 784 u32 src_w = (d->opc[1] == 0xB7u || d->opc[1] == 0xBFu) ? 2u : 1u; 785 u32 dst_w = ctx->rex_w ? 8u : 4u; 786 put_rm(sb, &rr, *ctx, src_w); 787 strbuf_puts(sb, ", "); 788 put_reg_ctx(sb, rr.reg, dst_w, ctx->has_rex); 789 return off + 1u + rr.bytes_after_modrm; 790 } 791 792 static u32 print_movsxd(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 793 u32 len, const X64DecodeCtx* ctx) { 794 u32 off = ctx->opc_off + d->opc_len; 795 RegRm rr; 796 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 797 put_rm(sb, &rr, *ctx, 4u); 798 strbuf_puts(sb, ", "); 799 put_reg_ctx(sb, rr.reg, 8u, ctx->has_rex); 800 return off + 1u + rr.bytes_after_modrm; 801 } 802 803 static u32 print_alu_rm_imm(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 804 u32 len, const X64DecodeCtx* ctx) { 805 u32 off = ctx->opc_off + d->opc_len; 806 RegRm rr; 807 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 808 u32 used = 1u + rr.bytes_after_modrm; 809 i64 imm = 0; 810 if (d->fmt == X64_FMT_ALU_RM_IMM8) { 811 if (off + used >= len) return 0; 812 imm = (i64)(i8)bytes[off + used]; 813 used += 1u; 814 } else { 815 if (off + used + 3u >= len) return 0; 816 imm = (i64)(i32)rd_u32_le(bytes + off + used); 817 used += 4u; 818 } 819 u32 w = width_for(d, ctx); 820 put_imm(sb, imm); 821 strbuf_puts(sb, ", "); 822 put_rm(sb, &rr, *ctx, w); 823 return off + used; 824 } 825 826 static u32 print_imul_rr(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 827 u32 len, const X64DecodeCtx* ctx) { 828 u32 off = ctx->opc_off + d->opc_len; 829 RegRm rr; 830 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 831 u32 w = width_for(d, ctx); 832 put_rm(sb, &rr, *ctx, w); 833 strbuf_puts(sb, ", "); 834 put_reg_ctx(sb, rr.reg, w, ctx->has_rex); 835 return off + 1u + rr.bytes_after_modrm; 836 } 837 838 static u32 print_imul_rri(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 839 u32 len, const X64DecodeCtx* ctx) { 840 /* 69 /r imm32 (full) or 6B /r imm8 (sign-extended). */ 841 u32 off = ctx->opc_off + d->opc_len; 842 RegRm rr; 843 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 844 u32 used = 1u + rr.bytes_after_modrm; 845 i64 imm = 0; 846 u8 op = d->opc[0]; 847 if (op == 0x6Bu) { 848 if (off + used >= len) return 0; 849 imm = (i64)(i8)bytes[off + used]; 850 used += 1u; 851 } else { 852 if (off + used + 3u >= len) return 0; 853 imm = (i64)(i32)rd_u32_le(bytes + off + used); 854 used += 4u; 855 } 856 u32 w = width_for(d, ctx); 857 put_imm(sb, imm); 858 strbuf_puts(sb, ", "); 859 put_rm(sb, &rr, *ctx, w); 860 strbuf_puts(sb, ", "); 861 put_reg_ctx(sb, rr.reg, w, ctx->has_rex); 862 return off + used; 863 } 864 865 static u32 print_f7_rm(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 866 u32 len, const X64DecodeCtx* ctx) { 867 u32 off = ctx->opc_off + d->opc_len; 868 RegRm rr; 869 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 870 u32 w = width_for(d, ctx); 871 put_rm(sb, &rr, *ctx, w); 872 return off + 1u + rr.bytes_after_modrm; 873 } 874 875 static u32 print_shift_imm(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 876 u32 len, const X64DecodeCtx* ctx) { 877 u32 off = ctx->opc_off + d->opc_len; 878 RegRm rr; 879 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 880 u32 used = 1u + rr.bytes_after_modrm; 881 if (off + used >= len) return 0; 882 u8 imm = bytes[off + used]; 883 ++used; 884 u32 w = width_for(d, ctx); 885 put_imm(sb, (i64)imm); 886 strbuf_puts(sb, ", "); 887 put_rm(sb, &rr, *ctx, w); 888 return off + used; 889 } 890 891 static u32 print_shift_cl(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 892 u32 len, const X64DecodeCtx* ctx) { 893 u32 off = ctx->opc_off + d->opc_len; 894 RegRm rr; 895 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 896 u32 w = width_for(d, ctx); 897 strbuf_puts(sb, "%cl, "); 898 put_rm(sb, &rr, *ctx, w); 899 return off + 1u + rr.bytes_after_modrm; 900 } 901 902 static u32 print_jcc_rel32(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 903 u32 len, const X64DecodeCtx* ctx, u64 vaddr) { 904 u32 off = ctx->opc_off + d->opc_len; 905 if (off + 4u > len) return 0; 906 i32 rel = (i32)rd_u32_le(bytes + off); 907 u64 tgt = vaddr + (u64)(off + 4u) + (u64)rel; 908 /* Mnemonic suffix from condition nibble: caller wrote "j"; we append. */ 909 strbuf_putc(sb, ' '); 910 strbuf_put_hex_u64(sb, tgt); 911 return off + 4u; 912 } 913 914 static u32 print_jmp_call_rel32(StrBuf* sb, const X64InsnDesc* d, 915 const u8* bytes, u32 len, 916 const X64DecodeCtx* ctx, u64 vaddr) { 917 u32 off = ctx->opc_off + d->opc_len; 918 if (off + 4u > len) return 0; 919 i32 rel = (i32)rd_u32_le(bytes + off); 920 u64 tgt = vaddr + (u64)(off + 4u) + (u64)rel; 921 strbuf_put_hex_u64(sb, tgt); 922 return off + 4u; 923 } 924 925 static u32 print_br_rm(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 926 u32 len, const X64DecodeCtx* ctx) { 927 u32 off = ctx->opc_off + d->opc_len; 928 RegRm rr; 929 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 930 strbuf_putc(sb, '*'); 931 put_rm(sb, &rr, *ctx, 8u); 932 return off + 1u + rr.bytes_after_modrm; 933 } 934 935 static u32 print_setcc(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 936 u32 len, const X64DecodeCtx* ctx) { 937 u32 off = ctx->opc_off + d->opc_len; 938 RegRm rr; 939 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 940 put_rm(sb, &rr, *ctx, 1u); 941 (void)d; 942 return off + 1u + rr.bytes_after_modrm; 943 } 944 945 static u32 print_cmovcc_rr(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 946 u32 len, const X64DecodeCtx* ctx) { 947 u32 off = ctx->opc_off + d->opc_len; 948 RegRm rr; 949 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 950 u32 w = width_for(d, ctx); 951 put_rm(sb, &rr, *ctx, w); 952 strbuf_puts(sb, ", "); 953 put_reg_ctx(sb, rr.reg, w, ctx->has_rex); 954 return off + 1u + rr.bytes_after_modrm; 955 } 956 957 static u32 print_bswap(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 958 u32 len, const X64DecodeCtx* ctx) { 959 (void)d; 960 (void)len; 961 u32 reg = (bytes[ctx->opc_off + 1u] & 7u) | ((u32)ctx->rex_b << 3); 962 u32 w = ctx->rex_w ? 8u : 4u; 963 put_reg_ctx(sb, reg, w, ctx->has_rex); 964 return ctx->opc_off + 2u; 965 } 966 967 static u32 print_bs(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, u32 len, 968 const X64DecodeCtx* ctx) { 969 /* dst = bsr/bsf(src). Operand order in AT&T is "src, dst". */ 970 u32 off = ctx->opc_off + d->opc_len; 971 RegRm rr; 972 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 973 u32 w = width_for(d, ctx); 974 put_rm(sb, &rr, *ctx, w); 975 strbuf_puts(sb, ", "); 976 put_reg_ctx(sb, rr.reg, w, ctx->has_rex); 977 return off + 1u + rr.bytes_after_modrm; 978 } 979 980 static u32 print_xmm_rr(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 981 u32 len, const X64DecodeCtx* ctx) { 982 u32 off = ctx->opc_off + d->opc_len; 983 RegRm rr; 984 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 985 /* Operand classes/order by opcode (AT&T src, dst): 986 * 2A CVTSI2* : rm=GP(src), reg=xmm(dst) -> "rm_gp, reg_xmm" 987 * 6E MOVD/Q : rm=GP(src), reg=xmm(dst) -> "rm_gp, reg_xmm" (gpr->xmm) 988 * 2C CVTT*2SI : rm=xmm(src), reg=GP(dst) -> "rm_xmm, reg_gp" 989 * 7E MOVD/Q : reg=xmm(src), rm=GP(dst) -> "reg_xmm, rm_gp" (reversed!) 990 * others : both xmm -> "rm_xmm, reg_xmm" 991 * GP width comes from REX.W (movd vs movq / 32- vs 64-bit operands). */ 992 u8 op = d->opc[1]; 993 u32 gp_w = ctx->rex_w ? 8u : 4u; 994 if (op == 0x7Eu) { 995 /* xmm -> r/m GPR: source is the reg-field xmm, dest is the r/m GPR. */ 996 put_xmm(sb, rr.reg); 997 strbuf_puts(sb, ", "); 998 put_rm(sb, &rr, *ctx, gp_w); 999 return off + 1u + rr.bytes_after_modrm; 1000 } 1001 /* Store-direction XMM moves (MOVSD/MOVSS/MOVUPS 0x11, MOVAPS 0x29): the 1002 * reg-field xmm is the SOURCE and the r/m (memory or xmm) is the 1003 * DESTINATION — AT&T order `reg_xmm, rm`. Without this the disassembler 1004 * prints them in load order, so re-assembly flips the data direction. */ 1005 if (op == 0x11u || op == 0x29u) { 1006 put_xmm(sb, rr.reg); 1007 strbuf_puts(sb, ", "); 1008 put_rm_xmm(sb, &rr, *ctx); 1009 return off + 1u + rr.bytes_after_modrm; 1010 } 1011 { 1012 int dst_is_gp = (op == 0x2Cu); /* CVTTSD/SS2SI */ 1013 int src_is_gp = (op == 0x2Au || op == 0x6Eu); /* CVTSI2*, MOVD/Q g->x */ 1014 if (src_is_gp) { 1015 put_rm(sb, &rr, *ctx, gp_w); 1016 } else { 1017 put_rm_xmm(sb, &rr, *ctx); 1018 } 1019 strbuf_puts(sb, ", "); 1020 if (dst_is_gp) { 1021 put_reg_ctx(sb, rr.reg, gp_w, ctx->has_rex); 1022 } else { 1023 put_xmm(sb, rr.reg); 1024 } 1025 } 1026 return off + 1u + rr.bytes_after_modrm; 1027 } 1028 1029 static u32 print_xadd_mem(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 1030 u32 len, const X64DecodeCtx* ctx) { 1031 /* XADD r/m, r — source is the reg, destination is r/m. */ 1032 u32 off = ctx->opc_off + d->opc_len; 1033 RegRm rr; 1034 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 1035 u32 w = width_for(d, ctx); 1036 put_reg_ctx(sb, rr.reg, w, ctx->has_rex); 1037 strbuf_puts(sb, ", "); 1038 put_rm(sb, &rr, *ctx, w); 1039 return off + 1u + rr.bytes_after_modrm; 1040 } 1041 1042 static u32 print_xchg_mem(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 1043 u32 len, const X64DecodeCtx* ctx) { 1044 u32 off = ctx->opc_off + d->opc_len; 1045 RegRm rr; 1046 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 1047 u32 w = width_for(d, ctx); 1048 put_reg_ctx(sb, rr.reg, w, ctx->has_rex); 1049 strbuf_puts(sb, ", "); 1050 put_rm(sb, &rr, *ctx, w); 1051 return off + 1u + rr.bytes_after_modrm; 1052 } 1053 1054 static u32 print_cmpxchg_mem(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 1055 u32 len, const X64DecodeCtx* ctx) { 1056 /* CMPXCHG r/m, r — implicit RAX is the comparand; not shown. */ 1057 u32 off = ctx->opc_off + d->opc_len; 1058 RegRm rr; 1059 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 1060 u32 w = width_for(d, ctx); 1061 put_reg_ctx(sb, rr.reg, w, ctx->has_rex); 1062 strbuf_puts(sb, ", "); 1063 put_rm(sb, &rr, *ctx, w); 1064 return off + 1u + rr.bytes_after_modrm; 1065 } 1066 1067 static u32 print_nop_multi(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 1068 u32 len, const X64DecodeCtx* ctx) { 1069 (void)sb; 1070 u32 off = ctx->opc_off + d->opc_len; 1071 RegRm rr; 1072 if (!read_modrm(bytes, len, off, *ctx, &rr)) return 0; 1073 return off + 1u + rr.bytes_after_modrm; 1074 } 1075 1076 /* ==================================================================== 1077 * Dispatch. 1078 * ==================================================================== */ 1079 1080 u32 x64_print_operands(StrBuf* sb, const X64InsnDesc* d, const u8* bytes, 1081 u32 len, const X64DecodeCtx* ctx, u64 vaddr) { 1082 switch ((X64Format)d->fmt) { 1083 case X64_FMT_NULLARY: 1084 return print_nullary(sb, d, bytes, len, ctx); 1085 case X64_FMT_NOP_MULTI: 1086 return print_nop_multi(sb, d, bytes, len, ctx); 1087 case X64_FMT_PUSH_POP: 1088 return print_push_pop(sb, d, bytes, len, ctx); 1089 case X64_FMT_MOV_RI: 1090 return print_mov_ri(sb, d, bytes, len, ctx); 1091 case X64_FMT_ALU_RR: 1092 return print_alu_rr(sb, d, bytes, len, ctx); 1093 case X64_FMT_MOV_RM_LOAD: 1094 return print_mov_rm_load(sb, d, bytes, len, ctx); 1095 case X64_FMT_MOVZX_MOVSX: 1096 return print_movzx_movsx(sb, d, bytes, len, ctx); 1097 case X64_FMT_MOVSXD: 1098 return print_movsxd(sb, d, bytes, len, ctx); 1099 case X64_FMT_ALU_RM_IMM8: 1100 case X64_FMT_ALU_RM_IMM32: 1101 return print_alu_rm_imm(sb, d, bytes, len, ctx); 1102 case X64_FMT_IMUL_RR: 1103 return print_imul_rr(sb, d, bytes, len, ctx); 1104 case X64_FMT_IMUL_RRI: 1105 return print_imul_rri(sb, d, bytes, len, ctx); 1106 case X64_FMT_F7_RM: 1107 return print_f7_rm(sb, d, bytes, len, ctx); 1108 case X64_FMT_SHIFT_IMM: 1109 return print_shift_imm(sb, d, bytes, len, ctx); 1110 case X64_FMT_SHIFT_CL: 1111 return print_shift_cl(sb, d, bytes, len, ctx); 1112 case X64_FMT_JCC_REL32: 1113 return print_jcc_rel32(sb, d, bytes, len, ctx, vaddr); 1114 case X64_FMT_JMP_REL32: 1115 case X64_FMT_CALL_REL32: 1116 return print_jmp_call_rel32(sb, d, bytes, len, ctx, vaddr); 1117 case X64_FMT_BR_RM: 1118 return print_br_rm(sb, d, bytes, len, ctx); 1119 case X64_FMT_SETCC_RM: 1120 return print_setcc(sb, d, bytes, len, ctx); 1121 case X64_FMT_CMOVCC_RR: 1122 return print_cmovcc_rr(sb, d, bytes, len, ctx); 1123 case X64_FMT_BSWAP: 1124 return print_bswap(sb, d, bytes, len, ctx); 1125 case X64_FMT_BS: 1126 return print_bs(sb, d, bytes, len, ctx); 1127 case X64_FMT_POPCNT: 1128 return print_bs(sb, d, bytes, len, ctx); /* same shape */ 1129 case X64_FMT_SSE_RR: 1130 case X64_FMT_SSE_LOAD: 1131 case X64_FMT_SSE_STORE: 1132 return print_xmm_rr(sb, d, bytes, len, ctx); 1133 case X64_FMT_XADD_MEM: 1134 return print_xadd_mem(sb, d, bytes, len, ctx); 1135 case X64_FMT_XCHG_MEM: 1136 return print_xchg_mem(sb, d, bytes, len, ctx); 1137 case X64_FMT_CMPXCHG_MEM: 1138 return print_cmpxchg_mem(sb, d, bytes, len, ctx); 1139 case X64_FMT_RAW_BYTE: 1140 return 0; 1141 } 1142 return 0; 1143 } 1144 1145 /* Resolve the condition nibble for Jcc/SETcc/CMOVcc to its AT&T mnemonic 1146 * suffix. Used by the disassembler to spell j → "je", set → "sete", etc. */ 1147 const char* x64_cc_name(u8 cc) { return g_cc_name[cc & 0xFu]; }