FFI code compilation error, STRANGE?!

The following piece of code is really weird - try compile it on the Rust Playground -

use std::os::raw::c_char;
use std::ffi::{CString};

#[no_mangle]
pub unsafe extern "C" fn free(data: *mut c_char) {
    drop(CString::from_raw(data));
}

fn main() {
    println!("{}", "ok");
}

The result is this:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 1.40s
     Running `target/debug/playground`
timeout: the monitored command dumped core
/playground/tools/entrypoint.sh: line 11:     8 Segmentation fault      timeout --signal=KILL ${timeout} "$@"

However, if you do one of the following things, it will compile and run all right:

  • comment out '#[no_mangle]' , or
  • rename the 'free' function to something like 'freee' or whatever, just not name it 'free', or
  • comment out the 'main' function, or
  • comment out the 'drop(CString::from_raw(data))' part

Am I missing anything obvious here? is this 'free' name colliding with the 'free' word in C or something? but what does the 'main' or 'drop' part have anything to do, given the function is not even called. What is the logic behind this magic?

Yes, it overrides the libc's free, so it is called by the system allocator whenever something is freed on the heap, be it your own Box or something from the Rust runtime. See this:

use std::os::raw::c_char;

#[no_mangle]
pub unsafe extern "C" fn free(data: *mut c_char) {
    println!("Freeing pointer: {:p}", data);
}

fn main() {
    let boxed = Box::new(42);
    println!("Allocated pointer: {:p}", boxed);
}

Playground
At the end of the output, you can see something like this:

Allocated pointer: 0x56187e7f3de0
Freeing pointer: 0x56187e7f3de0

with some other free calls beforehand.

Well, without main the program will not run at all, so there's nothing to crash. And without drop there's no invalid pointer access, so no reason to crash, just a couple of memory leaks.

3 Likes

cool. Got it. thx.

Would it be a problem for Rust to disallow overriding library symbols like this, at least by default? There's probably technical issues since the platform controls the linker, which is what's really controlling this, but would there be design issues too? Surely this isn't desired and depended on, perhaps outside of [language_item]s

See:

1 Like

Tldr: "yeah, this should be an error, but it's too hard", seems like? A little lame, but I guess #[no_mangle] is already pretty weird and unsafe like.

I suspect churn is a concern but that's just a guess. There's an unsafe_code lint, but it's allow by default so far.

And indeed, if you #![warn(unsafe_code)], you'll find out it warns on the no_mangle:

warning: declaration of a `no_mangle` function
 --> src/main.rs:6:1
  |
6 | #[no_mangle]
  | ^^^^^^^^^^^^
  |
  = note: the linker's behavior with multiple libraries exporting duplicate symbol names is undefined and Rust cannot provide guarantees when you manually override them

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=343b3d3ffa0f989996fa821ca089ae8b