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
显示最后两个库中的一些符号未导出。
...
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.so
和libtest1.so
。这可以通过在链接期间添加路径来解决。其次,访问像类这样的非extern "C"
对象需要一个必须在共享对象中创建的对象。因此lib2.h/cpp
、lib2.h/cpp
和DllTest.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?
您正在从这些库中查询错误类型的错误符号。 << pFunction()
如果你想调用 Base::printBase()
你必须先定义一个类 Base
的实例来应用这个函数。
感谢您的提示。所以你是说没有在libtest2.so
中添加一个create()
函数来创建一个对象,我不会从dlsym() 获得指针?关于指针和查询,我仍然有点困惑。我尝试查询dlsym(pHandle,"Base:print"
或Base::print()
,但似乎没有什么不同。同样不清楚为什么函数指针会有所不同,因为 Base::print 也是const char* <function> (void)
类型。也许可以告诉我dlsym
行在该示例中的外观。
dlsym(pHandle,"Base:print"
研究名称修改。您正在使用 nm -D
- 删除 -D
。 an 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.so
和 readelf -d libtest2.so
,即使使用您的线路,它也找不到所需的库 libtest1.so
。我原以为readelf
会将当前的 PWD 列为运行路径。它根本不显示运行路径。我仍然缺少一些东西......
感谢您指出 libtool。有趣的阅读(和很多)。但是我真的很想了解我对这个特定操作系统/编译器的错误
添加了更多细节。
感谢您添加readeff
。这正是我对您的更改所期望的输出,但我没有得到它。目前正试图找出原因....(可能是非常愚蠢的事情)
我发现了我的问题。我使用错误的括号输入了您的命令:$(PWD)
而不是 $PWD
或 $PWD
。现在它可以正常工作了。而且我还从 libtest2.so 获取 GetName() 的输出。以上是关于C++ 加载一个具有依赖关系的共享对象并访问它们的功能的主要内容,如果未能解决你的问题,请参考以下文章