ELF概述

ELF (Executable Linkable Format)即可执行可链接文件格式,是 Linux 支持的一种文件存储格式。


ELF 文件类型

ELF 文件的三种类型:

  • 可重定位文件(REL,Relocatable ),目标文件或静态库
  • 共享目标文件(DYN,Executable ),动态库
  • (EXEC),可直接执行的文件
  • core文件,吐核文件
ELF 文件类型 说明 实例
可重定位文件
(Relocatable File)
这类文件包含了代码和数据。可以被用来链接成可执行文件或共享目标文件。 目标文件 .o 文件和静态库 .a 文件
可执行文件
(Executable File)
可直接执行的程序 /bin/bash 可执行文件
共享目标文件
(Shared Object File)
这种文件包含的代码和数据。可以在两种情况下使用,一是链接器使用其与其它可重定位文件、共享目标文件链接,产生新的目标文件。二是动态连接器将其与可执行文件结合,作为进程映像的一部分来运行 动态库 .so 文件
核心转储文件
(Core Dump File)
core dump 文件

可通过 readelf -h 命令查看 ELF 文件头部信息,其中便包含了 ELF 文件类型。如下图所示:
image.png

ELF 文件结构

ELF 文件格式定义见 /usr/include/elf.h 。

image.png

链接视图

静态链接器(即编译后参与生成最终ELF过程的链接器,如ld )会以链接视图解析ELF。编译时生成的目标文件以及链接后的动态库均可通过链接视图解析,链接视图可以没有段表(如目标文件不会有段表)。

执行视图

动态链接器(即加载器,如x86架构 linux下的 /lib/ld-linux.so.2或者安卓系统下的 /system/linker均为动态链接器)会以执行视图解析ELF并动态链接,执行视图可以没有节表。

  1. ELF 头:
  2. Magic 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  3. 类别: ELF64
  4. 数据: 2 补码,小端序 (little endian)
  5. 版本: 1 (current)
  6. OS/ABI: UNIX - System V
  7. 程序头:
  8. Type Offset VirtAddr PhysAddr
  9. FileSiz MemSiz Flags Align
  10. PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
  11. 0x00000000000001f8 0x00000000000001f8 R E 8

readelf 命令

readelf 是在类 Unix 系统上显示关于目标文件的各种信息的程序。如其名字所示,它读取 ELF 格式的目标文件。它与 objdump 一起都是 GNU Binutils 的一部分。

常见参数:

  • -a,—all 显示全部信息
  • -h,—file-header 显示 ELF 文件头
  • -l,—program-headers 显示程序头(段头)

    —segments

  • -S,—section-headers 显示节头信息

    1. --sections
  • -s,—syms 显示符号表(.symtab)

  • -d,—dynamic 显示动态部分
  • —dyn-syms 显示动态符号表(.dynsym)
  • -W,—wide 运行显示宽度超过 80 字节

后面会结合示例进行分析,示例源码见本章最后一个小节。

1 文件头

TODO

2 节头(section)

readelf -SW 查看节信息,如下所示,共计划分了 35 个节头。

选几个比较重要的进行说明

