use alloc::vec::Vec;
use core::fmt;
use super::isa::{self, Arg, Class, Fields, Gen, ModRm, Op, Rep, seg};
pub const REG8: [&str; 8] = ["al", "cl", "dl", "bl", "ah", "ch", "dh", "bh"];
pub const REG16: [&str; 8] = ["ax", "cx", "dx", "bx", "sp", "bp", "si", "di"];
pub const REG32: [&str; 8] = ["eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi"];
const RM_TERMS: [&str; 8] = ["bx+si", "bx+di", "bp+si", "bp+di", "si", "di", "bp", "bx"];
const fn regs_for(size: u8) -> [&'static str; 8] {
match size {
1 => REG8,
2 => REG16,
_ => REG32,
}
}
pub const MAX_KEPT_BYTES: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Disassembled {
pub cs: u16,
pub ip: u32,
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) -> u32 {
let next = self.ip.wrapping_add(self.len as u32);
if self.fields.opsize == 2 {
(self.ip & 0xffff_0000) | (next & 0xffff)
} else {
next
}
}
#[must_use]
pub const fn branch_target(&self) -> Option<u32> {
match (self.fields.insn.dst, self.fields.insn.src) {
(Arg::Jb | Arg::Jv, _) => {
let target = self.next_ip().wrapping_add(self.fields.imm);
Some(if self.fields.opsize == 2 {
target & 0xffff
} else {
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
)
}
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 ")?;
}
match fields.rep {
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, _) => 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 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,
_ => {}
}
}
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: u32,
) -> fmt::Result {
let fields = &self.fields;
let osz = fields.opsize;
match arg {
Arg::None => Ok(()),
Arg::Eb | Arg::Ev | Arg::Ew => {
let size = match arg {
Arg::Eb => 1,
Arg::Ew => 2,
_ => osz,
};
let modrm = fields.modrm.unwrap_or(ModRm::new(0));
if modrm.is_register() {
f.write_str(regs_for(size)[modrm.rm 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)[modrm.rm as usize])
} else {
self.write_mem(f, modrm, 0, false)
}
}
Arg::Gb => f.write_str(REG8[fields.modrm.map_or(0, |m| m.reg) as usize]),
Arg::Gw => f.write_str(REG16[fields.modrm.map_or(0, |m| m.reg) as usize]),
Arg::Gv => f.write_str(regs_for(osz)[fields.modrm.map_or(0, |m| m.reg) as usize]),
Arg::Rd => f.write_str(REG32[fields.modrm.map_or(0, |m| m.rm) as usize]),
Arg::Cd => write!(f, "cr{}", fields.modrm.map_or(0, |m| m.reg)),
Arg::Dd => write!(f, "dr{}", fields.modrm.map_or(0, |m| m.reg)),
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::Ibs => {
write!(f, "{:#x}", if osz == 2 { imm & 0xffff } else { imm })
}
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(REG8[(fields.opcode & 7) as usize]),
Arg::Rv => f.write_str(regs_for(osz)[(fields.opcode & 7) 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)[0]),
Arg::Xb | Arg::Xv => write!(
f,
"[{}:{}]",
seg::name(fields.segment(seg::DS)),
if fields.addrsize == 2 { "si" } else { "esi" }
),
Arg::Yb | Arg::Yv => write!(
f,
"[es:{}]",
if fields.addrsize == 2 { "di" } else { "edi" }
),
}
}
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.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(REG32[sib.base as usize])?;
wrote = true;
}
if sib.has_index() {
if wrote {
f.write_str("+")?;
}
f.write_str(REG32[sib.index as usize])?;
if sib.scale != 0 {
write!(f, "*{}", 1u32 << sib.scale)?;
}
wrote = true;
}
} else if !(modrm.rm == 5 && modrm.md == 0) {
f.write_str(REG32[modrm.rm 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 ",
_ => "dword ",
}
}
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, false, cs, u32::from(ip), bytes)
}
#[must_use]
pub fn disassemble_as(map: Gen, bits32: bool, cs: u16, ip: u32, bytes: &[u8]) -> Disassembled {
let mut at = 0usize;
let mut kept = [0u8; MAX_KEPT_BYTES];
let fields = isa::decode_stream_as(map, bits32, &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(u32) -> 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: u32::from(ip),
fields,
bytes: kept,
len: fields.len,
truncated: fields.truncated,
}
}
#[must_use]
pub fn disassemble_at_as(
map: Gen,
bits32: bool,
cs: u16,
ip: u32,
mut read: impl FnMut(u32) -> Option<u8>,
) -> Disassembled {
let mut offset = 0u32;
let mut kept = [0u8; MAX_KEPT_BYTES];
let fields = isa::decode_stream_as(map, bits32, &mut || {
let at = if bits32 {
ip.wrapping_add(offset)
} else {
(ip & 0xffff_0000) | u32::from((ip as u16).wrapping_add(offset as u16))
};
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(u32) -> 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,
bits32: bool,
cs: u16,
ip: u32,
count: usize,
mut read: impl FnMut(u32) -> 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, bits32, 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()); }
}