Cast extern "C" function to raw memory address?

What is the proper way to cast an extern "C" function to a raw u64? I need it to store it into a struct, and I get a feeling that calling transmute on it would be a bad idea.

Do you have control over the struct definition? If so, you can make it hold a function pointer directly:

struct Foo {
    func: extern "C" fn(i32) -> i32, // or whatever signature you need
}

Nope, I don't have control over it. The definition demands a u64.

EDIT: Nevermind on fn -> usize, see @Michael-F-Bryan 's response below

Ah, in that case you will have to cast to function pointer, transmute that into usize, then cast into u64.

// assuming the function is called "bar"
// again, can be whatever signature you need it to be
std::mem::transmute<_, usize>(bar as extern "C" fn(i32) -> i32) as u64

And to get the function pointer back

// make sure you transmute into the same signature you transmuted from
std::mem::transmute<_, extern "C" fn(i32) -> i32>(x as usize) 

On some platforms, a function pointer might be the same size as u32, so transmuting directly into u64 wouldn't be allowed

There's no need for unsafe or transmute(). It's perfectly valid to cast a bare fn() to a usize.

fn main() {
    let func: unsafe extern "C" fn() = foo;
    let address = func as usize as u64;
}

unsafe extern "C" fn foo() {}

(playground)

Note that casting the address back to a function pointer is not allowed (Error: non-primitive cast: usize as unsafe extern "C" fn()) so you will need transmute() or some unsafe pointer casting.

5 Likes

Wait, it is? Weird, I could've sworn I've tried that before and had it not work. TIL, thanks!

1 Like

Thanks, I need to do this in a static context so this should work!

Okay, so running into a bit of another problem.

The struct I'm trying to set up takes a lot of addresses. For example, its a linked list: it has a next field which is a u64. I've tried to do the same trick on the struct but its a non-trivial cast. How do you cast C-style structs to u64s?

Can you share a minimal example of the code you are trying to write?

In general, you can't cast a struct to a u64 because, to point out the obvious, structs aren't integers. If your system has 64-bit pointers, you can cast a pointer to a struct to a u64, but I suspect that isn't what you are trying to achieve.

1 Like

Sounds like you need to cast &YourStruct, not YourStruct.

let _: u64 = &my_struct as *const S as u64;

Side note that the provenance of pointer-to-int-to-pointer round-trips in Rust is not yet determined.

The (external) struct definition is this (in Rust):

#[repr(C)]
pub struct Tag {
    pub identifier: u64,
    pub next: u64,
}

The next holds a memory address to the next item in the list. As an example, here is how I'm initializing everything (and how its breaking):

const MAX_STACK_SIZE: usize = 1024 * 256;
static STACK: [u8; MAX_STACK_SIZE] = [0; MAX_STACK_SIZE];

// Tags
static FB_TAG: stivale::header::FramebufferTag = stivale::header::FramebufferTag {
    tag: stivale::Tag {
        identifier: stivale::header::FRAMEBUFFER_ID,
        next: addr_of!(TERMINAL_TAG) as u64,
    },
    width: 0,
    height: 0,
    bpp: 32,
    unused: 0,
};

static TERMINAL_TAG: stivale::header::TerminalTag = stivale::header::TerminalTag {
    tag: stivale::Tag {
        identifier: stivale::header::TERMINAL_ID,
        next: addr_of!(SMP_TAG) as u64,
    },
    flags: 0,
    callback: terminal_write_cb as usize as u64,
};

static SMP_TAG: stivale::header::SmpTag = stivale::header::SmpTag {
    tag: stivale::Tag {
        identifier: stivale::header::SMP_ID,
        next: addr_of!(LVL5_PG_TAG) as u64,
    },
    flags: (1 << 0),
};

static LVL5_PG_TAG: stivale::Tag = stivale::Tag {
    identifier: stivale::header::FIVE_LVL_PAGING_ID,
    next: addr_of!(UNMAP_NULL_TAG) as u64,
};

