4
votes

I'm attempting to retrieve a raw pointer from on C function in rust, and use that same raw pointer as an argument in another C function from another library. When I pass the raw pointer, I end up with a NULL pointer on the C side.

I have tried to make a simplified version of my issue, but when I do it works as I would expect it to -

C Code -

struct MyStruct {
    int value;
};

struct MyStruct * get_struct() {
    struct MyStruct * priv_struct = (struct MyStruct*) malloc( sizeof(struct MyStruct));

    priv_struct->value = 0;
    return priv_struct;
}

void put_struct(struct MyStruct *priv_struct) {
    printf("Value - %d\n", priv_struct->value);
}

Rust Code -

#[repr(C)]
struct MyStruct {
    value: c_int,
}

extern {
    fn get_struct() -> *mut MyStruct;
}

extern {
    fn put_struct(priv_struct: *mut MyStruct) -> ();
}

fn rust_get_struct() -> *mut MyStruct {
    let ret = unsafe { get_struct() };
    ret
}

fn rust_put_struct(priv_struct: *mut MyStruct) {
    unsafe { put_struct(priv_struct) };
}

fn main() {
    let main_struct = rust_get_struct();
    rust_put_struct(main_struct);
}

When I run this I get the output of Value - 0

~/Dev/rust_test$ sudo ./target/debug/rust_test 
Value - 0
~/Dev/rust_test$

However, when trying to do this against a DPDK library, I retrieve and pass a raw pointer in the the same way but get a segfault. If I use gdb to debug, I can see that I'm passing a pointer on the Rust side, but I see it NULL on the C side -

(gdb) frame 0
#0  rte_eth_rx_queue_setup (port_id=0 '\000', rx_queue_id=<optimized out>, nb_rx_desc=<optimized out>, socket_id=0, rx_conf=0x0, mp=0x0)
   at /home/kenton/Dev/dpdk-16.07/lib/librte_ether/rte_ethdev.c:1216
1216    if (mp->private_data_size < sizeof(struct rte_pktmbuf_pool_private)) {

(gdb) frame 1
#1  0x000055555568953b in dpdk::ethdev::dpdk_rte_eth_rx_queue_setup (port_id=0 '\000', rx_queue_id=0, nb_tx_desc=128, socket_id=0, rx_conf=None, 
   mb=0x7fff3fe47640) at /home/kenton/Dev/dpdk_ffi/src/ethdev/mod.rs:32
