从另一个共享对象动态加载共享对象时的约束?

Posted

技术标签:

【中文标题】从另一个共享对象动态加载共享对象时的约束?【英文标题】:constraints when dynamically loading a shared object from another shared object? 【发布时间】:2010-08-30 14:52:13 【问题描述】:

我正在从main 动态加载(通过dlopen())一个共享对象(名为libprofile1.so)。

libprofile1.so 中我定义了工厂函数CreateProfile 和类ProfileCreateProfile 函数创建Profile 类的实例并返回指向它的指针。 Profile 类有一个方法 pMethod

在main中,加载libprofile1.so后,我调用CreateProfile方法返回指向Profile类对象的指针(调用它p)。 之后,我针对对象p (p->pMethod) 调用pMethod 方法。在这种方法中,我动态加载其他共享对象 (libdatasources.so)。

在这个共享对象中,我有一个工厂函数 CreateDataSource 和类 DataSourceCreateDataSource 函数创建一个 DataSource 类的实例并返回一个指向它的指针。 DataSource 类有方法 dsMethod

如您所见,两个共享对象的结构相似。

pMethod 加载libdatasources.so 后,我调用CreateDataSource 方法,它返回一个指向DataSource 类实例的指针,调用它ds。 然后我打电话给dsMethodds 对象 (ds->dsMethod)。


现在,问题来了。

当我尝试调用 ds 对象的 dsMethod 时,我首先加载的共享对象 (libprofile1.so) 不会加载。实际上dlopen() 返回NULL。当我在dlopen 之后阅读dlerror 时,我得到:

./libprofile1.so: undefined symbol: _ZN18DataSource13dsMethod

因此,如果我有一个电话 ds->Method,则不会加载第一个共享对象! 如果我从源注释掉调用ds->dsMethod,那么我的libprofile1.solibdatasources.so 将毫无问题地加载。 我没有看到从第二个 SO 调用方法与加载第一个 SO 之间的联系???

也许我不知道,但是从一个也被动态加载的共享对象中动态加载共享对象时是否有任何限制?

顺便说一句,dlopenRTLD_NOW|RTLD_GLOBAL 一起使用。我试过RTLD_LAZY,但还是同样的问题。

更新:

库是在 Eclipse 中构建的。两个库的 G++ 编译器和链接器选项相同。 这里是 G++ 编译器:

-O0 -g3 -Wall -c -fmessage-length=0

和 G++ 链接器:

-shared

选项,从Project Properties -> Settings -> Tool Settings粘贴​​p>

提前致谢。

【问题讨论】:

您能否更新您的问题,说明 libprofile1.so 和 libdataresources.so 是如何构建的? 【参考方案1】:

正如 Martin York 所指出的,这就是它在 Linux 中的工作方式。链接库时,您也必须链接到所有依赖项。这在 Windows 中有所不同,DLL 自己处理它们的依赖关系。当动态加载具有另一个库作为依赖项的库时,您需要首先使用 RTLD_GLOBAL 标志加载该库。这很尴尬,恕我直言,因为您可能无法知道另一个共享对象需要哪些依赖项,或者依赖项可能会随着二进制兼容的较新版本而改变。据我所知(以及阅读 g++ 和 ld 手册页),不可能创建类似于 Windows 的 DLL 的行为。 这是一个小测试用例:

两个.cpp:

#include <iostream>

extern "C"

   void bar()
   
      std::cout << "bar()\n";
   

一个.cpp:

#include <iostream>

extern "C"

   void bar();

   void foo()
   
      std::cout << "foo()\n";
      bar ();
   

test.cpp:

#include <dlfcn.h>
#include <iostream>

int main (int argc, char *argv[])

   using namespace std;
//       void *libtwo = dlopen("./libtwo.so", RTLD_NOW | RTLD_GLOBAL);
   void *libone = dlopen("./libone.so", RTLD_NOW);
   if (!libone)
   
      cout << "dlopen(libone.so) failed: " << dlerror() << "\n";
      return 1;
   
   typedef void (*foo_t)();
   foo_t foo = reinterpret_cast<foo_t>(dlsym(libone, "foo"));
   if (!foo)
   
      cout << "dlsym(libone.so, foo) failed\n";
      return 2;
   

one.cpp 被编译成libone.so 和two.cpp 在libtwo.so,test.cpp 被编译成test 二进制文件。 这将失败,只有在注释行未注释时才会成功。

【讨论】:

非常相似的例子,但问题是在foo() 中我正在加载libtwo.so,然后获取指向bar() 的函数指针,我也从foo() 调用它。我正在使用针对bar() 的包装函数barWrap() 来解决此问题。 barWrap() 不是我正在使用的类的一部分(DataSource),而是导出的独立函数。从这个函数我调用bar()(在我的例子中是ds-&gt;dsMethod),它工作正常。顺便说一句,我使用RTLD_NOW|RTLD_GLOBAL 作为dlopen 的参数,但仍然没有。一个很奇怪的问题,我似乎无法弄清楚。 您是否将 libone.so 与 libtwo.so(链接器的 -ltwo)链接起来? dlopen() 加载它应该没有问题。我将添加我的 gcc 命令行作为新注释。 $ g++ -shared -fpic -o libtwo.so -Wl,-no-undefined two.cpp $ g++ -shared -fpic -o libone.so -Wl,-no-undefined one.cpp -ltwo -L。 -Wl,-rpath,。 $ g++ test.cpp -ldl $ ./a.out【参考方案2】:

如果您动态加载一个 DLL,您必须确保它没有未解析的符号。

执行此操作的最简单方法是将其与所需的其他 DLL 链接,以便它们自动加载,而不必手动加载和解析所有依赖项。

因此,如果libprofile1 始终使用libdatasources,请确保它们相互关联。

如果您必须手动执行,请颠倒加载库的顺序。这样当你加载 libprofile1 时,它需要的函数已经加载了。

【讨论】:

以上是关于从另一个共享对象动态加载共享对象时的约束?的主要内容,如果未能解决你的问题,请参考以下文章

程序中动态加载共享库

无法加载动态库“libcupti.so.11.0”; dlerror: libcupti.so.11.0: 无法打开共享对象文件

无法加载动态库“libcublasLt.so.11”; dlerror:libcublasLt.so.11:无法打开共享对象文件:没有这样的文件或目录

无法加载动态库“libcublas.so.10”; dlerror:libcublas.so.10:无法打开共享对象文件:没有这样的文件或目录;

已加载共享对象的内存使用情况

《程序员自我修养》阅读笔记-动态链接