use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Arg {
None,
Eb,
Ev,
Gb,
Gv,
Sw,
M,
Mp,
Ib,
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) -> 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::Ev
| Arg::Gv
| Arg::Sw
| Arg::Iv
| Arg::Ibs
| Arg::Ov
| Arg::Rv
| Arg::Sr
| Arg::Ax
| Arg::Xv
| Arg::Yv
| Arg::Jv
| Arg::Dx => Some(2),
Arg::None | Arg::M | Arg::Mp | Arg::Ap => None,
}
}
#[must_use]
pub const fn needs_modrm(self) -> bool {
matches!(
self,
Arg::Eb | Arg::Ev | Arg::Gb | Arg::Gv | Arg::Sw | Arg::M | Arg::Mp
)
}
#[must_use]
pub const fn immediate_bytes(self) -> u8 {
match self {
Arg::Ib | Arg::Ibs | Arg::Jb => 1,
Arg::Iv | Arg::Jv | Arg::Ob | Arg::Ov => 2,
Arg::Ap => 4,
_ => 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,
}
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"),
];
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",
}
impl Op {
#[must_use]
pub const fn mnemonic(self) -> &'static str {
MNEMONICS[self as usize]
}
#[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_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
)
}
#[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,
_ => 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 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,
group,
class,
}
}
#[must_use]
pub const fn needs_modrm(self) -> bool {
!matches!(self.group, Grp::None) || self.dst.needs_modrm() || self.src.needs_modrm()
}
#[must_use]
pub const fn width_bytes(self) -> Option<u8> {
match self.dst.width_bytes() {
Some(w) => Some(w),
None => self.src.width_bytes(),
}
}
#[must_use]
pub const fn is_prefix(self) -> bool {
matches!(self.class, Class::Prefix)
}
}
macro_rules! primary {
($($opcode:literal $op:ident $dst:ident $src:ident $group:ident $class:ident;)*) => {
pub static TABLE: [Insn; 256] = {
let mut t =
[Insn::new(Op::NOP, Arg::None, Arg::None, Grp::None, Class::Undefined); 256];
$(t[$opcode as usize] =
Insn::new(Op::$op, Arg::$dst, Arg::$src, Grp::$group, Class::$class);)*
t
};
pub static LISTED: [bool; 256] = {
let mut t = [false; 256];
$(t[$opcode as usize] = true;)*
t
};
};
}
primary! {
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 Iv None None Alias;
0xc1 RET None None None Alias;
0xc2 RET Iv 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 Iv None None Alias;
0xc9 RETF None None None Alias;
0xca RETF Iv 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 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),
];
#[inline]
#[must_use]
pub const fn decode(opcode: u8) -> Insn {
TABLE[opcode as usize]
}
#[inline]
#[must_use]
pub const fn resolve(insn: Insn, reg: u8) -> Insn {
let reg = (reg & 7) as usize;
match insn.group {
Grp::None | Grp::Pop | Grp::MovImm => insn,
Grp::Alu => Insn {
op: GROUP_ALU[reg],
..insn
},
Grp::Shift => Insn {
op: GROUP_SHIFT[reg],
..insn
},
Grp::IncDec => GROUP_INCDEC[reg],
Grp::Unary => {
if matches!(insn.dst, Arg::Eb) {
GROUP_UNARY8[reg]
} else {
GROUP_UNARY16[reg]
}
}
Grp::Misc => GROUP_MISC[reg],
}
}
#[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;
#[must_use]
pub const fn name(sr: u8) -> &'static str {
match sr & 3 {
ES => "es",
CS => "cs",
SS => "ss",
_ => "ds",
}
}
}
#[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,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Rep {
While,
WhileNot,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Fields {
pub seg_override: Option<u8>,
pub rep: Option<Rep>,
pub lock: bool,
pub opcode: u8,
pub insn: Insn,
pub modrm: Option<ModRm>,
pub disp: u16,
pub imm: 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_seg(&self) -> u16 {
(self.imm >> 16) as u16
}
#[inline]
#[must_use]
pub const fn segment(&self, default: u8) -> u8 {
match self.seg_override {
Some(sr) => sr,
None => default,
}
}
}
pub const MAX_PREFIXES: u8 = 15;
pub fn decode_stream(next: &mut dyn FnMut() -> Option<u8>) -> Fields {
let mut f = Fields {
seg_override: None,
rep: None,
lock: false,
opcode: 0x90,
insn: decode(0x90),
modrm: None,
disp: 0,
imm: 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(byte);
if !row.is_prefix() || prefixes >= MAX_PREFIXES {
break byte;
}
prefixes += 1;
match row.op {
Op::SEG => f.seg_override = Some((byte >> 3) & 3),
Op::LOCK => f.lock = true,
Op::REP => f.rep = Some(Rep::While),
Op::REPNE => f.rep = Some(Rep::WhileNot),
_ => {}
}
};
f.opcode = opcode;
let mut insn = decode(opcode);
if insn.needs_modrm() {
let byte = take(&mut f);
let modrm = ModRm::new(byte);
f.modrm = Some(modrm);
insn = resolve(insn, modrm.reg);
let n = modrm.disp_bytes();
if n > 0 {
let lo = take(&mut f);
f.disp = if n == 1 {
lo as i8 as u16
} else {
let hi = take(&mut f);
u16::from(lo) | (u16::from(hi) << 8)
};
}
}
f.insn = insn;
let imm_bytes = insn.dst.immediate_bytes() + insn.src.immediate_bytes();
let mut imm: u32 = 0;
for i in 0..imm_bytes {
let byte = take(&mut f);
imm |= u32::from(byte) << (8 * u32::from(i));
}
if insn.src == Arg::Ibs || insn.dst == Arg::Ibs || insn.dst == Arg::Jb {
imm = u32::from(imm as u8 as i8 as u16);
}
f.imm = imm;
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)
|| GROUP_ALU.contains(&op)
|| GROUP_SHIFT.contains(&op)
|| GROUP_UNARY8.iter().any(|i| i.op == op)
|| GROUP_MISC.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(), Some(1));
assert_eq!(decode(0x01).width_bytes(), Some(2));
assert_eq!(decode(0xa4).width_bytes(), Some(1)); assert_eq!(decode(0xa5).width_bytes(), Some(2)); assert_eq!(decode(0x83).width_bytes(), Some(2)); assert_eq!(decode(0xf4).width_bytes(), None); }
#[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, 0xffff);
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);
}
}