C++ cpp: usr/bin/ld: 找不到 -l<nameOfTheLibrary>

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30600978/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 13:47:58  来源:igfitidea点击:

cpp: usr/bin/ld: cannot find -l<nameOfTheLibrary>

c++

提问by Yves

I created a cpp project, which used a lib file named: libblpapi3_64.soThis file comes from a library which I download it from Internet.

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

My project runs without any error. So I update it to bitbucket. Then my colleague downloads it and runs it at his own computer. But he gets an error:

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

usr/bin/ld: cannot find -lblpapi3_64.

usr/bin/ld: cannot find -lblpapi3_64.

In fact, I have copied it into my project repository. I mean I created a file named lib under my project and all lib files that I used are in it.

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

There are also other lib files such as liblog4cpp.a, but they are all good. Only the libblpapi3_64.sogets the error.

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

Is it because it's a .so file not .afile? Or there is other reason?
Btw, the file name of libblpapi3_64.sois greenand others files(.a) is white. I think it's not a link file, it's the original file.

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

回答by Victor Polevoy

Briefly:

简要地:

lddoes not know about where your project libs are located. You have to place it into ld's known directories or specify the full path of your library by -Lparameter to the linker.

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

To be able to build your program you need to have your library in /bin/ldsearch paths and your colleague too. Why? See detailed answer.

为了能够构建您的程序,您需要将您的库/bin/ld和您的同事一起放在搜索路径中。为什么?看详细回答。

Detailed:

详细的:

At first, we should understand what tools do what:

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

  1. The compiler produces simple object fileswith unresolved symbols (it does not care about symbols so much at it's running time).
  2. The linker combines a number of objectand archive files, relocates their data and ties up symbol references into a single file: an executable or a library.
  1. 编译器生成简单object files的未解析符号(它在运行时不太关心符号)。
  2. 链接器结合了许多objectarchive files,重新定位它们的数据并将符号引用绑定到一个文件中:一个可执行文件或一个库。

Let's start with some example. For example, you have a project which consists of 3 files: main.c, func.hand func.c.

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

main.c

主文件

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

func.h

函数文件

void func();

func.c

功能文件

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

So, when you compile your source code (main.c) into an object file (main.o) it can't be run yet because it has unresolved symbols. Let's start from the beginning of producing an executableworkflow (without details):

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

The preprocessor after its job produces the following main.c.preprocessed:

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

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

and the following func.c.preprocessed:

以及以下内容func.c.preprocessed

void func();
void func() { }

As you may see in main.c.preprocessed, there are no connections to your func.cfile and to the void func()'s implementation, the compiler simply does not know about it, it compiles all the source files separately. So, to be able to compile this project you have to compile both source files by using something like cc -c main.c -o main.oand cc -c func.c -o func.o, this will produce 2 object files, main.oand func.o. func.ohas all it's symbols resolved because it has only one function which body is written right inside the func.cbut main.odoes not have funcsymbol resolved yet because it does not know where it is implemented.

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

Let's look what is inside func.o:

让我们看看里面是什么func.o

$ nm func.o
0000000000000000 T func

Simply, it contains a symbol which is in text code section so this is our funcfunction.

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

And let's look inside main.o:

让我们看看里面main.o

$ nm main.o
                 U func
0000000000000000 T main

Our main.ohas an implemented and resolved static function mainand we are able to see it in the object file. But we also see funcsymbol which marked as unresolved U, and thus we are unable to see its address offset.

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

For fixing that problem, we have to use the linker. It will take all the object files and resolve all these symbols (void func();in our example). If the linker somehow is unable to do that it throws a error like unresolved external symbol: void func(). This may happen if you don't give the func.oobject file to the linker. So, let's give all the object files we have to the linker:

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

ld main.o func.o -o test

The linker will go through main.o, then through func.o, try to resolve symbols and if it goes okay - put it's output to the testfile. If we look at the produced output we will see all symbols are resolved:

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

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

Here our job is done. Let's look the situation with dynamic(shared) libraries. Let's make a shared library from our func.csource file:

到这里我们的工作就完成了。让我们看看动态(共享)库的情况。让我们从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

Voila, we have it. Now, let's put it into known dynamic linker library path, /usr/lib/:

瞧,我们有。现在,让我们将其放入已知的动态链接器库路径中/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

And let's make our project depend on that shared library by leaving func()symbol unresolved after compilation and static linkage process, creating an executable and linking it (dynamically) to our shared library (libfunc):

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

cc main.c -lfunc

Now if we look for the symbol in its symbols table we still have our symbol unresolved:

现在,如果我们在符号表中查找符号,我们仍然无法解析符号:

$ nm a.out | grep fun
             U func

But this is not a problem anymore because funcsymbol will be resolved by dynamic loader before each program start. Okay, now let's back to the theory.

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

Libraries, in fact, are just the object files which are placed into a single archive by using artool with a single symbols table which is created by ranlibtool.

实际上,库只是通过使用ar带有ranlib工具创建的单个符号表的工具放置到单个存档中的目标文件。

Compiler, when compiling object files, does not resolve symbols. These symbols will be replaced to addresses by a linker. So resolving symbols can be done by two things: the linkerand dynamic loader:

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

  1. The linker: ld, does 2 jobs:

    a) For static libs or simple object files, this linker changes external symbols in the object files to the addresses of the real entities. For example, if we use C++ name mangling linker will change _ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_to 0x07f4123f0.

    b) For dynamic libs it only checksif the symbols can be resolved(you try to link with correct library) at all but does not replace the symbols by address. If symbols can't be resolved (for example they are not implemented in the shared library you are linking to) - it throws undefined reference toerror and breaks up the building process because you try to use these symbols but linker can't find such symbol in it's object files which it is processing at this time. Otherwise, this linker adds some information to the ELFexecutable which is:

    i. .interpsection - request for an interpreter- dynamic loader to be called before executing, so this section just contains a path to the dynamic loader. If you look at your executable which depends on shared library (libfunc) for example you will see the interp section $ readelf -l a.out:

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

    ii. .dynamicsection - a list of shared libraries which interpreterwill be looking for before executing. You may see them by lddor 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]
    

    Note that lddalso finds all the libraries in your filesystem while readelf only shows what libraries does your program need. So, all of these libraries will be searched by dynamic loader (next paragraph). The linker works at build time.

  2. Dynamic loader: ld.soor ld-linux. It finds and loads all the shared libraries needed by a program (if they were not loaded before), resolves the symbols by replacing them to real addresses right before the start of the program, prepares the program to run, and then runs it. It works after the build and before running the program. Less speaking, dynamic linking means resolving symbols in your executable before each program start.

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

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

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

    一世。.interpsection - 请求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]
    

    ii. .dynamicsection -interpreter执行前要查找的共享库列表。您可以通过ldd或来查看它们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]
    

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

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

