Cdylib cannot resolve symbols

I'm investigating dynamic libraries in Rust, and I wrote an example:
app.rs:

use libloading::Symbol;

#[no_mangle]
pub static TARGET: &'static str = "main";

fn main() {
    unsafe {
        let lib = libloading::Library::new("libdylib.dylib").unwrap();
        let target: Symbol<fn() -> &'static str> = lib.get(b"print_target").unwrap();
        target()
    }
}

and the dylib.rs:

extern "C" {
    #[no_mangle]
    pub static TARGET: &'static str;
}

#[no_mangle]
extern "C" fn print_target() {
    unsafe {
        println!("{}", TARGET)
    }
}

what I want is checking if the bin "app" and the dynamic lib "dylib" can access each other's symbol(app exposes static variable "TARGET", and wants function "get_target", and "dylib" on the other way around).

Then I build dylib with crate-type = ["cdylib"], but the compile failed:

  = note: Undefined symbols for architecture x86_64:
            "_TARGET", referenced from:
                _print_target in dylib.3b1choogtyjl6snb.rcgu.o
          ld: symbol(s) not found for architecture x86_64
          clang: error: linker command failed with exit code 1 (use -v to see invocation)

It seems that the compiler didn't considered TARGET as an external linkage, which should just be passed since it is a dynamic library.

So what's wrong with this code?

Can you check using e.g. otool or nm whether the symbol is exported? I think you'd need extern for that, but AFAICT it can't be used with static.

The library cannot be built, I can't use tools on it. Do you mean use nm on object files? Or do you mean use nm on caller (in my case it called app)? I don't think whether app export symbols or not will affect the build procedure of dylib... (before dynamic loading, these two has totally no relationship with each other)

The symbol you are trying to export is (oddly enough) not in the library but in the binary. Thus you'll need to examine the binary.

You can in fact run nm or otool on objects files, too, the result should be the same.

If you want to export stuff from the app and you expect to use it from the lib, then it absolutely does matter whether the app exposes the symbol publicly. The important question is not "which binary contains fn main()". The important thing is: which binary depends on which one? In your case, both of your binaries depend on the other, so both need to expose the appropriate symbols.

They totally are related. You are trying to print TARGET in the lib not by dlopen()ing it but by linking to it. Whether linkage is dynamic or static doesn't matter; that merely influences when and how the backing data for the symbol is gathered and realized in memory. If you are using dynamic linking but not dlopen(), then the (static) linker has to know about the symbols to be imported at link time.

1 Like

The problem is that you try to import a symbol from the main executable, but don't tell the linker that you actually want to do this. I think you need to use -Clink-arg=-Wl,-z,undefs when conpiling the cdylib to allow unresolved symbols at link time. They can then be resolved at runtime by the dynamic linker.

2 Likes

Sorry Rust beginner here, I wrote this in my config.toml:

[build]
rustflags = [
    "-C",
    "link-args=-Wl,-z,undefs"
]

but the compiler said ld: unknown option: -z (tried both stable and nightly)

and I searched for a while and didn't find docs about this undefs parameter...

So how can I tell the compiler to link with my main program? I use -L in Clang or Gcc, but don't know how to do this in Rust.

Didn't notice you were on macOS. I gave the linux option. On macOS it would be -Clink-arg=-undefined -Clink-arg=dynamic_lookup. From the man page:

 -undefined treatment
   Specifies how undefined symbols are to be treated. Options are: error, warning, suppress, or dynamic_lookup.  The default is
   error.
2 Likes

Would you kindly teach me how you solved these problems? I'm now trying to export symbols in executables, but the compiler report error again: ld: unknown option: -export-dynamic, I think this is the same reason with the -z problem, but I can't find an alternative in MacOs...

P.S. I found that I can use link-args=-rdynamic, but I searched from stackoverflow, don't know how to solve it by myself...

Here's my final code, hope if it can help others:
main.rs:

use libloading::Symbol;

#[no_mangle]
pub static TARGET: &'static str = "main";

fn main() {
    unsafe {
        let lib = libloading::Library::new("libdylib.dylib").unwrap();
        let target: Symbol<fn() -> &'static str> = lib.get(b"print_target").unwrap();
        target();
    }
}

lib.rs:

extern "C" {
    pub static TARGET: &'static str;
}

#[no_mangle]
extern "C" fn print_target() {
    unsafe {
        println!("{}", TARGET)
    }
}

config.toml (MacOs):

[build]
rustflags = [
    "-Clink-arg=-undefined","-Clink-arg=dynamic_lookup", # allow undefined symbols
    "-Clink-args=-rdynamic" # export symbols in an executable
]

macos - Mac: How to export symbols from an executable? - Stack Overflow suggests -Wl,-exported_symbols_list,/path/to/symbol/file where /path/to/symbol/file is the path to a file containing the name of each symbol you want to be exported on a separate line. In this case I think the file would contain TARGET. It is also possible that you need

TARGET
main

instead.

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.