我创建了一个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
。我认为它不是链接文件,它是原始文件。
答案 0 :(得分:43)
<强>简言之:强>
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;
}
<强> 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.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
简单来说,它包含一个位于文本代码部分的符号,因此这是我们的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()
符号未解析,创建可执行文件并将其(动态)链接到我们的共享库({{1 }}):
libfunc
现在,如果我们在符号表中查找符号,我们的符号仍未解析:
cc main.c -lfunc
但这不再是问题,因为$ nm a.out | grep fun
U func
符号将在每个程序启动之前由动态加载程序解析。好的,现在让我们回到理论上。
实际上,库只是使用func
工具将ar
工具创建的单个符号表放入单个存档中的目标文件。
编译器在编译目标文件时无法解析ranlib
。这些符号将由链接器替换为地址。因此,解析符号可以通过两件事来完成:symbols
和the linker
:
链接器: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
在执行之前将要查找的共享库列表。您可以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]
还会查找文件系统中的所有库,而readelf仅显示程序所需的库。因此,所有这些库都将由动态加载器搜索(下一段)。
链接器的工作时间为构建时间。
动态加载器:ldd
或ld.so
。它找到并加载程序所需的所有共享库(如果它们之前未加载),通过在程序启动之前将它们替换为实际地址来解析符号,准备程序运行,然后运行它。它在构建之后和运行程序之前 。更少说来,动态链接意味着在每个程序开始之前解析可执行文件中的符号。
实际上,当你运行带有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中,因为.interp
和libc
库都是共享的,默认情况下编译器会动态地将任何项目链接到它们。您也可以静态链接它们,但这会扩大可执行文件的总大小。因此,如果找不到共享库,您的符号仍将无法解析,并且您将 UNABLE 来运行您的应用程序,因此它无法生成可执行文件。您可以获取通常搜索库的目录列表:
libstdc++
的输出。ld --verbose
的输出。其中一些方法已解释为here。
动态加载程序尝试使用以下命令查找所有库:
ldconfig
ELF文件的动态部分。DT_RPATH
部分可执行文件。DT_RUNPATH
环境变量。LD_LIBRARY_PATH
- 自己的缓存文件,其中包含先前在扩充库路径中找到的候选库的已编译列表。/etc/ld.so.cache
链接器选项链接,则跳过此步骤。另外,请注意,如果我们讨论的是共享库,则它们的名称不是-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.1
和libtest.so.1.1
)以及libtest.so.1.1.1
文件,但是为了运行您的应用,必须首先列出3个版本化的库文件。这也解释了为什么Debian或rpm包有libtest.so
- 包分开:正常包(只包含已经编译的应用程序运行它们所需的文件),它有3个版本化库文件和一个只包含devel包符号链接文件,用于编译项目。
<强>恢复强>
毕竟:
devel
作为参数来更改Makefile(或编译命令)以添加共享库位置目录。-L<somePathToTheSharedLibrary>
)搜索,因此它需要位于它的路径(见上文)或系统链接器路径中。在大多数Linux程序发行版中,例如来自steam的游戏,有一个shell脚本设置ld-linux
变量,该变量指向游戏所需的所有共享库。答案 1 :(得分:0)
您可以查看我们使用此库的Rblapi包。
您的基本问题&#34;如何让图书馆可见?#34;真的有两个答案:
使用ld.so
。最简单的方法是将blpapi3_64.so
复制到/usr/local/lib
。如果您再调用ldconfig
来更新缓存,则应该全部设置。您可以通过显示它的ldconfig -p | grep blpapi
进行测试。
在构建应用程序时使用rpath
指令;这基本上对路径进行编码,使您独立于ld.so
。