将Rust应用程序与运行时链接器搜索路径之外的动态库链接

29

我有一个共享库,希望将其动态链接到多个独立的二进制Cargo应用程序中。使用-- -L /path/to/dir格式在链接器中包含其位置,并且应用编译正确,二进制文件大小也明显减小,符合我的预期。但是,当使用ldd检查生成的二进制文件时,出现了一个消息,指示找不到库:

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

如果我将库添加到/lib/x86_64-linux-gnu目录中,应用程序可以无问题运行。

有没有办法让Rust在与二进制文件相同的目录或像二进制文件目录中的lib这样的目录中查找.so文件,在运行时加载?如果不可能,是否有一种方法至少让Rust插入被链接的库的绝对路径?

我尝试设置rpath = true但没有效果。


3
将路径添加到 /etc/ld.so.conf 并运行 ldconfig 对您不起作用吗?(如果这样做不起作用,可以尝试设置 LD_LIBRARY_PATH 环境变量。) - BurntSushi5
3
有没有办法让 Rust 查找 .so 文件?这与 Rust 创建二进制文件后无关,而是由操作系统和可执行文件的加载器决定。设置 rpath 是 Rust 可以控制的(更准确地说是 Rust 指示链接器处理)。 - Shepmaster
2
有没有办法至少让Rust插入其链接的库的绝对路径,这就是rpath的作用。您可以尝试打印可执行文件的rpath,也可以尝试使用cargo cleancargo build --verbose命令。然后查看在链接可执行文件时是否传递了rpath选项。 - Shepmaster
3个回答

45

这是一个最小化可重现示例,展示了你遇到的相同问题。我创建了一个导出简单加法函数的C库,并创建了一个Cargo项目来使用此函数。

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;
}

该库被编译为 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

进一步调查后,我请求 Rust 编译器打印出将要使用的链接器参数:


cargo rustc -- --print link-args

这个命令打印了很多东西,但一个重要的行是:

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

这是一个指示链接器将特定值添加到完成二进制文件的rpath中。缺少的是任何关于我们正在链接的动态库的引用。回想起来,这可能是有道理的,因为编译器如何知道我们要将其包含在rpath中呢?

解决方法是向链接器添加另一个指令。有一些有趣的选项(例如$ORIGIN),但为了简单起见,我们将只使用绝对路径:

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

生成的二进制文件可以正确打印 ldd 所需的内容,并且在不设置 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

谈到相对路径,我们可以使用$ORIGIN

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

请注意适当转义您的shell中的$ORIGIN,并记住路径是相对于可执行文件而不是当前工作目录。

另请参见:


谢谢您提供如此详细的回答!您将我在互联网上搜寻了数小时才找到的内容简化并澄清,使其以一种良好的格式呈现。只有一个问题:$ORIGIN 是相对于二进制文件位置还是编译源代码的位置? - Ameo
1
@Ameo $ORIGIN 相对于可执行文件。 - Shepmaster
@Shepmaster 每次我想运行程序,我都要运行那些命令吗?有没有办法让程序记住库,并且如果我更改了值或要求用户输入后,只需构建并运行它? - Parisa.H.R
@Parisa.H.R 每次我想运行程序都需要运行那些命令 — 我不明白你的问题,请进一步扩展和澄清。我展示的命令(cargo rustc ...)是关于构建可执行文件的。运行可执行文件(./target/debug/executable)会记住库;这就是动态链接和rpath工作的方式。 - Shepmaster
这是指如果我将库中的数字 from_the_library(1, 2) 更改为 3 和 4 并重新构建,那么我需要再次链接到该库吗?抱歉,我是 Rust 的新手。 - Parisa.H.R
1
@Parisa.H.R 请查看如何在构建脚本中指定链接器标志/参数?;简而言之,您也可以在构建脚本中使用cargo:rustc-link-arg - Shepmaster

14

补充Shepmaster所说的内容(显然我没有足够的声望来评论):我不确定这个功能是什么时候添加的,但至少在Rust 1.20及以上版本,您可以通过设置环境变量RUSTFLAGS来实现同样的效果:

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

如果您正在使用只调用cargo build的构建脚本,那么这比cargo rustc选项更方便。


2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接