【问题标题】:cpp: usr/bin/ld: cannot find -l<nameOfTheLibrary>cpp: usr/bin/ld: 找不到 -l<nameOfTheLibrary>
【发布时间】:2015-08-16 12:49:56
【问题描述】:

我创建了一个 cpp 项目,它使用了一个名为:libblpapi3_64.so 的 lib 文件 此文件来自我从 Internet 下载的库。

我的项目运行没有任何错误。所以我将它更新为bitbucket。 然后我的同事下载它并在他自己的计算机上运行它。但他得到一个错误:

usr/bin/ld: cannot find -lblpapi3_64.

事实上,我已经将它复制到我的项目存储库中。我的意思是我在我的项目下创建了一个名为 lib 的文件,我使用的所有 lib 文件都在其中。

还有liblog4cpp.a等其他lib文件,不过都不错。只有libblpapi3_64.so 得到错误。

是不是因为它是 .so 文件而不是 .a 文件?还是有其他原因?
顺便说一句,libblpapi3_64.so 的文件名是green,其他文件(.a)是white。我觉得不是链接文件,是原文件。

【问题讨论】:

    标签: c++


    【解决方案1】:

    简述:

    ld 不知道您的项目库的位置。您必须将其放入 ld 的已知目录或通过链接器的-L 参数指定库的完整路径。

    为了能够构建您的程序,您需要将您的库放在/bin/ld 搜索路径中,并且还需要您的同事。为什么?查看详细答案。

    详细说明:

    首先,我们应该了解什么工具做什么:

    1. 编译器生成简单的object files 带有未解析的符号(它在运行时不太关心符号)。
    2. 链接器将多个objectarchive files 组合在一起,重新定位它们的数据并将符号引用绑定到一个文件中:可执行文件或库。

    让我们从一些例子开始。例如,您有一个包含 3 个文件的项目:main.cfunc.hfunc.c

    ma​​in.c

    #include "func.h"
    int main() {
        func();
        return 0;
    }
    

    func.h

    void func();
    

    func.c

    #include "func.h"
    void func() { }
    

    因此,当您将源代码 (main.c) 编译成目标文件 (main.o) 时,它还不能运行,因为它有未解析的符号。让我们从producing an executable工作流的开头开始(不详):

    预处理器在其工作后产生以下main.c.preprocessed

    void func();
    int main() {
        func();
        return 0;
    }
    

    还有以下func.c.preprocessed

    void func();
    void func() { }
    

    正如您在main.c.preprocessed 中看到的,与您的func.c 文件和void func() 的实现没有任何联系,编译器根本不知道它,它单独编译所有源文件。所以,为了能够编译这个项目,你必须使用cc -c main.c -o main.occ -c func.c -o func.o之类的东西来编译这两个源文件,这将产生两个目标文件,main.ofunc.ofunc.o 已解析所有符号,因为它只有一个函数,其主体写在 func.c 内,但 main.o 尚未解析 func 符号,因为它不知道它在哪里实现。

    让我们看看func.o里面有什么:

    $ nm func.o
    0000000000000000 T func
    

    简单地说,它包含一个在文本代码部分中的符号,所以这是我们的func 函数。

    让我们看看main.o

    $ nm main.o
                     U func
    0000000000000000 T main
    

    我们的main.o 有一个实现和解析的静态函数main,我们可以在目标文件中看到它。但是我们也看到func符号标记为未解析U,因此我们无法看到它的地址偏移量。

    为了解决这个问题,我们必须使用链接器。它将获取所有目标文件并解析所有这些符号(在我们的示例中为void func();)。如果链接器无法做到这一点,它会抛出类似unresolved external symbol:void func() 的错误。如果您不将func.o 目标文件提供给链接器,则可能会发生这种情况。所以,让我们将我们拥有的所有目标文件提供给链接器:

    ld main.o func.o -o test
    

    链接器将通过main.o,然后通过func.o,尝试解析符号,如果没问题 - 将其输出到test 文件。如果我们查看生成的输出,我们将看到所有符号都已解析:

    $ nm test 
    0000000000601000 R __bss_start
    0000000000601000 R _edata
    0000000000601000 R _end
    00000000004000b0 T func
    00000000004000b7 T main
    

    到这里我们的工作就完成了。让我们看看动态(共享)库的情况。让我们从 func.c 源文件创建一个共享库:

    gcc -c func.c -o func.o
    gcc -shared -fPIC -Wl,-soname,libfunc.so.1 -o libfunc.so.1.5.0 func.o
    

    瞧,我们有它。现在,让我们把它放到已知的动态链接库路径/usr/lib/

    sudo mv libfunc.so.1.5.0 /usr/lib/ # to make program be able to run
    sudo ln -s libfunc.so.1.5.0 /usr/lib/libfunc.so.1  #creating symlink for the program to run
    sudo ln -s libfunc.so.1 /usr/lib/libfunc.so # to make compilation possible
    

    让我们的项目依赖于该共享库,方法是在编译和静态链接过程之后留下未解析的 func() 符号,创建可执行文件并将其(动态)链接到我们的共享库 (libfunc):

    cc main.c -lfunc
    

    现在,如果我们在其符号表中查找该符号,我们的符号仍然未解析:

    $ nm a.out | grep fun
                 U func
    

    但这不再是问题了,因为func 符号将在每个程序启动之前由动态加载器解析。好的,现在让我们回到理论。

    实际上,库只是使用ar 工具和ranlib 工具创建的单个符号表放入单个存档中的目标文件。

    编译器在编译目标文件时无法解析symbols。这些符号将被链接器替换为地址。所以解析符号可以通过两件事来完成:the linkerdynamic loader

    1. 链接器:ld,做 2 个工作:

      a) 对于静态库或简单目标文件,此链接器将目标文件中的外部符号更改为真实实体的地址。例如,如果我们使用 C++ 名称修饰,链接器会将 _ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_ 更改为 0x07f4123f0

      b) 对于动态库,它仅检查符号是否可以解析(您尝试链接到正确的库),但不会用地址替换符号.如果符号无法解析(例如它们未在您链接到的共享库中实现) - 它会抛出 undefined reference to 错误并中断构建过程,因为您尝试使用这些符号但链接器找不到这样此时它正在处理的目标文件中的符号。否则,此链接器会向 ELF 可执行文件添加一些信息,即:

      我。 .interp 部分 - 请求 interpreter - 在执行之前调用动态加载器,因此该部分仅包含动态加载器的路径。例如,如果您查看依赖于共享库 (libfunc) 的可执行文件,您将看到 interp 部分 $ readelf -l a.out

      INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                     0x000000000000001c 0x000000000000001c  R      1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
      

      二。 .dynamic 部分 - interpreter 将在执行之前查找的共享库列表。您可以通过lddreadelf 看到它们:

      $ ldd a.out
           linux-vdso.so.1 =>  (0x00007ffd577dc000)
           libfunc.so.1 => /usr/lib/libfunc.so.1 (0x00007fc629eca000)
           libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fefe148a000)
           /lib64/ld-linux-x86-64.so.2 (0x000055747925e000)
      
      $ readelf -d a.out
      
        Dynamic section at offset 0xe18 contains 25 entries:
        Tag        Type                         Name/Value
        0x0000000000000001 (NEEDED)             Shared library: [libfunc.so.1]
        0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
      

      请注意,ldd 还可以找到您文件系统中的所有库,而 readelf 仅显示您的程序需要哪些库。因此,所有这些库都将被动态加载器搜索(下一段)。 链接器在构建时工作。

    2. 动态加载程序:ld.sold-linux。它查找并加载程序所需的所有共享库(如果之前未加载),通过在程序启动之前将符号替换为真实地址来解析符号,准备程序运行,然后运行它。它在构建之后和运行程序之前工作。简单地说,动态链接意味着在每个程序启动之前解析可执行文件中的符号。

    实际上,当您运行带有.interp 部分的ELF 可执行文件(它需要加载一些共享库)时,操作系统(Linux)首先运行的是解释器,而不是您的程序。否则你有一个未定义的行为 - 你的程序中有符号但它们不是由地址定义的,这通常意味着程序将无法正常工作。

    您也可以自己运行动态加载程序,但这不是必需的(二进制是 /lib/ld-linux.so.2 用于 32 位架构精灵,/lib64/ld-linux-x86-64.so.2 用于 64 位架构精灵)。

    为什么链接器在您的情况下声称/usr/bin/ld: cannot find -lblpapi3_64?因为它试图找到它已知路径中的所有库。如果它将在运行时加载,为什么它会搜索库?因为它需要检查该库是否可以解析所有需要的符号,并将其名称放入动态加载器的.dynamic 部分。实际上,.interp 部分几乎存在于每个 c/c++ 精灵中,因为 libclibstdc++ 库都是共享的,编译器默认将任何项目动态链接到它们。您也可以静态链接它们,但这会扩大可执行文件的总大小。因此,如果找不到共享库,您的符号将保持未解析,并且您将 UNABLE 运行您的应用程序,因此它无法生成可执行文件。您可能会获得通常通过以下方式搜索库的目录列表:

    1. 在编译器参数中将命令传递给链接器。
    2. 通过解析ld --verbose的输出。
    3. 通过解析ldconfig的输出。

    其中一些方法在here进行了解释。

    动态加载器尝试使用以下方法查找所有库:

    1. DT_RPATH ELF 文件的动态部分。
    2. 可执行文件的DT_RUNPATH 部分。
    3. LD_LIBRARY_PATH 环境变量。
    4. /etc/ld.so.cache - 自己的缓存文件,其中包含先前在扩充库路径中找到的候选库的编译列表。
    5. 默认路径:在默认路径 /lib 中,然后是 /usr/lib。如果二进制文件使用-z nodeflib 链接器选项链接,则跳过此步骤。

    ld-linux search algorithm

    另外,请注意,如果我们谈论共享库,它们的名称不是.so,而是.so.version 格式。当您构建您的应用程序时,链接器将查找.so 文件(通常是指向.so.version 的符号链接),但是当您运行您的应用程序时,动态加载程序会查找.so.version 文件。例如,假设我们有一个库test,根据semver,它的版本是1.1.1。在文件系统中它看起来像:

    /usr/lib/libtest.so -> /usr/lib/libtest.so.1.1.1
    /usr/lib/libtest.so.1 -> /usr/lib/libtest.so.1.1.1
    /usr/lib/libtest.so.1.1 -> /usr/lib/libtest.so.1.1.1
    /usr/lib/libtest.so.1.1.1
    

    因此,为了能够编译,您必须拥有所有版本化文件(libtest.so.1libtest.so.1.1libtest.so.1.1.1)和一个 libtest.so 文件,但要运行您的应用程序,您必须只列出 3 个版本化库文件第一的。这也解释了为什么 Debian 或 rpm 软件包分别具有 devel-packages:普通的一个(仅包含已编译的应用程序运行它们所需的文件),它有 3 个版本库文件和一个只有符号链接文件的开发包使编译项目成为可能。

    简历

    毕竟:

    1. 您、您的同事和您的应用程序代码的 EACH 用户必须在其系统链接器路径中拥有所有库才能编译(构建您的应用程序)。否则,他们必须更改 Makefile(或编译命令)以通过添加 -L&lt;somePathToTheSharedLibrary&gt; 作为参数来添加共享库位置目录。
    2. 成功构建后,您还需要再次使用库才能运行程序。您的库将被动态加载器 (ld-linux) 搜索,因此它需要位于 它的路径(见上文)或系统链接器路径中。在大多数 Linux 程序发行版中,例如来自 steam 的游戏,都有一个 shell 脚本设置 LD_LIBRARY_PATH 变量,该变量指向游戏所需的所有共享库。

    【讨论】:

    • 你的意思是 -L(-I 用于包含路径)
    • 非常感谢。事实上,这是我第一次在 linux 下工作。我在想的是我的同事从 bitbucket 下载了我的项目,然后他可以立即使用它。因此,当我编写代码时,我会创建一个文件并将所有 lib 文件放入其中。我的 IDE 是 Netbeans,我在我的项目上单击右键,我可以在该文件中添加 lib 文件。之后,我将整个项目更新为 bitbucket。当我的同事下载它并将其导入他的 Netbeans 时,我可以看到所有 lib 文件都已配置——就像我之前所做的一样。所以你是在告诉我我所做的没有用?
    • 也就是说,有没有可能我的同事不用任何配置就可以立即编译?
    • 这取决于你的同事在他的库路径中是否有这个库。我会尽快修改答案
    【解决方案2】:

    您可以查看我们的 Rblapi 包,它也使用了这个库。

    “我如何让图书馆可见”这个基本问题确实有两个答案:

    1. 使用ld.so。最简单的方法是将blpapi3_64.so 复制到/usr/local/lib。如果你随后调用ldconfig 来更新缓存,你应该已经准备好了。您可以通过 ldconfig -p | grep blpapi 进行测试,它应该会显示出来。

    2. 在构建应用程序时使用rpath 指令;这基本上对路径进行了编码,并使您独立于ld.so

    【讨论】:

    • 非常感谢。事实上,这是我第一次在 linux 下工作。我在想的是我的同事从 bitbucket 下载了我的项目,然后他可以立即使用它。因此,当我编写代码时,我会创建一个文件并将所有 lib 文件放入其中。我的 IDE 是 Netbeans,我在我的项目上单击右键,我可以在该文件中添加 lib 文件。之后,我将整个项目更新为 bitbucket。当我的同事下载它并将其导入他的 Netbeans 时,我可以看到所有的 lib 文件都已配置——就像我之前所做的一样。所以你是在告诉我我所做的没有用?
    • 也就是说,有没有可能我的同事不用任何配置就可以立即编译?
    • 是的,如果您知道自己在做什么,您就可以做到这一点。例如,我们的 Rblpapi 包完全就是这样工作的。
    猜你喜欢
    • 2013-05-18
    • 1970-01-01
    • 1970-01-01
    • 2021-05-07
    • 2012-12-13
    • 2011-07-16
    • 2014-10-19
    • 2012-05-17
    • 2020-04-14
    相关资源
    最近更新 更多