use iced_x86::{
Decoder, DecoderOptions, FlowControl, Formatter, FormatterOutput, FormatterTextKind,
GasFormatter, Instruction, IntelFormatter,
};
use crate::types::VirtAddr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DisasmStyle {
Intel,
Gas,
}
#[derive(Debug, Clone)]
pub struct DisasmInstruction {
pub addr: VirtAddr,
pub bytes: Vec<u8>,
pub text: String,
pub len: usize,
}
pub fn disassemble(
code: &[u8],
base_addr: VirtAddr,
count: usize,
style: DisasmStyle,
) -> Vec<DisasmInstruction> {
let mut decoder = Decoder::with_ip(64, code, base_addr.addr(), DecoderOptions::NONE);
let mut results = Vec::new();
let mut output = FormatterOutputBuffer::new();
while decoder.can_decode() && results.len() < count {
let mut insn = Instruction::default();
decoder.decode_out(&mut insn);
output.clear();
match style {
DisasmStyle::Intel => {
let mut formatter = IntelFormatter::new();
formatter.format(&insn, &mut output);
}
DisasmStyle::Gas => {
let mut formatter = GasFormatter::new();
formatter.format(&insn, &mut output);
}
}
let insn_bytes = &code[(insn.ip() - base_addr.addr()) as usize
..(insn.ip() - base_addr.addr()) as usize + insn.len()];
results.push(DisasmInstruction {
addr: VirtAddr(insn.ip()),
bytes: insn_bytes.to_vec(),
text: output.text().to_string(),
len: insn.len(),
});
}
results
}
pub fn format_disassembly(instructions: &[DisasmInstruction]) -> String {
let mut out = String::new();
for insn in instructions {
out.push_str(&format!(" {:016x} ", insn.addr.addr()));
let mut bytes_str = String::new();
for b in &insn.bytes {
bytes_str.push_str(&format!("{:02x} ", b));
}
out.push_str(&format!("{:<30} ", bytes_str.trim_end()));
out.push_str(&insn.text);
out.push('\n');
}
out
}
#[derive(Debug, Clone)]
pub struct InstructionInfo {
pub len: usize,
pub is_call: bool,
pub is_ret: bool,
}
pub fn decode_instruction_info(code: &[u8], addr: VirtAddr) -> Option<InstructionInfo> {
let mut decoder = Decoder::with_ip(64, code, addr.addr(), DecoderOptions::NONE);
if !decoder.can_decode() {
return None;
}
let insn = decoder.decode();
Some(InstructionInfo {
len: insn.len(),
is_call: matches!(
insn.flow_control(),
FlowControl::Call | FlowControl::IndirectCall
),
is_ret: insn.flow_control() == FlowControl::Return,
})
}
struct FormatterOutputBuffer {
text: String,
}
impl FormatterOutputBuffer {
fn new() -> Self {
Self {
text: String::new(),
}
}
fn clear(&mut self) {
self.text.clear();
}
fn text(&self) -> &str {
&self.text
}
}
impl FormatterOutput for FormatterOutputBuffer {
fn write(&mut self, text: &str, _kind: FormatterTextKind) {
self.text.push_str(text);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn disassemble_nops() {
let code = [0x90, 0x90, 0x90];
let insns = disassemble(&code, VirtAddr(0x1000), 10, DisasmStyle::Intel);
assert_eq!(insns.len(), 3);
for insn in &insns {
assert_eq!(insn.text, "nop");
assert_eq!(insn.len, 1);
assert_eq!(insn.bytes, vec![0x90]);
}
assert_eq!(insns[0].addr, VirtAddr(0x1000));
assert_eq!(insns[1].addr, VirtAddr(0x1001));
assert_eq!(insns[2].addr, VirtAddr(0x1002));
}
#[test]
fn disassemble_push_rbp_sequence() {
let code = [
0x55, 0x48, 0x89, 0xe5, 0x48, 0x83, 0xec, 0x10, ];
let insns = disassemble(&code, VirtAddr(0x401000), 10, DisasmStyle::Intel);
assert_eq!(insns.len(), 3);
assert_eq!(insns[0].text, "push rbp");
assert_eq!(insns[0].len, 1);
assert!(insns[1].text.contains("mov"));
assert!(insns[1].text.contains("rbp"));
assert_eq!(insns[1].len, 3);
assert!(insns[2].text.contains("sub"));
assert_eq!(insns[2].len, 4);
}
#[test]
fn disassemble_count_limit() {
let code = [0x90; 100]; let insns = disassemble(&code, VirtAddr(0x0), 5, DisasmStyle::Intel);
assert_eq!(insns.len(), 5);
}
#[test]
fn disassemble_gas_style() {
let code = [0x55]; let insns = disassemble(&code, VirtAddr(0x0), 1, DisasmStyle::Gas);
assert_eq!(insns.len(), 1);
assert!(insns[0].text.contains("%rbp"));
}
#[test]
fn decode_call_instruction() {
let code = [0xFF, 0xD0];
let info = decode_instruction_info(&code, VirtAddr(0x1000)).unwrap();
assert!(info.is_call);
assert!(!info.is_ret);
assert_eq!(info.len, 2);
}
#[test]
fn decode_ret_instruction() {
let code = [0xC3];
let info = decode_instruction_info(&code, VirtAddr(0x1000)).unwrap();
assert!(!info.is_call);
assert!(info.is_ret);
assert_eq!(info.len, 1);
}
#[test]
fn decode_nop_instruction() {
let code = [0x90];
let info = decode_instruction_info(&code, VirtAddr(0x1000)).unwrap();
assert!(!info.is_call);
assert!(!info.is_ret);
assert_eq!(info.len, 1);
}
#[test]
fn decode_direct_call() {
let code = [0xE8, 0x00, 0x00, 0x00, 0x00];
let info = decode_instruction_info(&code, VirtAddr(0x1000)).unwrap();
assert!(info.is_call);
assert!(!info.is_ret);
assert_eq!(info.len, 5);
}
#[test]
fn format_disassembly_output() {
let code = [0x90, 0xcc]; let insns = disassemble(&code, VirtAddr(0x1000), 10, DisasmStyle::Intel);
let output = format_disassembly(&insns);
assert!(output.contains("0000000000001000"));
assert!(output.contains("nop"));
assert!(output.contains("int3"));
}
}