Skip to content

Commit 1dc124b

Browse files
authored
Merge pull request #2 from Lancern/master
fix: Image::set_callback did not properly marshal Rust functions
2 parents 0efe4ff + 9a59386 commit 1dc124b

File tree

1 file changed

+139
-9
lines changed

1 file changed

+139
-9
lines changed

src/image/image.rs

Lines changed: 139 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,62 @@ mod test {
3434
// not much to test for in the unit tests
3535
// the integration tests should have way more stuffs
3636

37+
#[test]
38+
fn test_box_func_and_call() {
39+
fn func(buf: &mut [u8], size: u64, asid: Asid) -> i32 {
40+
assert_eq!(buf, &[10u8, 20u8, 30u8, 40u8]);
41+
assert_eq!(size, 50);
42+
assert!(asid.cr3().is_none());
43+
assert!(asid.vmcs().is_none());
44+
42
45+
}
46+
47+
let boxed = BoxedCallback::box_callback(func);
48+
49+
let mut buf = vec![10u8, 20u8, 30u8, 40u8];
50+
let ret = unsafe {
51+
BoxedCallback::call(boxed.0, &mut buf, 50, Asid::new(None, None))
52+
};
53+
assert_eq!(ret, 42);
54+
}
55+
56+
#[test]
57+
fn test_box_no_capture_closure_and_call() {
58+
let boxed = BoxedCallback::box_callback(|buf, size, asid| {
59+
assert_eq!(buf, &[10u8, 20u8, 30u8, 40u8]);
60+
assert_eq!(size, 50);
61+
assert!(asid.cr3().is_none());
62+
assert!(asid.vmcs().is_none());
63+
42
64+
});
65+
66+
let mut buf = vec![10u8, 20u8, 30u8, 40u8];
67+
let ret = unsafe {
68+
BoxedCallback::call(boxed.0, &mut buf, 50, Asid::new(None, None))
69+
};
70+
assert_eq!(ret, 42);
71+
}
72+
73+
#[test]
74+
fn test_box_capture_closure_and_call() {
75+
let data = 60;
76+
77+
let boxed = BoxedCallback::box_callback(move |buf, size, asid| {
78+
assert_eq!(buf, &[10u8, 20u8, 30u8, 40u8]);
79+
assert_eq!(size, 50);
80+
assert_eq!(data, 60);
81+
assert!(asid.cr3().is_none());
82+
assert!(asid.vmcs().is_none());
83+
42
84+
});
85+
86+
let mut buf = vec![10u8, 20u8, 30u8, 40u8];
87+
let ret = unsafe {
88+
BoxedCallback::call(boxed.0, &mut buf, 50, Asid::new(None, None))
89+
};
90+
assert_eq!(ret, 42);
91+
}
92+
3793
#[test]
3894
fn test_img_alloc() {
3995
Image::new(None).unwrap();
@@ -118,9 +174,80 @@ unsafe extern "C" fn read_callback(buffer: *mut u8,
118174
asid: *const pt_asid,
119175
ip: u64,
120176
context: *mut c_void) -> i32 {
121-
let c: &mut &mut dyn FnMut(&mut [u8], u64, Asid) -> i32
122-
= mem::transmute(context);
123-
c(slice::from_raw_parts_mut(buffer, size), ip, Asid(*asid))
177+
let buffer = std::slice::from_raw_parts_mut(buffer, size);
178+
let asid = Asid(*asid);
179+
BoxedCallback::call(context, buffer, ip, asid)
180+
}
181+
182+
/// Represent a boxed Rust function that can be passed to and from C code.
183+
///
184+
/// # Internals
185+
///
186+
/// The wrapped raw pointer points to the following structure on the heap:
187+
///
188+
/// ```text
189+
/// ┌───────────────── Heap ──────────────────┐
190+
/// │ │
191+
/// │ ┌───────────────────────┼────►┌──────────┐
192+
/// │ │ │ │ │
193+
/// box_callback()────┼─►┌────────────┐ │ ┌───►┌────────────┐ │ │ vtable │
194+
/// │ │ │ │ │ │ │ │ │ │
195+
/// │ │ vtable ptr ├─┘ │ │ │ │ └──────────┘
196+
/// │ │ │ │ │ Captured │ │
197+
/// │ ├────────────┤ │ │ │ │
198+
/// │ │ │ │ │ Data │ │
199+
/// │ │ captures ├────┘ │ │ │
200+
/// │ │ │ │ │ │
201+
/// │ └────────────┘ └────────────┘ │
202+
/// │ │
203+
/// └─────────────────────────────────────────┘
204+
/// ```
205+
#[derive(Debug, Eq, PartialEq)]
206+
#[repr(transparent)]
207+
struct BoxedCallback(*mut c_void);
208+
209+
impl BoxedCallback {
210+
/// Box the given Rust closure into a `BoxedCallback`.
211+
fn box_callback<F>(callback: F) -> Self
212+
where
213+
F: FnMut(&mut [u8], u64, Asid) -> i32,
214+
{
215+
// The callback can be an arbitrary Rust closure. So move it onto the heap
216+
// (the allocation only takes place when the closure captures).
217+
let boxed_dyn_callback: Box<dyn FnMut(&mut [u8], u64, Asid) -> i32>
218+
= Box::new(callback);
219+
220+
// `boxed_dyn_callback` is itself a fat pointer and we cannot just return it.
221+
// Instead, move `boxed_dyn_callback` onto the heap and returns the pointer
222+
// to the fat pointer on heap.
223+
//
224+
// Note that we convert `boxed_dyn_callback` into raw pointer as below. This is
225+
// because `Box<T: ?Sized>` is not guaranteed to be ABI-compliant with `*T`.
226+
let boxed_ptr = Box::new(Box::into_raw(boxed_dyn_callback));
227+
let raw_ptr = Box::into_raw(boxed_ptr) as *mut c_void;
228+
Self(raw_ptr)
229+
}
230+
231+
/// Invoke the callback behind the given opaque boxed callback pointer.
232+
unsafe fn call(opaque_cb: *mut c_void, buf: &mut [u8], size: u64, asid: Asid) -> i32 {
233+
let raw_boxed_ptr = opaque_cb
234+
as *mut *mut dyn FnMut(&mut [u8], u64, Asid) -> i32;
235+
let func = (*raw_boxed_ptr).as_mut().unwrap();
236+
func(buf, size, asid)
237+
}
238+
}
239+
240+
impl Drop for BoxedCallback {
241+
fn drop(&mut self) {
242+
let raw_boxed_ptr = self.0
243+
as *mut *mut dyn FnMut(&mut [u8], u64, Asid) -> i32;
244+
245+
unsafe {
246+
// Drop from inside to outside.
247+
drop(Box::from(*raw_boxed_ptr));
248+
drop(Box::from(raw_boxed_ptr));
249+
}
250+
}
124251
}
125252

126253
/// An Image defines the memory image that was traced as a collection
@@ -129,7 +256,9 @@ pub struct Image<'a> {
129256
// the wrapped inst
130257
pub(crate) inner: &'a mut pt_image,
131258
// do we need to free this instance on drop?
132-
dealloc: bool
259+
dealloc: bool,
260+
// Any read data callback set by this `Image` instance.
261+
callback: Option<BoxedCallback>,
133262
}
134263

135264
impl<'a> Image<'a> {
@@ -144,7 +273,7 @@ impl<'a> Image<'a> {
144273
PtErrorCode::Invalid,
145274
"invalid @name string: contains null bytes"
146275
))?.as_ptr())
147-
}}).map(|i| Image { inner: i, dealloc: true })
276+
}}).map(|i| Image { inner: i, dealloc: true, callback: None })
148277
}
149278

150279
/// Get the image name.
@@ -198,12 +327,13 @@ impl<'a> Image<'a> {
198327
/// If @callback is None, the callback is removed.
199328
pub fn set_callback<F>(&mut self, callback: Option<F>) -> Result<(), PtError>
200329
where F: FnMut(&mut [u8], u64, Asid) -> i32 {
201-
ensure_ptok(unsafe { match callback {
330+
self.callback = callback.map(BoxedCallback::box_callback);
331+
ensure_ptok(unsafe { match &self.callback {
202332
None => pt_image_set_callback(self.inner, None, ptr::null_mut()),
203-
Some(mut cb) =>
333+
Some(cb) =>
204334
pt_image_set_callback(self.inner,
205335
Some(read_callback),
206-
&mut &mut cb as *mut _ as *mut c_void)
336+
cb.0)
207337
}})
208338
}
209339

@@ -266,7 +396,7 @@ impl<'a> Image<'a> {
266396

267397
impl<'a> From<&'a mut pt_image> for Image<'a> {
268398
fn from(img: &'a mut pt_image) -> Self {
269-
Image { inner: img, dealloc: false }
399+
Image { inner: img, dealloc: false, callback: None }
270400
}
271401
}
272402

0 commit comments

Comments
 (0)