Is it possible to dynamically reference a function by its name?

Hi,

So I'm writing the Rust library (libExt) that must be able to reference symbols (functions) from the other already loaded by the executable C library (libBase). You can think of it as an "extension", "plugin", etc. library.

Few things:

  1. if libBase is not loaded - crash or whatever, this is UB, don't care
  2. libBase can be both - statically and dynamically loaded in the main executable (in the worst case only dynamic library can be considered)
  3. libBase and libExt are both loaded by the executable, neither library should load any other
  4. I know about "function pointer tables/callbacks during initialisation/registration/loading" and need to avoid this at any cost. libBase is really huge and I need to be able to access any function from it

Try #1

// libBase - just an example, this is actually a C code and all functions are loadable by the main program
pub extern "C" fn already_loaded_symbol() {
    // do something
}
// libExt
extern "C" { // without providing #[link...]
    fn already_loaded_symbol();
}

fn main() {
already_loaded_symbol();
}

My expectation was for Rust to search in the already loaded symbol tables when library is loaded but it crashes (tried only on Android at this time):

java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "already_loaded_symbol" referenced by [path_to_libExt]

libBase is loaded before the crash happens, so already_loaded_symbol should be "loadable".

Try #2

The same thing but I've also added linking of the libBase library when building:

// build.rs
fn main() {
    println!("cargo:rustc-link-lib=dylib=libBase");
    println!("cargo:rustc-link-search=native=folder_of_libBase");
}

This way it at least works but it also requires libBase to be available during the build.

Questions

I would like an ability to write libExt without having a build dependency on the libBase and without writing all dlsym calls myself. What I had in mind is something like automatic dlopen(null)+dlsym by the extern block.

Is this even possible or maybe there are some nice workarounds / best practices taking to account limitation #4?

Thanks for help.

I’ve got no experience or deeper knowledge about linking myself, but you might be missing #[no_mangle]?

2 Likes

@steffahn, thanks for the reply. As far as I know - extern "C" automatically implies no_mangle. In any case libBase is a C library, I've just written it as an example.

Ah, I see. Nevermind then :slight_smile:

What you are asking for depends not on properties of Rust or C languages but on properties of linkers (static linkers if we are talking about static libraries, dynamic linking if we are talking about dynamic libraries).
In particular it should work fine with GNU/Linux (where lazy loading is expected and supported) and shouldn't work with bionic (Android's libc) — by design: Android loader only looks into libraries which are dependencies of your library and doesn't ever try to look into other libraries.
This was done specifically to prevent what you expect to happen — this way system libraries can introduce any symbols they want without them automatically polluting namespace of user-provided libraries.

3 Likes

No it doesn't.

The extern "C" is for specifying a call convention, and there are legitimate places where you might want a mangled symbol with the C calling convention (e.g. callbacks) or an unmangled symbol using the default Rust calling convention (e.g. the hooks Rust uses for accessing the global allocator).

You need to add #[no_mangle] to your function.

Edit: ignore me. I was referring to extern "C" as applied to function definitions (e.g. your already_loaded_symbol() in libBase), while you were asking about the extern "C" blockin libExt. You don't need to specify #[no_mangle] in libExt.

I guess they're talking abut the extern declaration on the calling end, not the definition, i. e. this code

I've never done linking with C code myself yet, but at least judging by the corresponding book

A little C with your Rust - The Embedded Rust Book

you don't seem to need any no_mangle here. But please, correct me if I'm wrong :slight_smile:

Yeah, you need to specify #[no_mangle] when defining a function, but extern blocks work by looking for symbols using the name you use in the function declaration.

In "try 1" it looked like they had forgotten a #[no_mangle] when defining already_loaded_symbol() in libBase, which would normally cause this "symbol not found" error.

1 Like

Do you know what flags are being used with dlopen? If libBase was loaded with RTLD_LOCAL, not RTLD_GLOBAL, then its symbols won't be available when libExt is loaded. This might be what was said here:

hey. as I wrote - libBase is just an example. it is actually written in C and functions can be loaded manually without any problem. There is no problem with mangling and nm also shows functions without mangling. The question is solely about libExt.

Very nice answer, thanks. From what I see there is no way to solve it besides my "try #2" :frowning:

If it's the disk space hugeness of libBase that is a problem, I'm pretty sure there are utilities to generate a dummy .so file with just the symbol list, no actual function bodies.

Actually I have just looked here and realized you can use lld's -z global option to build your libBase library as global library.

Then it should work on Android, too.

But yeah, shims are usually used if libBase is too big (or secret) to use it during the build process.

1 Like

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.