提问人:ABeginner 提问时间:11/8/2023 最后编辑:ABeginner 更新时间:11/8/2023 访问量:45
为什么加载一些自定义库会失败?
Why does loading some custom libraries fail?
问:
我用自己的代码编译了几个共享库,并在编译过程中添加了该选项。当我使用以下代码()函数加载它时,抛出了这个异常。-m32
dlopen
#1 例外:
terminate called after throwing an instance of 'LibraryLoadException'
what(): shared_library.cpp : 39 : lib/libCR_TcpControl.so: cannot allocate memory in static TLS block
#2 代码:
void SharedLibrary::load(bool initial)
{
if (handle_)
throw LibraryAlreadyLoadedException(path_);
handle_ = dlopen(path_.c_str(), RTLD_NOW | RTLD_DEEPBIND);
LOG("dlopen : {} : {} ", handle_, path_.c_str());
if (!handle_) {
const char* err = dlerror();
throw LibraryLoadException(err ? std::string(err) : path_);
}
reinterpret_cast<ModuleInitFunc>(getSymbol("moduleInit"))();
if (initial) {
std::string module_name = reinterpret_cast<GetModuleNameFunc>(getSymbol("getModuleName"))();
ModuleManager::instance()->initModule(module_name);
}
if (watch_)
file_watch_->addWatch(path_, std::bind(&SharedLibrary::watchEvent, this));
}
我从其他地方看到预加载可以用来解决这个问题。我紧随其后,但还会有另一个警告如下:
#3 警告:
ERROR: ld.so: object '/dobot/bin/lib/libCR_TcpControl.so' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.
/bin/sh: 1: wget: not found
ERROR: ld.so: object '/dobot/bin/lib/libCR_TcpControl.so' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.
file /qemu.sh is not exist ,will not run it .
file /develop.sh is not exist ,will not run it .
dobot_simulate start done !!!!
root@7c6404f6b677:/dobot/bin# this stream will redirect to channel1
ERROR: ld.so: object '/dobot/bin/lib/libCR_TcpControl.so' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.
ERROR: ld.so: object '/dobot/bin/lib/libCR_TcpControl.so' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.
/bin/sh: 1: kill: No such process
ERROR: ld.so: object '/dobot/bin/lib/libCR_TcpControl.so' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.
ERROR: ld.so: object '/dobot/bin/lib/libCR_TcpControl.so' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.
ERROR: ld.so: object '/dobot/bin/lib/libCR_TcpControl.so' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.
我检查了一些信息,说这是因为我的系统是 64 位,但我的库是 32 位,所以报告了这个警告。
然后我更困惑了,为什么我加载其他库(例如)没有问题,只有这样才会有问题。libCR_RobotFSM.so
libCR_TcpControl.so
#4 以下信息:file
root@7c6404f6b677:/dobot/bin# file lib/libCR_RobotFSM.so
lib/libCR_RobotFSM.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked, not stripped
root@7c6404f6b677:/dobot/bin# file lib/libCR_TcpControl.so
lib/libCR_TcpControl.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked, not stripped
顺便说一句,我的程序运行在 Docker 容器中,用于镜像,系统确实是 64 位。debian:bookworm
#5 dockerfile:
#FROM debian:latest
FROM debian:bookworm
# for 32bit
RUN dpkg --add-architecture i386
RUN apt-get update -y
RUN apt-get upgrade -y
RUN apt-get install net-tools \
uml-utilities \
bridge-utils\
iputils-ping \
python3 \
vim \
openssh-server \
openssh-client \
curl \
gpg \
psmisc \
samba \
samba-common \
redis \
unzip \
file \
libc-bin \
-y
RUN apt-get install libc6:i386 -y
RUN apt-get install lib32stdc++6 -y
答:
LD_PRELOAD
通常不会解决这个特定问题,实际上会使情况变得更糟。(您描述的问题实际上在使用时更频繁地发生,这就是为什么您可能会在此上下文中找到大量引用。LD_PRELOAD
问题是你通过加载的库使用线程本地存储(即具有用(C++标准)或(GNU扩展)声明的变量),例如.编译器可以通过各种不同的方式实现这些,这可以通过 指定。问题在于,如果编译器认为可能,它可能会尝试选择模型,如果您正在构建可执行文件或始终在可执行文件启动时加载的库,这是完全没问题的,但这是插件的问题。(但它使访问 TLS 变量的速度大大加快,这就是编译器尝试以这种方式进行优化的原因。问题在于,当程序执行时,模型需要内部向量中每个变量的空间,而当程序启动时,该向量的大小由可执行文件中的 TLS 变量数量 + 其所有依赖项 + 一些额外的剩余空间决定。(它实际上是变量的总大小,而不是它们的数量,但这对你没有帮助。dlopen()
thread_local
__thread
thread_local static int x;
-ftls-model=XXX
initial-exec
initial-exec
当您加载使用需要该向量空间的 TLS 模型的插件时,动态加载器将开始用完剩余部分。但是一旦满了,您将收到您看到的错误消息。
如果您控制正在加载的插件,则可以使用编译器选项编译插件(或可能从程序加载的其他插件),以切换到非静态线程本地存储。-ftls-model=global-dynamic
但是,如果您的插件本身与其他库链接,并且这些库使用静态 TLS,您仍然会遇到同样的问题。例如,如果你的插件名为 ,但该插件本身与 链接,并且具有 TLS 变量,那么您还需要使用编译器标志重新编译(如果可能的话)。dlopen()
plugin_FANCY.so
libFANCY.so
libFANCY.so
libFANCY.so
-ftls-model=global-dynamic
如果你不能改变足够多的你自己加载的库来使用,那么你就有点不走运了。从理论上讲,您可以重新编译系统 C 库 (glibc) 并增加它为 TLS 分配的剩余空间量,但我真的建议不要这样做。-ftls-model=global-dynamic
或者,如果插件的依赖关系是一个问题,并且您无法重新编译这些插件,则可以直接将可执行文件直接链接到至少其中一些库,这样程序启动时静态TLS区域的初始大小就会更大,并且已经包含了大多数有问题的变量。(不过,这在某种程度上违背了加载插件的目的。
关于这个问题的进一步高级阅读:
- -ftls-model 的 GCC 文档(请注意,和是不同的,现在库通常使用后者而不是前者进行编译是有充分理由的。
-fpic
-fPIC
- 深入了解(隐式)线程本地存储
- 一个动态链接器谋杀之谜,Rust 开发人员正在研究这个问题
- ELF Handling For Thread-Local Storage - Ulrich Drepper 的 ELF 中 TLS 的原始设计文档
评论
-ftls-model=global-dynamic
评论