常用段名
.text 代码区
.bss 未初始化数据区
.data 全局初始化数据区
.line 行号表
.plt
.got
动态链接器的跳转表和全局入口表 Procedure Linkage Table
Global Offset Table
.init
.fini
程序初始化与终结代码段
.symtab 符号表
  1. 共有 35 个节头,从偏移量 0x14f0 开始:
  2. 节头:
  3. [Nr] Name Type Address Off Size ES Flg Lk Inf Al
  4. [ 0] NULL 0000000000000000 000000 000000 00 0 0 0
  5. [ 1] .interp PROGBITS 0000000000400238 000238 00001c 00 A 0 0 1
  6. [ 2] .note.ABI-tag NOTE 0000000000400254 000254 000020 00 A 0 0 4
  7. [ 3] .note.gnu.build-id NOTE 0000000000400274 000274 000024 00 A 0 0 4
  8. [ 4] .gnu.hash GNU_HASH 0000000000400298 000298 000038 00 A 5 0 8
  9. [ 5] .dynsym DYNSYM 00000000004002d0 0002d0 000138 18 A 6 1 8
  10. [ 6] .dynstr STRTAB 0000000000400408 000408 0000be 00 A 0 0 1
  11. [ 7] .gnu.version VERSYM 00000000004004c6 0004c6 00001a 02 A 5 0 2
  12. [ 8] .gnu.version_r VERNEED 00000000004004e0 0004e0 000020 00 A 6 1 8
  13. [ 9] .rela.dyn RELA 0000000000400500 000500 000018 18 A 5 0 8
  14. [10] .rela.plt RELA 0000000000400518 000518 000060 18 A 5 12 8
  15. [11] .init PROGBITS 0000000000400578 000578 00001a 00 AX 0 0 4
  16. [12] .plt PROGBITS 00000000004005a0 0005a0 000050 10 AX 0 0 16
  17. [13] .text PROGBITS 00000000004005f0 0005f0 000182 00 AX 0 0 16
  18. [14] .fini PROGBITS 0000000000400774 000774 000009 00 AX 0 0 4
  19. [15] .rodata PROGBITS 0000000000400780 000780 000004 04 AM 0 0 4
  20. [16] .eh_frame_hdr PROGBITS 0000000000400784 000784 000034 00 A 0 0 4
  21. [17] .eh_frame PROGBITS 00000000004007b8 0007b8 0000f4 00 A 0 0 8
  22. [18] .init_array INIT_ARRAY 0000000000600df0 000df0 000008 00 WA 0 0 8
  23. [19] .fini_array FINI_ARRAY 0000000000600df8 000df8 000008 00 WA 0 0 8
  24. [20] .jcr PROGBITS 0000000000600e00 000e00 000008 00 WA 0 0 8
  25. [21] .dynamic DYNAMIC 0000000000600e08 000e08 0001f0 10 WA 6 0 8
  26. [22] .got PROGBITS 0000000000600ff8 000ff8 000008 08 WA 0 0 8
  27. [23] .got.plt PROGBITS 0000000000601000 001000 000038 08 WA 0 0 8
  28. [24] .data PROGBITS 0000000000601040 001040 0000a0 00 WA 0 0 32
  29. [25] .bss NOBITS 00000000006010e0 0010e0 0000a0 00 WA 0 0 32
  30. [26] .comment PROGBITS 0000000000000000 0010e0 00002b 01 MS 0 0 1
  31. [27] .debug_aranges PROGBITS 0000000000000000 00110b 000030 00 0 0 1
  32. [28] .debug_info PROGBITS 0000000000000000 00113b 0000d9 00 0 0 1
  33. [29] .debug_abbrev PROGBITS 0000000000000000 001214 000067 00 0 0 1
  34. [30] .debug_line PROGBITS 0000000000000000 00127b 000040 00 0 0 1
  35. [31] .debug_str PROGBITS 0000000000000000 0012bb 0000e6 01 MS 0 0 1
  36. [32] .shstrtab STRTAB 0000000000000000 0013a1 000148 00 0 0 1
  37. [33] .symtab SYMTAB 0000000000000000 001db0 0006d8 18 34 50 8
  38. [34] .strtab STRTAB 0000000000000000 002488 000263 00 0 0 1
  39. Key to Flags:
  40. W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  41. I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  42. O (extra OS processing required) o (OS specific), p (processor specific)

3 段头/程序头(segment)

程序头描述了 ELF 文件如何别系统映射到进程的虚拟空间。

  1. Elf 文件类型为 EXEC (可执行文件)
  2. 入口点 0x80484a0
  3. 共有 9 个程序头,开始于偏移量 52
  4. 程序头:
  5. Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
  6. PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
  7. INTERP 0x000154 0x08048154 0x08048154 0x00013 0x00013 R 0x1
  8. [Requesting program interpreter: /lib/ld-linux.so.2]
  9. LOAD 0x000000 0x08048000 0x08048000 0x0075c 0x0075c R E 0x1000
  10. LOAD 0x000ef8 0x08049ef8 0x08049ef8 0x001c8 0x00268 RW 0x1000
  11. DYNAMIC 0x000f04 0x08049f04 0x08049f04 0x000f8 0x000f8 RW 0x4
  12. NOTE 0x000168 0x08048168 0x08048168 0x00044 0x00044 R 0x4
  13. GNU_EH_FRAME 0x000684 0x08048684 0x08048684 0x0002c 0x0002c R 0x4
  14. GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10
  15. GNU_RELRO 0x000ef8 0x08049ef8 0x08049ef8 0x00108 0x00108 R 0x1
  16. Section to Segment mapping:
  17. 段节...
  18. 00
  19. 01 .interp
  20. 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .r
  21. lt .text .fini .rodata .eh_frame_hdr .eh_frame
  22. 03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
  23. 04 .dynamic
  24. 05 .note.ABI-tag .note.gnu.build-id
  25. 06 .eh_frame_hdr
  26. 07
  27. 08 .init_array .fini_array .jcr .dynamic .got

