use alloc::vec::Vec;
use core::fmt;
use super::isa::{self, Arg, Bits, Class, Fields, Gen, ModRm, Op, Rep, seg};
pub const REG8: [&str; 8] = ["al", "cl", "dl", "bl", "ah", "ch", "dh", "bh"];
pub const REG8_REX: [&str; 16] = [
"al", "cl", "dl", "bl", "spl", "bpl", "sil", "dil", "r8b", "r9b", "r10b", "r11b", "r12b",
"r13b", "r14b", "r15b",
];
pub const REG16: [&str; 16] = [
"ax", "cx", "dx", "bx", "sp", "bp", "si", "di", "r8w", "r9w", "r10w", "r11w", "r12w", "r13w",
"r14w", "r15w",
];
pub const REG32: [&str; 16] = [
"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi", "r8d", "r9d", "r10d", "r11d", "r12d",
"r13d", "r14d", "r15d",
];
pub const REG64: [&str; 16] = [
"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", "r8", "r9", "r10", "r11", "r12", "r13",
"r14", "r15",
];
const RM_TERMS: [&str; 8] = ["bx+si", "bx+di", "bp+si", "bp+di", "si", "di", "bp", "bx"];
const fn regs_for(size: u8, rex: bool) -> [&'static str; 16] {
match size {
1 if rex => REG8_REX,
1 => {
[
REG8[0], REG8[1], REG8[2], REG8[3], REG8[4], REG8[5], REG8[6], REG8[7], REG8[0],
REG8[1], REG8[2], REG8[3], REG8[4], REG8[5], REG8[6], REG8[7],
]
}
2 => REG16,
4 => REG32,
_ => REG64,
}
}
pub const MAX_KEPT_BYTES: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Disassembled {
pub cs: u16,
pub ip: u64,
pub fields: Fields,
pub bytes: [u8; MAX_KEPT_BYTES],
pub len: u8,
pub truncated: bool,
}
impl Disassembled {
#[must_use]
pub const fn next_ip(&self) -> u64 {
let next = self.ip.wrapping_add(self.len as u64);
match self.fields.bits {
Bits::B16 => (self.ip & !0xffff) | (next & 0xffff),
Bits::B32 => (self.ip & !0xffff_ffff) | (next & 0xffff_ffff),
Bits::B64 => next,
}
}
#[must_use]
pub const fn branch_target(&self) -> Option<u64> {
match (self.fields.insn.dst, self.fields.insn.src) {
(Arg::Jb | Arg::Jv, _) => {
let target = self.next_ip().wrapping_add(self.fields.imm);
Some(match self.fields.bits {
Bits::B16 => target & 0xffff,
Bits::B32 => target & 0xffff_ffff,
Bits::B64 => target,
})
}
_ => None,
}
}
#[must_use]
pub const fn is_undocumented(&self) -> bool {
!matches!(self.fields.insn.class, Class::Documented)
}
#[must_use]
pub const fn op(&self) -> Op {
self.fields.insn.op
}
}
const fn fixes_size(arg: Arg) -> bool {
matches!(
arg,
Arg::Gb
| Arg::Gv
| Arg::Gw
| Arg::Sw
| Arg::Rb
| Arg::Rv
| Arg::Rd
| Arg::Cd
| Arg::Dd
| Arg::Td
| Arg::Sr
| Arg::Al
| Arg::Ax
| Arg::Cl
| Arg::Dx
| Arg::St0
| Arg::Sti
| Arg::Vx
| Arg::Ux
| Arg::Wx
| Arg::Wq
| Arg::Wd
| Arg::Gy
| Arg::Ey
| Arg::Mf32
| Arg::Mf64
| Arg::Mf80
| Arg::Mi16
| Arg::Mi32
| Arg::Mi64
| Arg::Mfenv
| Arg::Mfsave
| Arg::Mfx
| Arg::Mq
)
}
impl fmt::Display for Disassembled {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let fields = &self.fields;
let insn = fields.insn;
if fields.lock {
f.write_str("lock ")?;
}
let mandatory = !matches!(fields.mandatory, isa::Mandatory::None);
match fields.rep {
_ if mandatory => {}
Some(Rep::While) if insn.op.repeat_tests_zf() => f.write_str("repe ")?,
Some(Rep::While) => f.write_str("rep ")?,
Some(Rep::WhileNot) => f.write_str("repne ")?,
None => {}
}
if let Some(sr) = fields.seg_override
&& !self.has_memory_operand()
{
write!(f, "{}: ", seg::name(sr))?;
}
f.write_str(self.mnemonic())?;
let show_size = self.needs_size_hint();
let mut first = true;
let mut slot = 0u8;
for arg in [insn.dst, insn.src, insn.aux] {
if arg == Arg::None {
continue;
}
let imm = if arg == Arg::Ap {
slot = 2;
fields.imm
} else if arg.immediate_bytes(fields.opsize, fields.addrsize) > 0 {
if slot == 0 {
slot = 1;
fields.imm
} else {
slot = 2;
fields.imm2
}
} else {
fields.imm
};
f.write_str(if first { " " } else { ", " })?;
first = false;
self.write_arg(f, arg, show_size, imm)?;
}
Ok(())
}
}
impl Disassembled {
fn mnemonic(&self) -> &'static str {
let wide = self.fields.opsize == 4;
match (self.fields.insn.op, wide) {
(Op::CBW, true) => "cwde",
(Op::CWD, true) => "cdq",
(Op::IRET, true) => "iretd",
(Op::PUSHA, true) => "pushad",
(Op::POPA, true) => "popad",
(Op::PUSHF, true) => "pushfd",
(Op::POPF, true) => "popfd",
(Op::MOVSW, true) => "movsd",
(Op::CMPSW, true) => "cmpsd",
(Op::STOSW, true) => "stosd",
(Op::LODSW, true) => "lodsd",
(Op::SCASW, true) => "scasd",
(Op::INSW, true) => "insd",
(Op::OUTSW, true) => "outsd",
(Op::MOVD, _) if self.fields.rex_w() => "movq",
(Op::FXSAVE, _) if self.fields.rex_w() => "fxsave64",
(Op::FXRSTOR, _) if self.fields.rex_w() => "fxrstor64",
(Op::CMPXCHG8B, _) if self.fields.rex_w() => "cmpxchg16b",
(op, _) => op.mnemonic(),
}
}
fn has_memory_operand(&self) -> bool {
let insn = self.fields.insn;
let modrm_mem = matches!(self.fields.modrm, Some(m) if !m.is_register());
for arg in [insn.dst, insn.src, insn.aux] {
match arg {
Arg::Eb | Arg::Ev | Arg::Ew | Arg::Ed if modrm_mem => return true,
Arg::Wx | Arg::Wq | Arg::Wd | Arg::Ey if modrm_mem => return true,
Arg::M | Arg::Mp | Arg::Ms => return true,
Arg::Ob | Arg::Ov | Arg::Xb | Arg::Xv | Arg::Yb | Arg::Yv => return true,
Arg::Mf32
| Arg::Mf64
| Arg::Mf80
| Arg::Mi16
| Arg::Mi32
| Arg::Mi64
| Arg::Mfenv
| Arg::Mfsave
| Arg::Mfx
| Arg::Mq => return true,
_ => {}
}
}
false
}
fn needs_size_hint(&self) -> bool {
let insn = self.fields.insn;
if !self.has_memory_operand() {
return false;
}
if insn.op.is_string() {
return false;
}
!fixes_size(insn.dst) && !fixes_size(insn.src)
}
fn write_arg(
&self,
f: &mut fmt::Formatter<'_>,
arg: Arg,
show_size: bool,
imm: u64,
) -> fmt::Result {
let fields = &self.fields;
let osz = fields.opsize;
let rex = fields.has_rex();
match arg {
Arg::None => Ok(()),
Arg::Eb | Arg::Ev | Arg::Ew | Arg::Ed => {
let size = match arg {
Arg::Eb => 1,
Arg::Ew => 2,
Arg::Ed => 4,
_ => osz,
};
let modrm = fields.modrm.unwrap_or(ModRm::new(0));
if modrm.is_register() {
f.write_str(regs_for(size, rex)[fields.rm_num() as usize])
} else {
self.write_mem(f, modrm, size, show_size)
}
}
Arg::M | Arg::Mp | Arg::Ms => {
let modrm = fields.modrm.unwrap_or(ModRm::new(0));
if modrm.is_register() {
f.write_str(regs_for(osz, rex)[fields.rm_num() as usize])
} else {
self.write_mem(f, modrm, 0, false)
}
}
Arg::Gb => f.write_str(regs_for(1, rex)[fields.reg_num() as usize]),
Arg::Gw => f.write_str(REG16[fields.reg_num() as usize]),
Arg::Gv => f.write_str(regs_for(osz, rex)[fields.reg_num() as usize]),
Arg::Rd => {
let width = if fields.bits.is_64() { 8 } else { 4 };
f.write_str(regs_for(width, rex)[fields.rm_num() as usize])
}
Arg::Cd => write!(f, "cr{}", fields.reg_num()),
Arg::Dd => write!(f, "dr{}", fields.reg_num()),
Arg::Td => write!(f, "tr{}", fields.modrm.map_or(0, |m| m.reg)),
Arg::Sw => {
let index = fields.modrm.map_or(0, |m| m.reg);
let index = if matches!(fields.map, Gen::I8086) {
index & 3
} else {
index
};
f.write_str(seg::name(index))
}
Arg::Ib => write!(f, "{:#x}", imm as u8),
Arg::Iw => write!(f, "{:#x}", imm as u16),
Arg::Iv | Arg::Iz | Arg::Ibs => {
let shown = match osz {
2 => imm & 0xffff,
4 => imm & 0xffff_ffff,
_ => imm,
};
write!(f, "{shown:#x}")
}
Arg::Jb | Arg::Jv => {
let target = self.branch_target().unwrap_or(0);
write!(f, "{target:#x}")
}
Arg::Ap => write!(f, "{:#x}:{:#x}", fields.imm_seg(), imm),
Arg::Ob | Arg::Ov => {
if show_size {
f.write_str(size_hint(if arg == Arg::Ob { 1 } else { osz }))?;
}
write!(f, "[{}:{:#x}]", seg::name(fields.segment(seg::DS)), imm)
}
Arg::Rb => f.write_str(regs_for(1, rex)[fields.opcode_reg() as usize]),
Arg::Rv => f.write_str(regs_for(osz, rex)[fields.opcode_reg() as usize]),
Arg::Sr => f.write_str(seg::name((fields.opcode >> 3) & 7)),
Arg::One => f.write_str("1"),
Arg::Cl => f.write_str("cl"),
Arg::Dx => f.write_str("dx"),
Arg::Al => f.write_str("al"),
Arg::Ax => f.write_str(regs_for(osz, rex)[0]),
Arg::Xb | Arg::Xv => write!(
f,
"[{}:{}]",
seg::name(fields.segment(seg::DS)),
index_name(fields.addrsize, 6)
),
Arg::Yb | Arg::Yv => write!(f, "[es:{}]", index_name(fields.addrsize, 7)),
Arg::St0 => f.write_str("st(0)"),
Arg::Sti => write!(f, "st({})", fields.modrm.map_or(0, |m| m.rm)),
Arg::Mf32
| Arg::Mf64
| Arg::Mf80
| Arg::Mi16
| Arg::Mi32
| Arg::Mi64
| Arg::Mfenv
| Arg::Mfsave
| Arg::Mfx
| Arg::Mq => {
let modrm = fields.modrm.unwrap_or(ModRm::new(0));
let size = arg.fp_bytes(osz).unwrap_or(0);
self.write_mem(f, modrm, size, true)
}
Arg::Vx => write!(f, "xmm{}", fields.reg_num()),
Arg::Ux => write!(f, "xmm{}", fields.rm_num()),
Arg::Wx | Arg::Wq | Arg::Wd => {
let modrm = fields.modrm.unwrap_or(ModRm::new(0));
if modrm.is_register() {
write!(f, "xmm{}", fields.rm_num())
} else {
let size = arg.fp_bytes(osz).unwrap_or(0);
self.write_mem(f, modrm, size, true)
}
}
Arg::Ey => {
let width = if fields.rex_w() { 8 } else { 4 };
let modrm = fields.modrm.unwrap_or(ModRm::new(0));
if modrm.is_register() {
f.write_str(regs_for(width, rex)[fields.rm_num() as usize])
} else {
self.write_mem(f, modrm, width, true)
}
}
Arg::Gy => {
let width = if fields.rex_w() { 8 } else { 4 };
f.write_str(regs_for(width, rex)[fields.reg_num() as usize])
}
}
}
fn write_mem(
&self,
f: &mut fmt::Formatter<'_>,
modrm: ModRm,
size: u8,
show_size: bool,
) -> fmt::Result {
if show_size && size != 0 {
f.write_str(size_hint(size))?;
}
let sr = self.fields.mem_segment();
write!(f, "[{}:", seg::name(sr))?;
let disp = self.fields.disp;
if self.fields.rip_relative {
f.write_str("rip")?;
write_disp(f, disp)?;
return f.write_str("]");
}
let wide = regs_for(self.fields.addrsize, false);
if self.fields.addrsize == 2 {
if modrm.md == 0 && modrm.rm == 6 {
return write!(f, "{:#x}]", disp as u16);
}
f.write_str(RM_TERMS[modrm.rm as usize])?;
write_disp(f, disp)?;
return f.write_str("]");
}
let mut wrote = false;
if modrm.rm == 4 {
let sib = self.fields.sib.unwrap_or(isa::Sib::new(0));
if !(sib.base == 5 && modrm.md == 0) {
f.write_str(wide[self.fields.base_num() as usize])?;
wrote = true;
}
if self.fields.has_index() {
if wrote {
f.write_str("+")?;
}
f.write_str(wide[self.fields.index_num() as usize])?;
if sib.scale != 0 {
write!(f, "*{}", 1u32 << sib.scale)?;
}
wrote = true;
}
} else if !(modrm.rm == 5 && modrm.md == 0) {
f.write_str(wide[self.fields.rm_num() as usize])?;
wrote = true;
}
if wrote {
write_disp(f, disp)?;
} else {
write!(f, "{:#x}", disp as u32)?;
}
f.write_str("]")
}
}
const fn size_hint(size: u8) -> &'static str {
match size {
1 => "byte ",
2 => "word ",
4 => "dword ",
8 => "qword ",
10 => "tbyte ",
16 => "xmmword ",
_ => "",
}
}
const fn index_name(addrsize: u8, index: usize) -> &'static str {
match addrsize {
2 => REG16[index],
4 => REG32[index],
_ => REG64[index],
}
}
fn write_disp(f: &mut fmt::Formatter<'_>, disp: i32) -> fmt::Result {
if disp == 0 {
return Ok(());
}
if disp < 0 {
write!(f, "-{:#x}", disp.unsigned_abs())
} else {
write!(f, "+{disp:#x}")
}
}
#[must_use]
pub fn disassemble(cs: u16, ip: u16, bytes: &[u8]) -> Disassembled {
disassemble_as(Gen::I8086, Bits::B16, cs, u64::from(ip), bytes)
}
#[must_use]
pub fn disassemble_as(map: Gen, bits: Bits, cs: u16, ip: u64, bytes: &[u8]) -> Disassembled {
let mut at = 0usize;
let mut kept = [0u8; MAX_KEPT_BYTES];
let fields = isa::decode_stream_as(map, bits, &mut || {
let b = bytes.get(at).copied();
if let Some(b) = b
&& at < MAX_KEPT_BYTES
{
kept[at] = b;
}
at += 1;
b
});
Disassembled {
cs,
ip,
fields,
bytes: kept,
len: fields.len,
truncated: fields.truncated,
}
}
#[must_use]
pub fn disassemble_at(cs: u16, ip: u16, mut read: impl FnMut(u64) -> Option<u8>) -> Disassembled {
let mut offset = 0u16;
let mut kept = [0u8; MAX_KEPT_BYTES];
let fields = isa::decode_stream(&mut || {
let addr = super::linear(cs, ip.wrapping_add(offset));
let b = read(addr);
if let Some(b) = b
&& (offset as usize) < MAX_KEPT_BYTES
{
kept[offset as usize] = b;
}
offset = offset.wrapping_add(1);
b
});
Disassembled {
cs,
ip: u64::from(ip),
fields,
bytes: kept,
len: fields.len,
truncated: fields.truncated,
}
}
#[must_use]
pub fn disassemble_at_as(
map: Gen,
bits: Bits,
cs: u16,
ip: u64,
mut read: impl FnMut(u64) -> Option<u8>,
) -> Disassembled {
let mut offset = 0u64;
let mut kept = [0u8; MAX_KEPT_BYTES];
let fields = isa::decode_stream_as(map, bits, &mut || {
let at = match bits {
Bits::B16 => (ip & !0xffff) | u64::from((ip as u16).wrapping_add(offset as u16)),
Bits::B32 => (ip & !0xffff_ffff) | u64::from((ip as u32).wrapping_add(offset as u32)),
Bits::B64 => ip.wrapping_add(offset),
};
let b = read(at);
if let Some(b) = b
&& (offset as usize) < MAX_KEPT_BYTES
{
kept[offset as usize] = b;
}
offset = offset.wrapping_add(1);
b
});
Disassembled {
cs,
ip,
fields,
bytes: kept,
len: fields.len,
truncated: fields.truncated,
}
}
#[must_use]
pub fn disassemble_run(
cs: u16,
ip: u16,
count: usize,
mut read: impl FnMut(u64) -> Option<u8>,
) -> Vec<Disassembled> {
let mut out = Vec::with_capacity(count);
let mut ip = ip;
for _ in 0..count {
let d = disassemble_at(cs, ip, &mut read);
let truncated = d.truncated;
ip = d.next_ip() as u16;
out.push(d);
if truncated {
break;
}
}
out
}
#[must_use]
pub fn disassemble_run_as(
map: Gen,
bits: Bits,
cs: u16,
ip: u64,
count: usize,
mut read: impl FnMut(u64) -> Option<u8>,
) -> Vec<Disassembled> {
let mut out = Vec::with_capacity(count);
let mut ip = ip;
for _ in 0..count {
let d = disassemble_at_as(map, bits, cs, ip, &mut read);
let truncated = d.truncated;
ip = d.next_ip();
out.push(d);
if truncated {
break;
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
use alloc::string::String;
fn text(bytes: &[u8]) -> String {
format!("{}", disassemble(0, 0x100, bytes))
}
#[test]
fn the_alu_group_prints_in_intel_order() {
assert_eq!(text(&[0x00, 0xc1]), "add cl, al");
assert_eq!(text(&[0x03, 0x06, 0x34, 0x12]), "add ax, [ds:0x1234]");
assert_eq!(text(&[0x83, 0xc0, 0xfe]), "add ax, 0xfffe");
assert_eq!(
text(&[0x80, 0x36, 0x34, 0x12, 0x0f]),
"xor byte [ds:0x1234], 0xf"
);
}
#[test]
fn addressing_modes_read_the_way_the_manual_writes_them() {
assert_eq!(text(&[0x8b, 0x00]), "mov ax, [ds:bx+si]");
assert_eq!(text(&[0x8b, 0x46, 0x00]), "mov ax, [ss:bp]");
assert_eq!(text(&[0x8b, 0x46, 0xfe]), "mov ax, [ss:bp-0x2]");
assert_eq!(text(&[0x8b, 0x86, 0x00, 0x01]), "mov ax, [ss:bp+0x100]");
assert_eq!(text(&[0x8b, 0x06, 0x00, 0x01]), "mov ax, [ds:0x100]");
assert_eq!(text(&[0x26, 0x8b, 0x07]), "mov ax, [es:bx]");
}
#[test]
fn a_size_hint_appears_only_when_nothing_else_gives_the_width() {
assert_eq!(text(&[0xff, 0x06, 0x34, 0x12]), "inc word [ds:0x1234]");
assert_eq!(text(&[0xfe, 0x06, 0x34, 0x12]), "inc byte [ds:0x1234]");
assert_eq!(text(&[0x8b, 0x07]), "mov ax, [ds:bx]");
assert_eq!(text(&[0xc6, 0x07, 0x42]), "mov byte [ds:bx], 0x42");
}
#[test]
fn relative_transfers_print_their_target() {
let d = disassemble(0, 0x100, &[0xeb, 0xfe]);
assert_eq!(format!("{d}"), "jmp 0x100");
assert_eq!(d.branch_target(), Some(0x100));
assert_eq!(text(&[0x74, 0x10]), "jz 0x112");
assert_eq!(text(&[0xe8, 0x00, 0x01]), "call 0x203");
}
#[test]
fn far_transfers_print_segment_and_offset() {
assert_eq!(text(&[0xea, 0x5b, 0xe0, 0x00, 0xf0]), "jmpf 0xf000:0xe05b");
assert_eq!(text(&[0xff, 0x1e, 0x00, 0x20]), "callf [ds:0x2000]");
}
#[test]
fn prefixes_are_printed_before_the_mnemonic() {
assert_eq!(text(&[0xf3, 0xa4]), "rep movsb [es:di], [ds:si]");
assert_eq!(text(&[0xf2, 0xae]), "repne scasb al, [es:di]");
assert_eq!(text(&[0xf3, 0xa6]), "repe cmpsb [ds:si], [es:di]");
assert_eq!(text(&[0x26, 0xa4]), "movsb [es:di], [es:si]");
assert_eq!(text(&[0xf0, 0x00, 0x07]), "lock add [ds:bx], al");
}
#[test]
fn a_segment_override_with_nothing_to_override_is_still_shown() {
assert_eq!(text(&[0x2e, 0x90]), "cs: nop");
}
#[test]
fn the_shift_group_names_its_count() {
assert_eq!(text(&[0xd0, 0xe0]), "shl al, 1");
assert_eq!(text(&[0xd3, 0xe8]), "shr ax, cl");
assert_eq!(text(&[0xd1, 0x26, 0x00, 0x20]), "shl word [ds:0x2000], 1");
assert_eq!(text(&[0xd0, 0xf0]), "setmo al, 1");
}
#[test]
fn segment_and_accumulator_forms_name_their_registers() {
assert_eq!(text(&[0x06]), "push es");
assert_eq!(text(&[0x1f]), "pop ds");
assert_eq!(text(&[0x8e, 0xd8]), "mov ds, ax");
assert_eq!(text(&[0x8c, 0xc8]), "mov ax, cs");
assert_eq!(text(&[0xa0, 0x34, 0x12]), "mov al, [ds:0x1234]");
assert_eq!(text(&[0xa3, 0x34, 0x12]), "mov [ds:0x1234], ax");
assert_eq!(text(&[0xe4, 0x60]), "in al, 0x60");
assert_eq!(text(&[0xee]), "out dx, al");
}
#[test]
fn a_run_walks_forward_by_the_decoded_length() {
let code = [0xb8u8, 0x34, 0x12, 0x40, 0x90];
let run = disassemble_run(0, 0, 3, |addr| code.get(addr as usize).copied());
assert_eq!(run.len(), 3);
assert_eq!(format!("{}", run[0]), "mov ax, 0x1234");
assert_eq!(run[1].ip, 3);
assert_eq!(format!("{}", run[1]), "inc ax");
assert_eq!(format!("{}", run[2]), "nop");
}
#[test]
fn undocumented_encodings_are_flagged() {
assert!(disassemble(0, 0, &[0xd6]).is_undocumented()); assert!(disassemble(0, 0, &[0x62, 0x00]).is_undocumented()); assert!(!disassemble(0, 0, &[0x72, 0x00]).is_undocumented()); }
}