当在工作区编译时,dylib无法加载libstd。

10

我有一个以下结构的项目:

Cargo.toml
my_script.py
my_lib:
    - Cargo.toml
    - src
my_bin:
    - Cargo.toml
    - src

其中:

  • my_lib 是一个 Rust 库,其 crate-type = ["dylib"]
  • my_bin 是一个使用 my_lib 的 Rust 可执行文件
  • my_script.py 是一个使用 my_lib 的 Python 3 脚本

根目录下的 Cargo.toml 包含一个基本的工作区声明:

[workspace]
members = [
    "my_lib",
    "my_bin"
]
如果我执行 cargo buildcargo run -p my_bin,一切都正常运行。问题出现在 Python 脚本中。
在此脚本中,我使用以下代码加载my_lib库文件:
from ctypes import cdll
from sys import platform

if platform == 'darwin':
    prefix = 'lib'
    ext = 'dylib'
elif platform == 'win32':
    prefix = ''
    ext = 'dll'
else:
    prefix = 'lib'
    ext = 'so'

# Working path:
# lib_path = './my_lib/target/debug/{}my_lib.{}'.format(prefix, ext)

# Buggy "Library not loaded: @rpath/libstd-d00eaa6834e55536.dylib" path:
lib_path = './target/debug/{}my_lib.{}'.format(prefix, ext)

lib = cdll.LoadLibrary(lib_path)
my_func = lib.my_func
my_func()

如果我使用来自库目录(./my_lib/target/...)的库文件,脚本加载库并执行其函数没有问题。

但是,如果我使用来自工作区目录(./target/...)的库文件,当尝试加载库时会出现以下错误:

OSError: dlopen(./target/debug/libpeglrs.dylib, 6): Library not loaded: @rpath/libstd-d00eaa6834e55536.dylib

同样地,尝试直接从工作区目标目录执行my_bin会导致相同的错误(即使cargo run -p my_bin可以正常工作)。

使用软件“Dependency Walker”,我发现my_lib库找不到Rust的libstd库(如前面的错误消息所解释的那样)。

将包含Rust工具链库的路径手动导出到环境PATH中可以解决此问题。但这远非理想,也不可移植。我还不明白为什么只有在使用工作区目标时才会出现这个问题。

那么,为什么工作区目标无法找到Rust的libstd,而每个项目目标都可以?是否有一种方法可以修复此问题,而无需查找工具链路径并修改环境变量?


我发现了这个问题:https://github.com/rust-lang/rustup.rs/issues/932 运行 rustup run beta python3 editor.py 是可以工作的,但仍然无法解释为什么只有工作区存在错误。 - Maeln
你尝试过使用cdylib构建动态库吗? - E net4
它能工作,但我失去了直接使用库的能力,因为二进制项目中有一个依赖项(由于它丢失了 Rust 特定信息)。这意味着我必须针对自己的 Rust 库进行 ffi。不是理想的 :/ - Maeln
1个回答

11

动态链接有时并不容易。错误信息Library not loaded: @rpath/libstd-d00eaa6834e55536.dylib非常清晰。你的问题在于DYLD_LIBRARY_PATH (macOS)。

简短总结:

你的DYLD_LIBRARY_PATH中没有包含Rust库路径。请将以下内容添加到~/.bash_profile 中:

source "$HOME/.cargo/env"
export RUST_SRC_PATH="$(rustc --print sysroot)/lib/rustlib/src/rust/src"
export DYLD_LIBRARY_PATH="$(rustc --print sysroot)/lib:$DYLD_LIBRARY_PATH"

解释

我按照你的项目结构进行了操作,唯一不同的是我去掉了_(例如,my_bin 改为 mybin,...)。

cargo run --bin mybintarget/debug/mybin 的区别

首先,检查一下otool -L target/debug/mybin的输出:

target/debug/mybin:
    /Users/robertvojta/Work/bar/target/debug/deps/libmylib.dylib (compatibility version 0.0.0, current version 0.0.0)
    @rpath/libstd-d4fbe66ddea5f3ce.dylib (compatibility version 0.0.0, current version 0.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.200.5)
    /usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)

注意 @rpath。如果你不知道它是什么,我建议阅读 Mike Ash 的文章: 同时运行man dlopen并阅读SEARCHING部分。此处太长,无法复制和粘贴,所以只提供第一句话:

dlopen()在由一组环境变量和进程的当前工作目录指定的目录中搜索兼容的Mach-O文件。

你将了解有关DYLD_LIBRARY_PATH和其他环境变量的信息。
在你的 shell 中,echo $DYLD_LIBRARY_PATH 命令的输出是什么?我假设它是空的/不包含Rust库路径。
请将以下行添加到 your mybin:main.rs...
println!(
    "DYLD_LIBRARY_PATH={}",
    std::env::var("DYLD_LIBRARY_PATH").unwrap_or("N/A".to_string())
);

