[英]Why does g++ detect undefined reference when dynamically linking
我可能错误地认为动态链接是如何工作的,因为我无法弄清楚这一点。 据我所知,当动态链接库时,它的符号在运行时被解析。 从这个答案:
当您动态链接时,指向要链接的文件的指针(例如,文件的文件名)包含在可执行文件中,并且链接时不包括所述文件的内容。 只有当您稍后运行可执行文件时才会购买这些动态链接文件,并且它们只会被购买到可执行文件的内存中副本,而不是磁盘上的副本。
[...]
在动态情况下,主程序与C运行时导入库链接(某些东西声明了动态库中的内容但实际上没有定义它)。 即使实际代码丢失,这也允许链接器链接。
然后,在运行时,操作系统加载程序执行主程序与C运行时DLL(动态链接库或共享库或其他命名法)的后期链接。
我很困惑为什么g++
似乎期望共享对象在动态链接时存在。 当然,我希望库的名称是必要的,以便它可以在运行时加载,但为什么在这个阶段需要.so
? 此外, g++
在链接库时会抱怨未定义的引用。
我的问题是:
g++
似乎在动态链接时需要共享对象,如果只在运行时加载库? 我理解如何指定共享对象的名称以便可以在运行时加载-l
标志,但我认为必须在链接时( -L
)提供.so
的路径或.so
本身。 g++
在动态链接时会尝试解析符号? 没有什么能阻止我在链接时拥有一个完整的.so
,然后在运行时提供一个不同的(不完整的) .so
,这会导致程序在尝试使用未定义的符号时崩溃。 我做了一个可重复的例子:
.
├── main.cpp
└── test
├── usertest.cpp
└── usertest.h
#ifndef USERTEST_H_4AD3C656_8109_11E8_BED5_5BE6E678B346
#define USERTEST_H_4AD3C656_8109_11E8_BED5_5BE6E678B346
namespace usertest
{
void helloWorld();
// This method is not defined anywhere
void byeWorld();
};
#endif /* USERTEST_H_4AD3C656_8109_11E8_BED5_5BE6E678B346 */
#include "usertest.h"
#include <iostream>
void usertest::helloWorld()
{
std::cout << "Hello, world\n";
}
#include "test/usertest.h"
int main()
{
usertest::helloWorld();
usertest::byeWorld();
}
$ cd test
$ g++ -c -fPIC usertest.cpp
$ g++ usertest.o -shared -o libusertest.so
$ cd ..
$ g++ main.cpp -L test/ -lusertest
$ LD_LIBRARY_PATH="test" ./a.out
我试图在尝试启动a.out
时崩溃,因为它无法在libusertest.so
找到必要的符号。
a.out
的构建在链接时失败,因为它无法找到byeWorld()
:
/tmp/ccVNcRRY.o: In function `main':
main.cpp:(.text+0xa): undefined reference to `usertest::byeWorld()'
collect2: error: ld returned 1 exit status
对于ELF格式,确实不必知道哪些符号属于哪个库,因为在执行程序时会发生实际的符号解析。 按照惯例,虽然ld
仍然会在生成二进制文件时解析符号。 这是为了您的方便,以便您在缺少符号时立即获得反馈,因为在这种情况下,您的程序无法正常工作。
使用--warn-unresolved-symbols
标志,您可以在这种情况下将ld
行为从错误更改为警告:
$ g++ -Wl,--warn-unresolved-symbols main.cpp -lusertest
应该发出警告但仍然创建可执行文件。 请注意,您仍然需要提供库名称,否则ld
将不知道在哪里查找所需的符号。
在Windows上,链接器需要确切地知道哪个符号属于哪个库,以便生成必要的导入表。 因此,无法使用未解析的符号构建PE二进制文件。
可执行文件的代码段始终是只读的安全措施,因此您不能拥有在运行时修改自己的代码的程序。 正如其他人所提到的,链接器正在做的是生成每个库提供的符号列表。
您建议将此过程延迟到运行时,但这意味着如果您在链接时提供的库列表不完整,则每次启动它时二进制文件都可能崩溃。 当你可以在链接时简单地检查一下,为什么你会冒风险? 将符号解析延迟到运行时意味着每次运行程序时,它将对所有未解析的符号执行相同的搜索。 此外,如果您不必在链接时提供库列表,则意味着它必须在运行时尝试所有可能的库。 您如何解析由多个库定义的符号?
据我所知(以非常简单的方式),动态链接器在运行时所做的是保留一个哈希表,在将其映射到程序的地址空间后,将这些符号转换为动态链接库中的地址(函数指针)。 在您的可执行文件中,链接器需要知道哪个库提供了每个符号(函数,变量等)来执行此解析。
所以,在这个非常简化的解释中 ,你调用usertest::helloWorld();
被翻译成类似dynamic_resolve("usertest::helloWorld", "libusertest.so")();
with dynamic_resolve
接收符号名称和库名称,并返回一个函数指针。 在内部, dynamic_resolve
(虚构名称)正在做的是加载库“libusertest.so”,检索库中函数的地址,在哈希表中缓存它,然后返回函数指针。 它可能正在使用这些系统调用。 在第一次调用之后,由于结果缓存在哈希表中并且库已经加载,所以后续调用都要便宜得多。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.