C++ 加载一个具有依赖关系的共享对象并访问它们的功能

Posted

技术标签:

【中文标题】C++ 加载一个具有依赖关系的共享对象并访问它们的功能【英文标题】:C++ Load one Shared Object with dependencies and access their functions 【发布时间】:2021-12-29 20:56:25 【问题描述】: 操作系统 Linux Ubuntu 18.04,gcc 7.4.0

程序应该加载一个依赖于其他 SO 的 SO。所有 SO 的所有导出函数都应由程序调用。 我发现了一些相关的问题,但没有一个明确涉及这种情况。

这是我想要做的一个例子: 共有 3 个共享库和 1 个应用程序:

libtest1.so    has a extern "C" print function
 libtest2.so    has a base class "Base" with a function usig the C-function
 libtest3.so    has a derived class "Test" with a function using the C-function-
 DllTest        Application: loads the *.so's

这是应用程序应该做的事情

pHandle = OpenSharedLib("./libtest1.so");      # works
 OpenSymbol(pHandle, "GetName");            # works
 CloseLib(pHandle);

 pHandle = OpenSharedLib("./libtest2.so");      # error
 OpenSymbol(pHandle, "GetName");
 OpenSymbol(pHandle, "printBase");
 CloseLib(pHandle);

 pHandle = OpenSharedLib("./libtest3.so");
 OpenSymbol(pHandle, "GetName");
 OpenSymbol(pHandle, "printBase");
 OpenSymbol(pHandle, "print");
 CloseLib(pHandle);

错误是由于未定义的符号而导致 dlopen() 加载失败:./libtest2.so: undefined symbol: GetName"。 nm 输出显示该符号丢失,但我不知道如何防止这种情况发生。

基本思想是有一个“前端 SO”,它收集各种单独的 SO,并为程序提供一个统一的库。在示例中,程序应该只加载 libtest3.so。然后它应该能够加载任何符号,只要它被任何单个 SO 暴露。

我的问题:是否可以做我想做的事以及如何做?或者换句话说:我的错误是什么?

这是代码,下面给出了我用来编译它们的命令。

  lib1.h, lib1.cpp     for libtest1.so
  lib2.h, lib2.cpp     for libtest2.so
  lib3.cpp             for libtest3.so
  DllTest.cpp          the application

libtest1.so

标题

extern "C" __attribute__((visibility("default"))) const char* GetName();

Cpp

#include <stdio.h>
#include <stdlib.h>
#include "lib1.h"
    
__attribute__((visibility("default"))) const char* GetName() 

   return "Hello from lib1";
 

编译

g++ -c -fvisibility=hidden -fPIC -o lib1.o  lib1.cpp
g++ -shared -o libtest1.so lib1.o

libtest2.so

标题

#include <stdio.h>
#include <stdlib.h>
    
class __attribute__((visibility("default"))) Base 

public:
 Base();
 virtual ~Base();

 const char* printBase();
 int nTest;
;

Cpp

#include <stdio.h>
#include <stdlib.h>
#include "lib2.h"

#include "lib1.h"   // for GetName()
    
Base::Base() 
 nTest=1; 

 
Base::~Base()
 

const char* Base::printBase()
   return GetName(); 

编译

g++ -c -fvisibility=hidden -fPIC -o lib2.o  lib2.cpp
g++ -shared -o libtest2.so -L. -ltest1 lib2.o

libtest3.so

#include <stdio.h>
#include <stdlib.h>

#include "lib1.h"
#include "lib2.h"

class __attribute__((visibility("default"))) Test : public Base

public:
 Test();
 virtual ~Test();
 const char* print();
;

Test::Test()
 

Test::~Test()
 


const char* Test::print() 
 char* pChar = (char*)GetName();
 printf( "hello from lib3: %d", nTest);
 return "test3";

编译

g++ -c -fvisibility=hidden -fPIC -o lib3.o  lib3.cpp
g++ -shared -o libtest3.so -L. -ltest1 -ltest2 lib3.o

** 加载应用程序 **

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