类型为“LOAD”的段是需要被映射的,

符号表

.dynsym 动态符号表保存了与动态链接相关的导入导出符号,不包括模块内部的符号。
.symtab 则保存所有符号,也包括 .dynsym 中的符号。

readelf -sW app 查看符号表

  1. Symbol table '.dynsym' contains 13 entries:
  2. Num: Value Size Type Bind Vis Ndx Name
  3. 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
  4. 1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTable
  5. 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
  6. 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND hello
  7. 4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
  8. 5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
  9. 6: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
  10. 7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@GLIBC_2.2.5 (2)
  11. 8: 00000000006010e0 0 NOTYPE GLOBAL DEFAULT 24 _edata
  12. 9: 0000000000601180 0 NOTYPE GLOBAL DEFAULT 25 _end
  13. 10: 00000000006010e0 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
  14. 11: 0000000000400578 0 FUNC GLOBAL DEFAULT 11 _init
  15. 12: 0000000000400774 0 FUNC GLOBAL DEFAULT 14 _fini
  16. Symbol table '.symtab' contains 73 entries:
  17. Num: Value Size Type Bind Vis Ndx Name
  18. 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
  19. 1: 0000000000400238 0 SECTION LOCAL DEFAULT 1
  20. 2: 0000000000400254 0 SECTION LOCAL DEFAULT 2
  21. 3: 0000000000400274 0 SECTION LOCAL DEFAULT 3
  22. 4: 0000000000400298 0 SECTION LOCAL DEFAULT 4
  23. 5: 00000000004002d0 0 SECTION LOCAL DEFAULT 5
  24. 6: 0000000000400408 0 SECTION LOCAL DEFAULT 6
  25. 7: 00000000004004c6 0 SECTION LOCAL DEFAULT 7
  26. 8: 00000000004004e0 0 SECTION LOCAL DEFAULT 8
  27. 9: 0000000000400500 0 SECTION LOCAL DEFAULT 9
  28. 10: 0000000000400518 0 SECTION LOCAL DEFAULT 10
  29. 11: 0000000000400578 0 SECTION LOCAL DEFAULT 11
  30. 12: 00000000004005a0 0 SECTION LOCAL DEFAULT 12
  31. 13: 00000000004005f0 0 SECTION LOCAL DEFAULT 13
  32. 14: 0000000000400774 0 SECTION LOCAL DEFAULT 14
  33. 15: 0000000000400780 0 SECTION LOCAL DEFAULT 15
  34. 16: 0000000000400784 0 SECTION LOCAL DEFAULT 16
  35. 17: 00000000004007b8 0 SECTION LOCAL DEFAULT 17
  36. 18: 0000000000600df0 0 SECTION LOCAL DEFAULT 18
  37. 19: 0000000000600df8 0 SECTION LOCAL DEFAULT 19
  38. 20: 0000000000600e00 0 SECTION LOCAL DEFAULT 20
  39. 21: 0000000000600e08 0 SECTION LOCAL DEFAULT 21
  40. 22: 0000000000600ff8 0 SECTION LOCAL DEFAULT 22
  41. 23: 0000000000601000 0 SECTION LOCAL DEFAULT 23
  42. 24: 0000000000601040 0 SECTION LOCAL DEFAULT 24
  43. 25: 00000000006010e0 0 SECTION LOCAL DEFAULT 25
  44. 26: 0000000000000000 0 SECTION LOCAL DEFAULT 26
  45. 27: 0000000000000000 0 SECTION LOCAL DEFAULT 27
  46. 28: 0000000000000000 0 SECTION LOCAL DEFAULT 28
  47. 29: 0000000000000000 0 SECTION LOCAL DEFAULT 29
  48. 30: 0000000000000000 0 SECTION LOCAL DEFAULT 30
  49. 31: 0000000000000000 0 SECTION LOCAL DEFAULT 31
  50. 32: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
  51. 33: 0000000000600e00 0 OBJECT LOCAL DEFAULT 20 __JCR_LIST__
  52. 34: 0000000000400620 0 FUNC LOCAL DEFAULT 13 deregister_tm_clones
  53. 35: 0000000000400650 0 FUNC LOCAL DEFAULT 13 register_tm_clones
  54. 36: 0000000000400690 0 FUNC LOCAL DEFAULT 13 __do_global_dtors_aux
  55. 37: 00000000006010e0 1 OBJECT LOCAL DEFAULT 25 completed.6982
  56. 38: 0000000000600df8 0 OBJECT LOCAL DEFAULT 19 __do_global_dtors_aux_fini_array_entry
  57. 39: 00000000004006b0 0 FUNC LOCAL DEFAULT 13 frame_dummy
  58. 40: 0000000000600df0 0 OBJECT LOCAL DEFAULT 18 __frame_dummy_init_array_entry
  59. 41: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
  60. 42: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
  61. 43: 00000000004008a8 0 OBJECT LOCAL DEFAULT 17 __FRAME_END__
  62. 44: 0000000000600e00 0 OBJECT LOCAL DEFAULT 20 __JCR_END__
  63. 45: 0000000000000000 0 FILE LOCAL DEFAULT ABS
  64. 46: 0000000000600df8 0 NOTYPE LOCAL DEFAULT 18 __init_array_end
  65. 47: 0000000000600e08 0 OBJECT LOCAL DEFAULT 21 _DYNAMIC
  66. 48: 0000000000600df0 0 NOTYPE LOCAL DEFAULT 18 __init_array_start
  67. 49: 0000000000601000 0 OBJECT LOCAL DEFAULT 23 _GLOBAL_OFFSET_TABLE_
  68. 50: 0000000000400770 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
  69. 51: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTable
  70. 52: 0000000000601040 0 NOTYPE WEAK DEFAULT 24 data_start
  71. 53: 00000000006010e0 0 NOTYPE GLOBAL DEFAULT 24 _edata
  72. 54: 0000000000400774 0 FUNC GLOBAL DEFAULT 14 _fini
  73. 55: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_2.2.5
  74. 56: 0000000000000000 0 FUNC GLOBAL DEFAULT UND hello
  75. 57: 0000000000601040 0 NOTYPE GLOBAL DEFAULT 24 __data_start
  76. 58: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
  77. 59: 0000000000601048 0 OBJECT GLOBAL HIDDEN 24 __dso_handle
  78. 60: 0000000000400780 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
  79. 61: 0000000000400700 101 FUNC GLOBAL DEFAULT 13 __libc_csu_init
  80. 62: 0000000000601100 128 OBJECT GLOBAL DEFAULT 25 global_uini_buffer
  81. 63: 0000000000601180 0 NOTYPE GLOBAL DEFAULT 25 _end
  82. 64: 00000000004005f0 0 FUNC GLOBAL DEFAULT 13 _start
  83. 65: 00000000006010e0 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
  84. 66: 0000000000601060 128 OBJECT GLOBAL DEFAULT 24 global_init_buffer
  85. 67: 00000000004006dd 21 FUNC GLOBAL DEFAULT 13 main
  86. 68: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
  87. 69: 00000000006010e0 0 OBJECT GLOBAL HIDDEN 24 __TMC_END__
  88. 70: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
  89. 71: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@@GLIBC_2.2.5
  90. 72: 0000000000400578 0 FUNC GLOBAL DEFAULT 11 _init

