25

I have a shared library that I'd like to dynamically link into several separate binary Cargo applications. I include its location in the linker using the -- -L /path/to/dir format and the application compiles correctly with the significant decrease in binary size I expect. However, when checking the generated binary using ldd, I get a message saying that the library couldn't be found:

casey@Gilthar-II:~/bot4/backtester/target/release$ ldd backtester 
    linux-vdso.so.1 =>  (0x00007ffc642f7000)
    libalgobot_util.so => not found

If I add the library to the /lib/x86_64-linux-gnu directory, the application runs without issue.

Is there a way to get Rust to look for .so files in the same directory as the binary or in a directory like lib in the binary's directory to be loaded at runtime? If that's not possible, is there a way to at least get Rust to insert the absolute path of the library it was linked with?

I've tried setting rpath = true with no effect.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Ameo
  • 2,307
  • 5
  • 21
  • 33
  • 3
    Does adding the path to `/etc/ld.so.conf` and running `ldconfig` not work for you? (Or failing that, setting the `LD_LIBRARY_PATH` environment variable?) – BurntSushi5 Nov 15 '16 at 11:48
  • 3
    *Is there a way to get Rust to look for .so files* — this isn't something about Rust once the binary is created; it's up to the OS and executable loaders. Setting the `rpath` *is* something that Rust can control (more accurately that Rust instructs the linker to deal with). – Shepmaster Nov 15 '16 at 14:09
  • 2
    *is there a way to at least get Rust to insert the absolute path of the library it was linked with* — that's what rpath is supposed to be. Can you try [printing the rpath of the executable](http://stackoverflow.com/a/6348364/155423)? You may also want to try a `cargo clean` and `cargo build --verbose`. Then see if an `rpath` option is passed when the executable is linked. – Shepmaster Nov 15 '16 at 14:21

3 Answers3

43

Here's a Minimal, Reproducible Example that exhibits the same problem that you experienced. I created a C library exporting a simple addition function. I also created a Cargo project to use this function.

dynlink/
├── executable
│   ├── build.rs
│   ├── Cargo.lock
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── library
    ├── awesome_math.c
    └── libawesome_math.so

awesome_math.c

#include <stdint.h>

uint8_t from_the_library(uint8_t a, uint8_t b) {
  return a + b;
}

The library was compiled as gcc -g -shared awesome_math.c -o libawesome_math.so.

src/main.rs

extern {
    fn from_the_library(a: u8, b: u8) -> u8;
}

fn main() {
    unsafe {
        println!("Adding: {}", from_the_library(1, 2));
    }
}

build.rs

fn main() {
    println!("cargo:rustc-link-lib=dylib=awesome_math");
    println!("cargo:rustc-link-search=native=/home/shep/rust/dynlink/library");
}

Cargo.toml

[package]
name = "executable"
version = "0.1.0"
edition = "2021"

[profile.dev]
rpath = true

Investigating further, I asked the Rust compiler to print out the linker args it was going to use:

cargo rustc -- --print link-args

This printed out a bunch of stuff, but an important line was:

"-Wl,-rpath,$ORIGIN/../../../../../../.rustup/toolchains/nightly-aarch64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu/lib"

This is a directive to the linker to add specific values to the rpath of the finished binary. Missing is any reference to the dynamic library that we are linking to. In retrospect, this probably makes sense, as how would the compiler know that we want to include it in the rpath?

A workaround is to add another directive to the linker. There are interesting options (like $ORIGIN), but for simplicity, we will just use an absolute path:

cargo rustc -- -C link-args="-Wl,-rpath,/home/shep/rust/dynlink/library/"

And the resulting binary prints the right thing for ldd and runs without setting LD_LIBRARY_PATH:

$ ldd ./target/debug/executable | grep awesome
    libawesome_math.so => /home/shep/rust/dynlink/library/libawesome_math.so (0x0000ffffb1e56000)
    
$ ./target/debug/executable
Adding: 3

Turning to making it relative, we can use $ORIGIN:

cargo rustc -- -C link-args='-Wl,-rpath,$ORIGIN/../../../library/'

Be careful to escape $ORIGIN properly for your shell, and remember that the path is relative to the executable, not the current working directory.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thanks for this incredibly informative response! You simplified and clarified what took me several hours of trawling the internet into a single well-written format. Just one question: Is `$ORIGIN` relative to the location of the binary or the location of the source code being compiled? – Ameo Nov 18 '16 at 02:35
  • 1
    @Ameo `$ORIGIN` is relative to the executable. – Shepmaster Nov 18 '16 at 02:41
  • @Shepmaster Each time I want to run the program I should run those commands? Is there any way that the program remembers the library and If I changed the value or ask it from the user just build and run it? – Parisa.H.R Feb 22 '22 at 14:03
  • @Parisa.H.R *Each time I want to run the program I should run those commands* — I don't understand your question; please expand and clarify it. The commands I show (`cargo rustc ...`) are about **building** the executable. **Running** the executable (`./target/debug/executable`) does remember the library; that's how dynamic linking and `rpath` work. – Shepmaster Feb 22 '22 at 14:11
  • means that if I changed numbers `from_the_library(1, 2)` to 3 and 4 and build it again I should link to the library again? Sorry I am new to rust – Parisa.H.R Feb 22 '22 at 14:13
  • 1
    @Parisa.H.R see [How can I specify linker flags/arguments in a build script?](https://stackoverflow.com/q/50642574/155423); TL;DR you can also use`cargo:rustc-link-arg` in your build script. – Shepmaster Feb 22 '22 at 14:29
12

Adding to what Shepmaster said (apparently I don't have enough reputation to comment): I'm not sure when this feature was added, but as of at least Rust 1.20, you can achieve the same effect by setting the environment variable RUSTFLAGS:

$ RUSTFLAGS="-C link-args=-Wl,-rpath,/the/lib/path" cargo build

This can be more convenient than the cargo rustc option if, for instance, you're using build scripts that just invoke cargo build.

Peter
  • 349
  • 2
  • 7
1

add line in build.rs (I had the similar issue. I got clue from this post, and extend this into build.rs per https://doc.rust-lang.org/cargo/reference/build-scripts.html

println!("cargo:rustc-link-arg=-Wl,-rpath,/home/shep/rust/dynlink/library");

charlie
  • 11
  • 1