void OpenSymbol(void* pHandle, char* strName)
 
 typedef char* (*pfnChar)(void);
 pfnChar pFunction   = NULL;
 char*  cError;

 printf(" Find symbol %s\n", strName);
 dlerror();
 pFunction = (pfnChar)dlsym( pHandle, strName );
 cError = dlerror();
 if (cError != 0)   
  std::cout << cError << std::endl;
  exit(1);   
 printf(" Exec symbol: %p\n", pFunction );
 std::cout << pFunction() << std::endl;
 


void* OpenSharedLib(char* strName)
 
 void*   pHandle; 
 char*  cError;

 printf(" open lib %s\n", strName);
 dlerror();
 pHandle = dlopen( strName, RTLD_NOW ); 
 cError = dlerror();
 if (cError != 0)  
  std::cout << cError << std::endl;
  exit(1);  
 printf(" found DLL %p\n", pHandle );
 return pHandle;
 

void* CloseLib(void* pHandle)
   dlclose(pHandle);  

main()

 void*   pHandle; 

 pHandle = OpenSharedLib("./libtest1.so");
 OpenSymbol(pHandle, "GetName");
 CloseLib(pHandle);

 pHandle = OpenSharedLib("./libtest2.so");
 OpenSymbol(pHandle, "GetName");
 OpenSymbol(pHandle, "printBase");
 CloseLib(pHandle);

 pHandle = OpenSharedLib("./libtest3.so");
 OpenSymbol(pHandle, "GetName");
 OpenSymbol(pHandle, "printBase");
 OpenSymbol(pHandle, "print");
 CloseLib(pHandle);

 std::cout << "done" << std::endl;

运行nm -DC 显示最后两个库中的一些符号未导出。

符号 libtest1.so:
...
000000000000057a T GetName
符号 libtest2.so:
...
                 U GetName
                 U operator delete(void*, unsigned long)
000000000000094c T Base::printBase()
00000000000008da T Base::Base()
00000000000008da T Base::Base()
0000000000000920 T Base::~Base()
0000000000000902 T Base::~Base()
0000000000000902 T Base::~Base()
0000000000200e08 V typeinfo for Base
0000000000000969 V typeinfo name for Base
0000000000200de8 V vtable for Base
                 U vtable for __cxxabiv1::__class_type_info
符号 libtest3.so:
...
                 U GetName
                 U printf
                 U operator delete(void*, unsigned long)
                 U Base::Base()
                 U Base::~Base()
0000000000000ab2 T Test::print()
0000000000000a2a T Test::Test()
0000000000000a2a T Test::Test()
0000000000000a86 T Test::~Test()
0000000000000a58 T Test::~Test()
0000000000000a58 T Test::~Test()
                 U typeinfo for Base
0000000000200df0 V typeinfo for Test
0000000000000b0f V typeinfo name for Test
0000000000200dd0 V vtable for Test
                 U vtable for __cxxabiv1::__si_class_type_info
最后,输出 DllTest
 open lib ./libtest1.so
 found DLL 0x55965d711ea0
 Find symbol GetName
 Exec symbol: 0x7f902c38157a
Hello from lib1
 open lib ./libtest2.so
./libtest2.so: undefined symbol: GetName

选择答案后编辑 代码不起作用有两个主要问题。首先,加载失败是因为动态加载器没有找到依赖的共享对象,即加载libtest2.so时找不到libtest1.so,加载libtest3.so时找不到libtest2.solibtest1.so。这可以通过在链接期间添加路径来解决。其次,访问像类这样的非extern "C" 对象需要一个必须在共享对象中创建的对象。因此lib2.h/cpplib2.h/cppDllTest.cpp必须重构。

为方便起见,这里是完整的编译序列以及答案中的更正

g++ -c -fvisibility=hidden -fPIC -o lib1.o  lib1.cpp
g++ -shared -o libtest1.so lib1.o
  
g++ -c -fvisibility=hidden -fPIC -o lib2.o  lib2.cpp
g++ -shared -o libtest2.so -Wl,-rpath,$PWD -L.lib2.o -ltest1 

g++ -c -fvisibility=hidden -fPIC -o lib3.o  lib3.cpp
g++ -shared -o libtest3.so -Wl,-rpath,$PWD -L. lib3.o -ltest1 -ltest2 

g++ -o DllTest DllTest.cpp -ldl

这样,代码允许使用const char* GetName() 函数,无论加载三个共享对象中的哪个。

【问题讨论】:

