代码之家  ›  专栏  ›  技术社区  ›  user2891462

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

  •  2
  • user2891462  · 技术社区  · 8 年前

    我可能误解了动态链接是如何工作的,因为我无法理解这一点。据我所知,当库被动态链接时,它的符号在运行时被解析。从 this answer:

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

    […]

    在动态情况下,主程序与C运行时链接 导入库(声明动态库中的内容 但实际上并没有定义它)。 这允许链接器甚至链接 尽管实际的代码丢失了。

    然后,在运行时,操作系统加载器对 带有c运行时dll的主程序(动态链接库或 共享库或其他术语)。

    我不明白为什么 g++ 当动态链接到共享对象时,似乎期望共享对象在那里。当然,我希望库的名称是必需的,这样它就可以在运行时加载,但是为什么 .so 在这个阶段有必要吗?此外, G+ 在链接到库时抱怨未定义的引用。

    我的问题是:

    1. 为什么 克++ 如果只在运行时加载库,则在动态链接时似乎需要共享对象?我明白 -l 可能需要使用标志来指定共享对象的名称,以便在运行时加载该对象,但我认为没有必要提供到 .所以 连接时( -L )或者 .所以 本身。
    2. 为什么 克++ 尝试在动态链接时解析符号?没有什么能阻止我 .所以 在链接时,但随后提供不同的(不完整的) .所以 在运行时,这会导致程序在尝试使用未定义的符号时崩溃。

    我举了一个可复制的例子:

    目录结构:

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

    文件内容:

    测试/用户测试.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";
    }
    

    主.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.退出 在链接时失败,因为它找不到 byeWorld() 以下内容:

    /tmp/ccVNcRRY.o: In function `main':
    main.cpp:(.text+0xa): undefined reference to `usertest::byeWorld()'
    collect2: error: ld returned 1 exit status
    
    2 回复  |  直到 8 年前
        1
  •  5
  •   rustyx    8 年前

    对于elf格式,确实不需要知道哪些符号属于哪个库,因为实际的符号解析是在程序执行时发生的。但按惯例 ld 仍将在生成二进制文件时解析符号。这是为了您的方便,所以当您丢失符号时,可以立即得到反馈,因为在这种情况下,您的程序将无法工作。

    使用 --warn-unresolved-symbols 可以更改的标志 LD 在这种情况下,从错误到警告的行为:

    $ g++ -Wl,--warn-unresolved-symbols main.cpp -lusertest
    

    应该发出警告,但仍然创建可执行文件。请注意,您仍然需要提供库名称,否则 LD 不知道在哪里可以找到需要的符号。

    在windows上,链接器需要确切地知道哪个符号属于哪个库,以便生成必要的导入表。因此,不可能用未解析的符号构建pe二进制文件。

        2
  •  3
  •   miravalls    8 年前

    作为安全措施,可执行文件的代码段始终是只读的,因此不能让程序在运行时修改自己的代码。正如其他人所提到的,链接器所做的是生成每个库提供的符号的列表。

    您建议将此进程推迟到运行时,但这意味着,如果在链接时提供的库列表不完整,则每次启动此进程时,二进制文件都可能崩溃。为什么你会冒这个险,当你可以简单地检查链接时? 将符号解析推迟到运行时意味着每次运行程序时,它将在所有依赖项中对所有未解析符号执行相同的搜索。 此外,如果在链接时不必给出库列表,则意味着它必须尝试 全部的 运行时可能的库。如何解析由多个库定义的符号?

    据我所知(以非常简单的方式),动态链接器在运行时所做的是保留一个散列表,将这些符号映射到程序的地址空间后,将它们转换为动态链接库中的地址(函数指针)。在可执行文件中,链接器需要知道哪个库提供每个符号(函数、变量等)来执行此解析。

    所以,在这个 非常简单的解释 ,你的电话 usertest::helloWorld(); 被翻译成 dynamic_resolve("usertest::helloWorld", "libusertest.so")(); 具有 dynamic_resolve 接收符号名和库名,并返回函数指针。在内部,什么 动态解算 (化名)正在加载库“libusertest.so”,检索库中函数的地址,将其缓存在哈希表中,然后返回函数指针。可能是在用 these 系统调用。在第一次调用之后,由于结果缓存在哈希表中,并且库已经加载,所以所有后续调用都要便宜得多。

    推荐文章