Search code examples
rustffi

Unexpected value after passing raw mutable pointer to C FFI


I'm trying to create an FFI for a C function that takes a pointer to a struct and modifies it. I've tested the C code and gotten the values that I am expecting, but when I try and print the values from Rust I get completely different results. The element in question is a slice of u32.

The code looks something like this:

C:

struct my_struct {
    uint64_t len;
    uint32_t state[4];
    int count;
};

int modify_struct(struct my_struct *x) 
{
    // modify here

    // prints the expected values
    uint8_t *bytes = (uint8_t *)x->state;
    for (int i = 0; i < 16; i++)
        printf("%02x", bytes[i]);
}

Rust:

#[repr(C)]
#[derive(Debug, Default)]
struct MyStruct {
    length: u64,
    state: [u32; 4],
    count: libc::c_int
}

extern "C" {
    fn modify_struct(x: *mut MyStruct) -> libc::c_int;
}

fn main() {
    let mut x = MyStruct::default();
    unsafe { modify_struct(&mut x); }

    for i in 0..4 {
        println!("{:08x}", x.state[i]); // the values here differ from what the C code prints
    }
}

I print the value in C right before returning to Rust, and then print in Rust right after and the values are completely different.


Solution

  • I found the issue. It wasn't a problem in the FFI or C code, I was getting tripped up on endianess. I've edited the code above so it's more clear where the issue is (and better reflects the actual code).

    Problem

    I realized that when I print in Rust, I'm concatenating the actual 32-bit numbers together:

    for i in 0..4 {
        println!("{:08x}", x.state[i]); // the values here differ from what the C code prints
    }
    

    But what I was unknowingly looking for was the exact (little endian) order in memory, which the C code was printing like so:

    uint8_t *bytes = (uint8_t *)x->state;
    for (int i = 0; i < 16; i++)
        printf("%02x", bytes[i]);
    

    Solution

    The solution for me was to change the way Rust represents the struct from:

    struct MyStruct {
        length: u64,
        state: [u32; 4],
        count: libc::c_int
    }
    

    to:

    struct MyStruct {
        length: u64,
        state: [u8; 16],
        count: libc::c_int
    }
    

    Which allows me to access the data in the exact byte order I'm looking for.