...并运行cargo run --bin mybin。您应该会看到类似于这样的东西:

DYLD_LIBRARY_PATH=~/.rustup/toolchains/stable-x86_64-apple-darwin/lib

cargo run 会为您注入此环境变量。

您可以从哪里获取正确的值? 运行 rustc --print sysroot 并将结果追加 /lib

如果您想直接运行mybin(而不使用),可以按照以下方式操作:

DYLD_LIBRARY_PATH="$(rustc --print sysroot)/lib:$DYLD_LIBRARY_PATH" target/debug/mybin

Python脚本

将类似的行添加到您的run.py脚本中:

import os
print('DYLD_LIBRARY_PATH: {}'.format(os.environ.get('DYLD_LIBRARY_PATH', 'N/A')))

如果打印出N/A,则表示DYLD_LIBRARY_PATH未设置。您可以采用类似的方式进行修复:


DYLD_LIBRARY_PATH="$(rustc --print sysroot)/lib:$DYLD_LIBRARY_PATH" python run.py

macOS和系统完整性保护

请注意,您无法使用系统Python进行此操作...

$ echo $DYLD_LIBRARY_PATH
~/.rustup/toolchains/stable-x86_64-apple-darwin/lib:
$ /usr/bin/python run.py
DYLD_LIBRARY_PATH: N/A
Traceback (most recent call last):
  File "./run.py", line 21, in <module>
    lib = cdll.LoadLibrary(lib_path)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/ctypes/__init__.py", line 443, in LoadLibrary
    return self._dlltype(name)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/ctypes/__init__.py", line 365, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: dlopen(./target/debug/libmylib.dylib, 6): Library not loaded: @rpath/libstd-d4fbe66ddea5f3ce.dylib
  Referenced from: /Users/robertvojta/Work/bar/target/debug/libmylib.dylib
  Reason: image not found

... 但是你可以使用通过brew安装的例子...

$ echo $DYLD_LIBRARY_PATH
/Users/robertvojta/.rustup/toolchains/stable-x86_64-apple-darwin/lib:
$ /usr/local/bin/python run.py
DYLD_LIBRARY_PATH: /Users/robertvojta/.rustup/toolchains/stable-x86_64-apple-darwin/lib:

原因是SIP。SIP在El Capitan中引入,可能会阻碍您。您可能会遇到以下问题:

$ env | grep DYLD
$ echo $DYLD_LIBRARY_PATH
/Users/robertvojta/.rustup/toolchains/stable-x86_64-apple-darwin/lib:

这是SIP描述页面。 SIP 保护像/usr/bin/sbin这样的文件夹,但不保护/usr/local等文件夹。

这意味着什么? SIP会执行许多功能,其中一个功能是删除DYLD_LIBRARY_PATH值。Shebang行例如...

  • #!/usr/bin/env python
  • #!/usr/bin/python

...对你来说将无法工作。您必须使用Python解释器,该解释器未安装在系统(和受保护)文件夹中。通过brew安装一个解释器,也可以安装Anaconda。

SIP可以被禁用,但不要这样做。

另一种解决方法是通过install_name_toolman install_name_tool)将mylib中的@rpath替换为完整路径。更多信息请参见为什么Mach-O库在Mac OS X中需要install_name_tool和otool?

示例:

$ otool -L target/debug/mybin
target/debug/mybin:
    /Users/robertvojta/Work/bar/target/debug/deps/libmylib.dylib (compatibility version 0.0.0, current version 0.0.0)
    @rpath/libstd-d4fbe66ddea5f3ce.dylib (compatibility version 0.0.0, current version 0.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.200.5)
    /usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)
$ install_name_tool -change @rpath/libstd-d4fbe66ddea5f3ce.dylib /Users/robertvojta/.rustup/toolchains/stable-x86_64-apple-darwin/lib/libstd-d4fbe66ddea5f3ce.dylib target/debug/libmylib.dylib
$ otool -L target/debug/libmylib.dylib
target/debug/libmylib.dylib:
    /Users/robertvojta/Work/bar/target/debug/deps/libmylib.dylib (compatibility version 0.0.0, current version 0.0.0)
    /Users/robertvojta/.rustup/toolchains/stable-x86_64-apple-darwin/lib/libstd-d4fbe66ddea5f3ce.dylib (compatibility version 0.0.0, current version 0.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.200.5)
    /usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)
$ /usr/bin/python run.py
DYLD_LIBRARY_PATH: N/A
Hallo

如您所见,现在没有 @rpath,也没有设置 DYLD_LIBRARY_PATH,但使用系统 Python 解释器可以正常工作(hallo 函数从 libmylib.dylib 中打印出 Hallo)。

请注意一点 - macOS 动态库与 Linux 等其他操作系统的动态库行为不同。

如果您不想处理它,可以将 mylibcrate-type 更改为 ["rlib", "cdylib"],但这可能不是您想要的。


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