How can I call a raw address from Rust?
Asked Answered
R

1

14

I am writing an OS in Rust and need to directly call into a virtual address that I'm calculating (of type u32). I expected this to be relatively simple:

let code = virtual_address as (extern "C" fn ());
(code)();

However, this complains that the cast is non-primitive. It suggests I use the From trait, but I don't see how this could help (although I am relatively new to Rust and so could be missing something).

error[E0605]: non-primitive cast: `u32` as `extern "C" fn()`
 --> src/main.rs:3:16
  |
3 |     let code = virtual_address as (extern "C" fn ());
  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait

I have everything in libcore at my disposal, but haven't ported std and so can't rely on anything that isn't no_std

Redoubt answered 9/9, 2017 at 19:41 Comment(0)
L
23

Casts of the type _ as f-ptr are not allowed (see the Rustonomicon chapter on casts). So, as far as I can tell, the only way to cast to function pointer types is to use the all mighty weapon mem::transmute().

But before we can use transmute(), we have to bring our input into the right memory layout. We do this by casting to *const () (a void pointer). Afterwards we can use transmute() to get what we want:

let ptr = virtual_address as *const ();
let code: extern "C" fn() = unsafe { std::mem::transmute(ptr) };
(code)();

If you find yourself doing this frequently, various kinds of macros can remove the boilerplate. One possibility:

macro_rules! example {
    ($address:expr, $t:ty) => {
        std::mem::transmute::<*const (), $t>($address as _)
    };
}
let f = unsafe { example!(virtual_address, extern "C" fn()) };
f(); 

However, a few notes on this:

  • If you, future reader, want to use this to do simple FFI things: please take a moment to think about it again. Calculating function pointers yourself is rarely necessary.
  • Usually extern "C" functions have the type unsafe extern "C" fn(). This means that those functions are unsafe to call. You should probably add the unsafe to your function.
Lorineloriner answered 9/9, 2017 at 20:17 Comment(6)
Please note that I'm absolutely not an expert on unsafe-things! If someone knows better, please let me know; then I can edit or delete my answer!Lorineloriner
Rust doesn't do anything fancy here. fn() is a plain function pointer. As long as there's a function at virtual_address, calling conventions and signatures match, it's OK to do the cast and call the function.Calculable
Awesome, I'll need to do some more setup to see if this actually works, but it does compile! Thanks very much @LukasKalbertodt.Redoubt
@Calculable The code itself isn't fancy, but does rely on being passed a valid virtual address in my case (with numerous things to think about: physical -> virtual calculation, actually mapping the code etc.) so I think it does fit as an unsafe fn in Rust's paranoia (in a good sense of the word)Redoubt
can we tell Rust which calling convention such an inline-casted function has?Thicken
@Benni, the "C" in extern "C" fn() is the calling convention.Mentalist

© 2022 - 2024 — McMap. All rights reserved.