前言
绝大部分程序都或多或少共享了一些相同的功能,比如读写文件、创建HTTP连接等。这些相同的功能无需在每个程序中包含,只需大家共享一份相同的即可。
使用这种共享技术的,就是动态链接;相反不使用这种共享技术的,就是静态链接。
对于一个传统的二进制可执行程序(PE for Windows, ELF for Linux),除却系统特定的一些内容之外,都遵循相同的概念:
- 每个功能都可以被划分成一个对应函数
- 函数的二进制名称叫做“符号”
- 静态链接还是动态链接,将决定这些符号解析至何处
- 在可执行程序被编译时,实际被划分为编译和链接两个大步骤
- 编译时只会将程序源代码变成机器码
- 链接时将解决符号的解析问题
- 因此动态链接和静态链接,通常是在程序链接时决定的
运行可执行程序时,操作系统负责查找动态链接的符号,如果都能够找到且互相匹配,那么这个程序很大概率即可正常启动。
ELF格式
ELF 格式是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。
一般一个 ELF 文件都会有两个 header ,一个叫 ELF header,作用是描述整个 ELF 文件的一些信息,比如适用的平台是不是 x64,内容是大端序还是小端序等。
另一个 header 叫 program header,它的作用主要是为了表明自己应该如何被加载和执行。
通过 readelf 命令,可以在 Linux 系统中查看任意 ELF 文件的信息,包括这两个 header :
lzb@lzb:~/G-ORBSLAM/ORB_SLAM2-master/Examples/Monocular$ readelf --file-header mono_euroc
ELF Header:
Magic: 7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - GNU
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x95b0
Start of program headers: 64 (bytes into file)
Start of section headers: 86992 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 31
Section header string table index: 30
lzb@lzb:~/G-ORBSLAM/ORB_SLAM2-master/Examples/Monocular$ readelf --program-header mono_euroc
Elf file type is DYN (Shared object file)
Entry point 0x95b0
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000001f8 0x00000000000001f8 R 0x8
INTERP 0x0000000000000238 0x0000000000000238 0x0000000000000238
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x000000000000daee 0x000000000000daee R E 0x200000
LOAD 0x000000000000e1a8 0x000000000020e1a8 0x000000000020e1a8
0x0000000000000ec8 0x00000000000011b8 RW 0x200000
DYNAMIC 0x000000000000eb58 0x000000000020eb58 0x000000000020eb58
0x0000000000000280 0x0000000000000280 RW 0x8
NOTE 0x0000000000000254 0x0000000000000254 0x0000000000000254
0x0000000000000044 0x0000000000000044 R 0x4
GNU_EH_FRAME 0x000000000000c7a0 0x000000000000c7a0 0x000000000000c7a0
0x00000000000003fc 0x00000000000003fc R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x000000000000e1a8 0x000000000020e1a8 0x000000000020e1a8
0x0000000000000e58 0x0000000000000e58 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame .gcc_except_table
03 .init_array .fini_array .data.rel.ro .dynamic .got .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .data.rel.ro .dynamic .got
在这两个 header 之后,有很多 section。这些 section 会在 ELF 文件被加载时,归纳为若干 segment 。这里面既有代码也有数据,其中我们最关心的一个 section 叫做 .dynamic。
.dynamic 包含了可执行程序所需的动态链接库,使用 readelf 也可以解析:
lzb@lzb:~/G-ORBSLAM/ORB_SLAM2-master/Examples/Monocular$ readelf -d mono_euroc
Dynamic section at offset 0xeb58 contains 36 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libORB_SLAM2.so]
0x0000000000000001 (NEEDED) Shared library: [libopencv_imgcodecs.so.3.2]
0x0000000000000001 (NEEDED) Shared library: [libopencv_core.so.3.2]
0x0000000000000001 (NEEDED) Shared library: [libpangolin.so]
0x0000000000000001 (NEEDED) Shared library: [libGL.so.1]
0x0000000000000001 (NEEDED) Shared library: [libGLEW.so.2.0]
0x0000000000000001 (NEEDED) Shared library: [libstdc++.so.6]
0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000001d (RUNPATH) Library runpath: [/home/lzb/G-ORBSLAM/ORB_SLAM2-master/lib:/usr/local/lib:/home/lzb/G-ORBSLAM/ORB_SLAM2-master/Thirdparty/DBoW2/lib:/home/lzb/G-ORBSLAM/ORB_SLAM2-master/Thirdparty/g2o/lib]
0x000000000000000c (INIT) 0x8158
0x000000000000000d (FINI) 0xbfd4
0x0000000000000019 (INIT_ARRAY) 0x20e1a8
0x000000000000001b (INIT_ARRAYSZ) 16 (bytes)
0x000000000000001a (FINI_ARRAY) 0x20e1b8
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x298
0x0000000000000005 (STRTAB) 0x2810
0x0000000000000006 (SYMTAB) 0xa88
0x000000000000000a (STRSZ) 15231 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x20edd8
0x0000000000000002 (PLTRELSZ) 1392 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x7be8
0x0000000000000007 (RELA) 0x66e8
0x0000000000000008 (RELASZ) 5376 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000000000001e (FLAGS) BIND_NOW
0x000000006ffffffb (FLAGS_1) Flags: NOW PIE
0x000000006ffffffe (VERNEED) 0x6608
0x000000006fffffff (VERNEEDNUM) 3
0x000000006ffffff0 (VERSYM) 0x6390
0x000000006ffffff9 (RELACOUNT) 171
0x0000000000000000 (NULL) 0x0
上面的回显里,标有NEEDED字样的行,即是这个 ELF 文件所需的动态链接库。
不过要注意,readelf 本质上只解析这一个 ELF 文件。如果一个 ELF 文件依赖的动态链接库,又依赖了其他动态链接库,那么这条命令就不够用了。为此,可以使用 ldd 命令,这个命令会递归地处理依赖:
lzb@lzb:~/G-ORBSLAM/ORB_SLAM2-master/Examples/Monocular$ ldd mono_euroc
linux-vdso.so.1 (0x00007ffc9e7f4000)
libORB_SLAM2.so => not found
libopencv_imgcodecs.so.3.2 => /usr/local/lib/libopencv_imgcodecs.so.3.2 (0x00007f74563d6000)
libopencv_core.so.3.2 => /usr/local/lib/libopencv_core.so.3.2 (0x00007f7455697000)
libpangolin.so => /usr/local/lib/libpangolin.so (0x00007f745523d000)
libGL.so.1 => /usr/lib/x86_64-linux-gnu/libGL.so.1 (0x00007f7454fb1000)
libGLEW.so.2.0 => /usr/lib/x86_64-linux-gnu/libGLEW.so.2.0 (0x00007f7454d1c000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f7454993000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f745477b000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f745438a000)
libopencv_imgproc.so.3.2 => /usr/local/lib/libopencv_imgproc.so.3.2 (0x00007f7452b72000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f7452953000)
libjpeg.so.8 => /usr/lib/x86_64-linux-gnu/libjpeg.so.8 (0x00007f74526eb000)
libwebp.so.6 => /usr/lib/x86_64-linux-gnu/libwebp.so.6 (0x00007f7452482000)
libpng16.so.16 => /usr/lib/x86_64-linux-gnu/libpng16.so.16 (0x00007f7452250000)
libtiff.so.5 => /usr/lib/x86_64-linux-gnu/libtiff.so.5 (0x00007f7451fd9000)
libIlmImf-2_2.so.22 => /usr/lib/x86_64-linux-gnu/libIlmImf-2_2.so.22 (0x00007f7451b16000)
libHalf.so.12 => /usr/lib/x86_64-linux-gnu/libHalf.so.12 (0x00007f74518d3000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f7451535000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f7451318000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f7451114000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f7450f0c000)
libEGL.so.1 => /usr/lib/x86_64-linux-gnu/libEGL.so.1 (0x00007f7450cf8000)
libwayland-client.so.0 => /usr/lib/x86_64-linux-gnu/libwayland-client.so.0 (0x00007f7450ae9000)
libwayland-egl.so.1 => /usr/lib/x86_64-linux-gnu/libwayland-egl.so.1 (0x00007f74508e7000)
libwayland-cursor.so.0 => /usr/lib/x86_64-linux-gnu/libwayland-cursor.so.0 (0x00007f74506df000)
libxkbcommon.so.0 => /usr/lib/x86_64-linux-gnu/libxkbcommon.so.0 (0x00007f74504a0000)
libX11.so.6 => /usr/lib/x86_64-linux-gnu/libX11.so.6 (0x00007f7450168000)
后面省略...
linux-vdso.so.1
ldd 命令非常友好地打印出了所有动态链接库的地址,但怎么唯独没有打印出 linux-vdso.so.1 呢?而且这个文件 ldd 并不是没有找到,它甚至给出了入口地址。如果我们自己在系统里搜索,也是无法搜索到这个文件的。
原来这个 linux-vdso.so.1 文件,并不是一个真实存在的文件,而是 Linux 中的一个虚拟文件,专门用于将内核中一些常用的函数从内核空间映射到用户空间。也就是说,这个文件不用复制。
LD_LIBRARY_PATH & RPATH & RUNPATH
我们复制了目标程序所需的动态链接库,但是我们如何确定程序启动时,真的能够顺利找到这些动态链接库呢?
在 Linux 中,主要有三个因素可以决定特定可执行文件的动态链接库的搜索路径:环境变量 LD_LIBRARY_PATH 、rpath 和 runpath。
其中RPATH与RUNPATH是编译期间就已经固定的。
动态库搜索顺序
1. 先寻找RPATH下的文件,除非RUNPATH被设置
2. LD_LIBRARY_PATH路径下的文件
3. RUNPATH路径下的文件
4. /etc/ld.so.conf
5. /lib, /usr/lib
参考资料
zhuanlan.zhihu.com/p/59590848
stackoverflow.com/questions/7967848/use-rpath-but-not-runpath
software.intel.com/sites/default/files/m/a/1/e/dsohowto.pdf
amir.rachum.com/blog/2016/09/17/shared-libraries/