Actually, when you run an ELFexecutable with .interpsection (it needs to load some shared libraries) the OS (Linux) runs an interpreter at first but not your program. Otherwise you have an undefined behavior - you have symbols in your program but they are not defined by addresses which usually means that the program will be unable to work properly.

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

You may also run dynamic loader by yourself but it is unnecessary (binary is /lib/ld-linux.so.2for 32-bit architecture elf and /lib64/ld-linux-x86-64.so.2for 64-bit architecture elf).

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

Why does the linker claim that /usr/bin/ld: cannot find -lblpapi3_64in your case? Because it tries to find all the libraries in it's known paths. Why does it search the library if it will be loaded during runtime? Because it needs to check if all the needed symbols can be resolved by this library and to put it's name into the .dynamicsection for dynamic loader. Actually, the .interpsection exists in almost every c/c++ elf because the libcand libstdc++libraries are both shared, and compiler by default links any project dynamically to them. You may link them statically as well but this will enlarge the total executable size. So, if the shared library can't be found your symbols will remain unresolved and you will be UNABLEto run your application, thus it can't produce an executable. You may get the list of directories where libraries are usually searched by:

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

  1. Passing a command to the linker in compiler arguments.
  2. By parsing ld --verbose's output.
  3. By parsing ldconfig's output.
  1. 在编译器参数中将命令传递给链接器。
  2. 通过解析ld --verbose的输出。
  3. 通过解析ldconfig的输出。

Some of these methods are explained here.

此处解释其中一些方法。

