繁体   English   中英

为什么g ++在动态链接时检测未定义的引用

[英]Why does g++ detect undefined reference when dynamically linking

我可能错误地认为动态链接是如何工作的,因为我无法弄清楚这一点。 据我所知,当动态链接库时,它的符号在运行时被解析。 这个答案:

当您动态链接时,指向要链接的文件的指针(例如,文件的文件名)包含在可执行文件中,并且链接时不包括所述文件的内容。 只有当您稍后运行可执行文件时才会购买这些动态链接文件,并且它们只会被购买到可执行文件的内存中副本,而不是磁盘上的副本。

[...]

在动态情况下,主程序与C运行时导入库链接(某些东西声明了动态库中的内容但实际上没有定义它)。 即使实际代码丢失,这也允许链接器链接。

然后,在运行时,操作系统加载程序执行主程序与C运行时DLL(动态链接库或共享库或其他命名法)的后期链接。

我很困惑为什么g++似乎期望共享对象在动态链接时存在。 当然,我希望库的名称是必要的,以便它可以在运行时加载,但为什么在这个阶段需要.so 此外, g++在链接库时会抱怨未定义的引用。

我的问题是:

  1. 为什么g++似乎在动态链接时需要共享对象,如果只在运行时加载库? 我理解如何指定共享对象的名称以便可以在运行时加载-l标志,但我认为必须在链接时( -L )提供.so的路径或.so本身。
  2. 为什么g++在动态链接时会尝试解析符号? 没有什么能阻止我在链接时拥有一个完整的.so ,然后在运行时提供一个不同的(不完整的) .so ,这会导致程序在尝试使用未定义的符号时崩溃。

我做了一个可重复的例子:

目录结构:

.
├── main.cpp
└── test
    ├── usertest.cpp
    └── usertest.h

文件内容:

测试/ 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 */

测试/ usertest.cpp

#include "usertest.h"
#include <iostream>

void usertest::helloWorld()
{
    std::cout << "Hello, world\n";
}

main.cpp中

#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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM