ELF文件格式

前言

绝大部分程序都或多或少共享了一些相同的功能,比如读写文件、创建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/

posted @ 2020-02-05 11:51  心田居士  阅读(793)  评论(0编辑  收藏  举报