我创建了一个cpp项目,它使用了一个名为的lib文件:libblpapi3_64.so
这个文件来自一个我从Internet上下载的库.
我的项目运行没有任何错误.所以我将它更新为bitbucket.然后我的同事下载并在自己的计算机上运行它.但他得到一个错误:
usr/bin/ld: cannot find -lblpapi3_64.
实际上,我已将其复制到我的项目存储库中.我的意思是我在我的项目下创建了一个名为lib的文件,我使用的所有lib文件都在其中.
还有其他的lib文件liblog4cpp.a,但它们都很好.只有libblpapi3_64.so得到错误.
是因为它是.so文件不是.a文件吗?还是有其他原因?
顺便说一句,libblpapi3_64.sois 的文件名green和其他文件(.a)是white.我认为这不是一个链接文件,它是原始文件.
Vic*_*voy 46
简述:
ld不知道您的项目库位于何处.您必须将它放入ld的已知目录中,或者通过-L参数指定链接器的完整路径.
为了能够构建您的程序,您需要在/bin/ld搜索路径中使用您的库以及您的同事.为什么?详细解答.
详细:
首先,我们应该了解哪些工具可以做什么:
object files带有未解析符号的简单符号(它不关心符号的运行时间).object并archive files重新定位其数据并将符号引用绑定到单个文件中:可执行文件或库.让我们从一些例子开始吧.例如,你有包括3个文件的项目:main.c,func.h和func.c.
main.c中
#include "func.h"
int main() {
func();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
func.h
void func();
Run Code Online (Sandbox Code Playgroud)
func.c
#include "func.h"
void func() { }
Run Code Online (Sandbox Code Playgroud)
因此,当您将源代码(main.c)编译为目标文件(main.o)时,它无法运行,因为它具有未解析的符号.让我们从producing an executable工作流程的开始(没有细节)开始:
作业后的预处理器产生以下结果main.c.preprocessed:
void func();
int main() {
func();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
以下内容func.c.preprocessed:
void func();
void func() { }
Run Code Online (Sandbox Code Playgroud)
正如您所看到的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符号尚未得到解决,因为它不知道它在哪里执行.
让我们看看里面是什么func.o:
$ nm func.o
0000000000000000 T func
Run Code Online (Sandbox Code Playgroud)
简单地说,它包含一个在文本代码部分的符号,所以这是我们的func功能.
让我们看看里面是什么main.o:
$ nm main.o
U func
0000000000000000 T main
Run Code Online (Sandbox Code Playgroud)
我们main.o有一个已实现和解析的静态函数main,我们可以在目标文件中看到它.但我们也看到func标记为未解决的符号U,因此我们无法看到它的地址偏移.
为了解决这个问题,我们必须使用链接器.它将获取所有目标文件并解析所有这些符号(void func();在我们的示例中).如果链接器以某种方式无法做到这一点,则会抛出如下错误unresolved external symbol:void func().如果您不将func.o目标文件提供给链接器,则可能会发生这种情况.那么,让我们将所有目标文件提供给链接器:
ld main.o func.o -o test
Run Code Online (Sandbox Code Playgroud)
链接器将通过main.o,然后通过func.o,尝试解析符号,如果它正常 - 将它的输出放到test文件中.如果我们查看生成的输出,我们将看到所有符号都已解析:
$ nm test
0000000000601000 R __bss_start
0000000000601000 R _edata
0000000000601000 R _end
00000000004000b0 T func
00000000004000b7 T main
Run Code Online (Sandbox Code Playgroud)
我们的工作已经完成.让我们看一下动态(共享)库的情况.让我们从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
Run Code Online (Sandbox Code Playgroud)
瞧,我们拥有它.现在,让我们将它放入已知的动态链接器库路径中/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
Run Code Online (Sandbox Code Playgroud)
让我们的项目依赖于共享库,func()在编译和静态链接过程之后保留符号未解析,创建可执行文件并将其(动态)链接到我们的共享库(libfunc):
cc main.c -lfunc
Run Code Online (Sandbox Code Playgroud)
现在,如果我们在符号表中查找符号,我们的符号仍未解析:
$ nm a.out | grep fun
U func
Run Code Online (Sandbox Code Playgroud)
但这不再是问题,因为func符号将在每个程序启动之前由动态加载程序解析.好的,现在让我们回到理论上.
实际上,库只是一个目标文件,它通过使用ar工具创建的单个符号表放在一个存档中ranlib.
编译器在编译目标文件时无法解析symbols.这些符号将由链接器替换为地址.所以解析符号可以通过两件事来完成:the linker和dynamic loader:
链接器:ld,做2个工作:
a)对于静态库或简单对象文件,此链接器将对象文件中的外部符号更改为实体的地址.例如,如果我们使用C++名称,则会将更改链接器更改_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]
Run Code Online (Sandbox Code Playgroud)
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]
Run Code Online (Sandbox Code Playgroud)
请注意,ldd还会查找文件系统中的所有库,而readelf仅显示程序所需的库.因此,所有这些库都将由动态加载器搜索(下一段).链接器在构建时工作.
动态加载器:ld.so或ld-linux.它找到并加载程序所需的所有共享库(如果之前未加载它们),通过在程序启动之前将它们替换为实际地址来解析符号,准备程序运行,然后运行它.它在构建之后和运行程序之前工作.更少说来,动态链接意味着在每个程序启动之前解析可执行文件中的符号.
实际上,当你运行ELF带有.interpsection 的可执行文件(它需要加载一些共享库)时,操作系统(Linux)首先会运行一个解释器而不是你的程序.否则你有一个未定义的行为 - 你的程序中有符号,但它们不是由地址定义的,这通常意味着程序将无法正常工作.
您也可以自己运行动态加载器,但这是不必要的(二进制/lib/ld-linux.so.2用于32位架构精灵和/lib64/ld-linux-x86-64.so.264位架构精灵).
为什么链接器声称/usr/bin/ld: cannot find -lblpapi3_64在您的情况下?因为它试图在其已知路径中找到所有库.如果它将在运行时加载,为什么它会搜索库?因为它需要检查此库是否可以解析所有需要的符号,并将其名称放入.dynamic动态加载器的部分.实际上,该.interp部分几乎存在于每个c/c ++ elf中,因为libc和libstdc++库都是共享的,默认情况下编译器会动态地将任何项目链接到它们.您也可以静态链接它们,但这会扩大可执行文件的总大小.因此,如果找不到共享库,则您的符号将保持未解析状态,并且您将无法运行应用程序,因此无法生成可执行文件.您可以获取通常搜索库的目录列表:
ld --verbose输出.ldconfig输出.动态加载程序尝试使用以下命令查找所有库:
DT_RPATH ELF文件的动态部分.DT_RUNPATH 可执行文件的一部分.LD_LIBRARY_PATH 环境变量./etc/ld.so.cache - 自己的缓存文件,其中包含先前在扩充库路径中找到的候选库的编译列表.-z nodeflib链接器选项链接,则跳过此步骤.另外,请注意,如果我们讨论的是共享库,则它们不会以格式命名,.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
Run Code Online (Sandbox Code Playgroud)
因此,为了能够编译,您必须拥有所有版本化文件(libtest.so.1,libtest.so.1.1和libtest.so.1.1.1)和libtest.so文件,但是为了运行您的应用程序,您必须首先列出3个版本化库文件.这也解释了为什么debian或rpm包分别包含devel-packages:normal(只包含已编译的应用程序运行它们所需的文件),它有3个版本化的库文件和一个只有symlink文件的devel包用于制作它可以编译项目.
恢复
毕竟:
-L<somePathToTheSharedLibrary>as参数来更改Makefile(或编译命令)以添加共享库位置目录.ld-linux)搜索,因此它需要位于其路径中(参见上文)或系统链接器路径中.在大多数linux程序发行版中,例如来自steam的游戏,有一个shell脚本,它设置LD_LIBRARY_PATH指向游戏所需的所有共享库的变量.