libtest2.so 中没有GetName 符号,所以...?也没有printBase 符号,也没有char* (*pfnChar)(void); 类型,它是Base::printBase() 函数,正如您所指出的。 ` 是否可以做我想做的事以及如何做?` 是的 what is my error? 您正在从这些库中查询错误类型的错误符号。 &lt;&lt; pFunction() 如果你想调用 Base::printBase() 你必须先定义一个类 Base 的实例来应用这个函数。 感谢您的提示。所以你是说没有在libtest2.so 中添加一个create() 函数来创建一个对象,我不会从dlsym() 获得指针?关于指针和查询,我仍然有点困惑。我尝试查询dlsym(pHandle,"Base:print"Base::print(),但似乎没有什么不同。同样不清楚为什么函数指针会有所不同,因为 Base::print 也是const char* &lt;function&gt; (void) 类型。也许可以告诉我dlsym 行在该示例中的外观。 dlsym(pHandle,"Base:print" 研究名称修改。您正在使用 nm -D - 删除 -Dan object I will not get a pointer from dlsym()? 一般来说,是的。但是这些类可能只是命名不同,你可以声明它们.. 我之前一直在尝试“Base::printBase”,但显然没有创建对象。我看到我需要用类检查 dlsym 的答案。 'nm' 我没有选择使用。你能确认上面的代码至少应该可以从libtest2.so调用GetName吗? 这能回答你的问题吗? Why does the order in which libraries are linked sometimes cause errors in GCC? 【参考方案1】:

首先,更改链接命令中参数的顺序

g++ -g -shared -o libtest2.so -L. lib2.o -ltest1
g++ -g -shared -o libtest3.so -L. lib3.o -ltest1 -ltest2

注意:每个链接器都不需要,但较新的gold 链接器只解析“从左到右”。还有一个@987654325 @ 选项,对于捕获这些错误非常有用。

然后将rpath 添加到这些命令中,以便共享对象在运行时找到它们的依赖项:

g++ -g -shared -o libtest2.so "-Wl,-rpath,$PWD" -L. lib2.o -ltest1
g++ -g -shared -o libtest3.so "-Wl,-rpath,$PWD" -L. lib3.o -ltest1 -ltest2

链接后,readelf -d 应该像这样显示 RPATH:

$ readelf -d libtest3.so |head -n10

Dynamic section at offset 0xdd8 contains 28 entries:
 Tag        Type                 Name/Value
 0x0000000000000001 (NEEDED)     Shared library: [libtest1.so]
 0x0000000000000001 (NEEDED)     Shared library: [libtest2.so]
 0x0000000000000001 (NEEDED)     Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)     Shared library: [libc.so.6]
 0x000000000000001d (RUNPATH)    Library runpath: [/home/projects/proba/CatMan]

(还有libtool处理共享库。)

请注意,由于name-mangling,应该执行以下更改(尽管它是特定于编译器的)

- OpenSymbol(pHandle, "printBase");
+ OpenSymbol(pHandle, "_ZN4Base9printBaseEv");

【讨论】:

您能否详细说明为什么lib2.o 的位置很重要?我可以看到正在运行 ldd libtest2.soreadelf -d libtest2.so,即使使用您的线路,它也找不到所需的库 libtest1.so。我原以为readelf 会将当前的 PWD 列为运行路径。它根本不显示运行路径。我仍然缺少一些东西...... 感谢您指出 libtool。有趣的阅​​读(和很多)。但是我真的很想了解我对这个特定操作系统/编译器的错误 添加了更多细节。 感谢您添加readeff。这正是我对您的更改所期望的输出,但我没有得到它。目前正试图找出原因....(可能是非常愚蠢的事情) 我发现了我的问题。我使用错误的括号输入了您的命令:$(PWD) 而不是 $PWD$PWD。现在它可以正常工作了。而且我还从 libtest2.so 获取 GetName() 的输出。

以上是关于C++ 加载一个具有依赖关系的共享对象并访问它们的功能的主要内容,如果未能解决你的问题,请参考以下文章

使用 ctypes/cffi 解决循环共享对象依赖关系

加载具有相同符号的两个共享库时是不是存在符号冲突

如何“构建”具有依赖关系的 python 脚本

di.xml

从具有依赖关系的另一个文件夹加载另一个应用程序域中的程序集

Web扩展的依赖关系管理