4
votes

I'm a fairly novice programmer and I'm running into a problem that I think I understand but don't know how to fix. I'm attempting to use the Rust FFI to interact with Intel's DPDK which is all in C. My first attempt at this is recreating the helloworld example app.

I'm reaching a compilation error that I think is due to functions from DPDK being static and not directly available from the library. My FFI interface is here:

use libc::{c_uint, c_int, c_void, c_char};

pub type LcoreFunctionT =
    extern "C" fn(arg1: *mut c_void) -> c_int;

extern {
    pub fn rte_eal_init(argc: c_int,
                        argv: *mut *mut c_char) -> c_int;
    pub fn rte_eal_remote_launch(f: *mut LcoreFunctionT,
                                    arg: *mut c_void,
                                    slave_id: c_uint) -> c_int;
    pub fn rte_eal_mp_wait_lcore() -> ();
    pub fn rte_lcore_id() -> c_uint;
    pub fn rte_get_next_lcore(i: c_uint,
                                skip_master: c_int,
                                wrap: c_int) -> c_uint;
}

I've also got a library referencing this and wrapping the functions:

extern crate libc;

use libc::{c_uint, c_int, c_char, c_void};
use std::ffi::CString;
use std::ptr;

mod ffi_rte_eal;

pub fn dpdk_rte_eal_init(argc: i32, argv: Vec<String>) -> i32 {
    let mut args: Vec<*mut c_char> =
        argv.iter().map(|x| CString::new(x.clone()).unwrap().into_raw()).collect();
    let retc: c_int = unsafe {ffi_rte_eal::rte_eal_init(argc as c_int, args.as_mut_ptr())};
    let ret: i32 = retc as i32;
    ret
}

pub fn dpdk_rte_eal_remote_launch(f: extern "C" fn(*mut c_void) -> i32,
                                    slave_id: u32 ) -> i32 {
    let mut fc: ffi_rte_eal::LcoreFunctionT = f;
    let retc: c_int = unsafe {ffi_rte_eal::rte_eal_remote_launch(&mut fc,
                                                                ptr::null_mut() as *mut c_void,
                                                                slave_id as c_uint)};
    let ret: i32 = retc as i32;
    ret
}

pub fn dpdk_rte_eal_mp_wait_lcore() -> (){
    unsafe {
        ffi_rte_eal::rte_eal_mp_wait_lcore();
    }
}

pub fn dpdk_rte_lcore_id() -> u32 {
    let retc: c_uint = unsafe {ffi_rte_eal::rte_lcore_id()};
    let ret: u32 = retc as u32;
    ret
}

pub fn dpdk_rte_get_next_lcore(i: u32,
                                skip_master: i32,
                                wrap: i32) -> u32 {
    let retc: c_uint = unsafe {ffi_rte_eal::rte_get_next_lcore(i as c_uint,
                                                               skip_master as c_int,
                                                               wrap as c_int)};
    let ret: u32 = retc as u32;
    ret
}

And a build.rs file for linking the libraries -

//build.rs

fn main() {
    println!("cargo:rustc-link-lib=static=rte_eal");
    println!("cargo:rustc-link-search=native=/usr/local/lib");
    println!("cargo:rustc-link-lib=static=rte_mempool");
    println!("cargo:rustc-link-search=native=/usr/local/lib");
    println!("cargo:rustc-link-lib=static=rte_ring");
    println!("cargo:rustc-link-search=native=/usr/local/lib");
}

When I attempt to compile my own application against the FFI interface, I keep getting errors about undefined references to rte_lcore_id and rte_get_next_lcore. According to the DPDK's API documentation, those functions are part of the librte_eal library, but are defined in rte_lcore.h as static functions. I'm assuming that these being static functions I will not be able to see them from Rust.

In the helloworld example app that is bundled with DPDK they are importing rte_lcore.h directly. I take it this is why they can access these functions without just referencing librte_eal?

Is there a method to access this in Rust or would there have to be something like a shim in C that let me wrap those functions and make them available via FFI?

1
Tricky. As you surmise, the problem of static function is that they are not supposed to be used outside the current compilation unit and therefore even if not inlined their symbols can be private (and inaccessible outside the library).Matthieu M.

1 Answers

7
votes

As you can see if you open the respective header file, these functions are declared directly there. This means that these functions will be included in every .c/.cpp file which includes this header, but since they are static, the linker won't create a symbol for them, so they are not actually present in the compiled version of the library. Here it is described why this may be needed, but unfortunately such design is not very FFI-friendly.

What you can do is to create a stub C library which contains exactly the same functions which delegate to the static functions from the header but which are not themselves static. Like this:

#include <rte_lcore.h>

unsigned my_rte_lcore_count(void) {
    return rte_lcore_count();
}

// and so forth for every function you need

Then you can compile this stub file into a static library, using either a custom-written Makefile or a Cargo build script, and link the final program to it. Then, naturally, you should write these functions in your extern block, not the original ones:

extern {
    fn my_rte_lcore_count() -> libc::c_uint;
}

I don't think there is any easier and more correct way.

Update: oh, I didn't notice your last sentence in the question. Yes, you're right, writing such a shim would be the correct way to do it.