static UNMAP_NULL_TAG: stivale::Tag = stivale::Tag {
    identifier: stivale::header::UNMAP_NULL_ID,
    next: addr_of!(SLIDE_HHDM_TAG) as u64,
};

static SLIDE_HHDM_TAG: stivale::header::SlideHhdmTag = stivale::header::SlideHhdmTag {
    tag: stivale::Tag {
        identifier: stivale::header::SLIDE_HHDM_ID,
        next: 0,
    },
    flags: 0,
    alignment: 4096,
};

#[link_section = ".stivale2hdr"]
#[used]
static BOOT_LOADER_HEADER: stivale::header::Header = stivale::header::Header {
    entry_point: 0,
    stack: (addr_of!(STACK) as u64) + (core::mem::size_of::<[u8; MAX_STACK_SIZE]>() as u64),
    flags: (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4),
    tags: addr_of!(FB_TAG) as u64,
};

You can learn about what all this means here.

That's a bit more code than I was expecting, but I think your issue reduces down to something like this:

struct Node {
    next: u64,
}

static FIRST: Node = Node { next: 0 };
static SECOND: Node = Node {
    next: &FIRST as *const Node as usize as u64,
};

(playground)

When I tried to build this, the compiler errored out with:

   Compiling playground v0.0.1 (/playground)
error: pointers cannot be cast to integers during const eval
 --> src/lib.rs:7:11
  |
7 |     next: &FIRST as *const Node as usize as u64,
  |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: at compile-time, pointers do not have an integer value
  = note: avoiding this restriction via `transmute`, `union`, or raw pointers leads to compile-time undefined behavior

error: could not compile `playground` due to previous error
Warnings

The underlying issue is that the compiler needs to know what value to store in next because next is an integer, but the FIRST variable won't actually have an address until quite late in the codegen phase.

On the other hand, references/pointers to other static variables are fine because LLVM knows to patch the pointers up afterwards. That means your code should look like this:

struct Node {
    next: *const Node,
}

static FIRST: Node = Node {
    next: std::ptr::null(),
};
static SECOND: Node = Node { next: &FIRST };

// Safety: safe because we'll manually manage how these nodes are accessed.
unsafe impl Sync for Node {}

The stivale crate probably shouldn't be using u64 for the next pointer here. Instead, they should be using raw pointers and making sure the crate only gets used on platforms with 64-bit pointers.

I couldn't find stivale::header::SlideHhdmTag in the crate's API docs, but there is a type called Header5LevelPagingTag that has const functions you can use to set up your linked list. Your SlideHhdmTag type might have something similar.

use stivale::header::Header5LevelPagingTag;

static FIRST: Header5LevelPagingTag = Header5LevelPagingTag::new();
static SECOND: Header5LevelPagingTag = Header5LevelPagingTag::new()
    .next(&FIRST as *const Header5LevelPagingTag as *const ());
1 Like

Oh, I'm not using the stivale crate; I ported the header myself (I didn't know there was a crate for it and I was following the specification). I'll check out the crate and see what I can make of it.

So I checked out the stivale_boot crate, and I'm really wondering if the trivial_casts linter has a major bug in it or not. I'm running into this weird catch-22 situation where I can't cast (say) &stivale_boot::v2::StivaleFramebufferHeaderTag as *const (), but doing &FB_TAG as *const StivaleFramebufferHeaderTag as *const() is somehow suddenly seen as a trivial cast from &StivaleFramebufferHeaderTag to *const StivaleFramebufferHeaderTag while ignoring the rest of the cast entirely. Is this a bug in the linter? I enabled it initially to catch unnecessary casts, but if its going to be this picky about things I'm going to turn it off.

Do you have a minimal example we can run on the playground?

If the lint tells you to change your code to something that doesn't compile then it's a bug that should be reported on the Rust project's issue tracker.

Yeah. Here's the code that's causing it:

