Need some help struggling with unsafe Rust

I'm trying to implement a memory manager with Rust.

The main idea is to use a static size ( generally 64Mb which depends on my project logic) memory chunk using a capped Vec.

pub struct Mem {
    // indicates that how many memories has been allocated actually
    pub(super) offset: AtomicUsize,
    pub(super) mem: Vec<u8>,
}

Below is a method called alloc_bytes to allocate some u8 in memory.
(Slice is something like [T]. You can consider it like mem::transmute([T]))

fn alloc_bytes(&self, data: &Slice) -> u32 {
    let start = self.offset.fetch_add(data.size(), Ordering::SeqCst);
    unsafe {
        let ptr = self.mem.as_ptr().add(start) as *mut u8;
        for (i, b) in data.to_slice().iter().enumerate() {
            let p = ptr.add(i) as *mut u8;
            // FIXME: not working in ubuntu
            (*p) = *b;
        }
    }
    start as u32
}

and I've written a simple test case:

#[test]
fn test_simple_alloc_bytes() {
    let mut m = Mem::new(100);
    let input = vec![1u8, 2u8, 3u8, 4u8, 5u8];
    let offset = m.alloc_bytes(&Slice::from(input.clone()));
    unsafe {
        let ptr = m.mem.as_mut_ptr().add(offset as usize) as *mut u8;
        for (i, b) in input.clone().iter().enumerate() {
            let p = ptr.add(i);
            assert_eq!(*p, *b);
        }
    }
}

The test case works fine in my Mac, but fails in Ubuntu 16.04 LTS.

I printed the mem after calling alloc_bytes and it shows me [1,2,3,4,5] in Mac but [0,0,0,0,0] in Ubuntu which is very confusing.

This is a chicken-and-egg-problem. You can't have a Vec without an allocator, but you try to implement an allocator with a Vec. Use a ptr::NonNull instead.

I would consider to stick with the Alloc trait which defines functions you could use to implement your own allocator.

@hellow
Thank you for the suggestion. This allocator is not for everything in Rust. It's just used in some situations for manipulating memory directly by raw pointer.
I'll re-consider about the implementation. But is there something wrong with the current version of handling raw pointer ? Besides I also have a concurrency version test case of it and it's also works fine in my Mac but fails in Ubuntu.

Oh sorry, then I totally got you wrong :slight_smile:

Can you post the complete code then? At least that the test can be run. You could use the playground to share it easily with us.

The test fails in the playground :joy:

The problem is with this method. It drops the vector so the pointer is invalid.

impl From<Vec<u8>> for Slice {
    #[inline]
    fn from(v: Vec<u8>) -> Self {
        Slice::new(v.as_ptr(), v.len())
    }
}

It works fine if you use in the testing this

let offset = m.alloc_bytes(&Slice::from(&input));
1 Like

Thank you very much! I think it's the problem. But I still want to know why it works in Mac.

Per accident. This is undefined bahavoir.
If you managed to run it on the playground, you can use miri to check your program. Click in the upper right corner on tools and then miri. For you code it gives me:

Compiling playground v0.0.1 (/playground)
error[E0080]: constant evaluation error: dangling pointer was dereferenced
    --> /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore/slice/mod.rs:4899:5
     |
4899 |     Repr { raw: FatPtr { data, len } }.rust
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dangling pointer was dereferenced
     |
note: inside call to `std::slice::from_raw_parts::<u8>` at src/main.rs:17:18
    --> src/main.rs:17:18
     |
17   |         unsafe { slice::from_raw_parts(self.data, self.size) }
     |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside call to `Slice::to_slice` at src/main.rs:76:27
    --> src/main.rs:76:27
     |
76   |             for (i, b) in data.to_slice().iter().enumerate() {
     |                           ^^^^^^^^^^^^^^^
note: inside call to `Mem::alloc_bytes` at src/main.rs:89:22
    --> src/main.rs:89:22
     |
89   |         let offset = m.alloc_bytes(&Slice::from(input.clone()));
     |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     = note: inside call to `main` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:64:34
     = note: inside call to closure at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:52:53
     = note: inside call to closure at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:297:40
     = note: inside call to `std::panicking::try::do_call::<[closure@DefId(1/1:1897 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panicking.rs:293:5
     = note: inside call to `std::panicking::try::<i32, [closure@DefId(1/1:1897 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe]>` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panic.rs:388:9
     = note: inside call to `std::panic::catch_unwind::<[closure@DefId(1/1:1897 ~ std[82ff]::rt[0]::lang_start_internal[0]::{{closure}}[0]) 0:&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe], i32>` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:52:25
     = note: inside call to `std::rt::lang_start_internal` at /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/rt.rs:64:5
     = note: inside call to `std::rt::lang_start::<()>`

error: aborting due to previous error

As you can see, it points out, that you have a dangling pointer and therefore UB :wink:

4 Likes

Oh that's really cool. I even dont know anything about using the tool. Thank you very much !