diff --git a/capstone-rs/src/capstone.rs b/capstone-rs/src/capstone.rs index 9e77f9d6..9168ff65 100644 --- a/capstone-rs/src/capstone.rs +++ b/capstone-rs/src/capstone.rs @@ -11,7 +11,9 @@ use capstone_sys::*; use crate::constants::{Arch, Endian, ExtraMode, Mode, OptValue, Syntax}; use crate::error::*; use crate::ffi::str_from_cstr_ptr; -use crate::instruction::{Insn, InsnDetail, InsnGroupId, InsnId, Instructions, RegId}; +use crate::instruction::{ + Insn, InsnDetail, InsnGroupId, InsnId, InsnRegsAccess, Instructions, RegId, +}; /// An instance of the capstone disassembler @@ -378,6 +380,17 @@ impl Capstone { } } + /// Returns `RegsAccess` structure for a given instruction + /// + /// Requires: + /// + /// 1. Instruction was created with detail enabled + /// 2. Skipdata is disabled + /// 3. Capstone was not compiled in diet mode + pub fn insn_regs_access<'s, 'i: 's>(&'s self, insn: &'i Insn) -> CsResult { + insn.regs_access(self.csh()) + } + /// Returns a tuple (major, minor) indicating the version of the capstone C library. pub fn lib_version() -> (u32, u32) { let mut major: c_int = 0; diff --git a/capstone-rs/src/instruction.rs b/capstone-rs/src/instruction.rs index 476cc283..eab0a0eb 100644 --- a/capstone-rs/src/instruction.rs +++ b/capstone-rs/src/instruction.rs @@ -8,6 +8,7 @@ use capstone_sys::*; use crate::arch::ArchDetail; use crate::constants::Arch; use crate::ffi::str_from_cstr_ptr; +use crate::CsResult; /// Representation of the array of instructions returned by disasm #[derive(Debug)] @@ -145,6 +146,16 @@ pub struct Insn<'a> { /// `ArchDetail` enum. pub struct InsnDetail<'a>(pub(crate) &'a cs_detail, pub(crate) Arch); +// Can't derive `PartialEq` and `Eq` because `regs_read` and `regs_write` are bigger than 32 +#[derive(Clone)] +/// Contains information about registers accessed by an instruction, either explicitly or implicitly +pub struct InsnRegsAccess { + pub(crate) regs_read: cs_regs, + pub(crate) regs_read_count: u8, + pub(crate) regs_write: cs_regs, + pub(crate) regs_write_count: u8, +} + impl<'a> Insn<'a> { /// The mnemonic for the instruction pub fn mnemonic(&self) -> Option<&str> { @@ -184,6 +195,15 @@ impl<'a> Insn<'a> { pub(crate) unsafe fn detail(&self, arch: Arch) -> InsnDetail { InsnDetail(&*self.insn.detail, arch) } + + /// Returns the `RegsAccess` object, if there is one. It is up to the caller to determine + /// the pre-conditions are satisfied. + /// + /// Be careful this is still in early stages and largely untested with various `cs_option` and + /// architecture matrices + pub(crate) fn regs_access(&self, cs: csh) -> CsResult { + InsnRegsAccess::new(cs, &self.insn) + } } impl<'a> Debug for Insn<'a> { @@ -328,6 +348,69 @@ impl<'a> InsnDetail<'a> { } } +impl<'a> InsnRegsAccess { + fn new(cs: csh, ins: &cs_insn) -> CsResult { + let mut regs_read = [0u16; 64]; + let mut regs_read_count = 0u8; + let mut regs_write = [0u16; 64]; + let mut regs_write_count = 0u8; + + let result = unsafe { + cs_regs_access( + cs, + ins, + regs_read.as_mut_ptr(), + &mut regs_read_count, + regs_write.as_mut_ptr(), + &mut regs_write_count, + ) + }; + + if result == cs_err::CS_ERR_OK { + Ok(Self { + regs_read, + regs_read_count, + regs_write, + regs_write_count, + }) + } else { + Err(result.into()) + } + } + + /// Returns the explicit and implicit accessed registers + pub fn regs_read(&self) -> RegsIter { + RegsIter(self.regs_read[..self.regs_read_count as usize].iter()) + } + + /// Returns the number of explicit and implicit read registers + pub fn regs_read_count(&self) -> u8 { + self.regs_read_count + } + + /// Returns the explicit and implicit write registers + pub fn regs_write(&self) -> RegsIter { + RegsIter(self.regs_write[..self.regs_write_count as usize].iter()) + } + + /// Returns the number of explicit and implicit write registers + pub fn regs_write_count(&self) -> u8 { + self.regs_write_count + } +} + +// Can't derive `Debug` because `regs_read` and `regs_write` are bigger than 32 +impl Debug for InsnRegsAccess { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + fmt.debug_struct("RegsAccess") + .field("regs_read", &self.regs_read()) + .field("regs_read_count", &self.regs_read_count()) + .field("regs_write", &self.regs_write()) + .field("regs_write_count", &self.regs_write_count()) + .finish() + } +} + impl<'a> Debug for InsnDetail<'a> { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { fmt.debug_struct("Detail") diff --git a/capstone-rs/src/lib.rs b/capstone-rs/src/lib.rs index fe06ef6c..88117dd1 100644 --- a/capstone-rs/src/lib.rs +++ b/capstone-rs/src/lib.rs @@ -165,7 +165,7 @@ pub mod prelude { BuildsCapstoneSyntax, DetailsArchInsn, }; pub use crate::{ - Capstone, CsResult, InsnDetail, InsnGroupId, InsnGroupIdInt, InsnId, InsnIdInt, RegId, - RegIdInt, + Capstone, CsResult, InsnDetail, InsnGroupId, InsnGroupIdInt, InsnId, InsnIdInt, + InsnRegsAccess, RegId, RegIdInt, }; } diff --git a/capstone-rs/src/test.rs b/capstone-rs/src/test.rs index 93bcfcac..38d4600c 100644 --- a/capstone-rs/src/test.rs +++ b/capstone-rs/src/test.rs @@ -127,6 +127,14 @@ fn test_detail_false_fail() { assert_eq!(cs.insn_detail(&insns[0]).unwrap_err(), Error::DetailOff); assert_eq!(cs.insn_detail(&insns[1]).unwrap_err(), Error::DetailOff); + assert_eq!( + cs.insn_regs_access(&insns[0]).unwrap_err(), + Error::DetailOff + ); + assert_eq!( + cs.insn_regs_access(&insns[1]).unwrap_err(), + Error::DetailOff + ); } #[test] @@ -494,6 +502,72 @@ fn test_insns_match(cs: &mut Capstone, insns: &[(&str, &[u8])]) { } } +#[test] +fn test_instruction_register_access() { + use crate::arch::x86::X86Reg::*; + + let expected: &[(&[u8], &[_], &[_])] = &[ + // add rax, rax + ( + b"\x48\x01\xc0", + &[X86_REG_RAX], + &[X86_REG_EFLAGS, X86_REG_RAX], + ), + // mov rax, 0x1234567812345678 + ( + b"\x48\xb8\x78\x56\x34\x12\x78\x56\x34\x12", + &[], + &[X86_REG_RAX], + ), + // mov DWORD PTR [rbp+rbx*8-0x4], eax + ( + b"\x89\x44\xdd\xfc", + &[X86_REG_RBP, X86_REG_RBX, X86_REG_EAX], + &[], + ), + // mov rax, QWORD PTR [rbp+rbx*8-0x4] + ( + b"\x48\x8b\x44\xdd\xfc", + &[X86_REG_RBP, X86_REG_RBX], + &[X86_REG_RAX], + ), + ]; + + let cs = Capstone::new() + .x86() + .mode(x86::ArchMode::Mode64) + .detail(true) + .build() + .unwrap(); + + macro_rules! assert_regs_match { + ($expected:expr, $actual_regs:expr, $msg:expr) => {{ + assert_eq!($expected.len(), $actual_regs.len(), $msg); + + for (expected, actual) in $expected.iter().zip($actual_regs) { + println!( + "expected = {:?}, actual = {:?}", + cs.reg_name(RegId(*expected as u16)), + cs.reg_name(actual) + ); + assert_eq!(*expected, actual.0 as u32, $msg); + } + }}; + } + + for (code, regs_read, regs_write) in expected { + let insns = cs.disasm_count(code, START_TEST_ADDR, 1).unwrap(); + let insn = insns.iter().next().unwrap(); + let access = cs.insn_regs_access(&insn).unwrap(); + + assert_eq!(regs_read.len(), access.regs_read_count() as usize, "regs_read_count did not match"); + assert_regs_match!(regs_read, access.regs_read(), "read_regs did not match"); + + assert_eq!(regs_write.len(), access.regs_write_count() as usize, "regs_write_count did not match"); + assert_regs_match!(regs_write, access.regs_write(), "regs_write did not match"); + } +} + fn test_extra_mode_helper( arch: Arch, mode: Mode,