use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Gen {
I8086,
I386,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Arg {
None,
Eb,
Ev,
Ew,
Gb,
Gv,
Gw,
Sw,
M,
Mp,
Ms,
Rd,
Cd,
Dd,
Td,
Ib,
Iw,
Iv,
Ibs,
Jb,
Jv,
Ap,
Ob,
Ov,
Rb,
Rv,
Sr,
One,
Cl,
Dx,
Al,
Ax,
Xb,
Xv,
Yb,
Yv,
}
impl Arg {
#[must_use]
pub const fn width_bytes(self, osz: u8) -> Option<u8> {
match self {
Arg::Eb
| Arg::Gb
| Arg::Ib
| Arg::Ob
| Arg::Rb
| Arg::Al
| Arg::Xb
| Arg::Yb
| Arg::Jb
| Arg::One
| Arg::Cl => Some(1),
Arg::Ew | Arg::Gw | Arg::Iw | Arg::Sw | Arg::Sr => Some(2),
Arg::Cd | Arg::Dd | Arg::Td | Arg::Rd => Some(4),
Arg::Ev
| Arg::Gv
| Arg::Iv
| Arg::Ibs
| Arg::Ov
| Arg::Rv
| Arg::Ax
| Arg::Xv
| Arg::Yv
| Arg::Jv => Some(osz),
Arg::None | Arg::M | Arg::Mp | Arg::Ms | Arg::Ap | Arg::Dx => None,
}
}
#[must_use]
pub const fn needs_modrm(self) -> bool {
matches!(
self,
Arg::Eb
| Arg::Ev
| Arg::Ew
| Arg::Gb
| Arg::Gv
| Arg::Gw
| Arg::Sw
| Arg::M
| Arg::Mp
| Arg::Ms
| Arg::Rd
| Arg::Cd
| Arg::Dd
| Arg::Td
)
}
#[must_use]
pub const fn immediate_bytes(self, osz: u8, asz: u8) -> u8 {
match self {
Arg::Ib | Arg::Ibs | Arg::Jb => 1,
Arg::Iw => 2,
Arg::Iv | Arg::Jv => osz,
Arg::Ob | Arg::Ov => asz,
Arg::Ap => osz + 2,
_ => 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Class {
Documented,
Alias,
Undocumented,
Undefined,
Prefix,
Escape,
}
impl Class {
#[must_use]
pub const fn is_documented(self) -> bool {
matches!(self, Class::Documented)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Grp {
None,
Alu,
Shift,
Pop,
IncDec,
Unary,
Misc,
MovImm,
Grp6,
Grp7,
Grp8,
}
macro_rules! define_ops {
($($name:ident = $summary:literal,)*) => {
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Op {
$(
#[doc = $summary]
$name,
)*
}
impl Op {
#[must_use]
pub const fn summary(self) -> &'static str {
match self { $(Op::$name => $summary,)* }
}
pub const ALL: &'static [Op] = &[$(Op::$name,)*];
}
const MNEMONICS: &[&str] = &[$(lower(stringify!($name)),)*];
};
}
const fn lower(name: &'static str) -> &'static str {
let bytes = name.as_bytes();
let mut i = 0;
while i < LOWERCASE.len() {
let cand = LOWERCASE[i].0.as_bytes();
if cand.len() == bytes.len() {
let mut j = 0;
let mut same = true;
while j < bytes.len() {
if cand[j] != bytes[j] {
same = false;
break;
}
j += 1;
}
if same {
return LOWERCASE[i].1;
}
}
i += 1;
}
panic!("mnemonic missing from LOWERCASE");
}
const LOWERCASE: &[(&str, &str)] = &[
("AAA", "aaa"),
("AAD", "aad"),
("AAM", "aam"),
("AAS", "aas"),
("ADC", "adc"),
("ADD", "add"),
("AND", "and"),
("CALL", "call"),
("CALLF", "callf"),
("CBW", "cbw"),
("CLC", "clc"),
("CLD", "cld"),
("CLI", "cli"),
("CMC", "cmc"),
("CMP", "cmp"),
("CMPSB", "cmpsb"),
("CMPSW", "cmpsw"),
("CWD", "cwd"),
("DAA", "daa"),
("DAS", "das"),
("DEC", "dec"),
("DIV", "div"),
("ESC", "esc"),
("HLT", "hlt"),
("IDIV", "idiv"),
("IMUL", "imul"),
("IN", "in"),
("INC", "inc"),
("INT", "int"),
("INT3", "int3"),
("INTO", "into"),
("IRET", "iret"),
("JA", "ja"),
("JB", "jb"),
("JBE", "jbe"),
("JCXZ", "jcxz"),
("JG", "jg"),
("JGE", "jge"),
("JL", "jl"),
("JLE", "jle"),
("JMP", "jmp"),
("JMPF", "jmpf"),
("JNB", "jnb"),
("JNO", "jno"),
("JNP", "jnp"),
("JNS", "jns"),
("JNZ", "jnz"),
("JO", "jo"),
("JP", "jp"),
("JS", "js"),
("JZ", "jz"),
("LAHF", "lahf"),
("LDS", "lds"),
("LEA", "lea"),
("LES", "les"),
("LOCK", "lock"),
("LODSB", "lodsb"),
("LODSW", "lodsw"),
("LOOP", "loop"),
("LOOPE", "loope"),
("LOOPNE", "loopne"),
("MOV", "mov"),
("MOVSB", "movsb"),
("MOVSW", "movsw"),
("MUL", "mul"),
("NEG", "neg"),
("NOP", "nop"),
("NOT", "not"),
("OR", "or"),
("OUT", "out"),
("POP", "pop"),
("POPF", "popf"),
("PUSH", "push"),
("PUSHF", "pushf"),
("RCL", "rcl"),
("RCR", "rcr"),
("REP", "rep"),
("REPNE", "repne"),
("RET", "ret"),
("RETF", "retf"),
("ROL", "rol"),
("ROR", "ror"),
("SAHF", "sahf"),
("SALC", "salc"),
("SAR", "sar"),
("SBB", "sbb"),
("SCASB", "scasb"),
("SCASW", "scasw"),
("SEG", "seg"),
("SETMO", "setmo"),
("SHL", "shl"),
("SHR", "shr"),
("STC", "stc"),
("STD", "std"),
("STI", "sti"),
("STOSB", "stosb"),
("STOSW", "stosw"),
("SUB", "sub"),
("TEST", "test"),
("WAIT", "wait"),
("XCHG", "xchg"),
("XLAT", "xlat"),
("XOR", "xor"),
("ARPL", "arpl"),
("BOUND", "bound"),
("BSF", "bsf"),
("BSR", "bsr"),
("BSWAP", "bswap"),
("BT", "bt"),
("BTC", "btc"),
("BTR", "btr"),
("BTS", "bts"),
("CLTS", "clts"),
("CMPXCHG", "cmpxchg"),
("CPUID", "cpuid"),
("ENTER", "enter"),
("ICEBP", "icebp"),
("INSB", "insb"),
("INSW", "insw"),
("INVD", "invd"),
("INVLPG", "invlpg"),
("LAR", "lar"),
("LEAVE", "leave"),
("LFS", "lfs"),
("LGDT", "lgdt"),
("LGS", "lgs"),
("LIDT", "lidt"),
("LLDT", "lldt"),
("LMSW", "lmsw"),
("LSL", "lsl"),
("LSS", "lss"),
("LTR", "ltr"),
("MOVSX", "movsx"),
("MOVZX", "movzx"),
("OUTSB", "outsb"),
("OUTSW", "outsw"),
("POPA", "popa"),
("PUSHA", "pusha"),
("SETA", "seta"),
("SETB", "setb"),
("SETBE", "setbe"),
("SETG", "setg"),
("SETGE", "setge"),
("SETL", "setl"),
("SETLE", "setle"),
("SETNB", "setnb"),
("SETNO", "setno"),
("SETNP", "setnp"),
("SETNS", "setns"),
("SETNZ", "setnz"),
("SETO", "seto"),
("SETP", "setp"),
("SETS", "sets"),
("SETZ", "setz"),
("SGDT", "sgdt"),
("SHLD", "shld"),
("SHRD", "shrd"),
("SIDT", "sidt"),
("SLDT", "sldt"),
("SMSW", "smsw"),
("STR", "str"),
("UD", "ud"),
("VERR", "verr"),
("VERW", "verw"),
("WBINVD", "wbinvd"),
("XADD", "xadd"),
];
define_ops! {
AAA = "ASCII adjust AL after addition",
AAD = "ASCII adjust AX before division",
AAM = "ASCII adjust AX after multiplication",
AAS = "ASCII adjust AL after subtraction",
ADC = "add with carry",
ADD = "add",
AND = "bitwise AND",
CALL = "call a near procedure",
CALLF = "call a far procedure",
CBW = "sign-extend AL into AX",
CLC = "clear the carry flag",
CLD = "clear the direction flag",
CLI = "clear the interrupt-enable flag",
CMC = "complement the carry flag",
CMP = "compare by subtracting and discarding the result",
CMPSB = "compare a byte at DS:SI with one at ES:DI",
CMPSW = "compare a word at DS:SI with one at ES:DI",
CWD = "sign-extend AX into DX:AX",
DAA = "decimal adjust AL after addition",
DAS = "decimal adjust AL after subtraction",
DEC = "decrement by one, leaving the carry flag alone",
DIV = "unsigned divide",
ESC = "coprocessor escape: read the operand and ignore it",
HLT = "halt until an interrupt or reset",
IDIV = "signed divide",
IMUL = "signed multiply",
IN = "read a byte or word from an I/O port",
INC = "increment by one, leaving the carry flag alone",
INT = "software interrupt",
INT3 = "breakpoint interrupt (the one-byte encoding of INT 3)",
INTO = "interrupt 4 if the overflow flag is set",
IRET = "return from an interrupt, restoring the flags",
JA = "jump if above (unsigned greater)",
JB = "jump if below (unsigned less; carry set)",
JBE = "jump if below or equal",
JCXZ = "jump if CX is zero",
JG = "jump if greater (signed)",
JGE = "jump if greater or equal (signed)",
JL = "jump if less (signed)",
JLE = "jump if less or equal (signed)",
JMP = "jump",
JMPF = "jump to a far address",
JNB = "jump if not below (carry clear)",
JNO = "jump if the overflow flag is clear",
JNP = "jump if parity odd",
JNS = "jump if the sign flag is clear",
JNZ = "jump if not equal (zero clear)",
JO = "jump if the overflow flag is set",
JP = "jump if parity even",
JS = "jump if the sign flag is set",
JZ = "jump if equal (zero set)",
LAHF = "load the low flags byte into AH",
LDS = "load a far pointer into DS and a register",
LEA = "load the effective address, without accessing memory",
LES = "load a far pointer into ES and a register",
LOCK = "prefix: assert the LOCK pin for the next instruction",
LODSB = "load a byte from DS:SI into AL",
LODSW = "load a word from DS:SI into AX",
LOOP = "decrement CX and jump if it is not zero",
LOOPE = "decrement CX and jump if it is not zero and the zero flag is set",
LOOPNE = "decrement CX and jump if it is not zero and the zero flag is clear",
MOV = "move",
MOVSB = "move a byte from DS:SI to ES:DI",
MOVSW = "move a word from DS:SI to ES:DI",
MUL = "unsigned multiply",
NEG = "two's complement negate",
NOP = "no operation (the XCHG AX,AX encoding)",
NOT = "one's complement",
OR = "bitwise OR",
OUT = "write a byte or word to an I/O port",
POP = "pop a word from the stack",
POPF = "pop the flags register",
PUSH = "push a word onto the stack",
PUSHF = "push the flags register",
RCL = "rotate left through the carry flag",
RCR = "rotate right through the carry flag",
REP = "prefix: repeat a string operation while CX is non-zero (and, for CMPS/SCAS, while equal)",
REPNE = "prefix: repeat a string operation while CX is non-zero and not equal",
RET = "return from a near procedure",
RETF = "return from a far procedure",
ROL = "rotate left",
ROR = "rotate right",
SAHF = "store AH into the low flags byte",
SALC = "undocumented: set AL to 0xff if the carry flag is set, else 0",
SAR = "arithmetic shift right, preserving the sign",
SBB = "subtract with borrow",
SCASB = "compare AL with the byte at ES:DI",
SCASW = "compare AX with the word at ES:DI",
SEG = "prefix: override the default segment",
SETMO = "undocumented: set the operand to all ones",
SHL = "shift left",
SHR = "logical shift right",
STC = "set the carry flag",
STD = "set the direction flag",
STI = "set the interrupt-enable flag",
STOSB = "store AL at ES:DI",
STOSW = "store AX at ES:DI",
SUB = "subtract",
TEST = "AND and set flags, discarding the result",
WAIT = "wait for the coprocessor's TEST pin",
XCHG = "exchange two operands",
XLAT = "load AL from the table at DS:BX indexed by AL",
XOR = "bitwise exclusive OR",
BOUND = "check an array index against a pair of bounds, or raise #BR",
ENTER = "make a stack frame, copying the enclosing frames' pointers",
INSB = "read a byte from the port in DX to ES:DI",
INSW = "read a word or dword from the port in DX to ES:DI",
LEAVE = "unmake the stack frame ENTER made",
OUTSB = "write the byte at DS:SI to the port in DX",
OUTSW = "write the word or dword at DS:SI to the port in DX",
POPA = "pop the eight general registers, discarding the saved SP",
PUSHA = "push the eight general registers",
ARPL = "raise a selector's requested privilege level to the caller's",
CLTS = "clear the task-switched flag in CR0",
LAR = "load a descriptor's access rights, if the selector is visible",
LGDT = "load the global descriptor table register",
LIDT = "load the interrupt descriptor table register",
LLDT = "load the local descriptor table register",
LMSW = "load the low sixteen bits of CR0 (the 286's machine status word)",
LSL = "load a descriptor's segment limit, if the selector is visible",
LTR = "load the task register",
SGDT = "store the global descriptor table register",
SIDT = "store the interrupt descriptor table register",
SLDT = "store the local descriptor table register's selector",
SMSW = "store the low sixteen bits of CR0",
STR = "store the task register's selector",
VERR = "set ZF if a segment is readable from the current privilege level",
VERW = "set ZF if a segment is writable from the current privilege level",
BSF = "scan for the least significant set bit",
BSR = "scan for the most significant set bit",
BT = "copy one bit of the operand into the carry flag",
BTC = "copy one bit into the carry flag and complement it",
BTR = "copy one bit into the carry flag and clear it",
BTS = "copy one bit into the carry flag and set it",
ICEBP = "undocumented: interrupt 1, the in-circuit emulator breakpoint",
LFS = "load a far pointer into FS and a register",
LGS = "load a far pointer into GS and a register",
LSS = "load a far pointer into SS and a register",
MOVSX = "move with sign extension",
MOVZX = "move with zero extension",
SETA = "set the operand to 1 if above (unsigned greater), else 0",
SETB = "set the operand to 1 if below (carry set), else 0",
SETBE = "set the operand to 1 if below or equal, else 0",
SETG = "set the operand to 1 if greater (signed), else 0",
SETGE = "set the operand to 1 if greater or equal (signed), else 0",
SETL = "set the operand to 1 if less (signed), else 0",
SETLE = "set the operand to 1 if less or equal (signed), else 0",
SETNB = "set the operand to 1 if not below (carry clear), else 0",
SETNO = "set the operand to 1 if the overflow flag is clear, else 0",
SETNP = "set the operand to 1 if parity odd, else 0",
SETNS = "set the operand to 1 if the sign flag is clear, else 0",
SETNZ = "set the operand to 1 if not equal, else 0",
SETO = "set the operand to 1 if the overflow flag is set, else 0",
SETP = "set the operand to 1 if parity even, else 0",
SETS = "set the operand to 1 if the sign flag is set, else 0",
SETZ = "set the operand to 1 if equal, else 0",
SHLD = "shift left, filling from a second operand",
SHRD = "shift right, filling from a second operand",
UD = "undefined encoding: raises an invalid-opcode exception",
BSWAP = "reverse the byte order of a 32-bit register",
CMPXCHG = "compare with the accumulator and exchange",
CPUID = "report the processor's identity and features",
INVD = "invalidate the caches without writing them back",
INVLPG = "invalidate one page's translation-lookaside-buffer entry",
WBINVD = "write the caches back and invalidate them",
XADD = "exchange, then add",
}
impl Op {
#[must_use]
pub const fn mnemonic(self) -> &'static str {
MNEMONICS[self as usize]
}
#[must_use]
pub const fn condition_code(self) -> Option<u8> {
let cc = match self {
Op::JO | Op::SETO => 0,
Op::JNO | Op::SETNO => 1,
Op::JB | Op::SETB => 2,
Op::JNB | Op::SETNB => 3,
Op::JZ | Op::SETZ => 4,
Op::JNZ | Op::SETNZ => 5,
Op::JBE | Op::SETBE => 6,
Op::JA | Op::SETA => 7,
Op::JS | Op::SETS => 8,
Op::JNS | Op::SETNS => 9,
Op::JP | Op::SETP => 10,
Op::JNP | Op::SETNP => 11,
Op::JL | Op::SETL => 12,
Op::JGE | Op::SETGE => 13,
Op::JLE | Op::SETLE => 14,
Op::JG | Op::SETG => 15,
_ => return None,
};
Some(cc)
}
#[must_use]
pub const fn is_conditional_jump(self) -> bool {
matches!(
self,
Op::JO
| Op::JNO
| Op::JB
| Op::JNB
| Op::JZ
| Op::JNZ
| Op::JBE
| Op::JA
| Op::JS
| Op::JNS
| Op::JP
| Op::JNP
| Op::JL
| Op::JGE
| Op::JLE
| Op::JG
)
}
#[must_use]
pub const fn is_setcc(self) -> bool {
self.condition_code().is_some() && !self.is_conditional_jump()
}
#[must_use]
pub const fn is_string(self) -> bool {
matches!(
self,
Op::MOVSB
| Op::MOVSW
| Op::CMPSB
| Op::CMPSW
| Op::STOSB
| Op::STOSW
| Op::LODSB
| Op::LODSW
| Op::SCASB
| Op::SCASW
| Op::INSB
| Op::INSW
| Op::OUTSB
| Op::OUTSW
)
}
#[must_use]
pub const fn repeat_tests_zf(self) -> bool {
matches!(self, Op::CMPSB | Op::CMPSW | Op::SCASB | Op::SCASW)
}
#[must_use]
pub const fn clocks(self) -> u32 {
match self {
Op::MUL => 70,
Op::IMUL => 80,
Op::DIV => 80,
Op::IDIV => 101,
Op::AAM => 83,
Op::AAD => 60,
Op::CALL => 11,
Op::CALLF => 20,
Op::RET => 8,
Op::RETF => 10,
Op::INT | Op::INT3 => 35,
Op::INTO => 37,
Op::IRET => 12,
Op::JMP | Op::JMPF => 7,
Op::LOOP | Op::LOOPE | Op::LOOPNE => 9,
Op::JCXZ => 6,
Op::MOVSB | Op::MOVSW => 9,
Op::CMPSB | Op::CMPSW => 14,
Op::SCASB | Op::SCASW => 11,
Op::LODSB | Op::LODSW => 8,
Op::STOSB | Op::STOSW => 7,
Op::XLAT => 7,
Op::AAA | Op::AAS | Op::DAA | Op::DAS => 4,
Op::PUSH | Op::PUSHF => 7,
Op::POP | Op::POPF => 4,
Op::LES | Op::LDS => 8,
Op::LEA => 2,
Op::IN | Op::OUT => 6,
Op::ESC => 2,
Op::HLT => 2,
Op::PUSHA | Op::POPA => 18,
Op::ENTER => 10,
Op::LEAVE => 4,
Op::BOUND => 10,
Op::ARPL => 20,
Op::LAR | Op::LSL => 15,
Op::VERR | Op::VERW => 10,
Op::LGDT | Op::LIDT | Op::LLDT | Op::LTR => 11,
Op::SGDT | Op::SIDT | Op::SLDT | Op::STR | Op::SMSW => 2,
Op::LMSW => 10,
Op::CLTS => 5,
Op::BSF | Op::BSR => 10,
Op::BT => 3,
Op::BTS | Op::BTR | Op::BTC => 6,
Op::SHLD | Op::SHRD => 3,
Op::MOVZX | Op::MOVSX => 3,
Op::CPUID => 14,
Op::INVD | Op::WBINVD => 4,
Op::INVLPG => 12,
Op::CMPXCHG | Op::XADD => 6,
Op::BSWAP => 1,
Op::INSB | Op::INSW | Op::OUTSB | Op::OUTSW => 9,
Op::UD => 0,
_ => 3,
}
}
}
impl fmt::Display for Op {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.mnemonic())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Insn {
pub op: Op,
pub dst: Arg,
pub src: Arg,
pub aux: Arg,
pub group: Grp,
pub class: Class,
}
impl Insn {
const fn new(op: Op, dst: Arg, src: Arg, group: Grp, class: Class) -> Insn {
Insn {
op,
dst,
src,
aux: Arg::None,
group,
class,
}
}
const fn with_aux(mut self, aux: Arg) -> Insn {
self.aux = aux;
self
}
#[must_use]
pub const fn needs_modrm(self) -> bool {
!matches!(self.group, Grp::None)
|| self.dst.needs_modrm()
|| self.src.needs_modrm()
|| self.aux.needs_modrm()
}
#[must_use]
pub const fn width_bytes(self, osz: u8) -> Option<u8> {
match self.dst.width_bytes(osz) {
Some(w) => Some(w),
None => self.src.width_bytes(osz),
}
}
#[must_use]
pub const fn is_prefix(self) -> bool {
matches!(self.class, Class::Prefix)
}
}
const UNASSIGNED: Insn = Insn::new(Op::UD, Arg::None, Arg::None, Grp::None, Class::Undefined);
macro_rules! opmap {
(
base $base:expr;
$($opcode:literal $op:ident $dst:ident $src:ident $(+ $aux:ident)? $group:ident $class:ident;)*
) => {{
let mut t: [Insn; 256] = $base;
let mut listed = [false; 256];
$(
t[$opcode as usize] =
Insn::new(Op::$op, Arg::$dst, Arg::$src, Grp::$group, Class::$class)
$(.with_aux(Arg::$aux))?;
listed[$opcode as usize] = true;
)*
(t, listed)
}};
}
const PRIMARY_8086: ([Insn; 256], [bool; 256]) = opmap! {
base [UNASSIGNED; 256];
0x00 ADD Eb Gb None Documented;
0x01 ADD Ev Gv None Documented;
0x02 ADD Gb Eb None Documented;
0x03 ADD Gv Ev None Documented;
0x04 ADD Al Ib None Documented;
0x05 ADD Ax Iv None Documented;
0x06 PUSH Sr None None Documented;
0x07 POP Sr None None Documented;
0x08 OR Eb Gb None Documented;
0x09 OR Ev Gv None Documented;
0x0a OR Gb Eb None Documented;
0x0b OR Gv Ev None Documented;
0x0c OR Al Ib None Documented;
0x0d OR Ax Iv None Documented;
0x0e PUSH Sr None None Documented;
0x0f POP Sr None None Undocumented;
0x10 ADC Eb Gb None Documented;
0x11 ADC Ev Gv None Documented;
0x12 ADC Gb Eb None Documented;
0x13 ADC Gv Ev None Documented;
0x14 ADC Al Ib None Documented;
0x15 ADC Ax Iv None Documented;
0x16 PUSH Sr None None Documented;
0x17 POP Sr None None Documented;
0x18 SBB Eb Gb None Documented;
0x19 SBB Ev Gv None Documented;
0x1a SBB Gb Eb None Documented;
0x1b SBB Gv Ev None Documented;
0x1c SBB Al Ib None Documented;
0x1d SBB Ax Iv None Documented;
0x1e PUSH Sr None None Documented;
0x1f POP Sr None None Documented;
0x20 AND Eb Gb None Documented;
0x21 AND Ev Gv None Documented;
0x22 AND Gb Eb None Documented;
0x23 AND Gv Ev None Documented;
0x24 AND Al Ib None Documented;
0x25 AND Ax Iv None Documented;
0x26 SEG Sr None None Prefix;
0x27 DAA None None None Documented;
0x28 SUB Eb Gb None Documented;
0x29 SUB Ev Gv None Documented;
0x2a SUB Gb Eb None Documented;
0x2b SUB Gv Ev None Documented;
0x2c SUB Al Ib None Documented;
0x2d SUB Ax Iv None Documented;
0x2e SEG Sr None None Prefix;
0x2f DAS None None None Documented;
0x30 XOR Eb Gb None Documented;
0x31 XOR Ev Gv None Documented;
0x32 XOR Gb Eb None Documented;
0x33 XOR Gv Ev None Documented;
0x34 XOR Al Ib None Documented;
0x35 XOR Ax Iv None Documented;
0x36 SEG Sr None None Prefix;
0x37 AAA None None None Documented;
0x38 CMP Eb Gb None Documented;
0x39 CMP Ev Gv None Documented;
0x3a CMP Gb Eb None Documented;
0x3b CMP Gv Ev None Documented;
0x3c CMP Al Ib None Documented;
0x3d CMP Ax Iv None Documented;
0x3e SEG Sr None None Prefix;
0x3f AAS None None None Documented;
0x40 INC Rv None None Documented;
0x41 INC Rv None None Documented;
0x42 INC Rv None None Documented;
0x43 INC Rv None None Documented;
0x44 INC Rv None None Documented;
0x45 INC Rv None None Documented;
0x46 INC Rv None None Documented;
0x47 INC Rv None None Documented;
0x48 DEC Rv None None Documented;
0x49 DEC Rv None None Documented;
0x4a DEC Rv None None Documented;
0x4b DEC Rv None None Documented;
0x4c DEC Rv None None Documented;
0x4d DEC Rv None None Documented;
0x4e DEC Rv None None Documented;
0x4f DEC Rv None None Documented;
0x50 PUSH Rv None None Documented;
0x51 PUSH Rv None None Documented;
0x52 PUSH Rv None None Documented;
0x53 PUSH Rv None None Documented;
0x54 PUSH Rv None None Documented;
0x55 PUSH Rv None None Documented;
0x56 PUSH Rv None None Documented;
0x57 PUSH Rv None None Documented;
0x58 POP Rv None None Documented;
0x59 POP Rv None None Documented;
0x5a POP Rv None None Documented;
0x5b POP Rv None None Documented;
0x5c POP Rv None None Documented;
0x5d POP Rv None None Documented;
0x5e POP Rv None None Documented;
0x5f POP Rv None None Documented;
0x60 JO Jb None None Alias;
0x61 JNO Jb None None Alias;
0x62 JB Jb None None Alias;
0x63 JNB Jb None None Alias;
0x64 JZ Jb None None Alias;
0x65 JNZ Jb None None Alias;
0x66 JBE Jb None None Alias;
0x67 JA Jb None None Alias;
0x68 JS Jb None None Alias;
0x69 JNS Jb None None Alias;
0x6a JP Jb None None Alias;
0x6b JNP Jb None None Alias;
0x6c JL Jb None None Alias;
0x6d JGE Jb None None Alias;
0x6e JLE Jb None None Alias;
0x6f JG Jb None None Alias;
0x70 JO Jb None None Documented;
0x71 JNO Jb None None Documented;
0x72 JB Jb None None Documented;
0x73 JNB Jb None None Documented;
0x74 JZ Jb None None Documented;
0x75 JNZ Jb None None Documented;
0x76 JBE Jb None None Documented;
0x77 JA Jb None None Documented;
0x78 JS Jb None None Documented;
0x79 JNS Jb None None Documented;
0x7a JP Jb None None Documented;
0x7b JNP Jb None None Documented;
0x7c JL Jb None None Documented;
0x7d JGE Jb None None Documented;
0x7e JLE Jb None None Documented;
0x7f JG Jb None None Documented;
0x80 ADD Eb Ib Alu Documented;
0x81 ADD Ev Iv Alu Documented;
0x82 ADD Eb Ib Alu Alias;
0x83 ADD Ev Ibs Alu Documented;
0x84 TEST Eb Gb None Documented;
0x85 TEST Ev Gv None Documented;
0x86 XCHG Eb Gb None Documented;
0x87 XCHG Ev Gv None Documented;
0x88 MOV Eb Gb None Documented;
0x89 MOV Ev Gv None Documented;
0x8a MOV Gb Eb None Documented;
0x8b MOV Gv Ev None Documented;
0x8c MOV Ev Sw None Documented;
0x8d LEA Gv M None Documented;
0x8e MOV Sw Ev None Documented;
0x8f POP Ev None Pop Documented;
0x90 NOP None None None Documented;
0x91 XCHG Ax Rv None Documented;
0x92 XCHG Ax Rv None Documented;
0x93 XCHG Ax Rv None Documented;
0x94 XCHG Ax Rv None Documented;
0x95 XCHG Ax Rv None Documented;
0x96 XCHG Ax Rv None Documented;
0x97 XCHG Ax Rv None Documented;
0x98 CBW None None None Documented;
0x99 CWD None None None Documented;
0x9a CALLF Ap None None Documented;
0x9b WAIT None None None Documented;
0x9c PUSHF None None None Documented;
0x9d POPF None None None Documented;
0x9e SAHF None None None Documented;
0x9f LAHF None None None Documented;
0xa0 MOV Al Ob None Documented;
0xa1 MOV Ax Ov None Documented;
0xa2 MOV Ob Al None Documented;
0xa3 MOV Ov Ax None Documented;
0xa4 MOVSB Yb Xb None Documented;
0xa5 MOVSW Yv Xv None Documented;
0xa6 CMPSB Xb Yb None Documented;
0xa7 CMPSW Xv Yv None Documented;
0xa8 TEST Al Ib None Documented;
0xa9 TEST Ax Iv None Documented;
0xaa STOSB Yb Al None Documented;
0xab STOSW Yv Ax None Documented;
0xac LODSB Al Xb None Documented;
0xad LODSW Ax Xv None Documented;
0xae SCASB Al Yb None Documented;
0xaf SCASW Ax Yv None Documented;
0xb0 MOV Rb Ib None Documented;
0xb1 MOV Rb Ib None Documented;
0xb2 MOV Rb Ib None Documented;
0xb3 MOV Rb Ib None Documented;
0xb4 MOV Rb Ib None Documented;
0xb5 MOV Rb Ib None Documented;
0xb6 MOV Rb Ib None Documented;
0xb7 MOV Rb Ib None Documented;
0xb8 MOV Rv Iv None Documented;
0xb9 MOV Rv Iv None Documented;
0xba MOV Rv Iv None Documented;
0xbb MOV Rv Iv None Documented;
0xbc MOV Rv Iv None Documented;
0xbd MOV Rv Iv None Documented;
0xbe MOV Rv Iv None Documented;
0xbf MOV Rv Iv None Documented;
0xc0 RET Iw None None Alias;
0xc1 RET None None None Alias;
0xc2 RET Iw None None Documented;
0xc3 RET None None None Documented;
0xc4 LES Gv Mp None Documented;
0xc5 LDS Gv Mp None Documented;
0xc6 MOV Eb Ib MovImm Documented;
0xc7 MOV Ev Iv MovImm Documented;
0xc8 RETF Iw None None Alias;
0xc9 RETF None None None Alias;
0xca RETF Iw None None Documented;
0xcb RETF None None None Documented;
0xcc INT3 None None None Documented;
0xcd INT Ib None None Documented;
0xce INTO None None None Documented;
0xcf IRET None None None Documented;
0xd0 ROL Eb One Shift Documented;
0xd1 ROL Ev One Shift Documented;
0xd2 ROL Eb Cl Shift Documented;
0xd3 ROL Ev Cl Shift Documented;
0xd4 AAM Ib None None Documented;
0xd5 AAD Ib None None Documented;
0xd6 SALC None None None Undocumented;
0xd7 XLAT None None None Documented;
0xd8 ESC Ev None None Escape;
0xd9 ESC Ev None None Escape;
0xda ESC Ev None None Escape;
0xdb ESC Ev None None Escape;
0xdc ESC Ev None None Escape;
0xdd ESC Ev None None Escape;
0xde ESC Ev None None Escape;
0xdf ESC Ev None None Escape;
0xe0 LOOPNE Jb None None Documented;
0xe1 LOOPE Jb None None Documented;
0xe2 LOOP Jb None None Documented;
0xe3 JCXZ Jb None None Documented;
0xe4 IN Al Ib None Documented;
0xe5 IN Ax Ib None Documented;
0xe6 OUT Ib Al None Documented;
0xe7 OUT Ib Ax None Documented;
0xe8 CALL Jv None None Documented;
0xe9 JMP Jv None None Documented;
0xea JMPF Ap None None Documented;
0xeb JMP Jb None None Documented;
0xec IN Al Dx None Documented;
0xed IN Ax Dx None Documented;
0xee OUT Dx Al None Documented;
0xef OUT Dx Ax None Documented;
0xf0 LOCK None None None Prefix;
0xf1 LOCK None None None Prefix;
0xf2 REPNE None None None Prefix;
0xf3 REP None None None Prefix;
0xf4 HLT None None None Documented;
0xf5 CMC None None None Documented;
0xf6 TEST Eb Ib Unary Documented;
0xf7 TEST Ev Iv Unary Documented;
0xf8 CLC None None None Documented;
0xf9 STC None None None Documented;
0xfa CLI None None None Documented;
0xfb STI None None None Documented;
0xfc CLD None None None Documented;
0xfd STD None None None Documented;
0xfe INC Eb None IncDec Documented;
0xff INC Ev None Misc Documented;
};
pub static TABLE: [Insn; 256] = PRIMARY_8086.0;
pub static LISTED: [bool; 256] = PRIMARY_8086.1;
const PRIMARY_386: ([Insn; 256], [bool; 256]) = opmap! {
base PRIMARY_8086.0;
0x0f ESC None None None Escape;
0x60 PUSHA None None None Documented;
0x61 POPA None None None Documented;
0x62 BOUND Gv M None Documented;
0x63 ARPL Ew Gw None Documented;
0x64 SEG None None None Prefix;
0x65 SEG None None None Prefix;
0x66 SEG None None None Prefix;
0x67 SEG None None None Prefix;
0x68 PUSH Iv None None Documented;
0x69 IMUL Gv Ev +Iv None Documented;
0x6a PUSH Ibs None None Documented;
0x6b IMUL Gv Ev +Ibs None Documented;
0x6c INSB Yb Dx None Documented;
0x6d INSW Yv Dx None Documented;
0x6e OUTSB Dx Xb None Documented;
0x6f OUTSW Dx Xv None Documented;
0x8e MOV Sw Ew None Documented;
0xc0 ROL Eb Ib Shift Documented;
0xc1 ROL Ev Ib Shift Documented;
0xc8 ENTER Iw Ib None Documented;
0xc9 LEAVE None None None Documented;
0xf1 ICEBP None None None Undocumented;
};
pub static TABLE_386: [Insn; 256] = PRIMARY_386.0;
pub static CHANGED_386: [bool; 256] = PRIMARY_386.1;
const SECONDARY: ([Insn; 256], [bool; 256]) = opmap! {
base [UNASSIGNED; 256];
0x00 SLDT Ew None Grp6 Documented;
0x01 SGDT Ms None Grp7 Documented;
0x02 LAR Gv Ev None Documented;
0x03 LSL Gv Ev None Documented;
0x06 CLTS None None None Documented;
0x08 INVD None None None Documented;
0x09 WBINVD None None None Documented;
0x20 MOV Rd Cd None Documented;
0x21 MOV Rd Dd None Documented;
0x22 MOV Cd Rd None Documented;
0x23 MOV Dd Rd None Documented;
0x24 MOV Rd Td None Documented;
0x26 MOV Td Rd None Documented;
0x80 JO Jv None None Documented;
0x81 JNO Jv None None Documented;
0x82 JB Jv None None Documented;
0x83 JNB Jv None None Documented;
0x84 JZ Jv None None Documented;
0x85 JNZ Jv None None Documented;
0x86 JBE Jv None None Documented;
0x87 JA Jv None None Documented;
0x88 JS Jv None None Documented;
0x89 JNS Jv None None Documented;
0x8a JP Jv None None Documented;
0x8b JNP Jv None None Documented;
0x8c JL Jv None None Documented;
0x8d JGE Jv None None Documented;
0x8e JLE Jv None None Documented;
0x8f JG Jv None None Documented;
0x90 SETO Eb None None Documented;
0x91 SETNO Eb None None Documented;
0x92 SETB Eb None None Documented;
0x93 SETNB Eb None None Documented;
0x94 SETZ Eb None None Documented;
0x95 SETNZ Eb None None Documented;
0x96 SETBE Eb None None Documented;
0x97 SETA Eb None None Documented;
0x98 SETS Eb None None Documented;
0x99 SETNS Eb None None Documented;
0x9a SETP Eb None None Documented;
0x9b SETNP Eb None None Documented;
0x9c SETL Eb None None Documented;
0x9d SETGE Eb None None Documented;
0x9e SETLE Eb None None Documented;
0x9f SETG Eb None None Documented;
0xa0 PUSH Sr None None Documented;
0xa1 POP Sr None None Documented;
0xa2 CPUID None None None Documented;
0xa3 BT Ev Gv None Documented;
0xa4 SHLD Ev Gv +Ib None Documented;
0xa5 SHLD Ev Gv +Cl None Documented;
0xa8 PUSH Sr None None Documented;
0xa9 POP Sr None None Documented;
0xab BTS Ev Gv None Documented;
0xac SHRD Ev Gv +Ib None Documented;
0xad SHRD Ev Gv +Cl None Documented;
0xaf IMUL Gv Ev None Documented;
0xb0 CMPXCHG Eb Gb None Documented;
0xb1 CMPXCHG Ev Gv None Documented;
0xb2 LSS Gv Mp None Documented;
0xb3 BTR Ev Gv None Documented;
0xb4 LFS Gv Mp None Documented;
0xb5 LGS Gv Mp None Documented;
0xb6 MOVZX Gv Eb None Documented;
0xb7 MOVZX Gv Ew None Documented;
0xba BT Ev Ib Grp8 Documented;
0xbb BTC Ev Gv None Documented;
0xbc BSF Gv Ev None Documented;
0xbd BSR Gv Ev None Documented;
0xbe MOVSX Gv Eb None Documented;
0xbf MOVSX Gv Ew None Documented;
0xc0 XADD Eb Gb None Documented;
0xc1 XADD Ev Gv None Documented;
0xc8 BSWAP Rv None None Documented;
0xc9 BSWAP Rv None None Documented;
0xca BSWAP Rv None None Documented;
0xcb BSWAP Rv None None Documented;
0xcc BSWAP Rv None None Documented;
0xcd BSWAP Rv None None Documented;
0xce BSWAP Rv None None Documented;
0xcf BSWAP Rv None None Documented;
};
pub static TABLE_0F: [Insn; 256] = SECONDARY.0;
pub static LISTED_0F: [bool; 256] = SECONDARY.1;
pub static GROUP_ALU: [Op; 8] = [
Op::ADD,
Op::OR,
Op::ADC,
Op::SBB,
Op::AND,
Op::SUB,
Op::XOR,
Op::CMP,
];
pub static GROUP_SHIFT: [Op; 8] = [
Op::ROL,
Op::ROR,
Op::RCL,
Op::RCR,
Op::SHL,
Op::SHR,
Op::SETMO,
Op::SAR,
];
const fn unary_group(byte: bool) -> [Insn; 8] {
let (e, i) = if byte {
(Arg::Eb, Arg::Ib)
} else {
(Arg::Ev, Arg::Iv)
};
[
Insn::new(Op::TEST, e, i, Grp::None, Class::Documented),
Insn::new(Op::TEST, e, i, Grp::None, Class::Alias),
Insn::new(Op::NOT, e, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::NEG, e, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::MUL, e, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::IMUL, e, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::DIV, e, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::IDIV, e, Arg::None, Grp::None, Class::Documented),
]
}
pub static GROUP_UNARY8: [Insn; 8] = unary_group(true);
pub static GROUP_UNARY16: [Insn; 8] = unary_group(false);
pub static GROUP_MISC: [Insn; 8] = [
Insn::new(Op::INC, Arg::Ev, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::DEC, Arg::Ev, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::CALL, Arg::Ev, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::CALLF, Arg::Mp, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::JMP, Arg::Ev, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::JMPF, Arg::Mp, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::PUSH, Arg::Ev, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::PUSH, Arg::Ev, Arg::None, Grp::None, Class::Undefined),
];
pub static GROUP_INCDEC: [Insn; 8] = [
Insn::new(Op::INC, Arg::Eb, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::DEC, Arg::Eb, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::CALL, Arg::Ev, Arg::None, Grp::None, Class::Undefined),
Insn::new(Op::CALLF, Arg::Mp, Arg::None, Grp::None, Class::Undefined),
Insn::new(Op::JMP, Arg::Ev, Arg::None, Grp::None, Class::Undefined),
Insn::new(Op::JMPF, Arg::Mp, Arg::None, Grp::None, Class::Undefined),
Insn::new(Op::PUSH, Arg::Ev, Arg::None, Grp::None, Class::Undefined),
Insn::new(Op::PUSH, Arg::Ev, Arg::None, Grp::None, Class::Undefined),
];
static GROUP_MISC_386: [Insn; 8] = {
let mut t = GROUP_MISC;
t[7] = UNASSIGNED;
t
};
static GROUP_INCDEC_386: [Insn; 8] = {
let mut t = [UNASSIGNED; 8];
t[0] = GROUP_INCDEC[0];
t[1] = GROUP_INCDEC[1];
t
};
pub static GROUP6: [Insn; 8] = [
Insn::new(Op::SLDT, Arg::Ew, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::STR, Arg::Ew, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::LLDT, Arg::Ew, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::LTR, Arg::Ew, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::VERR, Arg::Ew, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::VERW, Arg::Ew, Arg::None, Grp::None, Class::Documented),
UNASSIGNED,
UNASSIGNED,
];
pub static GROUP7: [Insn; 8] = [
Insn::new(Op::SGDT, Arg::Ms, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::SIDT, Arg::Ms, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::LGDT, Arg::Ms, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::LIDT, Arg::Ms, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::SMSW, Arg::Ew, Arg::None, Grp::None, Class::Documented),
UNASSIGNED,
Insn::new(Op::LMSW, Arg::Ew, Arg::None, Grp::None, Class::Documented),
Insn::new(Op::INVLPG, Arg::M, Arg::None, Grp::None, Class::Documented),
];
pub static GROUP8: [Insn; 8] = [
UNASSIGNED,
UNASSIGNED,
UNASSIGNED,
UNASSIGNED,
Insn::new(Op::BT, Arg::Ev, Arg::Ib, Grp::None, Class::Documented),
Insn::new(Op::BTS, Arg::Ev, Arg::Ib, Grp::None, Class::Documented),
Insn::new(Op::BTR, Arg::Ev, Arg::Ib, Grp::None, Class::Documented),
Insn::new(Op::BTC, Arg::Ev, Arg::Ib, Grp::None, Class::Documented),
];
#[inline]
#[must_use]
pub const fn decode(opcode: u8) -> Insn {
TABLE[opcode as usize]
}
#[inline]
#[must_use]
pub const fn decode_as(map: Gen, opcode: u8) -> Insn {
match map {
Gen::I8086 => TABLE[opcode as usize],
Gen::I386 => TABLE_386[opcode as usize],
}
}
#[inline]
#[must_use]
pub const fn decode_0f(opcode: u8) -> Insn {
TABLE_0F[opcode as usize]
}
#[inline]
#[must_use]
pub const fn resolve(insn: Insn, reg: u8) -> Insn {
resolve_as(Gen::I8086, insn, reg)
}
#[inline]
#[must_use]
pub const fn resolve_as(map: Gen, insn: Insn, reg: u8) -> Insn {
let idx = (reg & 7) as usize;
let strict = matches!(map, Gen::I386);
match insn.group {
Grp::None => insn,
Grp::Pop | Grp::MovImm => {
if strict && idx != 0 {
UNASSIGNED
} else {
insn
}
}
Grp::Alu => Insn {
op: GROUP_ALU[idx],
..insn
},
Grp::Shift => Insn {
op: GROUP_SHIFT[idx],
..insn
},
Grp::IncDec => {
if strict {
GROUP_INCDEC_386[idx]
} else {
GROUP_INCDEC[idx]
}
}
Grp::Unary => {
if matches!(insn.dst, Arg::Eb) {
GROUP_UNARY8[idx]
} else {
GROUP_UNARY16[idx]
}
}
Grp::Misc => {
if strict {
GROUP_MISC_386[idx]
} else {
GROUP_MISC[idx]
}
}
Grp::Grp6 => GROUP6[idx],
Grp::Grp7 => GROUP7[idx],
Grp::Grp8 => GROUP8[idx],
}
}
#[must_use]
pub const fn ea_clocks(md: u8, rm: u8, override_seg: bool) -> u32 {
let base = match (md, rm) {
(3, _) => return 0,
(0, 6) => 6,
(0, 4 | 5 | 7) => 5,
(0, 0 | 3) => 7,
(0, 1 | 2) => 8,
(1 | 2, 4..=7) => 9,
(1 | 2, 0 | 3) => 11,
(1 | 2, 1 | 2) => 12,
_ => 5,
};
if override_seg { base + 2 } else { base }
}
pub mod seg {
pub const ES: u8 = 0;
pub const CS: u8 = 1;
pub const SS: u8 = 2;
pub const DS: u8 = 3;
pub const FS: u8 = 4;
pub const GS: u8 = 5;
pub const COUNT: usize = 6;
#[must_use]
pub const fn name(sr: u8) -> &'static str {
match sr {
ES => "es",
CS => "cs",
SS => "ss",
DS => "ds",
FS => "fs",
_ => "gs",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Sib {
pub scale: u8,
pub index: u8,
pub base: u8,
}
impl Sib {
#[inline]
#[must_use]
pub const fn new(byte: u8) -> Sib {
Sib {
scale: byte >> 6,
index: (byte >> 3) & 7,
base: byte & 7,
}
}
#[inline]
#[must_use]
pub const fn has_index(self) -> bool {
self.index != 4
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ModRm {
pub md: u8,
pub reg: u8,
pub rm: u8,
}
impl ModRm {
#[inline]
#[must_use]
pub const fn new(byte: u8) -> ModRm {
ModRm {
md: byte >> 6,
reg: (byte >> 3) & 7,
rm: byte & 7,
}
}
#[inline]
#[must_use]
pub const fn is_register(self) -> bool {
self.md == 3
}
#[must_use]
pub const fn disp_bytes(self) -> u8 {
match (self.md, self.rm) {
(0, 6) => 2,
(1, _) => 1,
(2, _) => 2,
_ => 0,
}
}
#[must_use]
pub const fn default_segment(self) -> u8 {
match (self.md, self.rm) {
(0, 6) => seg::DS,
(_, 2 | 3 | 6) => seg::SS,
_ => seg::DS,
}
}
#[must_use]
pub const fn disp_bytes32(self, sib: Option<Sib>) -> u8 {
match self.md {
1 => 1,
2 => 4,
0 => match (self.rm, sib) {
(5, _) => 4,
(4, Some(s)) if s.base == 5 => 4,
_ => 0,
},
_ => 0,
}
}
#[must_use]
pub const fn default_segment32(self, sib: Option<Sib>) -> u8 {
match (self.md, self.rm, sib) {
(0, 5, _) => seg::DS,
(0, 4, Some(s)) if s.base == 5 => seg::DS,
(_, 4, Some(s)) => {
if s.base == 4 || s.base == 5 {
seg::SS
} else {
seg::DS
}
}
(_, 5, _) => seg::SS,
_ => seg::DS,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Rep {
While,
WhileNot,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Fields {
pub map: Gen,
pub seg_override: Option<u8>,
pub rep: Option<Rep>,
pub lock: bool,
pub two_byte: bool,
pub opsize: u8,
pub addrsize: u8,
pub opcode: u8,
pub insn: Insn,
pub modrm: Option<ModRm>,
pub sib: Option<Sib>,
pub disp: i32,
pub mem_seg: u8,
pub imm: u32,
pub imm2: u32,
pub len: u8,
pub truncated: bool,
}
impl Fields {
#[inline]
#[must_use]
pub const fn imm16(&self) -> u16 {
self.imm as u16
}
#[inline]
#[must_use]
pub const fn imm_sized(&self) -> u32 {
if self.opsize == 2 {
self.imm & 0xffff
} else {
self.imm
}
}
#[inline]
#[must_use]
pub const fn imm_seg(&self) -> u16 {
self.imm2 as u16
}
#[inline]
#[must_use]
pub const fn segment(&self, default: u8) -> u8 {
match self.seg_override {
Some(sr) => sr,
None => default,
}
}
#[inline]
#[must_use]
pub const fn mem_segment(&self) -> u8 {
self.segment(self.mem_seg)
}
#[inline]
#[must_use]
pub const fn rm_is_register(&self) -> bool {
if matches!(self.insn.dst, Arg::Rd) || matches!(self.insn.src, Arg::Rd) {
return true;
}
match self.modrm {
Some(m) => m.is_register(),
None => false,
}
}
}
pub const MAX_PREFIXES: u8 = 15;
pub fn decode_stream(next: &mut dyn FnMut() -> Option<u8>) -> Fields {
decode_stream_as(Gen::I8086, false, next)
}
#[allow(clippy::too_many_lines)]
pub fn decode_stream_as(map: Gen, default32: bool, next: &mut dyn FnMut() -> Option<u8>) -> Fields {
let wide = matches!(map, Gen::I386) && default32;
let base_size = if wide { 4 } else { 2 };
let mut f = Fields {
map,
seg_override: None,
rep: None,
lock: false,
two_byte: false,
opsize: base_size,
addrsize: base_size,
opcode: 0x90,
insn: decode_as(map, 0x90),
modrm: None,
sib: None,
disp: 0,
mem_seg: seg::DS,
imm: 0,
imm2: 0,
len: 0,
truncated: false,
};
let mut take = |f: &mut Fields| -> u8 {
match next() {
Some(b) => {
f.len = f.len.saturating_add(1);
b
}
None => {
f.truncated = true;
0
}
}
};
let mut prefixes = 0u8;
let opcode = loop {
let byte = take(&mut f);
if f.truncated {
return f;
}
let row = decode_as(map, byte);
if !row.is_prefix() || prefixes >= MAX_PREFIXES {
break byte;
}
prefixes += 1;
match row.op {
Op::SEG => match byte {
0x26 => f.seg_override = Some(seg::ES),
0x2e => f.seg_override = Some(seg::CS),
0x36 => f.seg_override = Some(seg::SS),
0x3e => f.seg_override = Some(seg::DS),
0x64 => f.seg_override = Some(seg::FS),
0x65 => f.seg_override = Some(seg::GS),
0x66 => f.opsize = if wide { 2 } else { 4 },
_ => f.addrsize = if wide { 2 } else { 4 },
},
Op::LOCK => f.lock = true,
Op::REP => f.rep = Some(Rep::While),
Op::REPNE => f.rep = Some(Rep::WhileNot),
_ => {}
}
};
let mut insn;
if matches!(map, Gen::I386) && opcode == 0x0f {
f.two_byte = true;
let second = take(&mut f);
if f.truncated {
return f;
}
f.opcode = second;
insn = decode_0f(second);
} else {
f.opcode = opcode;
insn = decode_as(map, opcode);
}
if insn.needs_modrm() {
let byte = take(&mut f);
let modrm = ModRm::new(byte);
f.modrm = Some(modrm);
insn = resolve_as(map, insn, modrm.reg);
let register_only = matches!(insn.dst, Arg::Rd | Arg::Cd | Arg::Dd | Arg::Td)
|| matches!(insn.src, Arg::Rd);
if register_only {
f.mem_seg = seg::DS;
} else if f.addrsize == 2 {
f.mem_seg = modrm.default_segment();
match modrm.disp_bytes() {
1 => {
let lo = take(&mut f);
f.disp = i32::from(lo as i8);
}
2 => {
let lo = take(&mut f);
let hi = take(&mut f);
let value = u16::from(lo) | (u16::from(hi) << 8);
f.disp = i32::from(value as i16);
}
_ => {}
}
} else {
if !modrm.is_register() && modrm.rm == 4 {
let byte = take(&mut f);
f.sib = Some(Sib::new(byte));
}
f.mem_seg = modrm.default_segment32(f.sib);
match modrm.disp_bytes32(f.sib) {
1 => {
let lo = take(&mut f);
f.disp = i32::from(lo as i8);
}
4 => {
let mut value = 0u32;
for i in 0..4 {
let byte = take(&mut f);
value |= u32::from(byte) << (8 * i);
}
f.disp = value as i32;
}
_ => {}
}
}
} else if matches!(insn.dst, Arg::Ob | Arg::Ov) || matches!(insn.src, Arg::Ob | Arg::Ov) {
f.mem_seg = seg::DS;
}
f.insn = insn;
let osz = f.opsize;
let asz = f.addrsize;
let mut read = |f: &mut Fields, n: u8| -> u32 {
let mut value = 0u32;
for i in 0..n {
let byte = take(f);
value |= u32::from(byte) << (8 * u32::from(i));
}
value
};
let mut slot = 0u8;
for arg in [insn.dst, insn.src, insn.aux] {
if arg == Arg::Ap {
let offset = read(&mut f, osz);
let selector = read(&mut f, 2);
f.imm = offset;
f.imm2 = selector;
slot = 2;
continue;
}
let n = arg.immediate_bytes(osz, asz);
if n == 0 {
continue;
}
let mut value = read(&mut f, n);
if matches!(arg, Arg::Ibs | Arg::Jb) {
value = (value as u8 as i8) as u32;
} else if arg == Arg::Jv && osz == 2 {
value &= 0xffff;
}
if slot == 0 {
f.imm = value;
slot = 1;
} else {
f.imm2 = value;
slot = 2;
}
}
f
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
fn decode_bytes(bytes: &[u8]) -> Fields {
let mut at = 0usize;
decode_stream(&mut || {
let b = bytes.get(at).copied();
at += 1;
b
})
}
#[test]
fn every_opcode_is_described_exactly_once() {
let missing: Vec<usize> = (0..256).filter(|i| !LISTED[*i]).collect();
assert!(missing.is_empty(), "opcodes not described: {missing:02x?}");
}
#[test]
fn mnemonics_are_all_known() {
assert_eq!(Op::MOV.mnemonic(), "mov");
assert_eq!(Op::CALLF.mnemonic(), "callf");
assert_eq!(Op::SETMO.mnemonic(), "setmo");
for op in Op::ALL {
assert!(!op.mnemonic().is_empty(), "{op:?} has no mnemonic");
assert!(!op.summary().is_empty(), "{op:?} has no summary");
}
}
#[test]
fn every_declared_operation_is_reachable() {
let reachable = |op: Op| {
TABLE.iter().any(|i| i.op == op)
|| TABLE_386.iter().any(|i| i.op == op)
|| TABLE_0F.iter().any(|i| i.op == op)
|| GROUP_ALU.contains(&op)
|| GROUP_SHIFT.contains(&op)
|| GROUP_UNARY8.iter().any(|i| i.op == op)
|| GROUP_MISC.iter().any(|i| i.op == op)
|| GROUP6.iter().any(|i| i.op == op)
|| GROUP7.iter().any(|i| i.op == op)
|| GROUP8.iter().any(|i| i.op == op)
};
for op in Op::ALL {
assert!(reachable(*op), "{op:?} is declared but unreachable");
}
}
#[test]
fn groups_resolve_to_the_right_operation() {
assert_eq!(resolve(decode(0x80), 5).op, Op::SUB);
assert_eq!(resolve(decode(0x81), 7).op, Op::CMP);
assert_eq!(resolve(decode(0xd1), 4).op, Op::SHL);
assert_eq!(resolve(decode(0xd3), 6).op, Op::SETMO);
assert_eq!(resolve(decode(0xf6), 6).op, Op::DIV);
assert_eq!(resolve(decode(0xf7), 5).op, Op::IMUL);
assert_eq!(resolve(decode(0xff), 3).op, Op::CALLF);
assert_eq!(resolve(decode(0x8f), 5).op, Op::POP);
assert_eq!(resolve(decode(0xc7), 3).op, Op::MOV);
}
#[test]
fn the_unary_group_keeps_its_operand_width() {
assert_eq!(resolve(decode(0xf6), 0).src, Arg::Ib);
assert_eq!(resolve(decode(0xf7), 0).src, Arg::Iv);
assert_eq!(resolve(decode(0xf6), 4).dst, Arg::Eb);
assert_eq!(resolve(decode(0xf7), 4).dst, Arg::Ev);
}
#[test]
fn prefixes_are_marked_as_such() {
for op in [0x26, 0x2e, 0x36, 0x3e, 0xf0, 0xf1, 0xf2, 0xf3] {
assert!(decode(op).is_prefix(), "{op:02x} should be a prefix");
}
assert!(!decode(0x90).is_prefix());
}
#[test]
fn modrm_is_required_exactly_where_the_encoding_needs_one() {
assert!(decode(0x00).needs_modrm()); assert!(decode(0x8d).needs_modrm()); assert!(decode(0xff).needs_modrm()); assert!(!decode(0x40).needs_modrm()); assert!(!decode(0xb8).needs_modrm()); assert!(!decode(0xa0).needs_modrm()); }
#[test]
fn operand_widths_follow_the_encoding() {
assert_eq!(decode(0x00).width_bytes(2), Some(1));
assert_eq!(decode(0x01).width_bytes(2), Some(2));
assert_eq!(decode(0xa4).width_bytes(2), Some(1)); assert_eq!(decode(0xa5).width_bytes(2), Some(2)); assert_eq!(decode(0x83).width_bytes(2), Some(2)); assert_eq!(decode(0xf4).width_bytes(2), None); assert_eq!(decode(0x01).width_bytes(4), Some(4));
assert_eq!(decode(0xa5).width_bytes(4), Some(4));
assert_eq!(decode(0x00).width_bytes(4), Some(1));
}
#[test]
fn the_alias_encodings_are_the_ones_the_hardware_corpus_names() {
let aliases: Vec<u8> = (0..=255u8)
.filter(|o| decode(*o).class == Class::Alias)
.collect();
let mut want: Vec<u8> = (0x60..=0x6f).collect();
want.extend([0x82, 0xc0, 0xc1, 0xc8, 0xc9]);
want.sort_unstable();
assert_eq!(aliases, want);
}
#[test]
fn ea_clocks_match_the_manual() {
assert_eq!(ea_clocks(0, 6, false), 6); assert_eq!(ea_clocks(0, 4, false), 5); assert_eq!(ea_clocks(0, 0, false), 7); assert_eq!(ea_clocks(0, 1, false), 8); assert_eq!(ea_clocks(1, 7, false), 9); assert_eq!(ea_clocks(2, 3, false), 11); assert_eq!(ea_clocks(2, 2, false), 12); assert_eq!(ea_clocks(3, 0, false), 0); assert_eq!(ea_clocks(0, 4, true), 7); }
#[test]
fn the_decoder_measures_instruction_length() {
assert_eq!(decode_bytes(&[0x90]).len, 1); assert_eq!(decode_bytes(&[0xb8, 0x34, 0x12]).len, 3); assert_eq!(decode_bytes(&[0x00, 0x00]).len, 2); assert_eq!(decode_bytes(&[0x00, 0x06, 0x34, 0x12]).len, 4); assert_eq!(decode_bytes(&[0x83, 0x46, 0x10, 0x05]).len, 4); assert_eq!(decode_bytes(&[0xea, 0x00, 0x10, 0x00, 0xf0]).len, 5); assert_eq!(
decode_bytes(&[0x26, 0x81, 0x86, 0x34, 0x12, 0x78, 0x56]).len,
7
);
}
#[test]
fn prefixes_accumulate_and_the_last_override_wins() {
let f = decode_bytes(&[0xf3, 0x26, 0x2e, 0xf0, 0xa4]);
assert_eq!(f.rep, Some(Rep::While));
assert_eq!(f.seg_override, Some(seg::CS));
assert!(f.lock);
assert_eq!(f.insn.op, Op::MOVSB);
assert_eq!(f.len, 5);
}
#[test]
fn a_truncated_stream_is_reported_rather_than_guessed() {
let f = decode_bytes(&[0xb8, 0x34]);
assert!(f.truncated);
assert_eq!(f.insn.op, Op::MOV);
}
#[test]
fn displacements_are_sign_extended_at_decode_time() {
let f = decode_bytes(&[0x00, 0x46, 0xff]);
assert_eq!(f.disp, -1);
let f = decode_bytes(&[0x83, 0xc0, 0xfe]);
assert_eq!(f.imm16(), 0xfffe);
}
#[test]
fn the_direct_address_encoding_is_not_bp_relative() {
let rm = ModRm::new(0b00_000_110);
assert_eq!(rm.disp_bytes(), 2);
assert_eq!(rm.default_segment(), seg::DS);
assert_eq!(ModRm::new(0b01_000_110).default_segment(), seg::SS);
assert_eq!(ModRm::new(0b00_000_010).default_segment(), seg::SS);
assert_eq!(ModRm::new(0b00_000_011).default_segment(), seg::SS);
assert_eq!(ModRm::new(0b00_000_000).default_segment(), seg::DS);
}
#[test]
fn a_far_pointer_immediate_carries_both_halves() {
let f = decode_bytes(&[0x9a, 0x00, 0x10, 0x00, 0xf0]);
assert_eq!(f.insn.op, Op::CALLF);
assert_eq!(f.imm16(), 0x1000);
assert_eq!(f.imm_seg(), 0xf000);
}
}