示例程序中定义了两个全局变量,一个未进行初始化,一个有进行初始化,可以在符号表中找到这两个符号。

  1. char global_uini_buffer[128];
  2. char global_init_buffer[128] = {1,2,4};

符号表,其大小和地址在程序启动后是不变的,与表中一致(函数亦然)。

  1. 62: 0000000000601100 128 OBJECT GLOBAL DEFAULT 25 global_uini_buffer
  2. 66: 0000000000601060 128 OBJECT GLOBAL DEFAULT 24 global_init_buffer

节头

  1. [24] .data PROGBITS 0000000000601040 001040 0000a0 00 WA 0 0 32
  2. [25] .bss NOBITS 00000000006010e0 0010e0 0000a0 00 WA 0 0 32

根据以上可以得知,global_uini_buffer 位于 .bss 区域, global_init_buffer 位于 .data 区域。同理能查询到函数位于.text 区域。

符号表解读

共计 8 列

  • Num: :符号编号,0 - n
  • Value :符合地址,16 进制格式
  • Size :符号大小,
  • Type :符号类型。FUNC 函数,FILE 源文件,OBJECT 变量等
  • Bind : 符合绑定。GLOBAL 符号全局可见,LOCAL 符号文件内可见,比如 static 函数,WEAK 全局弱符号,可被覆盖。
  • Vis :符号可见性。DEFAULT 缺省。 TODO
  • Ndx :符号节头索引。据此可以查看符号所属分区。比如函数符号位域代码区
  • Name :符号名称

示例程序

main.c

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include "foo.h"
  5. char global_uini_buffer[128];
  6. char global_init_buffer[128] = {1,2,4};
  7. int main(void)
  8. {
  9. hello();
  10. while(1) sleep(1);
  11. return 0;
  12. }

