I'm working on a wrapper around libhackrf: GitHub - wspeirs/rs-libhackrf: libhackrf as a Rust crate
I'm having a problem packaging up a context (generic) and function (callback), casting it as a c_void pointer, and then casting it back on the other side. The problem I have is that Valgrind indicates an uninitialized read.
The following code snipit will make more sense:
#[repr(C)]
struct CallbackContext<'a, T> {
context: Option<&'a mut T>,
function: &'a mut FnMut(&[u8], &Option<&mut T>) -> Error
}
impl <'a> Device<'a> {
unsafe extern "C" fn rx_callback<C>(transfer: *mut hackrf_transfer) -> i32
where C: std::fmt::Debug {
let buffer :&[u8] = slice::from_raw_parts((*transfer).buffer, (*transfer).valid_length as usize);
let ctx = (*(*transfer).device).rx_ctx;
let ctx = &mut *(ctx as *mut Box<CallbackContext<C>>);
Into::into((ctx.function)(buffer, &ctx.context)) // line 88
}
pub fn start_rx<T, F>(&mut self, mut callback: F, rx_ctx: Option<&mut T>) -> Result<(), Error>
where F: FnMut(&[u8], &Option<&mut T>) -> Error,
T: std::fmt::Debug {
unsafe {
// package up our context and function
let mut ctx = Box::new(CallbackContext {
context: rx_ctx,
function: &mut callback
});
// calling leak so when Box is dropped, the memory
let ctx = Box::leak(ctx) as *mut _ as *mut c_void;isn't freed
// this function creates a thread (pthread) and runs it
let ret = hackrf_start_rx(self.device_ptr, Some(Device::rx_callback::<T>), ctx);
if ret != hackrf_error_HACKRF_SUCCESS {
return Err(Error::from(ret));
}
}
// set the state so we can cleanup properly
self.state = State::RECEIVING;
Ok( () )
}
}
I'm calling start_rx as follows:
#[test]
fn start_stop_rx() {
LOGGER_INIT.call_once(|| simple_logger::init_with_level(log::Level::Trace).unwrap());
let mut hrf = HackRF::new().expect("Error creating HackRF");
let mut dev = hrf.open_device(0).expect("Error creating device; maybe not plugged in?");
let ctx = None::<&mut i32>;
dev.start_rx(|b, c| {
Error::SUCCESS
}, ctx).expect("Error calling start_rx");
thread::sleep_ms(250);
dev.stop_rx().expect("Error calling stop_rx");
}
... and the error in Valgrind:
==32509== Thread 4:
==32509== Invalid read of size 8
==32509== at 0x135E35: rs_libhackrf::device::Device::rx_callback::haf9645800687a434 (device.rs:88)
==32509== by 0x4E3E21C: hackrf_libusb_transfer_callback (in /usr/local/lib/libhackrf.so.0.5.0)
==32509== by 0x5F5D159: ??? (in /lib/x86_64-linux-gnu/libusb-1.0.so.0.1.0)
==32509== by 0x5F62F07: ??? (in /lib/x86_64-linux-gnu/libusb-1.0.so.0.1.0)
==32509== by 0x5F5CC48: ??? (in /lib/x86_64-linux-gnu/libusb-1.0.so.0.1.0)
==32509== by 0x5F5DB52: libusb_handle_events_timeout_completed (in /lib/x86_64-linux-gnu/libusb-1.0.so.0.1.0)
==32509== by 0x4E3E14A: transfer_threadproc (in /usr/local/lib/libhackrf.so.0.5.0)
==32509== by 0x54556B9: start_thread (pthread_create.c:333)
==32509== by 0x598841C: clone (clone.S:109)
==32509== Address 0x8 is not stack'd, malloc'd or (recently) free'd
==32509==
==32509==
==32509== Process terminating with default action of signal 11 (SIGSEGV)
==32509== Access not within mapped region at address 0x40008
==32509== at 0x135E35: rs_libhackrf::device::Device::rx_callback::haf9645800687a434 (device.rs:88)
==32509== by 0x4E3E21C: hackrf_libusb_transfer_callback (in /usr/local/lib/libhackrf.so.0.5.0)
==32509== by 0x5F5D159: ??? (in /lib/x86_64-linux-gnu/libusb-1.0.so.0.1.0)
==32509== by 0x5F62F07: ??? (in /lib/x86_64-linux-gnu/libusb-1.0.so.0.1.0)
==32509== by 0x5F5CC48: ??? (in /lib/x86_64-linux-gnu/libusb-1.0.so.0.1.0)
==32509== by 0x5F5DB52: libusb_handle_events_timeout_completed (in /lib/x86_64-linux-gnu/libusb-1.0.so.0.1.0)
==32509== by 0x4E3E14A: transfer_threadproc (in /usr/local/lib/libhackrf.so.0.5.0)
==32509== by 0x54556B9: start_thread (pthread_create.c:333)
==32509== by 0x598841C: clone (clone.S:109)
==32509== If you believe this happened as a result of a stack
==32509== overflow in your program's main thread (unlikely but
==32509== possible), you can try to increase the size of the
==32509== main thread stack using the --main-stacksize= flag.
==32509== The main thread stack size used in this run was 8388608.
==32509==
==32509== HEAP SUMMARY:
==32509== in use at exit: 0 bytes in 0 blocks
==32509== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==32509==
==32509== All heap blocks were freed -- no leaks are possible
==32509==
==32509== For counts of detected and suppressed errors, rerun with: -v
==32509== ERROR SUMMARY: 2 errors from 1 contexts (suppressed: 0 from 0)
Killed
I have gotten this to "work" as it runs to completion, but I aways get the invalid/uninitialized read error on line 88 when I go to use the context. When it does work, any attempt to access ctx.context causes the error.
How should I go about doing this? I think I need to put my CallbackContext on the heap so that it survives the call to hackrf_start_rx. However, I'm not 100% sure how to clean it up after. Thoughts? Thanks!