use alloc::boxed::Box;
use alloc::string::String;
use core::convert::From;
use core::ffi::{c_int, c_uint, c_void, CStr};
use core::marker::PhantomData;
use core::mem::MaybeUninit;
#[cfg(feature = "std")]
use std::ffi::CString;
use capstone_sys::cs_opt_value::*;
use capstone_sys::*;
use crate::arch::CapstoneBuilder;
use crate::constants::{Arch, Endian, ExtraMode, Mode, OptValue, Syntax};
use crate::instruction::{Insn, InsnDetail, InsnGroupId, InsnId, Instructions, RegId};
use crate::{error::*, PartialInitRegsAccess};
use {crate::ffi::str_from_cstr_ptr, alloc::string::ToString};
pub(crate) const REGS_ACCESS_BUF_LEN: usize = 64;
pub(crate) type RegsAccessBuf = [MaybeUninit<RegId>; REGS_ACCESS_BUF_LEN];
static_assertions::assert_eq_size!(RegId, u16);
static_assertions::assert_eq_size!(RegsAccessBuf, cs_regs);
static_assertions::assert_type_eq_all!([u16; REGS_ACCESS_BUF_LEN], cs_regs);
#[derive(Debug)]
pub struct Capstone {
csh: *mut c_void,
mode: cs_mode,
endian: cs_mode,
syntax: cs_opt_value::Type,
extra_mode: cs_mode,
detail_enabled: bool,
skipdata_enabled: bool,
unsigned_enabled: bool,
raw_mode: cs_mode,
arch: Arch,
}
macro_rules! define_set_mode {
(
$( #[$func_attr:meta] )*
=> $($visibility:ident)*, $fn_name:ident,
$opt_type:ident, $param_name:ident : $param_type:ident ;
$cs_base_type:ident
) => {
$( #[$func_attr] )*
$($visibility)* fn $fn_name(&mut self, $param_name: $param_type) -> CsResult<()> {
let old_val = self.$param_name;
self.$param_name = $cs_base_type::from($param_name);
let old_raw_mode = self.raw_mode;
let new_raw_mode = self.update_raw_mode();
let result = self._set_cs_option(
cs_opt_type::$opt_type,
new_raw_mode.0 as usize,
);
if result.is_err() {
self.raw_mode = old_raw_mode;
self.$param_name = old_val;
}
result
}
}
}
pub static NO_EXTRA_MODE: EmptyExtraModeIter = EmptyExtraModeIter(PhantomData);
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct EmptyExtraModeIter(PhantomData<()>);
impl Iterator for EmptyExtraModeIter {
type Item = ExtraMode;
fn next(&mut self) -> Option<Self::Item> {
None
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct RegAccessRef<'a> {
pub(crate) read: &'a [RegId],
pub(crate) write: &'a [RegId],
}
impl RegAccessRef<'_> {
pub fn read(&self) -> &[RegId] {
self.read
}
pub fn write(&self) -> &[RegId] {
self.write
}
}
impl Capstone {
#[allow(clippy::new_ret_no_self)]
pub fn new() -> CapstoneBuilder {
CapstoneBuilder::new()
}
pub fn new_raw<T: Iterator<Item = ExtraMode>>(
arch: Arch,
mode: Mode,
extra_mode: T,
endian: Option<Endian>,
) -> CsResult<Capstone> {
let mut handle: csh = 0;
let csarch: cs_arch = arch.into();
let csmode: cs_mode = mode.into();
let endian = match endian {
Some(endian) => cs_mode::from(endian),
None => cs_mode(0),
};
let extra_mode = Self::extra_mode_value(extra_mode);
let combined_mode = csmode | endian | extra_mode;
let err = unsafe { cs_open(csarch, combined_mode, &mut handle) };
if cs_err::CS_ERR_OK == err {
let syntax = CS_OPT_SYNTAX_DEFAULT;
let raw_mode = cs_mode(0);
let detail_enabled = false;
let skipdata_enabled = false;
let unsigned_enabled = false;
let mut cs = Capstone {
csh: handle as *mut c_void,
syntax,
endian,
mode: csmode,
extra_mode,
detail_enabled,
skipdata_enabled,
unsigned_enabled,
raw_mode,
arch,
};
cs.update_raw_mode();
Ok(cs)
} else {
Err(err.into())
}
}
pub fn disasm_iter<'a, 'b>(
&'a self,
code: &'b [u8],
addr: u64,
) -> CsResult<DisasmIter<'a, 'b>> {
let insn = unsafe { cs_malloc(self.csh()) };
if insn.is_null() {
return Err(Error::OutOfMemory);
}
Ok(DisasmIter {
insn,
csh: self.csh,
code: code.as_ptr(),
size: code.len(),
addr,
_data1: PhantomData,
_data2: PhantomData,
})
}
pub fn disasm_all<'a>(&'a self, code: &[u8], addr: u64) -> CsResult<Instructions<'a>> {
self.disasm(code, addr, 0)
}
pub fn disasm_count<'a>(
&'a self,
code: &[u8],
addr: u64,
count: usize,
) -> CsResult<Instructions<'a>> {
if count == 0 {
return Err(Error::CustomError("Invalid dissasemble count; must be > 0"));
}
self.disasm(code, addr, count)
}
fn disasm<'a>(&'a self, code: &[u8], addr: u64, count: usize) -> CsResult<Instructions<'a>> {
let mut ptr: *mut cs_insn = core::ptr::null_mut();
let insn_count =
unsafe { cs_disasm(self.csh(), code.as_ptr(), code.len(), addr, count, &mut ptr) };
if insn_count == 0 {
match self.error_result() {
Ok(_) => Ok(Instructions::new_empty()),
Err(err) => Err(err),
}
} else {
Ok(unsafe { Instructions::from_raw_parts(ptr, insn_count) })
}
}
#[inline]
fn csh(&self) -> csh {
self.csh as csh
}
#[allow(dead_code)]
pub(crate) fn raw_mode(&self) -> cs_mode {
self.raw_mode
}
fn update_raw_mode(&mut self) -> cs_mode {
self.raw_mode = self.mode | self.extra_mode | self.endian;
self.raw_mode
}
fn extra_mode_value<T: Iterator<Item = ExtraMode>>(extra_mode: T) -> cs_mode {
extra_mode.fold(cs_mode(0), |acc, x| acc | cs_mode::from(x))
}
pub fn set_extra_mode<T: Iterator<Item = ExtraMode>>(&mut self, extra_mode: T) -> CsResult<()> {
let old_val = self.extra_mode;
self.extra_mode = Self::extra_mode_value(extra_mode);
let old_mode = self.raw_mode;
let new_mode = self.update_raw_mode();
let result = self._set_cs_option(cs_opt_type::CS_OPT_MODE, new_mode.0 as usize);
if result.is_err() {
self.raw_mode = old_mode;
self.extra_mode = old_val;
}
result
}
pub fn set_syntax(&mut self, syntax: Syntax) -> CsResult<()> {
let syntax_int = cs_opt_value::Type::from(syntax);
let result = self._set_cs_option(cs_opt_type::CS_OPT_SYNTAX, syntax_int as usize);
if result.is_ok() {
self.syntax = syntax_int;
}
result
}
define_set_mode!(
=> pub, set_endian, CS_OPT_MODE, endian : Endian; cs_mode);
define_set_mode!(
=> pub, set_mode, CS_OPT_MODE, mode : Mode; cs_mode);
fn error_result(&self) -> CsResult<()> {
let errno = unsafe { cs_errno(self.csh()) };
if errno == cs_err::CS_ERR_OK {
Ok(())
} else {
Err(errno.into())
}
}
fn _set_cs_option(&mut self, option_type: cs_opt_type, option_value: usize) -> CsResult<()> {
let err = unsafe { cs_option(self.csh(), option_type, option_value) };
if cs_err::CS_ERR_OK == err {
Ok(())
} else {
Err(err.into())
}
}
pub fn set_detail(&mut self, enable_detail: bool) -> CsResult<()> {
let option_value: usize = OptValue::from(enable_detail).0 as usize;
let result = self._set_cs_option(cs_opt_type::CS_OPT_DETAIL, option_value);
if result.is_ok() {
self.detail_enabled = enable_detail;
}
result
}
pub fn set_skipdata(&mut self, enable_skipdata: bool) -> CsResult<()> {
let option_value: usize = OptValue::from(enable_skipdata).0 as usize;
let result = self._set_cs_option(cs_opt_type::CS_OPT_SKIPDATA, option_value);
if result.is_ok() {
self.skipdata_enabled = enable_skipdata;
}
result
}
pub fn set_unsigned(&mut self, enable_unsigned: bool) -> CsResult<()> {
let option_value: usize = OptValue::from(enable_unsigned).0 as usize;
let result = self._set_cs_option(cs_opt_type::CS_OPT_UNSIGNED, option_value);
if result.is_ok() {
self.unsigned_enabled = enable_unsigned;
}
result
}
#[cfg(feature = "std")]
pub fn set_mnemonic(&mut self, insn_id: InsnId, mnemonic: Option<&str>) -> CsResult<()> {
let mnemonic_cstr = match mnemonic {
Some(s) => Some(CString::new(s).map_err(|_err| {
Error::CustomError(
"Failed to convert mnemonic to C-String due to internal NUL characters",
)
})?),
None => None,
};
self.set_mnemonic_cstr(insn_id, mnemonic_cstr.as_deref())
}
pub fn set_mnemonic_cstr(
&mut self,
insn_id: InsnId,
mnemonic_cstr: Option<&CStr>,
) -> CsResult<()> {
let option_value: cs_opt_mnem = cs_opt_mnem {
id: insn_id.0,
mnemonic: mnemonic_cstr
.map(|s| s.as_ptr())
.unwrap_or(core::ptr::null()),
};
self._set_cs_option(
cs_opt_type::CS_OPT_MNEMONIC,
&option_value as *const cs_opt_mnem as usize,
)
}
pub fn reg_name(&self, reg_id: RegId) -> Option<String> {
if cfg!(feature = "full") {
let reg_name = unsafe {
let _reg_name = cs_reg_name(self.csh(), c_uint::from(reg_id.0));
str_from_cstr_ptr(_reg_name)?.to_string()
};
Some(reg_name)
} else {
None
}
}
pub fn insn_name(&self, insn_id: InsnId) -> Option<String> {
if cfg!(feature = "full") {
let insn_name = unsafe {
let _insn_name = cs_insn_name(self.csh(), insn_id.0 as c_uint);
str_from_cstr_ptr(_insn_name)?.to_string()
};
Some(insn_name)
} else {
None
}
}
pub(crate) fn regs_access<'buf>(
&self,
insn: &Insn,
regs_read: &'buf mut RegsAccessBuf,
regs_write: &'buf mut RegsAccessBuf,
) -> CsResult<RegAccessRef<'buf>> {
if cfg!(feature = "full") {
let mut regs_read_count: u8 = 0;
let mut regs_write_count: u8 = 0;
let err = unsafe {
cs_regs_access(
self.csh(),
&insn.insn as *const cs_insn,
regs_read.as_mut_ptr() as *mut cs_regs,
&mut regs_read_count as *mut _,
regs_write.as_mut_ptr() as *mut cs_regs,
&mut regs_write_count as *mut _,
)
};
if err != cs_err::CS_ERR_OK {
return Err(err.into());
}
let regs_read_slice: &[RegId] = unsafe {
core::slice::from_raw_parts(
regs_read.as_mut_ptr() as *mut RegId,
regs_read_count as usize,
)
};
let regs_write_slice: &[RegId] = unsafe {
core::slice::from_raw_parts(
regs_write.as_mut_ptr() as *mut RegId,
regs_write_count as usize,
)
};
Ok(RegAccessRef {
read: regs_read_slice,
write: regs_write_slice,
})
} else {
Err(Error::DetailOff)
}
}
pub fn group_name(&self, group_id: InsnGroupId) -> Option<String> {
if cfg!(feature = "full") {
let group_name = unsafe {
let _group_name = cs_group_name(self.csh(), c_uint::from(group_id.0));
str_from_cstr_ptr(_group_name)?.to_string()
};
Some(group_name)
} else {
None
}
}
pub fn insn_detail<'s, 'i: 's>(&'s self, insn: &'i Insn) -> CsResult<InsnDetail<'i>> {
if !self.detail_enabled {
Err(Error::DetailOff)
} else if insn.id().0 == 0 {
Err(Error::IrrelevantDataInSkipData)
} else {
let partial_init_regs_access = {
let mut regs_buf = Box::new(crate::RWRegsAccessBuf::new());
match self.regs_access(insn, &mut regs_buf.read_buf, &mut regs_buf.write_buf) {
Ok(regs_access) => {
let read_len = regs_access.read.len() as u16;
let write_len = regs_access.write.len() as u16;
Some(PartialInitRegsAccess {
regs_buf,
read_len,
write_len,
})
}
Err(_) => None,
}
};
Ok(unsafe { insn.detail(self.arch, partial_init_regs_access) })
}
}
pub fn lib_version() -> (u32, u32) {
let mut major: c_int = 0;
let mut minor: c_int = 0;
let major_ptr: *mut c_int = &mut major;
let minor_ptr: *mut c_int = &mut minor;
let _ = unsafe { cs_version(major_ptr, minor_ptr) };
(major as u32, minor as u32)
}
pub fn supports_arch(arch: Arch) -> bool {
unsafe { cs_support(cs_arch::from(arch) as c_int) }
}
pub fn is_diet() -> bool {
unsafe { cs_support(CS_SUPPORT_DIET as c_int) }
}
}
impl Drop for Capstone {
fn drop(&mut self) {
unsafe { cs_close(&mut self.csh()) };
}
}
pub struct DisasmIter<'cs, 'buf> {
insn: *mut cs_insn, csh: *mut c_void, code: *const u8, size: usize, addr: u64, _data1: PhantomData<&'cs ()>, _data2: PhantomData<&'buf ()>, }
impl<'cs, 'buf> Drop for DisasmIter<'cs, 'buf> {
fn drop(&mut self) {
unsafe { cs_free(self.insn, 1) };
}
}
impl<'cs, 'buf> DisasmIter<'cs, 'buf> {
pub fn next<'iter>(&'iter mut self) -> Option<Insn<'iter>> {
unsafe {
if cs_disasm_iter(
self.csh as csh,
&mut self.code,
&mut self.size,
&mut self.addr,
self.insn,
) {
return Some(Insn::from_raw(self.insn));
}
}
None
}
pub fn code(&self) -> &[u8] {
unsafe { core::slice::from_raw_parts(self.code, self.size) }
}
pub fn addr(&self) -> u64 {
self.addr
}
pub fn reset(&mut self, code: &'buf [u8], addr: u64) {
self.code = code.as_ptr();
self.size = code.len();
self.addr = addr;
}
}