mod data_type;
mod decode;
mod evex;
mod iter;
mod register;
pub use data_type::OperandDataType;
pub use evex::{FpControl, Masking, RoundMode};
pub use iter::{Instructions, InstructionsIn};
pub use register::{Register, RegisterClass};
pub(crate) use decode::classify;
use serde::{Deserialize, Serialize};
use snafu::Snafu;
use crate::Database;
use crate::address::Address;
impl Database {
#[doc(alias("decode_insn"))]
pub fn decode(&self, address: Address) -> Result<Instruction, DecodeError> {
let data = self.decode_insn(address);
classify(&data, address)
}
}
#[derive(Debug, Snafu, PartialEq, Eq)]
#[snafu(visibility(pub(crate)))]
pub enum DecodeError {
#[snafu(display("no instruction at {address:#x}"))]
NotCode {
address: u64,
},
#[snafu(display("no instruction decoder for this processor (x86/x64 only)"))]
UnsupportedProcessor,
#[snafu(display("unmodeled operand {slot} (raw operand type {operand_type}) at {address:#x}"))]
UnsupportedOperand {
address: u64,
slot: u8,
#[doc(alias("optype_t"))]
operand_type: u8,
},
#[snafu(display("unmodeled register {register_number} at operand {slot}, {address:#x}"))]
UnsupportedRegister {
address: u64,
slot: u8,
#[doc(alias("regnum"))]
register_number: u8,
},
#[snafu(display("unmodeled data type {data_type} at operand {slot}, {address:#x}"))]
UnsupportedDataType {
address: u64,
slot: u8,
#[doc(alias("dtype"))]
data_type: u8,
},
#[snafu(display("malformed operand {slot} at {address:#x}: {reason}"))]
MalformedOperand {
address: u64,
slot: u8,
reason: &'static str,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Isa {
X86,
X64,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[doc(alias("insn_t"))]
pub struct Instruction {
pub address: Address,
pub len: u8,
pub isa: Isa,
#[doc(alias("itype"))]
pub canonical_code: u16,
pub mnemonic: Box<str>,
pub ops: Vec<Operand>,
pub flow: Flow,
pub masking: Option<Masking>,
pub fp_control: Option<FpControl>,
}
impl Instruction {
pub fn registers(&self) -> impl Iterator<Item = &Register> {
self.ops.iter().flat_map(|op| {
let regs: [Option<&Register>; 3] = match &op.kind {
OperandKind::Register(r) => [Some(r), None, None],
OperandKind::Memory(m) => [m.base.as_ref(), m.index.as_ref(), m.segment.as_ref()],
_ => [None, None, None],
};
regs.into_iter().flatten()
})
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[doc(alias("op_t"))]
pub struct Operand {
pub slot: u8,
#[doc(alias("offb"))]
pub byte_offset: u8,
pub kind: OperandKind,
pub data_type: OperandDataType,
pub access: Access,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[doc(alias("op_t"))]
pub enum OperandKind {
Register(Register),
Memory(Memory),
Immediate {
value: u64,
},
Near(Address),
Far {
selector: u16,
offset: u64,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Memory {
pub base: Option<Register>,
pub index: Option<Register>,
pub scale: u8,
pub displacement: i64,
pub segment: Option<Register>,
pub target: Option<Address>,
pub broadcast: Option<u8>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub struct Access {
pub read: bool,
pub written: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Flow {
pub is_call: bool,
pub is_ret: bool,
pub is_jump: bool,
pub is_indirect: bool,
pub stops: bool,
pub target: Option<Address>,
}
#[cfg(test)]
mod tests {
use assert2::assert;
use super::*;
use crate::address::Address;
const fn assert_send<T: Send>() {}
const _: () = assert_send::<Instruction>();
fn reg(name: &str) -> Register {
Register {
number: 0,
class: RegisterClass::GeneralPurpose,
width: 8,
name: name.into(),
}
}
fn op(kind: OperandKind) -> Operand {
Operand {
slot: 0,
byte_offset: 0,
kind,
data_type: OperandDataType::Qword,
access: Access::default(),
}
}
#[test]
fn registers_walks_operand_and_memory_components_in_order() {
let insn = Instruction {
address: Address::try_new(0x1000).expect("valid"),
len: 4,
isa: Isa::X64,
canonical_code: 0,
mnemonic: "lea".into(),
ops: vec![
op(OperandKind::Register(reg("rax"))),
op(OperandKind::Memory(Memory {
base: Some(reg("rbx")),
index: Some(reg("rcx")),
scale: 1,
displacement: 0,
segment: None,
target: None,
broadcast: None,
})),
op(OperandKind::Immediate { value: 5 }),
],
masking: None,
fp_control: None,
flow: Flow {
is_call: false,
is_ret: false,
is_jump: false,
is_indirect: false,
stops: false,
target: None,
},
};
let names: Vec<&str> = insn.registers().map(|r| r.name.as_ref()).collect();
assert!(names == ["rax", "rbx", "rcx"]);
}
fn sample_instruction() -> Instruction {
Instruction {
address: Address::try_new(0x1000).expect("valid"),
len: 4,
isa: Isa::X64,
canonical_code: 0,
mnemonic: "lea".into(),
ops: vec![
op(OperandKind::Register(reg("rax"))),
op(OperandKind::Memory(Memory {
base: Some(reg("rbx")),
index: Some(reg("rcx")),
scale: 1,
displacement: -8,
segment: None,
target: Some(Address::try_new(0x2000).expect("valid")),
broadcast: None,
})),
op(OperandKind::Immediate { value: 5 }),
op(OperandKind::Near(Address::try_new(0x3000).expect("valid"))),
op(OperandKind::Far {
selector: 0x33,
offset: 0x400,
}),
],
masking: None,
fp_control: None,
flow: Flow {
is_call: false,
is_ret: false,
is_jump: true,
is_indirect: false,
stops: true,
target: Some(Address::try_new(0x3000).expect("valid")),
},
}
}
#[test]
fn instruction_serde_roundtrip() {
let insn = sample_instruction();
let json = serde_json::to_string(&insn).expect("serialize");
let back: Instruction = serde_json::from_str(&json).expect("deserialize");
assert!(back == insn);
}
#[test]
fn instruction_hash_usable_in_set() {
use std::collections::HashSet;
let mut set = HashSet::new();
assert!(set.insert(sample_instruction()));
assert!(!set.insert(sample_instruction()));
}
}