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
cpp: usr/bin/ld: cannot find -l<nameOfTheLibrary>
提问by Yves
I created a cpp project, which used a lib file named: libblpapi3_64.so
This 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.so
gets the error.
还有其他 lib 文件,例如liblog4cpp.a
,但它们都很好。只有libblpapi3_64.so
得到错误。
Is it because it's a .so file not .a
file? Or there is other reason?
Btw, the file name of libblpapi3_64.so
is green
and others files(.a) is white
. I think it's not a link file, it's the original file.
是因为它是一个 .so 文件而不是.a
文件吗?还是有其他原因?
顺便说一句,libblpapi3_64.so
isgreen
和其他文件(.a)的文件名是white
. 我认为这不是链接文件,而是原始文件。
回答by Victor Polevoy
Briefly:
简要地:
ld
does 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 -L
parameter to the linker.
ld
不知道您的项目库位于何处。您必须将其放入 ld 的已知目录中,或者通过-L
链接器的参数指定库的完整路径。
To be able to build your program you need to have your library in /bin/ld
search paths and your colleague too. Why? See detailed answer.
为了能够构建您的程序,您需要将您的库/bin/ld
和您的同事一起放在搜索路径中。为什么?看详细回答。
Detailed:
详细的:
At first, we should understand what tools do what:
首先,我们应该了解什么工具做什么:
- The compiler produces simple
object files
with unresolved symbols (it does not care about symbols so much at it's running time). - The linker combines a number of
object
andarchive files
, relocates their data and ties up symbol references into a single file: an executable or a library.
- 编译器生成简单
object files
的未解析符号(它在运行时不太关心符号)。 - 链接器结合了许多
object
和archive files
,重新定位它们的数据并将符号引用绑定到一个文件中:一个可执行文件或一个库。
Let's start with some example. For example, you have a project which consists of 3 files: main.c
, func.h
and func.c
.
让我们从一些例子开始。例如,您有一个包含 3 个文件的项目:main.c
,func.h
和func.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 executable
workflow (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.c
file 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.o
and cc -c func.c -o func.o
, this will produce 2 object files, main.o
and func.o
. func.o
has all it's symbols resolved because it has only one function which body is written right inside the func.c
but main.o
does not have func
symbol 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.o
和func.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 func
function.
简单地说,它包含一个位于文本代码部分的符号,所以这是我们的func
函数。
And let's look inside main.o
:
让我们看看里面main.o
:
$ nm main.o
U func
0000000000000000 T main
Our main.o
has an implemented and resolved static function main
and we are able to see it in the object file. But we also see func
symbol 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.o
object 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 test
file. 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.c
source 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 func
symbol 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 ar
tool with a single symbols table which is created by ranlib
tool.
实际上,库只是通过使用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 linker
and dynamic loader
:
编译器在编译目标文件时不解析symbols
. 这些符号将被链接器替换为地址。所以解析符号可以通过两件事来完成:the linker
和dynamic loader
:
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_
to0x07f4123f0
.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 to
error 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 theELF
executable which is:i.
.interp
section - request for aninterpreter
- 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.
.dynamic
section - a list of shared libraries whichinterpreter
will be looking for before executing. You may see them byldd
orreadelf
:$ 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
ldd
also 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.Dynamic loader:
ld.so
orld-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.
链接器:
ld
,做 2 个工作:a) 对于静态库或简单目标文件,此链接器将目标文件中的外部符号更改为真实实体的地址。例如,如果我们使用 C++ name mangling 链接器将更
_ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_
改为0x07f4123f0
.b) 对于动态库,它只检查是否可以解析符号(您尝试与正确的库链接),但不会按地址替换符号。如果符号无法解析(例如它们未在您链接到的共享库中实现) - 它会引发
undefined reference to
错误并中断构建过程,因为您尝试使用这些符号但链接器在其中找不到这样的符号此时正在处理的目标文件。否则,此链接器会向ELF
可执行文件添加一些信息,即:一世。
.interp
section - 请求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.
.dynamic
section -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 仅显示您的程序需要哪些库。因此,所有这些库都将被动态加载器搜索(下一段)。链接器在构建时工作。动态加载器:
ld.so
或ld-linux
. 它查找并加载程序所需的所有共享库(如果它们之前没有加载过),通过在程序开始之前将它们替换为真实地址来解析符号,准备程序运行,然后运行它。它在构建之后和运行程序之前工作。少说,动态链接意味着在每个程序启动之前解析可执行文件中的符号。
Actually, when you run an ELF
executable with .interp
section (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.2
for 32-bit architecture elf and /lib64/ld-linux-x86-64.so.2
for 64-bit architecture elf).
您也可以自己运行动态加载器,但这是不必要的(二进制适用/lib/ld-linux.so.2
于 32 位架构精灵和/lib64/ld-linux-x86-64.so.2
64 位架构精灵)。
Why does the linker claim that /usr/bin/ld: cannot find -lblpapi3_64
in 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 .dynamic
section for dynamic loader. Actually, the .interp
section exists in almost every c/c++ elf because the libc
and 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 中,因为libc
和libstdc++
库都是共享的,并且默认情况下编译器会将任何项目动态链接到它们。您也可以静态链接它们,但这会扩大总可执行文件的大小。因此,如果找不到共享库,您的符号将无法解析,您将无法使用运行您的应用程序,因此它无法生成可执行文件。您可以获得通常通过以下方式搜索库的目录列表:
- Passing a command to the linker in compiler arguments.
- By parsing
ld --verbose
's output. - By parsing
ldconfig
's output.
- 在编译器参数中将命令传递给链接器。
- 通过解析
ld --verbose
的输出。 - 通过解析
ldconfig
的输出。
Some of these methods are explained here.
Dynamic loadertries to find all the libraries by using:
动态加载器尝试使用以下方法查找所有库:
DT_RPATH
dynamic section of an ELF file.DT_RUNPATH
section of the executable.LD_LIBRARY_PATH
environment variable./etc/ld.so.cache
- own cache file which contains a compiled list of candidate libraries previously found in the augmented library path.- Default paths: In the default path /lib, and then /usr/lib. If the binary was linked with
-z nodeflib
linker option, this step is skipped.
DT_RPATH
ELF 文件的动态部分。DT_RUNPATH
可执行文件的部分。LD_LIBRARY_PATH
环境变量。/etc/ld.so.cache
- 自己的缓存文件,其中包含先前在扩充库路径中找到的候选库的编译列表。- 默认路径:在默认路径 /lib 中,然后是 /usr/lib。如果二进制文件与
-z nodeflib
链接器选项链接,则跳过此步骤。
Also, note please, that if we are talking about shared libraries, they are not named .so
but in .so.version
format instead. When you build your application the linker will look for .so
file (which is usually a symlink to .so.version
) but when you run your application the dynamic loader looks for .so.version
file instead. For example, let's say we have a library test
which version is 1.1.1
according 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.1
and libtest.so.1.1.1
) and a libtest.so
file 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.1
和libtest.so.1.1.1
)和一个libtest.so
文件,但是为了运行您的应用程序,您必须首先只列出 3 个版本化库文件。这也解释了为什么 Debian 或 rpm 包devel
单独有-packages:普通包(仅包含已编译的应用程序运行它们所需的文件),它有 3 个版本库文件和一个 devel 包,它只有用于制作它的符号链接文件可以编译项目。
Resume
恢复
After all of that:
毕竟:
- 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. - 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 theLD_LIBRARY_PATH
variable which points to all shared libraries needed by the game.
- 您、您的同事和应用程序代码的每个用户都必须在其系统链接器路径中拥有所有库才能编译(构建您的应用程序)。否则,他们必须更改 Makefile(或编译命令)以通过添加
-L<somePathToTheSharedLibrary>
为参数来添加共享库位置目录。 - 成功构建后,您还需要再次使用您的库才能运行该程序。您的库将被动态加载器 (
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:
您关于“如何使库可见”的基本问题实际上有两个答案:
Use
ld.so
. The easiest way is to copyblpapi3_64.so
to/usr/local/lib
. If you then callldconfig
to update the cache you should be all set. You can test this vialdconfig -p | grep blpapi
which should show it.Use an
rpath
instruction when building your application; this basically encodes the path and makes you independent ofld.so
.
使用
ld.so
. 最简单的方法是复制blpapi3_64.so
到/usr/local/lib
. 如果您随后调用ldconfig
更新缓存,您应该一切就绪。您可以通过ldconfig -p | grep blpapi
它来测试它应该显示它。rpath
在构建应用程序时使用说明;这基本上对路径进行编码并使您独立于ld.so
.