245 lines
6.4 KiB
Rust
245 lines
6.4 KiB
Rust
use core::fmt;
|
|
|
|
// b: 8, w: 16, v: 16 -> i just treat v and w the same, if nothing blows up
|
|
#[allow(non_camel_case_types)]
|
|
pub type b = u8;
|
|
#[allow(non_camel_case_types)]
|
|
pub type w = u16;
|
|
|
|
#[derive(Debug)]
|
|
#[allow(dead_code)]
|
|
/// A single 'line' of executable ASM is called an Instruction, which
|
|
/// contains the `Opcode` that will be executed, alongside its starting offset
|
|
/// and the raw parsed bytes
|
|
pub struct Instruction {
|
|
pub start: usize, // location of the instruction start
|
|
pub raw: Vec<u8>, // raw value of instruction
|
|
pub opcode: Opcode, // actual instruction
|
|
}
|
|
|
|
impl Instruction {
|
|
pub fn new() -> Self {
|
|
Instruction {
|
|
start: 0,
|
|
raw: Vec::new(),
|
|
opcode: Opcode::NOP(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Instruction {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "{:04x}: ", self.start).unwrap();
|
|
for b in self.raw.iter() {
|
|
write!(f, "{:02x}", b).unwrap();
|
|
}
|
|
write!(f, "\t{}", self.opcode)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[allow(dead_code, non_camel_case_types)]
|
|
pub enum Opcode {
|
|
NOP(),
|
|
// ADD
|
|
ADD_EbGb(MemoryIndex, Register),
|
|
// MOV
|
|
MOV_BXIv(Register, ImmediateWord),
|
|
// INT
|
|
INT(ImmediateByte),
|
|
}
|
|
|
|
impl fmt::Display for Opcode {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
Self::INT(byte) => write!(f, "INT, {:x}", byte),
|
|
Self::ADD_EbGb(mem, reg) => write!(f, "ADD {}, {}", mem, reg),
|
|
Self::MOV_BXIv(reg, word) => write!(f, "MOV {}, {:04x}", reg, word),
|
|
_ => write!(f, "display not yet implemented"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Registers of a 8086 processor
|
|
#[derive(Debug)]
|
|
#[allow(dead_code)]
|
|
pub enum Register {
|
|
AX,
|
|
BX,
|
|
CX,
|
|
DX,
|
|
AH,
|
|
AL,
|
|
BL,
|
|
BH,
|
|
CH,
|
|
CL,
|
|
DH,
|
|
DL,
|
|
DI,
|
|
SI,
|
|
BP,
|
|
SP,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
impl Register {
|
|
/// Find the register corresponding to the 8086 bytecode ID
|
|
pub fn by_id(id: u8) -> Self {
|
|
match id {
|
|
0x00 => Self::AL,
|
|
0x01 => Self::CL,
|
|
0x02 => Self::DL,
|
|
0x03 => Self::BL,
|
|
0x04 => Self::AH,
|
|
0x05 => Self::CH,
|
|
0x06 => Self::DH,
|
|
0x07 => Self::BH,
|
|
0x10 => Self::AX,
|
|
0x11 => Self::CX,
|
|
0x12 => Self::DX,
|
|
0x13 => Self::BX,
|
|
0x14 => Self::SP,
|
|
0x15 => Self::BP,
|
|
0x16 => Self::SI,
|
|
0x17 => Self::DI,
|
|
_ => panic!("Invalid register ID encountered"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Register {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
Self::AX => write!(f, "AX"),
|
|
Self::BX => write!(f, "BX"),
|
|
Self::CX => write!(f, "CX"),
|
|
Self::DX => write!(f, "DX"),
|
|
Self::AH => write!(f, "AH"),
|
|
Self::AL => write!(f, "AL"),
|
|
Self::BL => write!(f, "BL"),
|
|
Self::BH => write!(f, "BH"),
|
|
Self::CH => write!(f, "CH"),
|
|
Self::CL => write!(f, "CL"),
|
|
Self::DH => write!(f, "DH"),
|
|
Self::DL => write!(f, "DL"),
|
|
Self::DI => write!(f, "DI"),
|
|
Self::SI => write!(f, "SI"),
|
|
Self::BP => write!(f, "BP"),
|
|
Self::SP => write!(f, "SP"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Segment Registers of a 8086 processor
|
|
#[derive(Debug)]
|
|
#[allow(dead_code)]
|
|
pub enum SegmentRegister {
|
|
DS,
|
|
ES,
|
|
SS,
|
|
CS,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
impl SegmentRegister {
|
|
/// Find the SRegister corresponding to the 8086 bytecode ID
|
|
pub fn by_id(id: u8) -> Self {
|
|
match id {
|
|
0x30 => Self::ES,
|
|
0x31 => Self::CS,
|
|
0x32 => Self::SS,
|
|
0x33 => Self::DS,
|
|
_ => panic!("Invalid segment register ID encountered"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for SegmentRegister {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
Self::DS => write!(f, "DS"),
|
|
Self::ES => write!(f, "ES"),
|
|
Self::SS => write!(f, "SS"),
|
|
Self::CS => write!(f, "CS"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An immediate byte value for an instruction.
|
|
#[derive(Debug)]
|
|
pub struct ImmediateByte(pub b);
|
|
|
|
/// An immediate word value for an instruction
|
|
#[derive(Debug)]
|
|
pub struct ImmediateWord(pub w);
|
|
|
|
macro_rules! impl_display_and_lowerhex {
|
|
($name:ident) => {
|
|
impl std::fmt::Display for $name {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
write!(f, "{}", self.0)
|
|
}
|
|
}
|
|
|
|
impl std::fmt::LowerHex for $name {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
std::fmt::LowerHex::fmt(&self.0, f)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
impl_display_and_lowerhex!(ImmediateByte);
|
|
impl_display_and_lowerhex!(ImmediateWord);
|
|
|
|
/// A memory index operand is usually created by ModRM bytes or words.
|
|
/// e.g. [bx+si]
|
|
#[derive(Debug)]
|
|
pub struct MemoryIndex {
|
|
pub base: Option<Register>,
|
|
pub index: Option<Register>,
|
|
pub displacement: Option<Displacement>,
|
|
}
|
|
|
|
impl fmt::Display for MemoryIndex {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match &self.base {
|
|
Some(base) => match &self.index {
|
|
Some(index) => match &self.displacement {
|
|
Some(displacement) => write!(f, "[{}+{}+{}]", base, index, displacement),
|
|
None => write!(f, "[{}+{}]", base, index),
|
|
},
|
|
None => match &self.displacement {
|
|
Some(displacement) => write!(f, "[{}+{}]", base, displacement),
|
|
None => write!(f, "[{}]", base),
|
|
},
|
|
},
|
|
None => match &self.index {
|
|
Some(index) => match &self.displacement {
|
|
Some(displacement) => write!(f, "{}+{}", index, displacement),
|
|
None => write!(f, "[{}]", index),
|
|
},
|
|
None => panic!("Invalid MemoryIndex encountered"),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[allow(dead_code)]
|
|
/// Displacement for ModRM
|
|
pub enum Displacement {
|
|
Byte(u8),
|
|
Word(u16),
|
|
}
|
|
|
|
impl fmt::Display for Displacement {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
Self::Byte(byte) => write!(f, "{}", byte),
|
|
Self::Word(word) => write!(f, "{}", word),
|
|
}
|
|
}
|
|
}
|