From c8e130a78ef7385867a3fe1b449a11683fcbf41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20W=C3=B6rner?= Date: Tue, 8 Oct 2019 20:04:02 +0200 Subject: [PATCH 1/6] added cs_regs_access support --- capstone-rs/src/capstone.rs | 23 ++++++++++- capstone-rs/src/instruction.rs | 75 ++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/capstone-rs/src/capstone.rs b/capstone-rs/src/capstone.rs index 9e77f9d6..a1a555b8 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,25 @@ 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 { + if !self.detail_enabled && false { + Err(Error::DetailOff) + } else if insn.id().0 == 0 { + Err(Error::IrrelevantDataInSkipData) + } else if Self::is_diet() { + Err(Error::IrrelevantDataInDiet) + } else { + Ok(unsafe { 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..a0c545c7 100644 --- a/capstone-rs/src/instruction.rs +++ b/capstone-rs/src/instruction.rs @@ -145,6 +145,14 @@ pub struct Insn<'a> { /// `ArchDetail` enum. pub struct InsnDetail<'a>(pub(crate) &'a cs_detail, pub(crate) Arch); +/// 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 +192,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) unsafe fn regs_access(&self, cs: csh) -> InsnRegsAccess { + InsnRegsAccess::new(cs, &self.insn) + } } impl<'a> Debug for Insn<'a> { @@ -328,6 +345,64 @@ impl<'a> InsnDetail<'a> { } } +impl<'a> InsnRegsAccess { + fn new(cs: csh, ins: &cs_insn) -> Self { + let mut regs_read = [0u16; 64]; + let mut regs_read_count = 0u8; + let mut regs_write = [0u16; 64]; + let mut regs_write_count = 0u8; + + unsafe { + cs_regs_access( + cs, + ins, + regs_read.as_mut_ptr(), + &mut regs_read_count, + regs_write.as_mut_ptr(), + &mut regs_write_count, + ); + } + + Self { + regs_read, + regs_read_count, + regs_write, + regs_write_count, + } + } + + /// 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 + } +} + +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") From 35ad1ec0d6587c2ad73b1a5a917a77fcf57bab9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20W=C3=B6rner?= Date: Tue, 8 Oct 2019 20:11:16 +0200 Subject: [PATCH 2/6] fixed detail_enabled check --- capstone-rs/src/capstone.rs | 2 +- capstone-rs/src/test.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/capstone-rs/src/capstone.rs b/capstone-rs/src/capstone.rs index a1a555b8..000bee77 100644 --- a/capstone-rs/src/capstone.rs +++ b/capstone-rs/src/capstone.rs @@ -388,7 +388,7 @@ impl Capstone { /// 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 { - if !self.detail_enabled && false { + if !self.detail_enabled { Err(Error::DetailOff) } else if insn.id().0 == 0 { Err(Error::IrrelevantDataInSkipData) diff --git a/capstone-rs/src/test.rs b/capstone-rs/src/test.rs index 93bcfcac..780b9554 100644 --- a/capstone-rs/src/test.rs +++ b/capstone-rs/src/test.rs @@ -127,6 +127,8 @@ 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] From 4791dab49a6ae653bf2c55f4491224b43d1969c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20W=C3=B6rner?= Date: Wed, 9 Oct 2019 15:54:31 +0200 Subject: [PATCH 3/6] added changes --- capstone-rs/src/capstone.rs | 10 +------- capstone-rs/src/instruction.rs | 30 +++++++++++++++--------- capstone-rs/src/test.rs | 43 ++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/capstone-rs/src/capstone.rs b/capstone-rs/src/capstone.rs index 000bee77..9168ff65 100644 --- a/capstone-rs/src/capstone.rs +++ b/capstone-rs/src/capstone.rs @@ -388,15 +388,7 @@ impl Capstone { /// 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 { - if !self.detail_enabled { - Err(Error::DetailOff) - } else if insn.id().0 == 0 { - Err(Error::IrrelevantDataInSkipData) - } else if Self::is_diet() { - Err(Error::IrrelevantDataInDiet) - } else { - Ok(unsafe { insn.regs_access(self.csh()) }) - } + insn.regs_access(self.csh()) } /// Returns a tuple (major, minor) indicating the version of the capstone C library. diff --git a/capstone-rs/src/instruction.rs b/capstone-rs/src/instruction.rs index a0c545c7..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,8 @@ 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, @@ -198,7 +201,7 @@ impl<'a> Insn<'a> { /// /// Be careful this is still in early stages and largely untested with various `cs_option` and /// architecture matrices - pub(crate) unsafe fn regs_access(&self, cs: csh) -> InsnRegsAccess { + pub(crate) fn regs_access(&self, cs: csh) -> CsResult { InsnRegsAccess::new(cs, &self.insn) } } @@ -346,13 +349,13 @@ impl<'a> InsnDetail<'a> { } impl<'a> InsnRegsAccess { - fn new(cs: csh, ins: &cs_insn) -> Self { + 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; - unsafe { + let result = unsafe { cs_regs_access( cs, ins, @@ -360,14 +363,18 @@ impl<'a> InsnRegsAccess { &mut regs_read_count, regs_write.as_mut_ptr(), &mut regs_write_count, - ); - } - - Self { - regs_read, - regs_read_count, - regs_write, - 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()) } } @@ -392,6 +399,7 @@ impl<'a> InsnRegsAccess { } } +// 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") diff --git a/capstone-rs/src/test.rs b/capstone-rs/src/test.rs index 780b9554..2c03817e 100644 --- a/capstone-rs/src/test.rs +++ b/capstone-rs/src/test.rs @@ -496,6 +496,49 @@ 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_regs_match!(regs_read, access.regs_read(), "read_regs 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, From 67a4617476d4b62e3bc8062846c41c01d2c0cbc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20W=C3=B6rner?= Date: Wed, 9 Oct 2019 15:56:02 +0200 Subject: [PATCH 4/6] cargo fmt --- capstone-rs/src/test.rs | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/capstone-rs/src/test.rs b/capstone-rs/src/test.rs index 2c03817e..03aed508 100644 --- a/capstone-rs/src/test.rs +++ b/capstone-rs/src/test.rs @@ -127,8 +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); + 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] @@ -502,13 +508,29 @@ fn test_instruction_register_access() { let expected: &[(&[u8], &[_], &[_])] = &[ // add rax, rax - (b"\x48\x01\xc0", &[X86_REG_RAX], &[X86_REG_EFLAGS, X86_REG_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]), + ( + 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], &[]), + ( + 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]), + ( + b"\x48\x8b\x44\xdd\xfc", + &[X86_REG_RBP, X86_REG_RBX], + &[X86_REG_RAX], + ), ]; let cs = Capstone::new() @@ -523,7 +545,11 @@ fn test_instruction_register_access() { 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)); + println!( + "expected = {:?}, actual = {:?}", + cs.reg_name(RegId(*expected as u16)), + cs.reg_name(actual) + ); assert_eq!(*expected, actual.0 as u32, $msg); } }}; From ada788ebddaa31ef43ddf69ded4af89464e3b49d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20W=C3=B6rner?= Date: Wed, 9 Oct 2019 16:53:27 +0200 Subject: [PATCH 5/6] added InsnRegsAccess to prelude --- capstone-rs/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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, }; } From 047587850003fe4ebbe4fbf6564c5ebda97475e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20W=C3=B6rner?= Date: Wed, 9 Oct 2019 16:59:09 +0200 Subject: [PATCH 6/6] added test for regs_read_count and regs_write_count --- capstone-rs/src/test.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/capstone-rs/src/test.rs b/capstone-rs/src/test.rs index 03aed508..38d4600c 100644 --- a/capstone-rs/src/test.rs +++ b/capstone-rs/src/test.rs @@ -560,7 +560,10 @@ fn test_instruction_register_access() { 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"); } }