32     let retc: c_int = unsafe {ffi::rte_eth_rx_queue_setup(port_id as uint8_t,

In frame 1, mb has an address and is being passed. In frame 0 the receiving function in the library is showing it as 0x0 for mp.

My code to receive the pointer -

let mb = dpdk_rte_pktmbuf_pool_create(CString::new("MBUF_POOL").unwrap().as_ptr(),
       (8191 * nb_ports) as u32 , 250, 0, 2176, dpdk_rte_socket_id());

This calls into an ffi library -

pub fn dpdk_rte_pktmbuf_pool_create(name: *const c_char,
                               n: u32,
                               cache_size: u32,
                               priv_size: u16,
                               data_room_size: u16,
                               socket_id: i32) -> *mut rte_mempool::ffi::RteMempool {
    let ret: *mut rte_mempool::ffi::RteMempool = unsafe {
        ffi::shim_rte_pktmbuf_pool_create(name,
                                          n as c_uint,
                                          cache_size as c_uint,
                                          priv_size as uint16_t,
                                          data_room_size as uint16_t,
                                          socket_id as c_int)
    };
    ret
}

ffi -

extern {
    pub fn shim_rte_pktmbuf_pool_create(name: *const c_char,
                                        n: c_uint,
                                        cache_size: c_uint,
                                        priv_size: uint16_t,
                                        data_room_size: uint16_t,
                                        socket_id: c_int) -> *mut rte_mempool::ffi::RteMempool;
}

C function -

struct rte_mempool *
rte_pktmbuf_pool_create(const char *name, unsigned n,
    unsigned cache_size, uint16_t priv_size, uint16_t data_room_size,
    int socket_id);

When I pass the pointer, it looks much the same as my simplified version up above. My variable mb contains a raw pointer that I pass to another function -

ret = dpdk_rte_eth_rx_queue_setup(port,q,128,0,None,mb);

ffi library -

pub fn dpdk_rte_eth_rx_queue_setup(port_id: u8,
                                   rx_queue_id: u16,
                                   nb_tx_desc: u16,
                                   socket_id: u32,
                                   rx_conf: Option<*const ffi::RteEthRxConf>,
                                   mb_pool: *mut rte_mempool::ffi::RteMempool ) -> i32 {
    let retc: c_int = unsafe {ffi::rte_eth_rx_queue_setup(port_id as uint8_t,
                                                          rx_queue_id as uint16_t,
                                                          nb_tx_desc as uint16_t,
                                                          socket_id as c_uint,
                                                          rx_conf,
                                                          mb)};
    let ret: i32 = retc as i32;
    ret
}

ffi -

extern {
    pub fn rte_eth_rx_queue_setup(port_id: uint8_t,
                              rx_queue_id: uint16_t,
                              nb_tx_desc: uint16_t,
                              socket_id: c_uint,
                              rx_conf: Option<*const RteEthRxConf>,
                              mb: *mut rte_mempool::ffi::RteMempool ) -> c_int;
}

C function -

int
rte_eth_rx_queue_setup(uint8_t port_id, uint16_t rx_queue_id,
               uint16_t nb_rx_desc, unsigned int socket_id,
               const struct rte_eth_rxconf *rx_conf,
               struct rte_mempool *mp);

I apologize for the length, but I feel like I'm missing something simple and haven't been able to figure it out. I've checked struct alignment for each field that is being passed, and I even see values for the pointer that is received as I'd expect -

(gdb) frame 1
#1  0x000055555568dcf4 in dpdk::ethdev::dpdk_rte_eth_rx_queue_setup (port_id=0 '\000', rx_queue_id=0, nb_tx_desc=128, socket_id=0, rx_conf=None, 
    mb=0x7fff3fe47640) at /home/kenton/Dev/dpdk_ffi/src/ethdev/mod.rs:32
32      let retc: c_int = unsafe {ffi::rte_eth_rx_queue_setup(port_id as uint8_t,

(gdb) print *mb
$1 = RteMempool = {name = "MBUF_POOL", '\000' <repeats 22 times>, pool_union = PoolUnionStruct = {data = 140734245862912}, pool_config = 0x0, 
  mz = 0x7ffff7fa4c68, flags = 16, socket_id = 0, size = 8191, cache_size = 250, elt_size = 2304, header_size = 64, trailer_size = 0, 
  private_data_size = 64, ops_index = 0, local_cache = 0x7fff3fe47700, populated_size = 8191, elt_list = RteMempoolObjhdrList = {
    stqh_first = 0x7fff3ebc7f68, stqh_last = 0x7fff3fe46ce8}, nb_mem_chunks = 1, mem_list = RteMempoolMemhdrList = {stqh_first = 0x7fff3ebb7d80, 
stqh_last = 0x7fff3ebb7d80}, __align = 0x7fff3fe47700}

Any ideas on why the pointer is turning to NULL on the C side?

1
how do you run the two programs? separately, or you run one from the other?monkeyStix
CString::new("MBUF_POOL").unwrap().as_ptr() looks dangerous. Are you sure that the raw pointer you get from this is valid long enough? TBH, I don't know when exactly this CString is dropped. If it's dropped before the function call to dpdk_rte_pktmbuf_pool_create, the pointer would be invalid. You really should avoid writing "safe" wrappers like dpdk_rte_pktmbuf_pool_create for FFI functions that aren't really safe!sellibitze

1 Answers

5
votes

CString::new("…").unwrap().as_ptr() does not work. The CString is temporary, hence the as_ptr() call returns the inner pointer of that temporary, which will likely be dangling when you use it. This is “safe” per Rust's definition of safety as long as you don't use the pointer, but you eventually do so in a unsafe block. You should bind the string to a variable and use as_ptr on that variable.

This is such a common problem, there is even a proposal to fix the CStr{,ing} API to avoid it.

Additionally raw pointer are nullable by themselves, so the Rust FFI equivalent of const struct rte_eth_rxconf * would be *const ffi::RteEthRxConf, not Option<*const ffi::RteEthRxConf>.