【问题标题】:Raw pointer turns null passing from Rust to C原始指针将 null 从 Rust 传递到 C
【发布时间】:2016-08-30 03:50:38
【问题描述】:

我正在尝试从 rust 中的 C 函数上检索原始指针,并将相同的原始指针用作另一个库中另一个 C 函数的参数。当我传递原始指针时,我最终在 C 端得到一个 NULL 指针。

我试图制作一个简化版本的问题,但是当我这样做时,它会像我期望的那样工作 -

C 代码 -

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 代码 -

#[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);
}

当我运行它时,我得到 Value - 0 的输出

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

但是,当尝试对 DPDK 库执行此操作时,我以相同的方式检索并传递原始指针,但出现了段错误。如果我使用 gdb 进行调试,我可以看到我在 Rust 端传递了一个指针,但我在 C 端看到它为 NULL -

(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,

在第 1 帧中,mb 有一个地址并且正在被传递。在第 0 帧中,库中的接收函数将其显示为 mp 的 0x0。

我的接收指针的代码 -

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());

这会调用 ffi 库 -

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
}

菲-

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 函数 -

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);

当我传递指针时,它看起来与我上面的简化版本非常相似。我的变量 mb 包含我传递给另一个函数的原始指针 -

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

ffi 库 -

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
}

菲-

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 函数 -

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);

我为冗长而道歉,但我觉得我错过了一些简单的东西并且无法弄清楚。我已经检查了每个正在传递的字段的结构对齐,我什至看到了我所期望的接收到的指针的值 -

(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}

关于为什么指针在 C 端变为 NULL 的任何想法?

【问题讨论】:

  • 如何运行这两个程序?分别运行,还是您从另一个运行一个?
  • CString::new("MBUF_POOL").unwrap().as_ptr() 看起来很危险。您确定从中获得的原始指针有效时间足够长吗? TBH,我不知道这个CString 何时被删除。如果在 函数调用dpdk_rte_pktmbuf_pool_create 之前将其删除,则指针将无效。你真的应该避免为不太安全的 FFI 函数编写像 dpdk_rte_pktmbuf_pool_create 这样的“安全”包装器!

标签: c rust ffi


【解决方案1】:

CString::new("…").unwrap().as_ptr() 不起作用。 CString 是临时的,因此 as_ptr() 调用返回该临时的内部指针,当您使用它时可能会悬空。根据 Rust 的安全定义,只要您不使用指针,这就是“安全的”,但您最终会在 unsafe 块中这样做。您应该将字符串绑定到一个变量并在该变量上使用as_ptr。

这是一个很常见的问题,甚至还有a proposal to fix the CStr{,ing} API to avoid it。

此外,原始指针本身可以为空,因此 const struct rte_eth_rxconf * 的 Rust FFI 等效项将是 *const ffi::RteEthRxConf,而不是 Option&lt;*const ffi::RteEthRxConf&gt;。

【讨论】:

  • 如果有一个giant warning block on that function 来警告人们这个...
  • 或者some tool able to warn about that,无论如何我们都会这样做。 (免责声明:我是 Clippy 开发者)
  • 感谢您的信息,我已将其更改为如下所示 - let name = CString::new("MBUF_POOL").unwrap(); let mb = dpdk::rte_mbuf::dpdk_rte_pktmbuf_pool_create(name.as_ptr(), (8191 * nb_ports) as u32 , 250, 0, 2176, dpdk::rte_eal::dpdk_rte_socket_id()); 但是,我仍然遇到相同的行为。我尝试将指针传递给包含“MBUF_POOL”字符串的结构,但在 C 端结果为 NULL。
  • 我不确定Option&lt;*const _&gt; 在 FFI 中是否有意义。删除该选项。
  • 采用 Option 的 C 函数接受指向结构或 NULL 的指针。有没有办法在不使用 Option 和 None 的情况下将 NULL 传递给 C?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-20
  • 1970-01-01
  • 1970-01-01
  • 2021-01-03
  • 1970-01-01
相关资源
最近更新 更多