mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
- Add Fadec x86 disassembler with hover popup for FuncPtr/void Pointer nodes - Separate pointer symbol from address: // prefix, green comment coloring, independent hover/click zones (address triggers popup, symbol is passive) - Fix RVA margin and inline local offset for pointer-expanded vtable children using ptrBase field threaded through composition - Expand multi-select context menu with quick-convert, duplicate, copy address - Remove Edit Value from hex node context menu - Fix heatmap flickering on hex nodes (remove per-byte alternation) - Fix popup repositioning when moving mouse between lines - Truncate disasm popup to 6 lines with ... indicator - Add BUILD_UI_TESTS option to skip widget tests on headless CI - Add test_disasm with 35 test cases for disassembly and hex dump - Add KUSER_SHARED_DATA example .rcx file
1404 lines
62 KiB
Python
1404 lines
62 KiB
Python
#!/usr/bin/python3
|
|
|
|
import argparse
|
|
import bisect
|
|
from collections import OrderedDict, defaultdict, namedtuple
|
|
from enum import Enum
|
|
from itertools import product
|
|
import re
|
|
from typing import NamedTuple, FrozenSet, List, Tuple, Union
|
|
|
|
INSTR_FLAGS_FIELDS, INSTR_FLAGS_SIZES = zip(*[
|
|
("modrm_idx", 2),
|
|
("modreg_idx", 2),
|
|
("vexreg_idx", 2), # note: vexreg w/o vex prefix is zeroreg_val
|
|
("imm_idx", 2),
|
|
("evex_bcst", 1),
|
|
("evex_mask", 1),
|
|
("zeroreg_val", 1),
|
|
("lock", 1),
|
|
("imm_control", 3),
|
|
("vsib", 1),
|
|
("modrm_size", 2),
|
|
("modreg_size", 2),
|
|
("vexreg_size", 2),
|
|
("imm_size", 2),
|
|
("legacy", 1),
|
|
("unused2", 1),
|
|
("size_fix1", 3),
|
|
("size_fix2", 2),
|
|
("instr_width", 1),
|
|
("modrm_ty", 3),
|
|
("modreg_ty", 3),
|
|
("vexreg_ty", 2),
|
|
("imm_ty", 0),
|
|
("evex_rc", 2),
|
|
("evex_bcst16", 1),
|
|
("opsize", 3),
|
|
("modrm", 1),
|
|
("ign66", 1),
|
|
][::-1])
|
|
class InstrFlags(namedtuple("InstrFlags", INSTR_FLAGS_FIELDS)):
|
|
def __new__(cls, **kwargs):
|
|
init = {**{f: 0 for f in cls._fields}, **kwargs}
|
|
return super(InstrFlags, cls).__new__(cls, **init)
|
|
def _encode(self):
|
|
enc = 0
|
|
for value, size in zip(self, INSTR_FLAGS_SIZES):
|
|
enc = enc << size | (value & ((1 << size) - 1))
|
|
return enc
|
|
|
|
ENCODINGS = {
|
|
"NP": InstrFlags(),
|
|
"M": InstrFlags(modrm=1, modrm_idx=0^3),
|
|
"R": InstrFlags(modrm=1, modreg_idx=0^3), # AMX TILEZERO
|
|
"M1": InstrFlags(modrm=1, modrm_idx=0^3, imm_idx=1^3, imm_control=1),
|
|
"MI": InstrFlags(modrm=1, modrm_idx=0^3, imm_idx=1^3, imm_control=4),
|
|
"IM": InstrFlags(modrm=1, modrm_idx=1^3, imm_idx=0^3, imm_control=4),
|
|
"MC": InstrFlags(modrm=1, modrm_idx=0^3, vexreg_idx=1^3, zeroreg_val=1),
|
|
"MR": InstrFlags(modrm=1, modrm_idx=0^3, modreg_idx=1^3),
|
|
"RM": InstrFlags(modrm=1, modrm_idx=1^3, modreg_idx=0^3),
|
|
"RMA": InstrFlags(modrm=1, modrm_idx=1^3, modreg_idx=0^3, vexreg_idx=2^3),
|
|
"MRI": InstrFlags(modrm=1, modrm_idx=0^3, modreg_idx=1^3, imm_idx=2^3, imm_control=4),
|
|
"RMI": InstrFlags(modrm=1, modrm_idx=1^3, modreg_idx=0^3, imm_idx=2^3, imm_control=4),
|
|
"MRC": InstrFlags(modrm=1, modrm_idx=0^3, modreg_idx=1^3, vexreg_idx=2^3, zeroreg_val=1),
|
|
"AM": InstrFlags(modrm=1, modrm_idx=1^3, vexreg_idx=0^3),
|
|
"MA": InstrFlags(modrm=1, modrm_idx=0^3, vexreg_idx=1^3),
|
|
"I": InstrFlags(imm_idx=0^3, imm_control=4),
|
|
"IA": InstrFlags(vexreg_idx=0^3, imm_idx=1^3, imm_control=4),
|
|
"O": InstrFlags(modrm_idx=0^3),
|
|
"OI": InstrFlags(modrm_idx=0^3, imm_idx=1^3, imm_control=4),
|
|
"OA": InstrFlags(modrm_idx=0^3, vexreg_idx=1^3),
|
|
"S": InstrFlags(modreg_idx=0^3), # segment register in bits 3,4,5
|
|
"A": InstrFlags(vexreg_idx=0^3),
|
|
"D": InstrFlags(imm_idx=0^3, imm_control=6),
|
|
"FD": InstrFlags(vexreg_idx=0^3, imm_idx=1^3, imm_control=2),
|
|
"TD": InstrFlags(vexreg_idx=1^3, imm_idx=0^3, imm_control=2),
|
|
|
|
"RVM": InstrFlags(modrm=1, modrm_idx=2^3, modreg_idx=0^3, vexreg_idx=1^3),
|
|
"RVMI": InstrFlags(modrm=1, modrm_idx=2^3, modreg_idx=0^3, vexreg_idx=1^3, imm_idx=3^3, imm_control=4),
|
|
"RVMR": InstrFlags(modrm=1, modrm_idx=2^3, modreg_idx=0^3, vexreg_idx=1^3, imm_idx=3^3, imm_control=3),
|
|
"RMV": InstrFlags(modrm=1, modrm_idx=1^3, modreg_idx=0^3, vexreg_idx=2^3),
|
|
"VM": InstrFlags(modrm=1, modrm_idx=1^3, vexreg_idx=0^3),
|
|
"VMI": InstrFlags(modrm=1, modrm_idx=1^3, vexreg_idx=0^3, imm_idx=2^3, imm_control=4),
|
|
"MVR": InstrFlags(modrm=1, modrm_idx=0^3, modreg_idx=2^3, vexreg_idx=1^3),
|
|
"MRV": InstrFlags(modrm=1, modrm_idx=0^3, modreg_idx=1^3, vexreg_idx=2^3),
|
|
}
|
|
ENCODING_OPTYS = ["modrm", "modreg", "vexreg", "imm"]
|
|
ENCODING_OPORDER = { enc: sorted(ENCODING_OPTYS, key=lambda ty: getattr(ENCODINGS[enc], ty+"_idx")^3) for enc in ENCODINGS}
|
|
|
|
OPKIND_CANONICALIZE = {
|
|
"I": "IMM", # immediate
|
|
"A": "IMM", # Direct address, far jmp
|
|
"J": "IMM", # RIP-relative address
|
|
"M": "MEM", # ModRM.r/m selects memory only
|
|
"O": "MEM", # Direct address, FD/TD encoding
|
|
"R": "GP", # ModRM.r/m selects GP
|
|
"B": "GP", # VEX.vvvv selects GP
|
|
"E": "GP", # ModRM.r/m selects GP or memory
|
|
"G": "GP", # ModRM.reg selects GP
|
|
"P": "MMX", # ModRM.reg selects MMX
|
|
"N": "MMX", # ModRM.r/m selects MMX
|
|
"Q": "MMX", # ModRM.r/m selects MMX or memory
|
|
"V": "XMM", # ModRM.reg selects XMM
|
|
"H": "XMM", # VEX.vvvv selects XMM
|
|
"L": "XMM", # bits7:4 of imm8 select XMM
|
|
"U": "XMM", # ModRM.r/m selects XMM
|
|
"W": "XMM", # ModRM.r/m selects XMM or memory
|
|
"S": "SEG", # ModRM.reg selects SEG
|
|
"C": "CR", # ModRM.reg selects CR
|
|
"D": "DR", # ModRM.reg selects DR
|
|
|
|
# Custom names
|
|
"F": "FPU", # F is used for RFLAGS by Intel
|
|
"K": "MASK",
|
|
"T": "TMM",
|
|
"Z": "BND",
|
|
}
|
|
OPKIND_SIZES = {
|
|
"b": 1,
|
|
"w": 2,
|
|
"d": 4,
|
|
"ss": 4, # Scalar single of XMM (d)
|
|
"q": 8,
|
|
"sd": 8, # Scalar double of XMM (q)
|
|
"t": 10, # FPU/ten-byte
|
|
"dq": 16,
|
|
"qq": 32,
|
|
"oq": 64, # oct-quadword
|
|
"": 0, # for MEMZ
|
|
"v": -1, # operand size (w/d/q)
|
|
"y": -1, # operand size (d/q)
|
|
"z": -1, # w/d (immediates, min(operand size, 4))
|
|
"a": -1, # z:z
|
|
"p": -1, # w:z
|
|
"x": -2, # vector size
|
|
"h": -3, # half x
|
|
"f": -4, # fourth x
|
|
"e": -5, # eighth x
|
|
"pd": -2, # packed double (x)
|
|
"ps": -2, # packed single (x)
|
|
|
|
# Custom names
|
|
"bs": -1, # sign-extended immediate
|
|
"zd": 4, # z-immediate, but always 4-byte operand
|
|
"zq": 8, # z-immediate, but always 8-byte operand
|
|
}
|
|
class OpKind(NamedTuple):
|
|
regkind: str
|
|
sizestr: str
|
|
|
|
SZ_OP = -1
|
|
SZ_VEC = -2
|
|
SZ_VEC_HALF = -3
|
|
SZ_VEC_QUARTER = -4
|
|
SZ_VEC_EIGHTH = -5
|
|
|
|
def abssize(self, opsz=None, vecsz=None):
|
|
res = opsz if self.size == self.SZ_OP else \
|
|
vecsz if self.size == self.SZ_VEC else \
|
|
vecsz >> 1 if self.size == self.SZ_VEC_HALF else \
|
|
vecsz >> 2 if self.size == self.SZ_VEC_QUARTER else \
|
|
vecsz >> 3 if self.size == self.SZ_VEC_EIGHTH else self.size
|
|
if res is None:
|
|
raise Exception("unspecified operand size")
|
|
return res
|
|
def immsize(self, opsz):
|
|
maxsz = 1 if self.sizestr == "bs" else 4 if self.sizestr[0] == "z" else 8
|
|
return min(maxsz, self.abssize(opsz))
|
|
@property
|
|
def kind(self):
|
|
return OPKIND_CANONICALIZE[self.regkind]
|
|
@property
|
|
def size(self):
|
|
return OPKIND_SIZES[self.sizestr]
|
|
@classmethod
|
|
def parse(cls, op):
|
|
return cls(op[0], op[1:])
|
|
|
|
def __eq__(self, other):
|
|
# Custom equality for canonicalization of kind/size.
|
|
return isinstance(other, OpKind) and self.kind == other.kind and self.size == other.size
|
|
|
|
class InstrDesc(NamedTuple):
|
|
mnemonic: str
|
|
encoding: str
|
|
operands: Tuple[str, ...]
|
|
flags: FrozenSet[str]
|
|
|
|
OPKIND_REGTYS = {
|
|
("modrm", "GP"): 1, ("modreg", "GP"): 1, ("vexreg", "GP"): 1,
|
|
("modrm", "XMM"): 0, ("modreg", "XMM"): 0, ("vexreg", "XMM"): 0,
|
|
("modrm", "MMX"): 5, ("modreg", "MMX"): 5,
|
|
("modrm", "FPU"): 4, ("vexreg", "FPU"): 3,
|
|
("modrm", "TMM"): 6, ("modreg", "TMM"): 6, ("vexreg", "TMM"): 3,
|
|
("modrm", "MASK"): 7, ("modreg", "MASK"): 7, ("vexreg", "MASK"): 2,
|
|
("modreg", "SEG"): 3,
|
|
("modreg", "DR"): 0, # handled in code
|
|
("modreg", "CR"): 0, # handled in code
|
|
("modrm", "MEM"): 0,
|
|
("imm", "MEM"): 0, ("imm", "IMM"): 0, ("imm", "XMM"): 0,
|
|
}
|
|
OPKIND_SIZES = {
|
|
0: 0, 1: 1, 2: 2, 4: 3, 8: 4, 16: 5, 32: 6, 64: 7, 10: 0,
|
|
# OpKind.SZ_OP: -2, OpKind.SZ_VEC: -3, OpKind.SZ_HALFVEC: -4,
|
|
}
|
|
|
|
@classmethod
|
|
def parse(cls, desc):
|
|
desc = desc.split()
|
|
mnem, _, compactDesc = desc[5].partition("+")
|
|
flags = frozenset(desc[6:] + [{
|
|
"w": "INSTR_WIDTH",
|
|
"a": "U67",
|
|
"s": "USEG",
|
|
"k": "MASK",
|
|
"b": "BCST",
|
|
"e": "SAE",
|
|
"r": "ER",
|
|
}[c] for c in compactDesc])
|
|
operands = tuple(OpKind.parse(op) for op in desc[1:5] if op != "-")
|
|
return cls(mnem, desc[0], operands, flags)
|
|
|
|
def imm_size(self, opsz):
|
|
flags = ENCODINGS[self.encoding]
|
|
if flags.imm_control < 3:
|
|
return 0
|
|
if flags.imm_control == 3:
|
|
return 1
|
|
if self.mnemonic == "ENTER":
|
|
return 3
|
|
return self.operands[flags.imm_idx^3].immsize(opsz)
|
|
|
|
def dynsizes(self):
|
|
dynopsz = set(op.size for op in self.operands if op.size < 0)
|
|
if {"INSTR_WIDTH", "SZ8"} & self.flags: dynopsz.add(OpKind.SZ_OP)
|
|
if OpKind.SZ_OP in dynopsz and len(dynopsz) > 1:
|
|
raise Exception(f"conflicting dynamic operand sizes in {self}")
|
|
return dynopsz
|
|
|
|
def encode(self, mnem, ign66, modrm):
|
|
flags = ENCODINGS[self.encoding]
|
|
extraflags = {}
|
|
|
|
dynopsz = self.dynsizes()
|
|
# Operand size either refers to vectors or GP, but not both
|
|
if dynopsz and OpKind.SZ_OP not in dynopsz: # Vector operand size
|
|
if self.flags & {"SZ8", "D64", "F64", "INSTR_WIDTH", "LOCK", "U66"}:
|
|
raise Exception(f"incompatible flags in {self}")
|
|
# Allow at most the vector size together with one alternative
|
|
dynsizes = [OpKind.SZ_VEC] + list(dynopsz - {OpKind.SZ_VEC})
|
|
extraflags["opsize"] = 4 | (OpKind.SZ_VEC - dynsizes[-1])
|
|
if len(dynsizes) > 2:
|
|
raise Exception(f"conflicting vector operand sizes in {self}")
|
|
else: # either empty or GP operand size
|
|
dynsizes = [OpKind.SZ_OP]
|
|
if "SZ8" in self.flags:
|
|
dynsizes = []
|
|
if "D64" in self.flags: extraflags["opsize"] = 2
|
|
if "F64" in self.flags: extraflags["opsize"] = 3
|
|
extraflags["lock"] = "LOCK" in self.flags
|
|
|
|
if (self.flags & {"SZ8", "INSTR_WIDTH"} or
|
|
mnem in ("MOVSX", "MOVZX", "XCHG_NOP", "3DNOW")):
|
|
extraflags["legacy"] = 1
|
|
# INSTR_WIDTH defaults to zero, so only enable when SZ8 is unset
|
|
if "INSTR_WIDTH" in self.flags and "SZ8" not in self.flags:
|
|
extraflags["instr_width"] = 1
|
|
|
|
imm_byte = self.imm_size(4) == 1
|
|
extraflags["imm_control"] = flags.imm_control | imm_byte
|
|
|
|
# Sort fixed sizes encodable in size_fix2 as second element.
|
|
# But: byte-sized immediates are handled specially and don't cost space.
|
|
fixed = set(self.OPKIND_SIZES[op.size] for op in self.operands if
|
|
op.size >= 0 and not (imm_byte and op.kind == "IMM"))
|
|
fixed = sorted(fixed, key=lambda x: 1 <= x <= 4)
|
|
if len(fixed) > 2 or (len(fixed) == 2 and not (1 <= fixed[1] <= 4)):
|
|
raise Exception(f"invalid fixed sizes {fixed} in {self}")
|
|
sizes = (fixed + [1, 1])[:2] + dynsizes # See operand_sizes in decode.c.
|
|
extraflags["size_fix1"] = sizes[0]
|
|
extraflags["size_fix2"] = sizes[1] - 1
|
|
|
|
for i, opkind in enumerate(self.operands):
|
|
sz = self.OPKIND_SIZES[opkind.size] if opkind.size >= 0 else opkind.size
|
|
if opkind.kind == "IMM":
|
|
if imm_byte and sz not in [1] + dynsizes[:1]:
|
|
raise Exception(f"imm_byte with opsize {sz} in {self}")
|
|
extraflags[f"imm_size"] = sz == 1 if imm_byte else sizes.index(sz)
|
|
else:
|
|
opname = ENCODING_OPORDER[self.encoding][i]
|
|
extraflags[f"{opname}_size"] = sizes.index(sz)
|
|
extraflags[f"{opname}_ty"] = self.OPKIND_REGTYS[opname, opkind.kind]
|
|
|
|
# Miscellaneous Flags
|
|
if "VSIB" in self.flags: extraflags["vsib"] = 1
|
|
if "BCST" in self.flags: extraflags["evex_bcst"] = 1
|
|
if "BCST16" in self.flags: extraflags["evex_bcst16"] = 1
|
|
if "MASK" in self.flags: extraflags["evex_mask"] = 1
|
|
if "SAE" in self.flags: extraflags["evex_rc"] = 1
|
|
if "ER" in self.flags: extraflags["evex_rc"] = 3
|
|
if modrm: extraflags["modrm"] = 1
|
|
|
|
if "U66" not in self.flags and (ign66 or "I66" in self.flags):
|
|
extraflags["ign66"] = 1
|
|
|
|
enc = flags._replace(**extraflags)._encode()
|
|
enc = tuple((enc >> i) & 0xffff for i in range(0, 48, 16))
|
|
# First 2 bytes are the mnemonic, last 6 bytes are the encoding.
|
|
return f"{{FDI_{mnem}, {enc[0]}, {enc[1]}, {enc[2]}}}"
|
|
|
|
class EntryKind(Enum):
|
|
NONE = 0x00
|
|
PREFIX = 0x10
|
|
INSTR = 0x20
|
|
WEAKINSTR = 0x30
|
|
TABLE16 = 0x01
|
|
TABLE8E = 0x11
|
|
ESCAPE = 0x02
|
|
TABLE256 = 0x12
|
|
TABLE_VEX = 0x22
|
|
TABLE_PREFIX = 0x03
|
|
TABLE_ROOT = -1
|
|
@property
|
|
def is_table(self):
|
|
return self != EntryKind.INSTR and self != EntryKind.WEAKINSTR and self != EntryKind.PREFIX
|
|
|
|
opcode_regex = re.compile(
|
|
r"^(?:(?P<prefixes>(?P<vex>E?VEX\.)?(?P<legacy>NP|66|F2|F3|NFx)\." +
|
|
r"(?:W(?P<rexw>[01])\.)?(?:L(?P<vexl>0|1|12|2|IG)\.)?))?" +
|
|
r"(?P<escape>0f38|0f3a|0f|M[567]\.|)" +
|
|
r"(?P<opcode>[0-9a-f]{2})" +
|
|
r"(?:/(?P<modreg>[0-7]|[rm][0-7]?|[0-7][rm])|(?P<opcext>[c-f][0-9a-f]))?(?P<extended>\+)?$")
|
|
|
|
class Opcode(NamedTuple):
|
|
prefix: Union[None, str] # None/NP/66/F2/F3/NFx
|
|
escape: int # [0, 0f, 0f38, 0f3a]
|
|
opc: int
|
|
extended: bool # Extend opc or opcext in ModRM.rm, if present
|
|
# Fixed ModRM.mod ("r"/"m"), ModRM.reg, ModRM.rm (opcext + AMX)
|
|
modrm: Tuple[Union[None, str], Union[None, int], Union[None, int]]
|
|
vex: int # 0 = legacy, 1 = VEX, 2 = EVEX
|
|
vexl: Union[str, None] # 0, 1, 12, 2, IG, None = used, both
|
|
rexw: Union[str, None] # 0, 1, None = both (or ignored)
|
|
|
|
@classmethod
|
|
def parse(cls, opcode_string):
|
|
match = opcode_regex.match(opcode_string)
|
|
if match is None:
|
|
raise Exception(opcode_string)
|
|
return None
|
|
|
|
opcext = int(match.group("opcext") or "0", 16)
|
|
modreg = match.group("modreg")
|
|
if opcext:
|
|
modrm = "r", (opcext >> 3) & 7, opcext & 7
|
|
elif modreg:
|
|
if modreg[0] in "rm":
|
|
modrm = modreg[0], None, int(modreg[1:]) if modreg[1:] else None
|
|
else:
|
|
modrm = modreg[1:] or None, int(modreg[0]), None
|
|
else:
|
|
modrm = None, None, None
|
|
|
|
return cls(
|
|
prefix=match.group("legacy"),
|
|
escape=["", "0f", "0f38", "0f3a", "M4.", "M5.", "M6.", "M7."].index(match.group("escape")),
|
|
opc=int(match.group("opcode"), 16),
|
|
extended=match.group("extended") is not None,
|
|
modrm=modrm,
|
|
vex=[None, "VEX.", "EVEX."].index(match.group("vex")),
|
|
vexl=match.group("vexl"),
|
|
rexw=match.group("rexw"),
|
|
)
|
|
|
|
def verifyOpcodeDesc(opcode, desc):
|
|
flags = ENCODINGS[desc.encoding]
|
|
oporder = ENCODING_OPORDER[desc.encoding]
|
|
expected_immkinds = ["", "I", "O", "L", "IA", "", "J"][flags.imm_control]
|
|
fixed_mod = opcode.modrm[0]
|
|
if opcode.extended or desc.mnemonic in ("MOV_CR2G", "MOV_DR2G", "MOV_G2CR", "MOV_G2DR"):
|
|
fixed_mod = "r"
|
|
expected_modrmkinds = {None: "EQWFKT", "r": "RNUFKT", "m": "M"}[fixed_mod]
|
|
# allow F and R for zeroreg, which we overlap with vexreg
|
|
expected_vexkinds = "BHKT" if opcode.vex else "BHRF"
|
|
for i, opkind in enumerate(desc.operands):
|
|
if oporder[i] == "modrm" and opkind.regkind not in expected_modrmkinds:
|
|
raise Exception(f"modrm operand-regkind mismatch {opcode}, {desc}")
|
|
if oporder[i] == "modreg" and opkind.regkind not in "GPVSCDFKT":
|
|
raise Exception(f"modreg operand-regkind mismatch {opcode}, {desc}")
|
|
if oporder[i] == "vexreg" and opkind.regkind not in expected_vexkinds:
|
|
raise Exception(f"vexreg operand-regkind mismatch {opcode}, {desc}")
|
|
if oporder[i] == "imm" and opkind.regkind not in expected_immkinds:
|
|
raise Exception(f"imm operand-regkind mismatch {opcode}, {desc}")
|
|
if "INSTR_WIDTH" in desc.flags and len(desc.operands) > 3:
|
|
raise Exception(f"+w with four operands {opcode}, {desc}")
|
|
if opcode.escape == 2 and flags.imm_control != 0:
|
|
raise Exception(f"0f38 has no immediate operand {opcode}, {desc}")
|
|
if opcode.escape == 3 and desc.imm_size(4) != 1:
|
|
raise Exception(f"0f3a must have immediate byte {opcode}, {desc}")
|
|
if opcode.escape == 0 and opcode.prefix is not None:
|
|
raise Exception(f"unescaped opcode has prefix {opcode}, {desc}")
|
|
if opcode.escape == 0 and opcode.vexl is not None:
|
|
raise Exception(f"unescaped opcode has L specifier {opcode}, {desc}")
|
|
if opcode.escape == 0 and opcode.rexw is not None:
|
|
raise Exception(f"unescaped opcode has W specifier {opcode}, {desc}")
|
|
if opcode.escape == 0 and opcode.vex:
|
|
raise Exception(f"VEX opcode without escape {opcode}, {desc}")
|
|
if opcode.vex and opcode.extended:
|
|
raise Exception(f"VEX/EVEX must not be extended {opcode}, {desc}")
|
|
if opcode.vex and opcode.prefix not in ("NP", "66", "F2", "F3"):
|
|
raise Exception(f"VEX/EVEX must have mandatory prefix {opcode}, {desc}")
|
|
if opcode.vexl == "IG" and desc.dynsizes() - {OpKind.SZ_OP}:
|
|
raise Exception(f"(E)VEX.LIG with dynamic vector size {opcode}, {desc}")
|
|
if "VSIB" in desc.flags and opcode.modrm[0] != "m":
|
|
raise Exception(f"VSIB for non-memory opcode {opcode}, {desc}")
|
|
if opcode.vex == 2 and flags.vexreg_idx:
|
|
# Checking this here allows to omit check for V' in decoder.
|
|
if desc.operands[flags.vexreg_idx ^ 3].kind != "XMM":
|
|
raise Exception(f"EVEX.vvvv must refer to XMM {opcode}, {desc}")
|
|
if opcode.vex == 2 and flags.modreg_idx and flags.modreg_idx ^ 3 != 0:
|
|
# EVEX.z=0 is only checked for mask operands in ModReg
|
|
if desc.operands[flags.modreg_idx ^ 3].kind == "MASK":
|
|
raise Exception(f"ModRM.reg mask not first operand {opcode}, {desc}")
|
|
# Verify tuple type
|
|
if opcode.vex == 2 and opcode.modrm[0] != "r":
|
|
tts = [s for s in desc.flags if s.startswith("TUPLE")]
|
|
if len(tts) != 1:
|
|
raise Exception(f"missing tuple type in {opcode}, {desc}")
|
|
if flags.modrm_idx == 3 ^ 3:
|
|
raise Exception(f"missing memory operand {opcode}, {desc}")
|
|
# From Intel SDM
|
|
bcst, evexw, vszs = {
|
|
"TUPLE_FULL_16": (2, "0", ( 16, 32, 64)),
|
|
"TUPLE_FULL_32": (4, "0", ( 16, 32, 64)),
|
|
"TUPLE_FULL_64": (8, "1", ( 16, 32, 64)),
|
|
"TUPLE_HALF_16": (2, "0", ( 8, 16, 32)),
|
|
"TUPLE_HALF_32": (4, "0", ( 8, 16, 32)),
|
|
"TUPLE_HALF_64": (8, "1", ( 8, 16, 32)),
|
|
"TUPLE_QUARTER_16": (2, "0", ( 4, 8, 16)),
|
|
"TUPLE_FULL_MEM": (None, None, ( 16, 32, 64)),
|
|
"TUPLE_HALF_MEM": (None, None, ( 8, 16, 32)),
|
|
"TUPLE_QUARTER_MEM": (None, None, ( 4, 8, 16)),
|
|
"TUPLE_EIGHTH_MEM": (None, None, ( 2, 4, 8)),
|
|
"TUPLE1_SCALAR_8": (None, None, ( 1, 1, 1)),
|
|
"TUPLE1_SCALAR_16": (None, None, ( 2, 2, 2)),
|
|
"TUPLE1_SCALAR_32": (None, "0", ( 4, 4, 4)),
|
|
"TUPLE1_SCALAR_64": (None, "1", ( 8, 8, 8)),
|
|
"TUPLE1_SCALAR_OPSZ": (None, None, ( 0, 0, 0)),
|
|
"TUPLE1_FIXED_32": (None, None, ( 4, 4, 4)),
|
|
"TUPLE1_FIXED_64": (None, None, ( 8, 8, 8)),
|
|
"TUPLE2_32": (None, "0", ( 8, 8, 8)),
|
|
"TUPLE2_64": (None, "1", (None, 16, 16)),
|
|
"TUPLE4_32": (None, "0", (None, 16, 16)),
|
|
"TUPLE4_64": (None, "1", (None, None, 32)),
|
|
"TUPLE8_32": (None, "0", (None, None, 32)),
|
|
"TUPLE_MEM128": (None, None, ( 16, 16, 16)),
|
|
# TODO: Fix MOVDDUP tuple size :(
|
|
"TUPLE_MOVDDUP": (None, None, ( 16, 32, 64)),
|
|
}[tts[0]]
|
|
if "BCST" in desc.flags:
|
|
if bcst is None:
|
|
raise Exception(f"broadcast on incompatible type {opcode}, {desc}")
|
|
if ("BCST16" in desc.flags) != (bcst == 2):
|
|
raise Exception(f"bcst16 mismatch, should be {bcst} {opcode}, {desc}")
|
|
# EVEX.W is used to distinguish 4/8-byte broadcast size
|
|
if evexw and opcode.rexw != evexw:
|
|
raise Exception(f"incompatible EVEX.W {opcode}, {desc}")
|
|
for l, tupsz in enumerate(vszs):
|
|
opsz = desc.operands[flags.modrm_idx ^ 3].abssize(0, 16 << l)
|
|
if tupsz is not None and opsz != tupsz:
|
|
raise Exception(f"memory size {opsz} != {tupsz} {opcode}, {desc}")
|
|
|
|
class Trie:
|
|
KIND_ORDER = (EntryKind.TABLE_ROOT, EntryKind.ESCAPE, EntryKind.TABLE256,
|
|
EntryKind.TABLE_PREFIX, EntryKind.TABLE16,
|
|
EntryKind.TABLE8E, EntryKind.TABLE_VEX)
|
|
TABLE_LENGTH = {
|
|
EntryKind.TABLE_ROOT: 256,
|
|
EntryKind.ESCAPE: 8,
|
|
EntryKind.TABLE256: 256,
|
|
EntryKind.TABLE_PREFIX: 4,
|
|
EntryKind.TABLE16: 16,
|
|
EntryKind.TABLE8E: 8,
|
|
EntryKind.TABLE_VEX: 8,
|
|
}
|
|
|
|
def __init__(self, root_count):
|
|
self.trie = []
|
|
self.trie.append([None] * root_count)
|
|
self.kindmap = defaultdict(list)
|
|
|
|
def _add_table(self, kind):
|
|
self.trie.append([None] * self.TABLE_LENGTH[kind])
|
|
self.kindmap[kind].append(len(self.trie) - 1)
|
|
return len(self.trie) - 1
|
|
|
|
def _clone(self, elem):
|
|
if not elem or not elem[0].is_table:
|
|
return elem
|
|
new_num = self._add_table(elem[0])
|
|
self.trie[new_num] = [self._clone(e) for e in self.trie[elem[1]]]
|
|
return elem[0], new_num
|
|
|
|
def _transform_opcode(self, opc):
|
|
realopcext = opc.extended and opc.modrm[2] is None
|
|
topc = [opc.opc + i for i in range(8 if realopcext else 1)]
|
|
if opc.escape == 0 and opc.opc in (0xc4, 0xc5, 0x62):
|
|
assert opc.prefix is None
|
|
assert opc.modrm == ("m", None, None)
|
|
assert opc.rexw is None
|
|
assert opc.vexl is None
|
|
# We do NOT encode /m, this is handled by prefix code.
|
|
# Order must match KIND_ORDER.
|
|
return topc, [0], None, None, None, None, None
|
|
elif opc.escape == 0:
|
|
troot, tescape, topc = topc, None, None
|
|
else:
|
|
troot = [[0x0f], [0xc4, 0xc5], [0x62]][opc.vex]
|
|
tescape = [opc.escape]
|
|
|
|
tprefix, t16, t8e, tvex = None, None, None, None
|
|
if opc.prefix == "NFx":
|
|
tprefix = [0, 1]
|
|
elif opc.prefix:
|
|
tprefix = [["NP", "66", "F3", "F2"].index(opc.prefix)]
|
|
if opc.modrm != (None, None, None):
|
|
# TODO: optimize for /r and /m specifiers to reduce size
|
|
mod = {"m": [0], "r": [1], None: [0, 1]}[opc.modrm[0]]
|
|
reg = [opc.modrm[1]] if opc.modrm[1] is not None else list(range(8))
|
|
t16 = [x + (y << 1) for x in mod for y in reg]
|
|
if opc.modrm[2] is not None and not opc.extended:
|
|
t8e = [opc.modrm[2]]
|
|
if opc.rexw is not None or (opc.vexl or "IG") != "IG":
|
|
rexw = {"0": [0], "1": [1<<0], None: [0, 1<<0]}[opc.rexw]
|
|
if opc.vex < 2:
|
|
vexl = {"0": [0], "1": [1<<1], "IG": [0, 1<<1]}[opc.vexl or "IG"]
|
|
else:
|
|
vexl = {"0": [0], "12": [1<<1, 2<<1], "2": [2<<1], "IG": [0, 1<<1, 2<<1, 3<<1]}[opc.vexl or "IG"]
|
|
tvex = list(map(sum, product(rexw, vexl)))
|
|
# Order must match KIND_ORDER.
|
|
return troot, tescape, topc, tprefix, t16, t8e, tvex
|
|
|
|
def add_opcode(self, opcode, descidx, root_idx, weak=False):
|
|
opcode = self._transform_opcode(opcode)
|
|
frontier = [(0, root_idx)]
|
|
for elem_kind, elem in zip(self.KIND_ORDER, opcode):
|
|
new_frontier = []
|
|
for entry_num, entry_idx in frontier:
|
|
entry = self.trie[entry_num]
|
|
if elem is None:
|
|
if entry[entry_idx] is None or entry[entry_idx][0] != elem_kind:
|
|
new_frontier.append((entry_num, entry_idx))
|
|
continue
|
|
elem = list(range(self.TABLE_LENGTH[elem_kind]))
|
|
if entry[entry_idx] is None:
|
|
new_num = self._add_table(elem_kind)
|
|
entry[entry_idx] = elem_kind, new_num
|
|
elif entry[entry_idx][0] != elem_kind:
|
|
# Need to add a new node here and copy entry one level below
|
|
new_num = self._add_table(elem_kind)
|
|
# Keep original entry, but clone others recursively
|
|
self.trie[new_num][0] = entry[entry_idx]
|
|
for i in range(1, len(self.trie[new_num])):
|
|
self.trie[new_num][i] = self._clone(entry[entry_idx])
|
|
entry[entry_idx] = elem_kind, new_num
|
|
for elem_idx in elem:
|
|
new_frontier.append((entry[entry_idx][1], elem_idx))
|
|
frontier = new_frontier
|
|
for entry_num, entry_idx in frontier:
|
|
entry = self.trie[entry_num]
|
|
if not entry[entry_idx] or entry[entry_idx][0] == EntryKind.WEAKINSTR:
|
|
kind = EntryKind.INSTR if not weak else EntryKind.WEAKINSTR
|
|
entry[entry_idx] = kind, descidx << 2
|
|
elif not weak:
|
|
raise Exception(f"redundant non-weak {opcode}")
|
|
|
|
def add_prefix(self, byte, prefix, root_idx):
|
|
if self.trie[0][root_idx] is None:
|
|
self.trie[0][root_idx] = EntryKind.TABLE_ROOT, self._add_table(EntryKind.TABLE_ROOT)
|
|
self.trie[self.trie[0][root_idx][1]][byte] = EntryKind.PREFIX, prefix
|
|
|
|
def deduplicate(self):
|
|
synonyms = {}
|
|
for kind in self.KIND_ORDER[::-1]:
|
|
entries = {}
|
|
for num in self.kindmap[kind]:
|
|
# Replace previous synonyms
|
|
entry = self.trie[num]
|
|
for i, elem in enumerate(entry):
|
|
if elem and elem[0].is_table and elem[1] in synonyms:
|
|
entry[i] = synonyms[elem[1]]
|
|
|
|
unique_entry = tuple(entry)
|
|
if len(set(unique_entry)) == 1:
|
|
# Omit kind if all entries point to the same child
|
|
synonyms[num] = entry[0]
|
|
self.trie[num] = None
|
|
elif unique_entry in entries:
|
|
# Deduplicate entries of this kind
|
|
synonyms[num] = kind, entries[unique_entry]
|
|
self.trie[num] = None
|
|
else:
|
|
entries[unique_entry] = num
|
|
|
|
def compile(self):
|
|
offsets = [None] * len(self.trie)
|
|
last_off = 0
|
|
for num, entry in enumerate(self.trie[1:], start=1):
|
|
if not entry:
|
|
continue
|
|
offsets[num] = last_off
|
|
last_off += (len(entry) + 3) & ~3
|
|
if last_off >= 0x8000:
|
|
raise Exception(f"maximum table size exceeded: {last_off:#x}")
|
|
|
|
data = [0] * last_off
|
|
for off, entry in zip(offsets, self.trie):
|
|
if off is None:
|
|
continue
|
|
for i, elem in enumerate(entry, start=off):
|
|
if elem is not None:
|
|
value = offsets[elem[1]] if elem[0].is_table else elem[1]
|
|
data[i] = value | (elem[0].value & 3)
|
|
return tuple(data), [offsets[v] for _, v in self.trie[0]]
|
|
|
|
@property
|
|
def stats(self):
|
|
return {k.name: sum(self.trie[e] is not None for e in v)
|
|
for k, v in self.kindmap.items()}
|
|
|
|
|
|
def superstring(strs):
|
|
# This faces the "shortest superstring" problem, which is NP-hard.
|
|
# Preprocessing: remove any strings which are already completely covered
|
|
realstrs = []
|
|
for s in sorted(strs, key=len, reverse=True):
|
|
for s2 in realstrs:
|
|
if s in s2:
|
|
break
|
|
else:
|
|
realstrs.append(s)
|
|
|
|
# Greedy heuristic generally yields acceptable results, though it depends on
|
|
# the order of the menmonics. More compact results are possible, but the
|
|
# expectable gains of an optimal result (probably with O(n!)) are small.
|
|
# First sort strings and later do a binary search for each possible prefix.
|
|
realstrs.sort()
|
|
merged = ""
|
|
while realstrs:
|
|
for i in range(min(16, len(merged)), 0, -1):
|
|
idx = bisect.bisect_left(realstrs, merged[-i:])
|
|
if idx < len(realstrs) and realstrs[idx][:i] == merged[-i:]:
|
|
merged += realstrs.pop(idx)[i:]
|
|
break
|
|
else:
|
|
merged += realstrs.pop()
|
|
return merged
|
|
|
|
def decode_table(entries, args):
|
|
modes = args.modes
|
|
|
|
trie = Trie(root_count=len(modes))
|
|
for i, mode in enumerate(modes):
|
|
# Magic values must match PF_* enum in decode.c.
|
|
trie.add_prefix(0x66, 0xfffa, i)
|
|
trie.add_prefix(0x67, 0xfffb, i)
|
|
trie.add_prefix(0xf0, 0xfffc, i)
|
|
trie.add_prefix(0xf2, 0xfffd, i)
|
|
trie.add_prefix(0xf3, 0xfffd, i)
|
|
trie.add_prefix(0x64, 0xfff9, i)
|
|
trie.add_prefix(0x65, 0xfff9, i)
|
|
for seg in (0x26, 0x2e, 0x36, 0x3e):
|
|
trie.add_prefix(seg, 0xfff8 + (mode <= 32), i)
|
|
if mode > 32:
|
|
for rex in range(0x40, 0x50):
|
|
trie.add_prefix(rex, 0xfffe, i)
|
|
|
|
# pause is hardcoded together with XCHG_NOP.
|
|
mnems, descs, desc_map = {"PAUSE"}, [], {}
|
|
descs.append("{0}") # desc index zero is "invalid"
|
|
for weak, opcode, desc in entries:
|
|
ign66 = opcode.prefix in ("NP", "66", "F2", "F3")
|
|
modrm = opcode.modrm != (None, None, None)
|
|
mnem = {
|
|
"PUSH_SEG": "PUSH", "POP_SEG": "POP",
|
|
"MOV_CR2G": "MOV_CR", "MOV_G2CR": "MOV_CR",
|
|
"MOV_DR2G": "MOV_DR", "MOV_G2DR": "MOV_DR",
|
|
"MMX_MOVD_M2G": "MMX_MOVD", "MMX_MOVD_G2M": "MMX_MOVD",
|
|
"MMX_MOVQ_M2G": "MMX_MOVQ", "MMX_MOVQ_G2M": "MMX_MOVQ",
|
|
"SSE_MOVD_X2G": "SSE_MOVD", "SSE_MOVD_G2X": "SSE_MOVD",
|
|
"SSE_MOVQ_X2G": "SSE_MOVQ", "SSE_MOVQ_G2X": "SSE_MOVQ",
|
|
"VMOVD_X2G": "VMOVD", "VMOVD_G2X": "VMOVD",
|
|
"VMOVQ_X2G": "VMOVQ", "VMOVQ_G2X": "VMOVQ",
|
|
}.get(desc.mnemonic, desc.mnemonic)
|
|
mnems.add(mnem)
|
|
descenc = desc.encode(mnem, ign66, modrm)
|
|
desc_idx = desc_map.get(descenc)
|
|
if desc_idx is None:
|
|
desc_idx = desc_map[descenc] = len(descs)
|
|
descs.append(descenc)
|
|
for i, mode in enumerate(modes):
|
|
if "IO"[mode <= 32]+"64" not in desc.flags:
|
|
trie.add_opcode(opcode, desc_idx, i, weak)
|
|
|
|
trie.deduplicate()
|
|
table_data, root_offsets = trie.compile()
|
|
|
|
mnems = sorted(mnems)
|
|
decode_mnems_lines = [f"FD_MNEMONIC({m},{i})\n" for i, m in enumerate(mnems)]
|
|
|
|
mnemonics_intel = [m.replace("SSE_", "").replace("MMX_", "")
|
|
.replace("EVX_", "V")
|
|
.replace("MOVABS", "MOV").replace("RESERVED_", "")
|
|
.replace("JMPF", "JMP FAR").replace("CALLF", "CALL FAR")
|
|
.replace("_S2G", "").replace("_G2S", "")
|
|
.replace("_X2G", "").replace("_G2X", "")
|
|
.replace("_CR", "").replace("_DR", "")
|
|
.replace("REP_", "REP ").replace("CMPXCHGD", "CMPXCHG")
|
|
.replace("JCXZ", "JCXZ JECXZJRCXZ")
|
|
.replace("C_SEP", "CWD CDQ CQO")
|
|
.replace("C_EX", "CBW CWDECDQE").replace("XCHG_NOP", "")
|
|
.lower() for m in mnems]
|
|
mnemonics_str = superstring(mnemonics_intel)
|
|
|
|
if args.stats:
|
|
print(f"Decode stats: Descs -- {len(descs)} ({8*len(descs)} bytes); ",
|
|
f"Trie -- {2*len(table_data)} bytes, {trie.stats}; "
|
|
f"Mnems -- {len(mnemonics_str)} + {3*len(mnemonics_intel)} bytes")
|
|
|
|
defines = ["FD_TABLE_OFFSET_%d %d\n"%k for k in zip(modes, root_offsets)]
|
|
|
|
return "".join(decode_mnems_lines), f"""// Auto-generated file -- do not modify!
|
|
#if defined(FD_DECODE_TABLE_DATA)
|
|
{"".join(f"{e:#06x}," for e in table_data)}
|
|
#elif defined(FD_DECODE_TABLE_DESCS)
|
|
{",".join(descs)}
|
|
#elif defined(FD_DECODE_TABLE_STRTAB1)
|
|
"{mnemonics_str}"
|
|
#elif defined(FD_DECODE_TABLE_STRTAB2)
|
|
{",".join(str(mnemonics_str.index(mnem)) for mnem in mnemonics_intel)}
|
|
#elif defined(FD_DECODE_TABLE_STRTAB3)
|
|
{",".join(str(len(mnem)) for mnem in mnemonics_intel)}
|
|
#elif defined(FD_DECODE_TABLE_DEFINES)
|
|
{"".join("#define " + line for line in defines)}
|
|
#else
|
|
#error "unspecified decode table"
|
|
#endif
|
|
"""
|
|
|
|
class EncodeVariant(NamedTuple):
|
|
opcode: Opcode
|
|
desc: InstrDesc
|
|
evexbcst: bool = False
|
|
evexmask: int = 0 # 0 = none, 1 = must have mask, 2 = mask + EVEX.z
|
|
evexsae: int = 0 # 0 = no EVEX.b, 1 = EVEX.b, 2 = EVEX.b + L'L is rounding mode
|
|
evexdisp8scale: int = 0 # EVEX disp8 shift
|
|
downgrade: int = 0 # 0 = none, 1 = to VEX, 2 = to VEX flipping REXW
|
|
flexcc: bool = False # Flexible condition code
|
|
|
|
def encode_mnems(entries):
|
|
# mapping from (mnem, opsize, ots) -> (opcode, desc)
|
|
mnemonics = defaultdict(list)
|
|
# Cannot have PAUSE in instrs.txt, because opcodes in without escape must
|
|
# not have mandatory prefixes. For decode, this is hardcoded.
|
|
mnemonics["PAUSE", 0, ""] = [EncodeVariant(Opcode.parse("F3.90"), InstrDesc.parse("NP - - - - NOP"))]
|
|
for weak, opcode, desc in entries:
|
|
if "I64" in desc.flags or desc.mnemonic[:9] == "RESERVED_":
|
|
continue
|
|
mnem_name = {"MOVABS": "MOV", "XCHG_NOP": "XCHG"}.get(desc.mnemonic, desc.mnemonic)
|
|
mnem_name = mnem_name.replace("EVX_", "V")
|
|
|
|
opsizes, vecsizes = {0}, {0}
|
|
prepend_opsize, prepend_vecsize = False, False
|
|
# Where to put the operand size in the mnemonic
|
|
separate_opsize = "ENC_SEPSZ" in desc.flags
|
|
|
|
if "ENC_NOSZ" in desc.flags or not desc.dynsizes():
|
|
pass
|
|
elif OpKind.SZ_OP in desc.dynsizes():
|
|
if opcode.rexw is not None:
|
|
raise Exception(f"unexpected REXW specifier {desc}")
|
|
opsizes = {8} if "SZ8" in desc.flags else {16, 32, 64}
|
|
if opcode.prefix in ("NP", "66", "F2", "F3") and "U66" not in desc.flags:
|
|
opsizes -= {16}
|
|
if "I66" in desc.flags:
|
|
opsizes -= {16}
|
|
if "D64" in desc.flags:
|
|
opsizes -= {32}
|
|
prepend_opsize = not separate_opsize
|
|
if "F64" in desc.flags:
|
|
opsizes = {64}
|
|
prepend_opsize = False
|
|
elif opcode.vex and opcode.vexl != "IG": # vectors; don't care for SSE
|
|
vecsizes = {128, 256, 512} if opcode.vex == 2 else {128, 256}
|
|
if opcode.vexl:
|
|
vecsizes = {128 << int(c) for c in opcode.vexl}
|
|
prepend_vecsize = not separate_opsize
|
|
|
|
# All encoding types; reg is r/k (mask); modrm is r/m/b (broadcast)
|
|
optypes_base = []
|
|
for i, opkind in enumerate(desc.operands):
|
|
reg = "k" if opkind.kind == "MASK" else "r"
|
|
opname = ENCODING_OPORDER[desc.encoding][i]
|
|
if opname == "modrm":
|
|
modrm_type = (opcode.modrm[0] or "rm").replace("r", reg)
|
|
if opcode.extended or desc.mnemonic in ("MOV_CR2G", "MOV_DR2G", "MOV_G2CR", "MOV_G2DR"):
|
|
modrm_type = reg
|
|
if "BCST" in desc.flags:
|
|
modrm_type += "b"
|
|
optypes_base.append(modrm_type)
|
|
elif opname == "modreg" or opname == "vexreg":
|
|
optypes_base.append(reg)
|
|
else:
|
|
optypes_base.append(" iariioo"[ENCODINGS[desc.encoding].imm_control])
|
|
optypes = ["".join(x) for x in product(*optypes_base)]
|
|
|
|
prefixes = [("", "")]
|
|
if "LOCK" in desc.flags:
|
|
prefixes.append(("LOCK_", "LOCK"))
|
|
if "ENC_REP" in desc.flags:
|
|
prefixes.append(("REP_", "F3"))
|
|
if "ENC_REPCC" in desc.flags:
|
|
prefixes.append(("REPNZ_", "F2"))
|
|
prefixes.append(("REPZ_", "F3"))
|
|
|
|
evexmasks = [0]
|
|
if "MASK" in desc.flags:
|
|
if "VSIB" in desc.flags:
|
|
evexmasks = [1]
|
|
else:
|
|
evexmasks.append(1)
|
|
if desc.operands[0].kind != "MASK":
|
|
evexmasks.append(2) # maskz only for non-mask destinations
|
|
evexsaes = [0]
|
|
if "SAE" in desc.flags:
|
|
evexsaes.append(1)
|
|
elif "ER" in desc.flags:
|
|
evexsaes.append(2)
|
|
|
|
keys = (opsizes, vecsizes, prefixes, optypes, evexmasks, evexsaes)
|
|
for opsize, vecsize, prefix, ots, evexmask, evexsae in product(*keys):
|
|
has_memory = "m" in ots or "b" in ots
|
|
if prefix[1] == "LOCK" and ots[0] != "m":
|
|
continue
|
|
if evexmask == 2 and ots[0] != "r":
|
|
continue # EVEX.z must be zero for memory destination
|
|
if evexsae and (vecsize not in (0, 512) or has_memory):
|
|
continue # SAE/ER only works with 512 bit width and no memory
|
|
|
|
spec_opcode = opcode
|
|
if prefix[1]:
|
|
spec_opcode = spec_opcode._replace(prefix=prefix[1])
|
|
if opsize == 64 and "D64" not in desc.flags and "F64" not in desc.flags:
|
|
spec_opcode = spec_opcode._replace(rexw="1")
|
|
if vecsize == 512:
|
|
spec_opcode = spec_opcode._replace(vexl="2")
|
|
if vecsize == 256:
|
|
spec_opcode = spec_opcode._replace(vexl="1")
|
|
if vecsize == 128:
|
|
spec_opcode = spec_opcode._replace(vexl="0")
|
|
if spec_opcode.vexl == "IG":
|
|
spec_opcode = spec_opcode._replace(vexl="0")
|
|
if ENCODINGS[desc.encoding].modrm_idx:
|
|
modrm = ("m" if has_memory else "r",) + spec_opcode.modrm[1:]
|
|
spec_opcode = spec_opcode._replace(modrm=modrm)
|
|
if ENCODINGS[desc.encoding].modrm or None not in opcode.modrm:
|
|
assert spec_opcode.modrm[0] in ("r", "m")
|
|
|
|
evexbcst = "b" in ots
|
|
evexdisp8scale = 0
|
|
if spec_opcode.vex == 2 and has_memory:
|
|
if not evexbcst:
|
|
op = desc.operands[ENCODINGS[desc.encoding].modrm_idx^3]
|
|
size = op.abssize(opsize//8, vecsize//8)
|
|
evexdisp8scale = size.bit_length() - 1
|
|
elif "BCST16" in desc.flags:
|
|
evexdisp8scale = 1
|
|
else:
|
|
evexdisp8scale = 2 if spec_opcode.rexw != "1" else 3
|
|
|
|
# Construct mnemonic name
|
|
name = prefix[0] + mnem_name
|
|
|
|
# Transform MOV_G2X/X2G into MOVD/MOVQ_G2X/X2G. This isn't done for
|
|
# VEX for historical reasons and there's no reason to break
|
|
# backwards compatibility. This enables EVEX->VEX fallback.
|
|
if desc.mnemonic in ("EVX_MOV_G2X", "EVX_MOV_X2G"):
|
|
name = name[:-4] + "DQ"[opsize == 64] + name[-4:]
|
|
prepend_opsize, opsize = False, 0
|
|
# For VMOVD with memory operand, there's no need to be explicit
|
|
# about G2X/X2G, as there's no alternative. For VMOVQ, another
|
|
# opcode exists, so keep G2X/X2G there for distinguishing.
|
|
if name in ("VMOVD_G2X", "VMOVD_X2G") and has_memory:
|
|
name = name.replace("_G2X", "").replace("_X2G", "")
|
|
# PEXTR/PBROADCAST/PINSR are stored without size suffix in the table
|
|
# to avoid having different tables for 32/64 bit mode due to EVEX.W
|
|
# being ignored in 32-bit mode. Add suffix here.
|
|
if desc.mnemonic == "EVX_PEXTR":
|
|
name += " BW D Q"[desc.operands[0].abssize(opsize//8, vecsize//8)]
|
|
prepend_opsize, opsize = False, 0
|
|
if desc.mnemonic == "EVX_PBROADCAST":
|
|
name += " BW D Q"[desc.operands[1].abssize(opsize//8, vecsize//8)]
|
|
name += "_GP"
|
|
prepend_opsize, opsize = False, 0
|
|
if desc.mnemonic == "EVX_PINSR":
|
|
name += " BW D Q"[desc.operands[2].abssize(opsize//8, vecsize//8)]
|
|
prepend_opsize, opsize = False, 0
|
|
|
|
if prepend_opsize and not ("D64" in desc.flags and opsize == 64):
|
|
name += f"_{opsize}"[name[-1] not in "0123456789":]
|
|
if prepend_vecsize:
|
|
name += f"_{vecsize}"[name[-1] not in "0123456789":]
|
|
for ot, op in zip(ots, desc.operands):
|
|
name += ot.replace("o", "")
|
|
if separate_opsize:
|
|
name += f"{op.abssize(opsize//8, vecsize//8)*8}"
|
|
if "VSIB" not in desc.flags:
|
|
# VSIB implies non-zero mask register, so suffix is not required
|
|
name += ["", "_mask", "_maskz"][evexmask]
|
|
name += ["", "_sae", "_er"][evexsae]
|
|
variant = EncodeVariant(spec_opcode, desc, evexbcst, evexmask, evexsae, evexdisp8scale)
|
|
mnemonics[name, opsize, ots].append(variant)
|
|
altname = {
|
|
"C_EX16": "CBW", "C_EX32": "CWDE", "C_EX64": "CDQE",
|
|
"C_SEP16": "CWD", "C_SEP32": "CDQ", "C_SEP64": "CQO",
|
|
"CMPXCHGD32m": "CMPXCHG8Bm", "CMPXCHGD64m": "CMPXCHG16Bm",
|
|
}.get(name)
|
|
if altname:
|
|
mnemonics[altname, opsize, ots].append(variant)
|
|
if "ENC_CC_BEGIN" in desc.flags:
|
|
# Replace last "O" with "cc"
|
|
ccname = "cc".join(name.rsplit("O", 1))
|
|
ccvariant = variant._replace(flexcc=True)
|
|
mnemonics[ccname, opsize, ots].append(ccvariant)
|
|
|
|
for (mnem, opsize, ots), all_variants in mnemonics.items():
|
|
dedup = OrderedDict()
|
|
for i, variant in enumerate(all_variants):
|
|
PRIO = ["O", "OA", "AO", "AM", "MA", "IA", "OI"]
|
|
enc_prio = PRIO.index(variant.desc.encoding) if variant.desc.encoding in PRIO else len(PRIO)
|
|
unique = 0 if variant.desc.encoding != "S" else i
|
|
# Prefer VEX over EVEX for shorter encoding
|
|
key = variant.desc.imm_size(opsize//8), variant.opcode.vex, enc_prio, unique
|
|
if key not in dedup:
|
|
dedup[key] = variant
|
|
variants = [dedup[k] for k in sorted(dedup.keys())]
|
|
if len(variants) > 1 and any(v.opcode.vex for v in variants):
|
|
# Case 1: VEX -> EVEX promotion (AVX-512, APX)
|
|
# Case 2: legacy -> EVEX promotion (APX)
|
|
# In any case, there should be exactly one EVEX opcode.
|
|
if len(variants) != 2:
|
|
raise Exception(f"VEX/EVEX mnemonic with more than two encodings {mnem} {opcode}")
|
|
if variants[0].opcode.vex == 2 or variants[1].opcode.vex != 2:
|
|
raise Exception(f"EVEX mnemonic not with non-EVEX pair {mnem} {opcode} {variants}")
|
|
no_evex, evex = variants[0], variants[1]
|
|
|
|
# Make sure that for promotions, only minor things vary.
|
|
# REX.W is special, EVEX might mandate W1 while VEX mandates W0/WIG.
|
|
# Technically ok: IG -> IG/IG -> 0/0 -> IG/0 -> 0/1 -> IG/1 -> 1
|
|
# rexwdowngrade = (no_evex.opcode.rexw is None or
|
|
# no_evex.opcode.rexw == evex.opcode.rexw)
|
|
#
|
|
# However, other encoders always use W0 in case of WIG for VEX, and
|
|
# that's probably most beneficial... so:
|
|
# Possible downgrades: IG -> IG/IG -> 0/0 -> IG/0 -> 0/1 -> 1
|
|
# This affects quite a few instructions, so we use an extra bit to
|
|
# flip EVEX.W to VEX.W.
|
|
|
|
if (no_evex.opcode.prefix != evex.opcode.prefix or
|
|
no_evex.opcode.escape != evex.opcode.escape or
|
|
no_evex.opcode.opc != evex.opcode.opc or
|
|
# reg/mem doesn't matter, it's already fixed in the mnemonic
|
|
no_evex.opcode.modrm[1:] != evex.opcode.modrm[1:] or
|
|
no_evex.opcode.vexl != evex.opcode.vexl or
|
|
# we don't check rexw_flip here, we can always handle it
|
|
no_evex.desc.encoding != evex.desc.encoding or
|
|
no_evex.desc.operands != evex.desc.operands):
|
|
print(mnem, no_evex)
|
|
print(mnem, evex)
|
|
# Should not happen.
|
|
raise Exception("cannot downgrade EVEX?")
|
|
else:
|
|
rexw_flip = (no_evex.opcode.rexw == "1") != (evex.opcode.rexw == "1")
|
|
variants = [evex._replace(downgrade=1 if not rexw_flip else 2)]
|
|
mnemonics[mnem, opsize, ots] = variants
|
|
|
|
return dict(mnemonics)
|
|
|
|
def encode_table(entries, args):
|
|
mnemonics = encode_mnems(entries)
|
|
mnemonics["NOP", 0, ""] = [EncodeVariant(Opcode.parse("90"), InstrDesc.parse("NP - - - - NOP"))]
|
|
mnem_map = {}
|
|
alt_table = [0] # first entry is unused
|
|
for (mnem, opsize, ots), variants in mnemonics.items():
|
|
supports_high_regs = []
|
|
if variants[0][1].mnemonic in ("MOVSX", "MOVZX") or opsize == 8:
|
|
# Should be the same for all variants
|
|
desc = variants[0][1]
|
|
for i, (ot, op) in enumerate(zip(ots, desc.operands)):
|
|
if ot == "r" and op.kind == "GP" and op.abssize(opsize//8) == 1:
|
|
supports_high_regs.append(i)
|
|
|
|
alt_indices = [i + len(alt_table) for i in range(len(variants) - 1)] + [0]
|
|
|
|
if variants[0][1].encoding == "D":
|
|
assert 1 <= len(variants) <= 2
|
|
# We handle jump (jmp/jcc) alternatives in code to support Jcc.
|
|
if len(variants) > 1:
|
|
assert variants[0][1].mnemonic[:1] == "J"
|
|
variants = variants[:1]
|
|
alt_indices = [0xff]
|
|
|
|
enc_opcs = []
|
|
for alt, variant in zip(alt_indices, variants):
|
|
opcode, desc = variant.opcode, variant.desc
|
|
encoding = ENCODINGS[desc.encoding]
|
|
opc_i = opcode.opc
|
|
if None not in opcode.modrm:
|
|
opc_i |= 0xc000 | opcode.modrm[1] << 11 | opcode.modrm[2] << 8
|
|
elif opcode.modrm[1] is not None:
|
|
opc_i |= opcode.modrm[1] << 8
|
|
if opcode.modrm == ("m", None, 4):
|
|
opc_i |= 0x2000000000 # FORCE_SIB
|
|
if not opcode.vex:
|
|
assert opcode.escape < 4
|
|
opc_i |= opcode.escape * 0x10000
|
|
opc_i |= 0x80000 if opcode.prefix == "66" or opsize == 16 else 0
|
|
opc_i |= 0x100000 if opcode.prefix == "F2" else 0
|
|
opc_i |= 0x200000 if opcode.prefix == "F3" else 0
|
|
else:
|
|
assert opcode.escape < 8
|
|
opc_i |= opcode.escape * 0x10000
|
|
if opcode.prefix == "66" or opsize == 16:
|
|
assert opcode.prefix not in ("F2", "F3")
|
|
opc_i |= 0x100000
|
|
if opcode.prefix == "F3":
|
|
opc_i |= 0x200000
|
|
elif opcode.prefix == "F2":
|
|
opc_i |= 0x300000
|
|
opc_i |= 0x400000 if opcode.rexw == "1" else 0
|
|
if opcode.prefix == "LOCK":
|
|
opc_i |= 0x800000
|
|
elif opcode.vex == 1:
|
|
opc_i |= 0x1000000 + 0x800000 * int(opcode.vexl or 0)
|
|
elif opcode.vex == 2:
|
|
opc_i |= 0x2000000
|
|
# L'L encodes SAE rounding mode otherwise
|
|
if not variant.evexsae:
|
|
opc_i |= 0x800000 * int(opcode.vexl or 0)
|
|
assert not (variant.evexsae and variant.evexbcst)
|
|
opc_i |= 0x4000000 if variant.evexsae or variant.evexbcst else 0
|
|
opc_i |= 0x8000000 if "VSIB" in desc.flags else 0
|
|
opc_i |= 0x1000000000 if variant.evexmask == 2 else 0
|
|
opc_i |= 0x4000000000 if variant.downgrade in (1, 2) else 0
|
|
opc_i |= 0x40000000000 if variant.downgrade == 2 else 0
|
|
opc_i |= 0x8000000000 * variant.evexdisp8scale
|
|
if alt >= 0x100:
|
|
raise Exception("encode alternate bits exhausted")
|
|
opc_i |= sum(1 << i for i in supports_high_regs) << 45
|
|
if encoding.imm_control >= 3:
|
|
opc_i |= desc.imm_size(opsize//8) << 47
|
|
elif encoding.imm_control in (1, 2):
|
|
# Must be an arbitrary non-zero value, replaced by address size
|
|
# for imm_ctl=2 and zero for imm_ctl=1 (constant 1).
|
|
opc_i |= 1 << 47
|
|
|
|
enc_encoding = desc.encoding
|
|
if desc.encoding != "I" and desc.encoding.endswith("I"):
|
|
enc_encoding = desc.encoding[:-1]
|
|
elif desc.encoding == "IA":
|
|
enc_encoding = "A"
|
|
opc_i |= ["NP", "M", "R", "M1", "MC", "MR", "RM", "RMA", "MRC",
|
|
"AM", "MA", "I", "O", "OA", "S", "A", "D", "FD", "TD", "IM",
|
|
"RVM", "RVMR", "RMV", "VM", "MVR", "MRV",
|
|
].index(enc_encoding) << 51
|
|
opc_i |= alt << 56
|
|
enc_opcs.append(opc_i)
|
|
mnem_map[f"FE_{mnem}"] = enc_opcs[0]
|
|
alt_table += enc_opcs[1:]
|
|
|
|
mnem_tab = "".join(f"#define {m} {v:#x}\n" for m, v in mnem_map.items())
|
|
alt_tab = "".join(f"[{i}] = {v:#x},\n" for i, v in enumerate(alt_table))
|
|
return mnem_tab, alt_tab
|
|
|
|
def unique(it):
|
|
vals = set(it)
|
|
if len(vals) != 1:
|
|
raise Exception(f"multiple values: {vals}")
|
|
return next(iter(vals))
|
|
|
|
def encode2_gen_legacy(variant: EncodeVariant, opsize: int, supports_high_regs: list[int], imm_expr: str, imm_size_expr: str, has_idx: bool) -> str:
|
|
opcode = variant.opcode
|
|
desc = variant.desc
|
|
flags = ENCODINGS[variant.desc.encoding]
|
|
code = ""
|
|
|
|
rex_expr = [] if opcode.rexw != "1" else ["0x48"]
|
|
rex_values = set()
|
|
for i in supports_high_regs:
|
|
rex_expr.append(f"(op_reg_idx(op{i}) >= 4 && op_reg_idx(op{i}) <= 15?0x40:0)")
|
|
rex_values.add(0x40)
|
|
has_modreg_rex = False
|
|
if flags.modreg_idx:
|
|
has_modreg_rex = desc.operands[flags.modreg_idx^3].kind in ("GP", "XMM", "CR", "DR")
|
|
if flags.modrm_idx:
|
|
if opcode.modrm[0] == "m":
|
|
rex_modreg_op = f"op_reg_idx(op{flags.modreg_idx^3})" if has_modreg_rex else "0"
|
|
rex_expr.append(f"enc_rex_mem(op{flags.modrm_idx^3}, {rex_modreg_op})")
|
|
rex_values |= {0x41, 0x42, 0x44}
|
|
elif desc.operands[flags.modrm_idx^3].kind in ("GP", "XMM"):
|
|
rex_expr.append(f"(op_reg_idx(op{flags.modrm_idx^3})&8?0x1:0)")
|
|
rex_values.add(0x41)
|
|
if has_modreg_rex:
|
|
rex_expr.append(f"(op_reg_idx(op{flags.modreg_idx^3})&8?0x4:0)")
|
|
rex_values.add(0x44)
|
|
elif has_modreg_rex: # O encoding
|
|
rex_expr.append(f"(op_reg_idx(op{flags.modreg_idx^3})&8?0x1:0)")
|
|
rex_values.add(0x41)
|
|
|
|
if rex_expr:
|
|
code += f" unsigned rex = {'|'.join(rex_expr) or '0'};\n"
|
|
for i in supports_high_regs:
|
|
code += f" if (rex && op_reg_gph(op{i})) return 0;\n"
|
|
|
|
if not has_idx:
|
|
code += " unsigned idx = 0;\n"
|
|
if opcode.prefix == "LOCK":
|
|
code += f" buf[idx++] = 0xF0;\n"
|
|
if opsize == 16 or opcode.prefix == "66":
|
|
code += " buf[idx++] = 0x66;\n"
|
|
if opcode.prefix in ("F2", "F3"):
|
|
code += f" buf[idx++] = 0x{opcode.prefix};\n"
|
|
if opcode.rexw == "1":
|
|
code += f" buf[idx++] = rex;\n"
|
|
elif len(rex_values) == 1:
|
|
code += f" buf[idx] = {next(iter(rex_values))};\n idx += rex != 0;\n"
|
|
elif len(rex_values) == 2:
|
|
code += f" buf[idx] = 0x40|rex;\n idx += rex != 0;\n"
|
|
elif rex_expr: # memory, multiplication is expensive
|
|
code += f" if (rex) buf[idx++] = 0x40|rex;\n"
|
|
if opcode.escape:
|
|
code += f" buf[idx++] = 0x0F;\n"
|
|
if opcode.escape == 2:
|
|
code += f" buf[idx++] = 0x38;\n"
|
|
elif opcode.escape == 3:
|
|
code += f" buf[idx++] = 0x3A;\n"
|
|
opcodestr = f"{opcode.opc:#x}" + ("|(flags>>16)" if variant.flexcc else "")
|
|
code += f" buf[idx++] = {opcodestr};\n"
|
|
if None not in opcode.modrm:
|
|
opcext = 0xc0 | opcode.modrm[1] << 3 | opcode.modrm[2]
|
|
code += f" buf[idx++] = {opcext:#x};\n"
|
|
|
|
if flags.modrm:
|
|
if flags.modreg_idx:
|
|
modreg = f"op_reg_idx(op{flags.modreg_idx^3})"
|
|
else:
|
|
modreg = opcode.modrm[1] or 0
|
|
if opcode.modrm[0] == "m":
|
|
assert "VSIB" not in desc.flags
|
|
assert opcode.modrm[2] is None
|
|
modrm = f"op{flags.modrm_idx^3}"
|
|
code += f" idx = enc_mem(buf+idx, idx+{imm_size_expr}, {modrm}, {modreg}, 0, 0);\n"
|
|
code += f" if (!idx) return 0;\n idx -= {imm_size_expr};\n"
|
|
else:
|
|
if flags.modrm_idx:
|
|
modrm = f"op_reg_idx(op{flags.modrm_idx^3})"
|
|
else:
|
|
modrm = f"{opcode.modrm[2] or 0}"
|
|
code += f" buf[idx++] = 0xC0|({modreg}<<3)|({modrm}&7);\n"
|
|
elif flags.modrm_idx:
|
|
code += f" buf[idx-1] |= op_reg_idx(op{flags.modrm_idx^3}) & 7;\n"
|
|
if flags.imm_control >= 2:
|
|
if flags.imm_control == 6:
|
|
imm_expr += " - idx"
|
|
code += f" enc_imm(buf+idx, {imm_expr}, {imm_size_expr});\n"
|
|
code += f" return idx + {imm_size_expr};\n"
|
|
else:
|
|
code += f" return idx;\n"
|
|
return code
|
|
|
|
def encode2_gen_vex(variant: EncodeVariant, imm_expr: str, imm_size_expr: str, has_idx: bool) -> str:
|
|
opcode = variant.opcode
|
|
flags = ENCODINGS[variant.desc.encoding]
|
|
code = ""
|
|
|
|
helperopc = opcode.opc << 16
|
|
helperopc |= ["NP", "66", "F3", "F2"].index(opcode.prefix) << 8
|
|
helperopc |= 0x8000 if opcode.rexw == "1" else 0
|
|
if not variant.evexsae:
|
|
# ER: L'L encodes rounding mode for SAE
|
|
helperopc |= 0x0020 * int(opcode.vexl or 0) # EVEX.L'L
|
|
helperopc |= opcode.escape << 10
|
|
helperopc |= 0x10 if variant.evexsae or variant.evexbcst else 0 # EVEX.b
|
|
helperopc |= 0x80 if variant.evexmask == 2 else 0 # EVEX.z
|
|
helperopc |= 0x1000000 if variant.downgrade in (1, 2) else 0
|
|
helperopc |= 0x2000000 if variant.downgrade == 2 else 0
|
|
helperopc = f"{helperopc:#x}"
|
|
if variant.flexcc:
|
|
helperopc += "|(flags&FE_CC_MASK)"
|
|
if variant.evexsae == 2:
|
|
helperopc += "|(flags&FE_RC_MASK)"
|
|
if variant.evexmask:
|
|
code += " if (!op_reg_idx(opmask)) return 0;\n"
|
|
helperopc += "|(op_reg_idx(opmask)&7)"
|
|
|
|
if flags.modreg_idx:
|
|
modreg = f"op_reg_idx(op{flags.modreg_idx^3})"
|
|
else:
|
|
modreg = opcode.modrm[1] or 0
|
|
vexop = f"op_reg_idx(op{flags.vexreg_idx^3})" if flags.vexreg_idx else 0
|
|
is_memory = opcode.modrm[0] == "m"
|
|
if not flags.modrm and opcode.modrm == (None, None, None):
|
|
# No ModRM, prefix only (VZEROUPPER/VZEROALL)
|
|
assert opcode.vex == 1
|
|
assert not has_idx
|
|
helperfn, helperargs = "enc_vex_common", f"0, 0, 0, 0"
|
|
elif opcode.modrm[0] == "m":
|
|
vsib = "VSIB" in variant.desc.flags
|
|
helperfn = "enc" + ["", "_vex", "_evex"][opcode.vex] + ["_mem", "_vsib"][vsib]
|
|
assert opcode.modrm[2] in (None, 4)
|
|
forcesib = 1 if opcode.modrm[2] == 4 else 0 # AMX
|
|
modrm = f"op{flags.modrm_idx^3}"
|
|
ripoff = imm_size_expr + ("" if not has_idx else "+idx")
|
|
helperargs = (f"{modrm}, {modreg}, {vexop}, {ripoff}, " +
|
|
f"{forcesib}, {variant.evexdisp8scale}")
|
|
else:
|
|
if flags.modrm_idx:
|
|
modrm = f"op_reg_idx(op{flags.modrm_idx^3})"
|
|
else:
|
|
modrm = f"{opcode.modrm[2] or 0}"
|
|
suffix = "_reg"
|
|
if (opcode.vex == 2 and flags.modrm_idx and
|
|
variant.desc.operands[flags.modrm_idx^3].kind == "XMM"):
|
|
suffix = "_xmm"
|
|
helperfn = "enc" + ["", "_vex", "_evex"][opcode.vex] + suffix
|
|
helperargs = f"{modrm}, {modreg}, {vexop}"
|
|
bufidx = "buf" if not has_idx else "buf+idx"
|
|
helpercall = f"{helperfn}({bufidx}, {helperopc}, {helperargs})"
|
|
if flags.imm_control >= 2:
|
|
assert flags.imm_control < 6, "jmp with VEX/EVEX?"
|
|
if is_memory:
|
|
code += f" unsigned instlen = {helpercall};\n"
|
|
code += f" if (instlen) enc_imm(buf+instlen-{imm_size_expr}, {imm_expr}, {imm_size_expr});\n"
|
|
code += f" return instlen;\n"
|
|
else:
|
|
code += f" unsigned vexoff = {helpercall};\n"
|
|
code += f" enc_imm({bufidx}+vexoff, {imm_expr}, {imm_size_expr});\n"
|
|
code += f" return vexoff ? vexoff+{imm_size_expr}{'+idx' if has_idx else ''} : 0;\n"
|
|
elif has_idx and not is_memory:
|
|
code += f" unsigned vexoff = {helpercall};\n"
|
|
code += f" return vexoff ? vexoff+idx : 0;\n"
|
|
else:
|
|
code += f" return {helpercall};\n"
|
|
return code
|
|
|
|
def encode2_table(entries, args):
|
|
mnemonics = encode_mnems(entries)
|
|
|
|
enc_decls, enc_code = "", ""
|
|
for (mnem, opsize, ots), variants in mnemonics.items():
|
|
max_imm_size = max(v.desc.imm_size(opsize//8) for v in variants)
|
|
|
|
supports_high_regs = []
|
|
if variants[0].desc.mnemonic in ("MOVSX", "MOVZX") or opsize == 8:
|
|
# Should be the same for all variants
|
|
for i, (ot, op) in enumerate(zip(ots, variants[0].desc.operands)):
|
|
if ot == "r" and op.kind == "GP" and op.abssize(opsize//8) == 1:
|
|
supports_high_regs.append(i)
|
|
supports_vsib = unique("VSIB" in v.desc.flags for v in variants)
|
|
opkinds = unique(tuple(op.kind for op in v.desc.operands) for v in variants)
|
|
evexmask = unique(v.evexmask for v in variants)
|
|
evexsae = unique(v.evexsae for v in variants)
|
|
|
|
OPKIND_LUT = {"FPU": "ST", "SEG": "SREG", "MMX": "MM"}
|
|
reg_tys = [OPKIND_LUT.get(opkind, opkind) for opkind in opkinds]
|
|
|
|
fnname = f"fe64_{mnem}"
|
|
op_tys = [{
|
|
"i": f"int{max_imm_size*8 if max_imm_size != 3 else 32}_t",
|
|
"a": "uintptr_t",
|
|
"r": f"FeReg{reg_ty if i not in supports_high_regs else 'GPLH'}",
|
|
"k": "FeRegMASK",
|
|
"m": "FeMem" if not supports_vsib else "FeMemV",
|
|
"b": "FeMem",
|
|
"o": "const void*",
|
|
}[ot] for i, (ot, reg_ty) in enumerate(zip(ots, reg_tys))]
|
|
fn_opargs = ", FeRegMASK opmask" if evexmask else ""
|
|
fn_opargs += "".join(f", {ty} op{i}" for i, ty in enumerate(op_tys))
|
|
fn_sig = f"unsigned ({fnname})(uint8_t* buf, int flags{fn_opargs})"
|
|
enc_decls += f"{fn_sig};\n"
|
|
if supports_high_regs:
|
|
enc_decls += f"#define fe64_{mnem}(buf, flags"
|
|
enc_decls += "".join(f", op{i}" for i in range(len(op_tys)))
|
|
enc_decls += f") {fnname}(buf, flags"
|
|
enc_decls += "".join(f", FE_MAKE_GPLH(op{i})" if i in supports_high_regs else f", op{i}" for i in range(len(op_tys)))
|
|
enc_decls += f")\n"
|
|
|
|
code = f"{fn_sig} {{\n"
|
|
|
|
has_memory = unique(v.opcode.modrm[0] == "m" for v in variants)
|
|
has_useg = unique("USEG" in v.desc.flags for v in variants)
|
|
has_u67 = unique("U67" in v.desc.flags for v in variants)
|
|
if has_memory or has_useg:
|
|
# segment override without addrsize override shouldn't happen
|
|
assert has_memory or has_u67
|
|
code += f" unsigned idx = UNLIKELY(flags & (FE_SEG_MASK|FE_ADDR32)) ? enc_seg67(buf, flags) : 0;\n"
|
|
elif has_u67:
|
|
# STOS, SCAS, JCXZ, LOOP, LOOPcc
|
|
code += f" unsigned idx = UNLIKELY(flags & FE_ADDR32) ? (*buf=0x67, 1) : 0;\n"
|
|
else:
|
|
code += " (void) flags;\n"
|
|
|
|
# indicate whether an idx variable exists
|
|
has_idx = has_memory or has_useg or has_u67
|
|
|
|
for i, variant in enumerate(variants):
|
|
opcode, desc = variant.opcode, variant.desc
|
|
flags = ENCODINGS[desc.encoding]
|
|
|
|
conds = []
|
|
# Select usable encoding.
|
|
if desc.encoding == "S":
|
|
# Segment encoding is weird.
|
|
conds.append(f"op_reg_idx(op0)=={(opcode.opc>>3)&0x7:#x}")
|
|
if desc.mnemonic == "XCHG_NOP" and opsize == 32:
|
|
# XCHG eax, eax must not be encoded as 90 -- that'd be NOP.
|
|
conds.append(f"!(op_reg_idx(op0)==0&&op_reg_idx(op1)==0)")
|
|
if flags.vexreg_idx and not opcode.vex: # vexreg w/o vex is zeroreg
|
|
conds.append(f"op_reg_idx(op{flags.vexreg_idx^3})=={flags.zeroreg_val}")
|
|
|
|
imm_size = desc.imm_size(opsize//8)
|
|
imm_size_expr = f"{imm_size}"
|
|
imm_expr = f"(int64_t) op{flags.imm_idx^3}"
|
|
if flags.imm_control == 1:
|
|
conds.append(f"op{flags.imm_idx^3} == 1")
|
|
elif flags.imm_control == 2:
|
|
imm_size_expr = "(flags & FE_ADDR32 ? 4 : 8)"
|
|
imm_expr = f"(int64_t) (flags & FE_ADDR32 ? (int32_t) {imm_expr} : {imm_expr})"
|
|
elif flags.imm_control == 3:
|
|
imm_expr = f"op_reg_idx(op{flags.imm_idx^3}) << 4"
|
|
code += f" if (op_reg_idx(op{flags.imm_idx^3}) >= 16) return 0;\n"
|
|
elif flags.imm_control == 4 and imm_size == 3: # ENTER
|
|
code += f" if ((uint32_t) op{flags.imm_idx^3} >= 0x1000000) return 0;\n"
|
|
elif flags.imm_control == 4 and imm_size < max_imm_size:
|
|
conds.append(f"op_imm_n({imm_expr}, {imm_size})")
|
|
elif flags.imm_control == 6:
|
|
imm_expr = f"{imm_expr} - (int64_t) buf - {imm_size}"
|
|
if i != len(variants) - 1: # only Jcc+JMP
|
|
conds.append(f"!(flags & FE_JMPL)")
|
|
# assume one-byte opcode without escape/prefixes
|
|
conds.append(f"op_imm_n({imm_expr}-1, {imm_size})")
|
|
|
|
if conds:
|
|
code += f" if ({'&&'.join(conds)}) {{\n"
|
|
|
|
if opcode.vex:
|
|
code += encode2_gen_vex(variant, imm_expr, imm_size_expr, has_idx)
|
|
else:
|
|
code += encode2_gen_legacy(variant, opsize, supports_high_regs, imm_expr, imm_size_expr, has_idx)
|
|
|
|
if conds:
|
|
code += " }\n"
|
|
else:
|
|
break
|
|
else:
|
|
code += " return 0;\n"
|
|
|
|
enc_code += code + "}\n"
|
|
|
|
return enc_decls, enc_code
|
|
|
|
|
|
if __name__ == "__main__":
|
|
generators = {
|
|
"decode": decode_table,
|
|
"encode": encode_table,
|
|
"encode2": encode2_table,
|
|
}
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--32", dest="modes", action="append_const", const=32)
|
|
parser.add_argument("--64", dest="modes", action="append_const", const=64)
|
|
parser.add_argument("--with-undoc", action="store_true")
|
|
parser.add_argument("--stats", action="store_true")
|
|
parser.add_argument("mode", choices=generators.keys())
|
|
parser.add_argument("table", type=argparse.FileType('r'))
|
|
parser.add_argument("out_public", type=argparse.FileType('w'))
|
|
parser.add_argument("out_private", type=argparse.FileType('w'))
|
|
args = parser.parse_args()
|
|
|
|
entries = []
|
|
for line in args.table.read().splitlines():
|
|
if not line or line[0] == "#": continue
|
|
line, weak = (line, False) if line[0] != "*" else (line[1:], True)
|
|
opcode_string, desc_string = tuple(line.split(maxsplit=1))
|
|
opcode, desc = Opcode.parse(opcode_string), InstrDesc.parse(desc_string)
|
|
verifyOpcodeDesc(opcode, desc)
|
|
if "UNDOC" not in desc.flags or args.with_undoc:
|
|
entries.append((weak, opcode, desc))
|
|
|
|
res_public, res_private = generators[args.mode](entries, args)
|
|
args.out_public.write(res_public)
|
|
args.out_private.write(res_private)
|