Dynamic loadertries to find all the libraries by using:

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

  1. DT_RPATHdynamic section of an ELF file.
  2. DT_RUNPATHsection of the executable.
  3. LD_LIBRARY_PATHenvironment variable.
  4. /etc/ld.so.cache- own cache file which contains a compiled list of candidate libraries previously found in the augmented library path.
  5. Default paths: In the default path /lib, and then /usr/lib. If the binary was linked with -z nodefliblinker option, this step is skipped.
  1. DT_RPATHELF 文件的动态部分。
  2. DT_RUNPATH可执行文件的部分。
  3. LD_LIBRARY_PATH环境变量。
  4. /etc/ld.so.cache- 自己的缓存文件,其中包含先前在扩充库路径中找到的候选库的编译列表。
  5. 默认路径:在默认路径 /lib 中,然后是 /usr/lib。如果二进制文件与-z nodeflib链接器选项链接,则跳过此步骤。

ld-linux search algorithm

ld-linux 搜索算法

Also, note please, that if we are talking about shared libraries, they are not named .sobut in .so.versionformat instead. When you build your application the linker will look for .sofile (which is usually a symlink to .so.version) but when you run your application the dynamic loader looks for .so.versionfile instead. For example, let's say we have a library testwhich version is 1.1.1according to semver. In the filesystem it will look like:

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

/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

So, to be able to compile you must have all of versioned files (libtest.so.1, libtest.so.1.1and libtest.so.1.1.1) and a libtest.sofile but for running your app you must have only 3 versioned library files listed first. This also explains why do Debian or rpm packages have devel-packages separately: normal one (which consists only of the files needed by already compiled applications for running them) which has 3 versioned library files and a devel package which has only symlink file for making it possible to compile the project.

因此,为了能够进行编译,您必须拥有所有版本化文件(libtest.so.1,libtest.so.1.1libtest.so.1.1.1)和一个libtest.so文件,但是为了运行您的应用程序,您必须首先只列出 3 个版本化库文件。这也解释了为什么 Debian 或 rpm 包devel单独有-packages:普通包(仅包含已编译的应用程序运行它们所需的文件),它有 3 个版本库文件和一个 devel 包,它只有用于制作它的符号链接文件可以编译项目。

Resume

恢复

After all of that:

毕竟:

  1. You, your colleague and EACHuser of your application code must have all the libraries in their system linker paths to be able to compile (build your application). Otherwise, they have to change Makefile (or compile command) to add the shared library location directory by adding -L<somePathToTheSharedLibrary>as argument.
  2. After successful build you also need your library again to be able to run the program. Your library will be searched by dynamic loader (ld-linux) so it needs to be in it's paths(see above) or in system linker paths. In most of linux program distributions, for example, games from steam, there is a shell-script which sets the LD_LIBRARY_PATHvariable which points to all shared libraries needed by the game.
  1. 您、您的同事和应用程序代码的每个用户都必须在其系统链接器路径中拥有所有库才能编译(构建您的应用程序)。否则,他们必须更改 Makefile(或编译命令)以通过添加-L<somePathToTheSharedLibrary>为参数来添加共享库位置目录。
  2. 成功构建后,您还需要再次使用您的库才能运行该程序。您的库将被动态加载器 ( ld-linux)搜索,因此它需要位于它的路径(见上文)或系统链接器路径中。在大多数 linux 程序发行版中,例如来自 Steam 的游戏,都有一个 shell 脚本来设置LD_LIBRARY_PATH变量,该变量指向游戏所需的所有共享库。

回答by Dirk Eddelbuettel

You could look at our Rblapipackage which uses this very library too.

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

Your basic question of "how do I make a library visible" really has two answers:

您关于“如何使库可见”的基本问题实际上有两个答案:

  1. Use ld.so. The easiest way is to copy blpapi3_64.soto /usr/local/lib. If you then call ldconfigto update the cache you should be all set. You can test this via ldconfig -p | grep blpapiwhich should show it.

  2. Use an rpathinstruction when building your application; this basically encodes the path and makes you independent of ld.so.

  1. 使用ld.so. 最简单的方法是复制blpapi3_64.so/usr/local/lib. 如果您随后调用ldconfig更新缓存,您应该一切就绪。您可以通过ldconfig -p | grep blpapi它来测试它应该显示它。

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