Merge pull request #1499 from Kritzefitz/rust-mmio

Implement MMIO in rust bindings.
This commit is contained in:
lazymio 2021-11-24 17:27:46 +01:00 committed by GitHub
commit 558fb9c155
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 303 additions and 0 deletions

View File

@ -41,6 +41,15 @@ extern "C" {
perms: u32,
ptr: *mut c_void,
) -> uc_error;
pub fn uc_mmio_map(
engine: uc_handle,
address: u64,
size: libc::size_t,
read_cb: *mut c_void,
user_data_read: *mut c_void,
write_cb: *mut c_void,
user_data_write: *mut c_void,
) -> uc_error;
pub fn uc_mem_unmap(engine: uc_handle, address: u64, size: libc::size_t) -> uc_error;
pub fn uc_mem_protect(
engine: uc_handle,
@ -87,6 +96,33 @@ pub trait IsUcHook<'a> {}
impl<'a, D, F> IsUcHook<'a> for UcHook<'a, D, F> {}
pub extern "C" fn mmio_read_callback_proxy<D, F> (
uc: uc_handle,
offset: u64,
size: usize,
user_data: *mut UcHook<D, F>,
) -> u64 where
F: FnMut(&mut crate::Unicorn<D>, u64, usize) -> u64,
{
let user_data = unsafe { &mut *user_data };
debug_assert_eq!(uc, user_data.uc.inner().uc);
(user_data.callback)(&mut user_data.uc, offset, size)
}
pub extern "C" fn mmio_write_callback_proxy<D, F> (
uc: uc_handle,
offset: u64,
size: usize,
value: u64,
user_data: *mut UcHook<D, F>,
) where
F: FnMut(&mut crate::Unicorn<D>, u64, usize, u64),
{
let user_data = unsafe { &mut *user_data };
debug_assert_eq!(uc, user_data.uc.inner().uc);
(user_data.callback)(&mut user_data.uc, offset, size, value);
}
pub extern "C" fn code_hook_proxy<D, F>(
uc: uc_handle,
address: u64,

View File

@ -76,11 +76,60 @@ impl Drop for Context {
}
}
pub struct MmioCallbackScope<'a> {
pub regions: Vec<(u64, usize)>,
pub read_callback: Option<Box<dyn ffi::IsUcHook<'a> + 'a>>,
pub write_callback: Option<Box<dyn ffi::IsUcHook<'a> + 'a>>,
}
impl<'a> MmioCallbackScope<'a> {
fn has_regions(&self) -> bool {
self.regions.len() > 0
}
fn unmap(&mut self, begin: u64, size: usize) {
let end: u64 = begin + size as u64;
self.regions = self.regions.iter().flat_map( |(b, s)| {
let e: u64 = b + *s as u64;
if begin > *b {
if begin >= e {
// The unmapped region is completely after this region
vec![(*b, *s)]
} else {
if end >= e {
// The unmapped region overlaps with the end of this region
vec![(*b, (begin - *b) as usize)]
} else {
// The unmapped region is in the middle of this region
let second_b = end + 1;
vec![(*b, (begin - *b) as usize), (second_b, (e - second_b) as usize)]
}
}
} else {
if end > *b {
if end >= e {
// The unmapped region completely contains this region
vec![]
} else {
// The unmapped region overlaps with the start of this region
vec![(end, (e - end) as usize)]
}
} else {
// The unmapped region is completely before this region
vec![(*b, *s)]
}
}
}).collect();
}
}
pub struct UnicornInner<'a, D> {
pub uc: uc_handle,
pub arch: Arch,
/// to keep ownership over the hook for this uc instance's lifetime
pub hooks: Vec<(ffi::uc_hook, Box<dyn ffi::IsUcHook<'a> + 'a>)>,
/// To keep ownership over the mmio callbacks for this uc instance's lifetime
pub mmio_callbacks: Vec<MmioCallbackScope<'a>>,
pub data: D,
}
@ -123,6 +172,7 @@ where
arch,
data,
hooks: vec![],
mmio_callbacks: vec![],
})),
})
} else {
@ -262,12 +312,112 @@ impl<'a, D> Unicorn<'a, D> {
}
}
/// Map in am MMIO region backed by callbacks.
///
/// `address` must be aligned to 4kb or this will return `Error::ARG`.
/// `size` must be a multiple of 4kb or this will return `Error::ARG`.
pub fn mmio_map<R: 'a, W: 'a>(
&mut self,
address: u64,
size: libc::size_t,
read_callback: Option<R>,
write_callback: Option<W>,
) -> Result<(), uc_error>
where
R: FnMut(&mut Unicorn<D>, u64, usize) -> u64,
W: FnMut(&mut Unicorn<D>, u64, usize, u64),
{
let mut read_data = read_callback.map( |c| {
Box::new(ffi::UcHook {
callback: c,
uc: Unicorn {
inner: self.inner.clone(),
},
})
});
let mut write_data = write_callback.map( |c| {
Box::new(ffi::UcHook {
callback: c,
uc: Unicorn {
inner: self.inner.clone(),
},
})
});
let err = unsafe {
ffi::uc_mmio_map(
self.inner().uc,
address,
size,
ffi::mmio_read_callback_proxy::<D, R> as _,
match read_data {
Some(ref mut d) => d.as_mut() as *mut _ as _,
None => ptr::null_mut(),
},
ffi::mmio_write_callback_proxy::<D, W> as _,
match write_data {
Some(ref mut d) => d.as_mut() as *mut _ as _,
None => ptr::null_mut(),
},
)
};
if err == uc_error::OK {
let rd = read_data.map( |c| c as Box<dyn ffi::IsUcHook> );
let wd = write_data.map( |c| c as Box<dyn ffi::IsUcHook> );
self.inner_mut().mmio_callbacks.push(MmioCallbackScope{
regions: vec![(address, size)],
read_callback: rd,
write_callback: wd,
});
Ok(())
} else {
Err(err)
}
}
/// Map in a read-only MMIO region backed by a callback.
///
/// `address` must be aligned to 4kb or this will return `Error::ARG`.
/// `size` must be a multiple of 4kb or this will return `Error::ARG`.
pub fn mmio_map_ro<F: 'a>(
&mut self,
address: u64,
size: libc::size_t,
callback: F,
) -> Result<(), uc_error>
where
F: FnMut(&mut Unicorn<D>, u64, usize) -> u64,
{
self.mmio_map(address, size, Some(callback), None::<fn(&mut Unicorn<D>, u64, usize, u64)>)
}
/// Map in a write-only MMIO region backed by a callback.
///
/// `address` must be aligned to 4kb or this will return `Error::ARG`.
/// `size` must be a multiple of 4kb or this will return `Error::ARG`.
pub fn mmio_map_wo<F: 'a>(
&mut self,
address: u64,
size: libc::size_t,
callback: F,
) -> Result<(), uc_error>
where
F: FnMut(&mut Unicorn<D>, u64, usize, u64),
{
self.mmio_map(address, size, None::<fn(&mut Unicorn<D>, u64, usize) -> u64>, Some(callback))
}
/// Unmap a memory region.
///
/// `address` must be aligned to 4kb or this will return `Error::ARG`.
/// `size` must be a multiple of 4kb or this will return `Error::ARG`.
pub fn mem_unmap(&mut self, address: u64, size: libc::size_t) -> Result<(), uc_error> {
let err = unsafe { ffi::uc_mem_unmap(self.inner().uc, address, size) };
self.mmio_unmap(address, size);
if err == uc_error::OK {
Ok(())
} else {
@ -275,6 +425,13 @@ impl<'a, D> Unicorn<'a, D> {
}
}
fn mmio_unmap(&mut self, address: u64, size: libc::size_t) {
for scope in self.inner_mut().mmio_callbacks.iter_mut() {
scope.unmap(address, size);
}
self.inner_mut().mmio_callbacks.retain( |scope| scope.has_regions() );
}
/// Set the memory permissions for an existing memory region.
///
/// `address` must be aligned to 4kb or this will return `Error::ARG`.

View File

@ -412,6 +412,116 @@ fn x86_insn_sys_callback() {
assert_eq!(emu.remove_hook(hook), Ok(()));
}
#[test]
fn x86_mmio() {
#[derive(PartialEq, Debug)]
struct MmioReadExpectation(u64, usize);
#[derive(PartialEq, Debug)]
struct MmioWriteExpectation(u64, usize, u64);
let read_expect = MmioReadExpectation(4, 4);
let write_expect = MmioWriteExpectation(8, 2, 42);
let mut emu = unicorn_engine::Unicorn::new(Arch::X86, Mode::MODE_64)
.expect("failed to initialize unicorn instance");
assert_eq!(emu.mem_map(0x1000, 0x1000, Permission::ALL), Ok(()));
{
// MOV eax, [0x2004]; MOV [0x2008], ax;
let x86_code: Vec<u8> = vec![0x8B, 0x04, 0x25, 0x04, 0x20, 0x00, 0x00, 0x66, 0x89, 0x04, 0x25, 0x08, 0x20, 0x00, 0x00];
let read_cell = Rc::new(RefCell::new(MmioReadExpectation(0, 0)));
let cb_read_cell = read_cell.clone();
let read_callback = move |_: &mut Unicorn<'_, ()>, offset, size| {
*cb_read_cell.borrow_mut() = MmioReadExpectation(offset, size);
42
};
let write_cell = Rc::new(RefCell::new(MmioWriteExpectation(0,0,0)));
let cb_write_cell = write_cell.clone();
let write_callback = move |_: &mut Unicorn<'_, ()>, offset, size, value| {
*cb_write_cell.borrow_mut() = MmioWriteExpectation(offset, size, value);
};
assert_eq!(emu.mem_write(0x1000, &x86_code), Ok(()));
assert_eq!(emu.mmio_map(0x2000, 0x1000, Some(read_callback), Some(write_callback)), Ok(()));
assert_eq!(
emu.emu_start(
0x1000,
0x1000 + x86_code.len() as u64,
10 * SECOND_SCALE,
1000
),
Ok(())
);
assert_eq!(read_expect, *read_cell.borrow());
assert_eq!(write_expect, *write_cell.borrow());
assert_eq!(emu.mem_unmap(0x2000, 0x1000), Ok(()));
}
{
// MOV eax, [0x2004];
let x86_code: Vec<u8> = vec![0x8B, 0x04, 0x25, 0x04, 0x20, 0x00, 0x00];
let read_cell = Rc::new(RefCell::new(MmioReadExpectation(0, 0)));
let cb_read_cell = read_cell.clone();
let read_callback = move |_: &mut Unicorn<'_, ()>, offset, size| {
*cb_read_cell.borrow_mut() = MmioReadExpectation(offset, size);
42
};
assert_eq!(emu.mem_write(0x1000, &x86_code), Ok(()));
assert_eq!(emu.mmio_map_ro(0x2000, 0x1000, read_callback), Ok(()));
assert_eq!(
emu.emu_start(
0x1000,
0x1000 + x86_code.len() as u64,
10 * SECOND_SCALE,
1000
),
Ok(())
);
assert_eq!(read_expect, *read_cell.borrow());
assert_eq!(emu.mem_unmap(0x2000, 0x1000), Ok(()));
}
{
// MOV ax, 42; MOV [0x2008], ax;
let x86_code: Vec<u8> = vec![0x66, 0xB8, 0x2A, 0x00, 0x66, 0x89, 0x04, 0x25, 0x08, 0x20, 0x00, 0x00];
let write_cell = Rc::new(RefCell::new(MmioWriteExpectation(0,0,0)));
let cb_write_cell = write_cell.clone();
let write_callback = move |_: &mut Unicorn<'_, ()>, offset, size, value| {
*cb_write_cell.borrow_mut() = MmioWriteExpectation(offset, size, value);
};
assert_eq!(emu.mem_write(0x1000, &x86_code), Ok(()));
assert_eq!(emu.mmio_map_wo(0x2000, 0x1000, write_callback), Ok(()));
assert_eq!(
emu.emu_start(
0x1000,
0x1000 + x86_code.len() as u64,
10 * SECOND_SCALE,
1000
),
Ok(())
);
assert_eq!(write_expect, *write_cell.borrow());
assert_eq!(emu.mem_unmap(0x2000, 0x1000), Ok(()));
}
}
#[test]
fn emulate_arm() {
let arm_code32: Vec<u8> = vec![0x83, 0xb0]; // sub sp, #0xc