foo.h

  1. #ifndef __FOO_H__
  2. #define __FOO_H__
  3. #ifdef __cplusplus
  4. extern "C" {
  5. #endif
  6. #include <unistd.h>
  7. #include <stdlib.h>
  8. #include <stdio.h>
  9. #include <string.h>
  10. void hello(void);
  11. #ifdef __cplusplus
  12. }
  13. #endif
  14. #endif

foo.c

  1. #include "foo.h"
  2. void hello(void)
  3. {
  4. printf("hello world\n");
  5. }

补充

  1. 链接的本质是吧不同的目标文件之间相互“粘”到一起。
  2. 目标文件之前的相互拼合实际上是其之间对地址的引用,即对函数和变量地址的引用。
  3. 链接中,将函数和变量统称为符号。
  4. 每个目标文件都会有一个相应的符号表,记录了目标文件所有用到的符号。每个定义的符号都有一个对应的值,叫做符号值,对于变量和函数,符号值就是它们的地址。
  • 定义在本目标文件的全局符号,可以被其它符号
  • 本目标文件中引用的全局符号,却没有在本目标文件定义,一般称为外部符号(External Symbol)
  • 局部符号,只在编译单元内部可见
  1. 静态两步链接
    1. 空间与地址分配

扫描所有输入的目标文件,将各表的符号定义和符号引用合并为一个全局符号表。

  1. 符号解析与重定位

读取输入文件中数据与重定位信息,进行符号解析与重定位、调制代码中的地址

  1. API 指源代码级别的接口,ABI 指二进制层面的接口。
  2. 进程的创建过程
  • 创建一个独立的虚拟地址空间
  • 读取可执行文件头,并且建立虚拟空间与可执行文件的映射

image.png

  • 将 CPU 指令寄存器设置成可执行文件的入口地址,启动运行

    查看进程虚拟空间分布

    /proc//maps 可查看进程的虚拟空间分布。如下所示,

    1. 08048000-08049000 r-xp 00000000 08:01 1971950 /home/admi/tmp/hello_lib/app
    2. 08049000-0804a000 r--p 00000000 08:01 1971950 /home/admi/tmp/hello_lib/app
    3. 0804a000-0804b000 rw-p 00001000 08:01 1971950 /home/admi/tmp/hello_lib/app
    4. f7569000-f756a000 rw-p 00000000 00:00 0
    5. f756a000-f7715000 r-xp 00000000 08:01 819068 /lib/i386-linux-gnu/libc-2.19.so
    6. f7715000-f7717000 r--p 001aa000 08:01 819068 /lib/i386-linux-gnu/libc-2.19.so
    7. f7717000-f7718000 rw-p 001ac000 08:01 819068 /lib/i386-linux-gnu/libc-2.19.so
    8. f7718000-f771b000 rw-p 00000000 00:00 0
    9. f7733000-f7734000 rw-p 00000000 00:00 0
    10. f7734000-f7735000 r-xp 00000000 08:01 1971941 /home/admi/tmp/hello_lib/libfoo.so
    11. f7735000-f7736000 r--p 00000000 08:01 1971941 /home/admi/tmp/hello_lib/libfoo.so
    12. f7736000-f7737000 rw-p 00001000 08:01 1971941 /home/admi/tmp/hello_lib/libfoo.so
    13. f7737000-f7738000 rw-p 00000000 00:00 0
    14. f7738000-f773a000 r--p 00000000 00:00 0 [vvar]
    15. f773a000-f773c000 r-xp 00000000 00:00 0 [vdso]
    16. f773c000-f775c000 r-xp 00000000 08:01 819065 /lib/i386-linux-gnu/ld-2.19.so
    17. f775c000-f775d000 r--p 0001f000 08:01 819065 /lib/i386-linux-gnu/ld-2.19.so
    18. f775d000-f775e000 rw-p 00020000 08:01 819065 /lib/i386-linux-gnu/ld-2.19.so
    19. ffb12000-ffb33000 rw-p 00000000 00:00 0 [stack]
  • 第一列是 虚拟地址空间范围

  • 第二列是 权限,r(可读)、w(可写)、x(可执行)、p(私有)、s(共享)
  • 第三列是 偏移,该段在映像文件中的偏移
  • 第四列是 主次设备号,
  • 第五列是 文件节点
  • 最后列是 映像文件路径

[stack] 主次设备号和文件节点都是0,表示没有映射到文件中

参考资料

[1] 程序员的自我修养