#![no_std]
#![no_main]
#![feature(alloc_error_handler)]
#![feature(proc_macro_hygiene)]
#![forbid(
    absolute_paths_not_starting_with_crate,
    anonymous_parameters,
    deprecated_in_future,
    explicit_outlives_requirements,
    indirect_structural_match,
    keyword_idents,
    macro_use_extern_crate,
    meta_variable_misuse,
    missing_abi,
    missing_copy_implementations,
    missing_debug_implementations,
    non_ascii_idents,
    noop_method_call,
    pointer_structural_match,
    private_doc_tests,
    semicolon_in_expressions_from_macros,
    single_use_lifetimes,
    trivial_casts,
    trivial_numeric_casts,
    unaligned_references,
    unreachable_pub,
    unsafe_op_in_unsafe_fn,
    unused_crate_dependencies,
    unused_import_braces,
    unused_lifetimes,
    unused_qualifications,
    variant_size_differences,
    warnings,
    box_pointers
)]
#![forbid(clippy::all)]
extern crate alloc;
use stivale_boot::v2::*;

const MAX_STACK_SIZE: usize = 1024 * 256;
static STACK: [u8; MAX_STACK_SIZE] = [0; MAX_STACK_SIZE];

// Tags
static FB_TAG: StivaleFramebufferHeaderTag = StivaleFramebufferHeaderTag::new().framebuffer_bpp(32).next(&TERMINAL_TAG as *const ());
static TERMINAL_TAG: StivaleTerminalHeaderTag = StivaleTerminalHeaderTag::new().next(&SMP_TAG as *const());
static SMP_TAG: StivaleSmpHeaderTag = StivaleSmpHeaderTag::new().flags(StivaleSmpHeaderTagFlags::X2APIC).next(&LVL5_PG_TAG as *const());
static LVL5_PG_TAG: Stivale5LevelPagingHeaderTag = Stivale5LevelPagingHeaderTag::new().next(&UNMAP_NULL_TAG as *const());
static UNMAP_NULL_TAG: StivaleUnmapNullHeaderTag = StivaleUnmapNullHeaderTag::new();

#[link_section = ".stivale2hdr"]
#[used]
static BOOT_LOADER_HEADER: StivaleHeader = StivaleHeader::new().stack(&STACK[0] as *const u8).flags(0x1E).tags(&FB_TAG as *const ());

I know not all the code is correct but no matter how I write the pointer conversions Rust doesn't allow it. You'll have to add your own _start() routine and such, but changing the type casts from the above code to this:

// Tags
static FB_TAG: StivaleFramebufferHeaderTag = StivaleFramebufferHeaderTag::new().framebuffer_bpp(32).next(&TERMINAL_TAG as *const StivaleTerminalHeaderTag as *const ());
static TERMINAL_TAG: StivaleTerminalHeaderTag = StivaleTerminalHeaderTag::new().next(&SMP_TAG as *const StivaleSmpHeaderTag as *const());
static SMP_TAG: StivaleSmpHeaderTag = StivaleSmpHeaderTag::new().flags(StivaleSmpHeaderTagFlags::X2APIC).next(&LVL5_PG_TAG as *const Stivale5LevelPagingHeaderTag as *const());
static LVL5_PG_TAG: Stivale5LevelPagingHeaderTag = Stivale5LevelPagingHeaderTag::new().next(&UNMAP_NULL_TAG as *const StivaleUnmapNullHeaderTag as *const());
static UNMAP_NULL_TAG: StivaleUnmapNullHeaderTag = StivaleUnmapNullHeaderTag::new();

#[link_section = ".stivale2hdr"]
#[used]
static BOOT_LOADER_HEADER: StivaleHeader = StivaleHeader::new().stack(&STACK[0] as *const u8).flags(0x1E).tags(&FB_TAG as *const StivaleFramebufferHeaderTag as *const ());

Causes this catch-22: no matter which way you use, its not accepted.

You’ve got a lot of #![forbid]s in that example; maybe one of them is blocking this.

I just turned off the trivial_casts lint for now.

Oh, the fun of const evaluation. (I wonder what this actually ends up doing, though, in terms of what parts of the compiler it ends up "exercising"/abusing.)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.