Call functions from a relative path lib.so/dll

Hi everyone,

I am trying to call a function from a local vendor shared object.
I created an example to explain it.
So in main.rs I am trying to call a function from vendor/libadd.so.
It works when i copy the libadd.so into the ./target/[debug|release]/deps/ folder and run it with cargo. When I execute the binary from command line I get "./target/debug/rust_call_c: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory".

I tried a lot of things like "//println!("cargo:rustc-link-search=dependency=./src/vendor");" in build.rs.
I think it is only a small thing which i miss, but I do not get it.

Hopefully someone can give me a hint how to fix this.

My code looks like this:

folder structure:
.
|-- scr
| |-- vendor
| | |-- add.c
| | |-- add.h
| | |-- libadd.so
| |-- main.rs
|-- build.rs
|-- Cargo.toml

add.c

int add(int a, int b) {
    return a + b;
}

add.h

int add(int a, int b) {}

complie to libadd.so with: "gcc -shared -o libadd.so -fPIC add.c"

main.rs

use libc::c_int;

// this extern block links to the libadd library
#[link(name = "add")]
extern "C" {
    // this is a foreign function
    fn add(a: c_int, b: c_int) -> c_int;
}

fn main() {
    let r = unsafe { add(1, 4) };
    println!("{}", r);
}

build.rs

fn main() {
    println!("cargo:rustc-flags=-L ./src/vendor");
}

Cargo.toml

[package]
name = "rust_call_c"
version = "0.1.0"
edition = "2018"
build = "build.rs"

[dependencies]
libc = "0.2"

[profile.dev]
rpath = true

[profile.release]
rpath = true

June 15th:
fixed usage of libc

(The following answer is for unix)

This is about the way a binary that depends on a dynamic library looks for it when run. You can see it with $ ldd ./the-binary.

Now, where does the binary look for the dynamic libraries? You can find all the information in the man page of ld.so.

Long story short, when running_ the binary, the dynamic libraries are searched for in directories:

  1. specified by the -rpath=... flag given, beforehand, to the linker when "compiling",

  2. then in the LD_LIBRARY_PATH environment variable,

  3. then in some places specified by some files in /etc/ld_preload...,

  4. and then in /lib and /usr/lib.

The -L linker parameter is of no use (it's just a sanity check, quite surprising to be honest).

Since you have specified the -C rpath flag to rustc in the Cargo.toml file, I guess it must be the reason it worked in the deps folder. The reason it works with cargo run is that cargo sets the LD_LIBRARY_PATH to include the deps folder.

But more generally, you need to add the -C link-args=-Wl,-rpath=PATH_TO_LIB_HERE to the rust flags, either with the RUSTFLAGS env var, or within the .cargo/config file:

  • absolute path;

    • RUSTFLAGS="-L src/vendor -C link-args=-Wl,-rpath=$PWD/src/vendor/"
  • relative path (not recommended, since it is relative to the calling site, not to the binary location);

    • RUSTFLAGS="-L src/vendor -C link-args=-Wl,-rpath=./src/vendor/"
  • relative to the binary location with the $ORIGIN meta-variable.

    • RUSTFLAGS='-L src/vendor -C link-args=-Wl,-rpath=$ORIGIN/../../src/vendor' (note the single quotes: $ORIGIN is not an env var but text which needs to be passed verbatim).

So, for instance, you could try to set the rpath to $ORIGIN/ to make it work when the binary and the library are in the same dir.


Aside

Your extern declaration is wrong: you should use ::libc::c_int instead of i8 to represent C's integer type:

use ::libc::c_int;

#[link(name = "add")]
extern "C" {
    fn add (a: c_int, b: c_int) -> c_int;
}
3 Likes

Thank you @Yandros for your detailed description and your great demo. :grinning:
Now I know what to do.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.