cpp:usr / bin / ld:找不到-l <nameofthelibrary>

时间:2015-06-02 15:59:00

标签: c++

我创建了一个cpp项目,该项目使用名为libblpapi3_64.so的lib文件 这个文件来自我从互联网上下载的图书馆。

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

usr/bin/ld: cannot find -lblpapi3_64

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

还有其他lib文件,例如liblog4cpp.a,但它们都很好。只有libblpapi3_64.so才会收到错误。

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

2 个答案:

答案 0 :(得分:43)

<强>简言之:

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

为了能够构建您的程序,您需要在/bin/ld搜索路径和您的同事中拥有您的库。为什么?详见答案。

<强>详细说明:

首先,我们应该了解哪些工具可以做什么:

  1. 编译器使用未解析的符号生成简单的object files(它不关心符号及其运行时间)。
  2. 链接器组合了许多objectarchive files,重新定位其数据并将符号引用绑定到单个文件中:可执行文件或库。
  3. 让我们从一些例子开始吧。例如,您有一个由3个文件组成的项目:main.cfunc.hfunc.c

    <强>的main.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之类的内容编译两个源文件,这将生成2个目标文件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 symbolvoid 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()符号未解析,创建可执行文件并将其(动态)链接到我们的共享库({{1 }}):

    libfunc

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

    cc main.c -lfunc
    

    但这不再是问题,因为$ nm a.out | grep fun U func 符号将在每个程序启动之前由动态加载程序解析。好的,现在让我们回到理论上。

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

    编译器在编译目标文件时无法解析ranlib。这些符号将由链接器替换为地址。因此,解析符号可以通过两件事来完成:symbolsthe linker

    1. 链接器:dynamic loader,完成2个作业:

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

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

      我。 ELF部分 - 在执行之前调用.interp - 动态加载程序的请求,因此本节仅包含动态加载程序的路径。如果您查看依赖于共享库(interpreter)的可执行文件,您将看到interp部分libfunc

      $ readelf -l a.out

      II。 INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238 0x000000000000001c 0x000000000000001c R 1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] 部分 - .dynamic在执行之前将要查找的共享库列表。您可以interpreterldd

      看到它们
      readelf

      请注意,$ 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] 还会查找文件系统中的所有库,而readelf仅显示程序所需的库。因此,所有这些库都将由动态加载器搜索(下一段)。 链接器的工作时间为构建时间

    2. 动态加载器:lddld.so。它找到并加载程序所需的所有共享库(如果它们之前未加载),通过在程序启动之前将它们替换为实际地址来解析符号,准备程序运行,然后运行它。它在构建之后和运行程序之前 。更少说来,动态链接意味着在每个程序开始之前解析可执行文件中的符号。

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

      您也可以自己运行动态加载器,但这是不必要的(对于32位架构精灵,二进制为.interp,对于64位架构精灵,为/lib/ld-linux.so.2

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

      1. 在编译器参数中将命令传递给链接器。
      2. 解析libstdc++的输出。
      3. 解析ld --verbose的输出。
      4. 其中一些方法已解释为here

        动态加载程序尝试使用以下命令查找所有库:

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

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

          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.1)以及libtest.so.1.1.1文件,但是为了运行您的应用,必须首先列出3个版本化的库文件。这也解释了为什么Debian或rpm包有libtest.so - 包分开:正常包(只包含已经编译的应用程序运行它们所需的文件),它有3个版本化库文件和一个只包含devel包符号链接文件,用于编译项目。

          <强>恢复

          毕竟:

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

答案 1 :(得分:0)

您可以查看我们使用此库的Rblapi包。

您的基本问题&#34;如何让图书馆可见?#34;真的有两个答案:

  1. 使用ld.so。最简单的方法是将blpapi3_64.so复制到/usr/local/lib。如果您再调用ldconfig来更新缓存,则应该全部设置。您可以通过显示它的ldconfig -p | grep blpapi进行测试。

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