1.5.3 Linux ELF

一个实例

1.5.1节 C语言基础 中我们看到了从源代码到可执行文件的全过程,现在我们来看一个更复杂的例子。

  1. #include<stdio.h>
  2. int global_init_var = 10;
  3. int global_uninit_var;
  4. void func(int sum) {
  5. printf("%d\n", sum);
  6. }
  7. void main(void) {
  8. static int local_static_init_var = 20;
  9. static int local_static_uninit_var;
  10. int local_init_val = 30;
  11. int local_uninit_var;
  12. func(global_init_var + local_init_val +
  13. local_static_init_var );
  14. }

然后分别执行下列命令生成三个文件:

  1. gcc -m32 -c elfDemo.c -o elfDemo.o
  2. gcc -m32 elfDemo.c -o elfDemo.out
  3. gcc -m32 -static elfDemo.c -o elfDemo_static.out

使用 ldd 命令打印所依赖的共享库:

  1. $ ldd elfDemo.out
  2. linux-gate.so.1 (0xf77b1000)
  3. libc.so.6 => /usr/lib32/libc.so.6 (0xf7597000)
  4. /lib/ld-linux.so.2 => /usr/lib/ld-linux.so.2 (0xf77b3000)
  5. $ ldd elfDemo_static.out
  6. not a dynamic executable

elfDemo_static.out 采用了静态链接的方式。

使用 file 命令查看相应的文件格式:

  1. $ file elfDemo.o
  2. elfDemo.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
  3. $ file elfDemo.out
  4. elfDemo.out: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=50036015393a99344897cbf34099256c3793e172, not stripped
  5. $ file elfDemo_static.out
  6. elfDemo_static.out: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=276c839c20b4c187e4b486cf96d82a90c40f4dae, not stripped
  7. $ file -L /usr/lib32/libc.so.6
  8. /usr/lib32/libc.so.6: ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked, interpreter /usr/lib32/ld-linux.so.2, BuildID[sha1]=ee88d1b2aa81f104ab5645d407e190b244203a52, for GNU/Linux 3.2.0, not stripped

于是我们得到了 Linux 可执行文件格式 ELF (Executable Linkable Format)文件的三种类型:

  • 可重定位文件(Relocatable file)
    • 包含了代码和数据,可以和其他目标文件链接生成一个可执行文件或共享目标文件。
    • elfDemo.o
  • 可执行文件(Executable File)
    • 包含了可以直接执行的文件。
    • elfDemo_static.out
  • 共享目标文件(Shared Object File)
    • 包含了用于链接的代码和数据,分两种情况。一种是链接器将其与其他的可重定位文件和共享目标文件链接起来,生产新的目标文件。另一种是动态链接器将多个共享目标文件与可执行文件结合,作为进程映像的一部分。
    • elfDemo.out
    • libc-2.25.so

此时他们的结构如图:

img

可以看到,在这个简化的 ELF 文件中,开头是一个“文件头”,之后分别是代码段、数据段和.bss段。程序源代码编译后,执行语句变成机器指令,保存在.text段;已初始化的全局变量和局部静态变量都保存在.data段;未初始化的全局变量和局部静态变量则放在.bss段。

把程序指令和程序数据分开存放有许多好处,从安全的角度讲,当程序被加载后,数据和指令分别被映射到两个虚拟区域。由于数据区域对于进程来说是可读写的,而指令区域对于进程来说是只读的,所以这两个虚存区域的权限可以被分别设置成可读写和只读,可以防止程序的指令被改写和利用。

elfDemo.o

接下来,我们更深入地探索目标文件,使用 objdump 来查看目标文件的内部结构:

  1. $ objdump -h elfDemo.o
  2. elfDemo.o: file format elf32-i386
  3. Sections:
  4. Idx Name Size VMA LMA File off Algn
  5. 0 .group 00000008 00000000 00000000 00000034 2**2
  6. CONTENTS, READONLY, GROUP, LINK_ONCE_DISCARD
  7. 1 .text 00000078 00000000 00000000 0000003c 2**0
  8. CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  9. 2 .data 00000008 00000000 00000000 000000b4 2**2
  10. CONTENTS, ALLOC, LOAD, DATA
  11. 3 .bss 00000004 00000000 00000000 000000bc 2**2
  12. ALLOC
  13. 4 .rodata 00000004 00000000 00000000 000000bc 2**0
  14. CONTENTS, ALLOC, LOAD, READONLY, DATA
  15. 5 .text.__x86.get_pc_thunk.ax 00000004 00000000 00000000 000000c0 2**0
  16. CONTENTS, ALLOC, LOAD, READONLY, CODE
  17. 6 .comment 00000012 00000000 00000000 000000c4 2**0
  18. CONTENTS, READONLY
  19. 7 .note.GNU-stack 00000000 00000000 00000000 000000d6 2**0
  20. CONTENTS, READONLY
  21. 8 .eh_frame 0000007c 00000000 00000000 000000d8 2**2
  22. CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

可以看到目标文件中除了最基本的代码段、数据段和 BSS 段以外,还有一些别的段。注意到 .bss 段没有 CONTENTS 属性,表示它实际上并不存在,.bss 段只是为为未初始化的全局变量和局部静态变量预留了位置而已。

代码段

  1. $ objdump -x -s -d elfDemo.o
  2. ......
  3. Sections:
  4. Idx Name Size VMA LMA File off Algn
  5. ......
  6. 1 .text 00000078 00000000 00000000 0000003c 2**0
  7. CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  8. ......
  9. Contents of section .text:
  10. 0000 5589e553 83ec04e8 fcffffff 05010000 U..S............
  11. 0010 0083ec08 ff75088d 90000000 005289c3 .....u.......R..
  12. 0020 e8fcffff ff83c410 908b5dfc c9c38d4c ..........]....L
  13. 0030 240483e4 f0ff71fc 5589e551 83ec14e8 $.....q.U..Q....
  14. 0040 fcffffff 05010000 00c745f4 1e000000 ..........E.....
  15. 0050 8b880000 00008b55 f401ca8b 80040000 .......U........
  16. 0060 0001d083 ec0c50e8 fcffffff 83c41090 ......P.........
  17. 0070 8b4dfcc9 8d61fcc3 .M...a..
  18. ......
  19. Disassembly of section .text:
  20. 00000000 <func>:
  21. 0: 55 push %ebp
  22. 1: 89 e5 mov %esp,%ebp
  23. 3: 53 push %ebx
  24. 4: 83 ec 04 sub $0x4,%esp
  25. 7: e8 fc ff ff ff call 8 <func+0x8>
  26. 8: R_386_PC32 __x86.get_pc_thunk.ax
  27. c: 05 01 00 00 00 add $0x1,%eax
  28. d: R_386_GOTPC _GLOBAL_OFFSET_TABLE_
  29. 11: 83 ec 08 sub $0x8,%esp
  30. 14: ff 75 08 pushl 0x8(%ebp)
  31. 17: 8d 90 00 00 00 00 lea 0x0(%eax),%edx
  32. 19: R_386_GOTOFF .rodata
  33. 1d: 52 push %edx
  34. 1e: 89 c3 mov %eax,%ebx
  35. 20: e8 fc ff ff ff call 21 <func+0x21>
  36. 21: R_386_PLT32 printf
  37. 25: 83 c4 10 add $0x10,%esp
  38. 28: 90 nop
  39. 29: 8b 5d fc mov -0x4(%ebp),%ebx
  40. 2c: c9 leave
  41. 2d: c3 ret
  42. 0000002e <main>:
  43. 2e: 8d 4c 24 04 lea 0x4(%esp),%ecx
  44. 32: 83 e4 f0 and $0xfffffff0,%esp
  45. 35: ff 71 fc pushl -0x4(%ecx)
  46. 38: 55 push %ebp
  47. 39: 89 e5 mov %esp,%ebp
  48. 3b: 51 push %ecx
  49. 3c: 83 ec 14 sub $0x14,%esp
  50. 3f: e8 fc ff ff ff call 40 <main+0x12>
  51. 40: R_386_PC32 __x86.get_pc_thunk.ax
  52. 44: 05 01 00 00 00 add $0x1,%eax
  53. 45: R_386_GOTPC _GLOBAL_OFFSET_TABLE_
  54. 49: c7 45 f4 1e 00 00 00 movl $0x1e,-0xc(%ebp)
  55. 50: 8b 88 00 00 00 00 mov 0x0(%eax),%ecx
  56. 52: R_386_GOTOFF global_init_var
  57. 56: 8b 55 f4 mov -0xc(%ebp),%edx
  58. 59: 01 ca add %ecx,%edx
  59. 5b: 8b 80 04 00 00 00 mov 0x4(%eax),%eax
  60. 5d: R_386_GOTOFF .data
  61. 61: 01 d0 add %edx,%eax
  62. 63: 83 ec 0c sub $0xc,%esp
  63. 66: 50 push %eax
  64. 67: e8 fc ff ff ff call 68 <main+0x3a>
  65. 68: R_386_PC32 func
  66. 6c: 83 c4 10 add $0x10,%esp
  67. 6f: 90 nop
  68. 70: 8b 4d fc mov -0x4(%ebp),%ecx
  69. 73: c9 leave
  70. 74: 8d 61 fc lea -0x4(%ecx),%esp
  71. 77: c3 ret

Contents of section .text.text 的数据的十六进制形式,总共 0x78 个字节,最左边一列是偏移量,中间 4 列是内容,最右边一列是 ASCII 码形式。下面的 Disassembly of section .text 是反汇编结果。

数据段和只读数据段

  1. ......
  2. Sections:
  3. Idx Name Size VMA LMA File off Algn
  4. 2 .data 00000008 00000000 00000000 000000b4 2**2
  5. CONTENTS, ALLOC, LOAD, DATA
  6. 4 .rodata 00000004 00000000 00000000 000000bc 2**0
  7. CONTENTS, ALLOC, LOAD, READONLY, DATA
  8. ......
  9. Contents of section .data:
  10. 0000 0a000000 14000000 ........
  11. Contents of section .rodata:
  12. 0000 25640a00 %d..
  13. .......

.data 段保存已经初始化了的全局变量和局部静态变量。elfDemo.c 中共有两个这样的变量,global_init_varlocal_static_init_var,每个变量 4 个字节,一共 8 个字节。由于小端序的原因,0a000000 表示 global_init_var 值(10)的十六进制 0x0a14000000 表示 local_static_init_var 值(20)的十六进制 0x14

.rodata 段保存只读数据,包括只读变量和字符串常量。elfDemo.c 中调用 printf 的时候,用到了一个字符串变量 %d\n,它是一种只读数据,保存在 .rodata 段中,可以从输出结果看到字符串常量的 ASCII 形式,以 \0 结尾。

BSS段

  1. Sections:
  2. Idx Name Size VMA LMA File off Algn
  3. 3 .bss 00000004 00000000 00000000 000000bc 2**2
  4. ALLOC

.bss 段保存未初始化的全局变量和局部静态变量。

ELF 文件结构

对象文件参与程序链接(构建程序)和程序执行(运行程序)。ELF 结构几相关信息在 /usr/include/elf.h 文件中。

img

  • ELF 文件头(ELF Header) 在目标文件格式的最前面,包含了描述整个文件的基本属性。
  • 程序头表(Program Header Table) 是可选的,它告诉系统怎样创建一个进程映像。可执行文件必须有程序头表,而重定位文件不需要。
  • 段(Section) 包含了链接视图中大量的目标文件信息。
  • 段表(Section Header Table) 包含了描述文件中所有段的信息。

32位数据类型

名称 长度 对其 描述 原始类型
Elf32_Addr 4 4 无符号程序地址 uint32_t
Elf32_Half 2 2 无符号短整型 uint16_t
Elf32_Off 4 4 无符号偏移地址 uint32_t
Elf32_Sword 4 4 有符号整型 int32_t
Elf32_Word 4 4 无符号整型 uint32_t

文件头

ELF 文件头必然存在于 ELF 文件的开头,表明这是一个 ELF 文件。定义如下:

  1. typedef struct
  2. {
  3. unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
  4. Elf32_Half e_type; /* Object file type */
  5. Elf32_Half e_machine; /* Architecture */
  6. Elf32_Word e_version; /* Object file version */
  7. Elf32_Addr e_entry; /* Entry point virtual address */
  8. Elf32_Off e_phoff; /* Program header table file offset */
  9. Elf32_Off e_shoff; /* Section header table file offset */
  10. Elf32_Word e_flags; /* Processor-specific flags */
  11. Elf32_Half e_ehsize; /* ELF header size in bytes */
  12. Elf32_Half e_phentsize; /* Program header table entry size */
  13. Elf32_Half e_phnum; /* Program header table entry count */
  14. Elf32_Half e_shentsize; /* Section header table entry size */
  15. Elf32_Half e_shnum; /* Section header table entry count */
  16. Elf32_Half e_shstrndx; /* Section header string table index */
  17. } Elf32_Ehdr;
  18. typedef struct
  19. {
  20. unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
  21. Elf64_Half e_type; /* Object file type */
  22. Elf64_Half e_machine; /* Architecture */
  23. Elf64_Word e_version; /* Object file version */
  24. Elf64_Addr e_entry; /* Entry point virtual address */
  25. Elf64_Off e_phoff; /* Program header table file offset */
  26. Elf64_Off e_shoff; /* Section header table file offset */
  27. Elf64_Word e_flags; /* Processor-specific flags */
  28. Elf64_Half e_ehsize; /* ELF header size in bytes */
  29. Elf64_Half e_phentsize; /* Program header table entry size */
  30. Elf64_Half e_phnum; /* Program header table entry count */
  31. Elf64_Half e_shentsize; /* Section header table entry size */
  32. Elf64_Half e_shnum; /* Section header table entry count */
  33. Elf64_Half e_shstrndx; /* Section header string table index */
  34. } Elf64_Ehdr;

e_ident 保存着 ELF 的幻数和其他信息,最前面四个字节是幻数,用字符串表示为 \177ELF,其后的字节如果是 32 位则是 ELFCLASS32 (1),如果是 64 位则是 ELFCLASS64 (2),再其后的字节表示端序,小端序为 ELFDATA2LSB (1),大端序为 ELFDATA2LSB (2)。最后一个字节则表示 ELF 的版本。

现在我们使用 readelf 命令来查看 elfDome.out 的文件头:

  1. $ readelf -h elfDemo.out
  2. ELF Header:
  3. Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  4. Class: ELF32
  5. Data: 2's complement, little endian
  6. Version: 1 (current)
  7. OS/ABI: UNIX - System V
  8. ABI Version: 0
  9. Type: DYN (Shared object file)
  10. Machine: Intel 80386
  11. Version: 0x1
  12. Entry point address: 0x3e0
  13. Start of program headers: 52 (bytes into file)
  14. Start of section headers: 6288 (bytes into file)
  15. Flags: 0x0
  16. Size of this header: 52 (bytes)
  17. Size of program headers: 32 (bytes)
  18. Number of program headers: 9
  19. Size of section headers: 40 (bytes)
  20. Number of section headers: 30
  21. Section header string table index: 29

程序头

程序头表是由 ELF 头的 e_phoff 指定的偏移量和 e_phentsizee_phnum 共同确定大小的表格组成。e_phentsize 表示表格中程序头的大小,e_phnum 表示表格中程序头的数量。

程序头的定义如下:

  1. typedef struct
  2. {
  3. Elf32_Word p_type; /* Segment type */
  4. Elf32_Off p_offset; /* Segment file offset */
  5. Elf32_Addr p_vaddr; /* Segment virtual address */
  6. Elf32_Addr p_paddr; /* Segment physical address */
  7. Elf32_Word p_filesz; /* Segment size in file */
  8. Elf32_Word p_memsz; /* Segment size in memory */
  9. Elf32_Word p_flags; /* Segment flags */
  10. Elf32_Word p_align; /* Segment alignment */
  11. } Elf32_Phdr;
  12. typedef struct
  13. {
  14. Elf64_Word p_type; /* Segment type */
  15. Elf64_Word p_flags; /* Segment flags */
  16. Elf64_Off p_offset; /* Segment file offset */
  17. Elf64_Addr p_vaddr; /* Segment virtual address */
  18. Elf64_Addr p_paddr; /* Segment physical address */
  19. Elf64_Xword p_filesz; /* Segment size in file */
  20. Elf64_Xword p_memsz; /* Segment size in memory */
  21. Elf64_Xword p_align; /* Segment alignment */
  22. } Elf64_Phdr;

使用 readelf 来查看程序头:

  1. $ readelf -l elfDemo.out
  2. Elf file type is DYN (Shared object file)
  3. Entry point 0x3e0
  4. There are 9 program headers, starting at offset 52
  5. Program Headers:
  6. Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
  7. PHDR 0x000034 0x00000034 0x00000034 0x00120 0x00120 R E 0x4
  8. INTERP 0x000154 0x00000154 0x00000154 0x00013 0x00013 R 0x1
  9. [Requesting program interpreter: /lib/ld-linux.so.2]
  10. LOAD 0x000000 0x00000000 0x00000000 0x00780 0x00780 R E 0x1000
  11. LOAD 0x000ef4 0x00001ef4 0x00001ef4 0x00130 0x0013c RW 0x1000
  12. DYNAMIC 0x000efc 0x00001efc 0x00001efc 0x000f0 0x000f0 RW 0x4
  13. NOTE 0x000168 0x00000168 0x00000168 0x00044 0x00044 R 0x4
  14. GNU_EH_FRAME 0x000624 0x00000624 0x00000624 0x00044 0x00044 R 0x4
  15. GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10
  16. GNU_RELRO 0x000ef4 0x00001ef4 0x00001ef4 0x0010c 0x0010c R 0x1
  17. Section to Segment mapping:
  18. Segment Sections...
  19. 00
  20. 01 .interp
  21. 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
  22. 03 .init_array .fini_array .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 .dynamic .got

段表(Section Header Table)是一个以 Elf32_Shdr 结构体为元素的数组,每个结构体对应一个段,它描述了各个段的信息。ELF 文件头的 e_shoff 成员给出了段表在 ELF 中的偏移,e_shnum 成员给出了段描述符的数量,e_shentsize 给出了每个段描述符的大小。

  1. typedef struct
  2. {
  3. Elf32_Word sh_name; /* Section name (string tbl index) */
  4. Elf32_Word sh_type; /* Section type */
  5. Elf32_Word sh_flags; /* Section flags */
  6. Elf32_Addr sh_addr; /* Section virtual addr at execution */
  7. Elf32_Off sh_offset; /* Section file offset */
  8. Elf32_Word sh_size; /* Section size in bytes */
  9. Elf32_Word sh_link; /* Link to another section */
  10. Elf32_Word sh_info; /* Additional section information */
  11. Elf32_Word sh_addralign; /* Section alignment */
  12. Elf32_Word sh_entsize; /* Entry size if section holds table */
  13. } Elf32_Shdr;
  14. typedef struct
  15. {
  16. Elf64_Word sh_name; /* Section name (string tbl index) */
  17. Elf64_Word sh_type; /* Section type */
  18. Elf64_Xword sh_flags; /* Section flags */
  19. Elf64_Addr sh_addr; /* Section virtual addr at execution */
  20. Elf64_Off sh_offset; /* Section file offset */
  21. Elf64_Xword sh_size; /* Section size in bytes */
  22. Elf64_Word sh_link; /* Link to another section */
  23. Elf64_Word sh_info; /* Additional section information */
  24. Elf64_Xword sh_addralign; /* Section alignment */
  25. Elf64_Xword sh_entsize; /* Entry size if section holds table */
  26. } Elf64_Shdr;

使用 readelf 命令查看目标文件中完整的段:

  1. $ readelf -S elfDemo.o
  2. There are 15 section headers, starting at offset 0x41c:
  3. Section Headers:
  4. [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
  5. [ 0] NULL 00000000 000000 000000 00 0 0 0
  6. [ 1] .group GROUP 00000000 000034 000008 04 12 16 4
  7. [ 2] .text PROGBITS 00000000 00003c 000078 00 AX 0 0 1
  8. [ 3] .rel.text REL 00000000 000338 000048 08 I 12 2 4
  9. [ 4] .data PROGBITS 00000000 0000b4 000008 00 WA 0 0 4
  10. [ 5] .bss NOBITS 00000000 0000bc 000004 00 WA 0 0 4
  11. [ 6] .rodata PROGBITS 00000000 0000bc 000004 00 A 0 0 1
  12. [ 7] .text.__x86.get_p PROGBITS 00000000 0000c0 000004 00 AXG 0 0 1
  13. [ 8] .comment PROGBITS 00000000 0000c4 000012 01 MS 0 0 1
  14. [ 9] .note.GNU-stack PROGBITS 00000000 0000d6 000000 00 0 0 1
  15. [10] .eh_frame PROGBITS 00000000 0000d8 00007c 00 A 0 0 4
  16. [11] .rel.eh_frame REL 00000000 000380 000018 08 I 12 10 4
  17. [12] .symtab SYMTAB 00000000 000154 000140 10 13 13 4
  18. [13] .strtab STRTAB 00000000 000294 0000a2 00 0 0 1
  19. [14] .shstrtab STRTAB 00000000 000398 000082 00 0 0 1
  20. Key to Flags:
  21. W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  22. L (link order), O (extra OS processing required), G (group), T (TLS),
  23. C (compressed), x (unknown), o (OS specific), E (exclude),
  24. p (processor specific)

注意,ELF 段表的第一个元素是被保留的,类型为 NULL。

字符串表

字符串表以段的形式存在,包含了以 null 结尾的字符序列。对象文件使用这些字符串来表示符号和段名称,引用字符串时只需给出在表中的偏移即可。字符串表的第一个字符和最后一个字符为空字符,以确保所有字符串的开始和终止。通常段名为 .strtab 的字符串表是 字符串表(Strings Table),段名为 .shstrtab 的是段表字符串表(Section Header String Table)。

偏移 +0 +1 +2 +3 +4 +5 +6 +7 +8 +9
+0 \0 h e l l o \0 w o r
+10 l d \0 h e l l o w o
+20 r l d \0
偏移 字符串
0 空字符串
1 hello
7 world
13 helloworld
18 world

可以使用 readelf 读取这两个表:

  1. $ readelf -x .strtab elfDemo.o
  2. Hex dump of section '.strtab':
  3. 0x00000000 00656c66 44656d6f 2e63006c 6f63616c .elfDemo.c.local
  4. 0x00000010 5f737461 7469635f 696e6974 5f766172 _static_init_var
  5. 0x00000020 2e323139 35006c6f 63616c5f 73746174 .2195.local_stat
  6. 0x00000030 69635f75 6e696e69 745f7661 722e3231 ic_uninit_var.21
  7. 0x00000040 39360067 6c6f6261 6c5f696e 69745f76 96.global_init_v
  8. 0x00000050 61720067 6c6f6261 6c5f756e 696e6974 ar.global_uninit
  9. 0x00000060 5f766172 0066756e 63005f5f 7838362e _var.func.__x86.
  10. 0x00000070 6765745f 70635f74 68756e6b 2e617800 get_pc_thunk.ax.
  11. 0x00000080 5f474c4f 42414c5f 4f464653 45545f54 _GLOBAL_OFFSET_T
  12. 0x00000090 41424c45 5f007072 696e7466 006d6169 ABLE_.printf.mai
  13. 0x000000a0 6e00
  14. $ readelf -x .shstrtab elfDemo.o
  15. Hex dump of section '.shstrtab':
  16. 0x00000000 002e7379 6d746162 002e7374 72746162 ..symtab..strtab
  17. 0x00000010 002e7368 73747274 6162002e 72656c2e ..shstrtab..rel.
  18. 0x00000020 74657874 002e6461 7461002e 62737300 text..data..bss.
  19. 0x00000030 2e726f64 61746100 2e746578 742e5f5f .rodata..text.__
  20. 0x00000040 7838362e 6765745f 70635f74 68756e6b x86.get_pc_thunk
  21. 0x00000050 2e617800 2e636f6d 6d656e74 002e6e6f .ax..comment..no
  22. 0x00000060 74652e47 4e552d73 7461636b 002e7265 te.GNU-stack..re
  23. 0x00000070 6c2e6568 5f667261 6d65002e 67726f75 l.eh_frame..grou
  24. 0x00000080 7000

符号表

目标文件的符号表保存了定位和重定位程序的符号定义和引用所需的信息。符号表索引是这个数组的下标。索引0指向表中的第一个条目,作为未定义的符号索引。

  1. typedef struct
  2. {
  3. Elf32_Word st_name; /* Symbol name (string tbl index) */
  4. Elf32_Addr st_value; /* Symbol value */
  5. Elf32_Word st_size; /* Symbol size */
  6. unsigned char st_info; /* Symbol type and binding */
  7. unsigned char st_other; /* Symbol visibility */
  8. Elf32_Section st_shndx; /* Section index */
  9. } Elf32_Sym;
  10. typedef struct
  11. {
  12. Elf64_Word st_name; /* Symbol name (string tbl index) */
  13. unsigned char st_info; /* Symbol type and binding */
  14. unsigned char st_other; /* Symbol visibility */
  15. Elf64_Section st_shndx; /* Section index */
  16. Elf64_Addr st_value; /* Symbol value */
  17. Elf64_Xword st_size; /* Symbol size */
  18. } Elf64_Sym;

查看符号表:

  1. $ readelf -s elfDemo.o
  2. Symbol table '.symtab' contains 20 entries:
  3. Num: Value Size Type Bind Vis Ndx Name
  4. 0: 00000000 0 NOTYPE LOCAL DEFAULT UND
  5. 1: 00000000 0 FILE LOCAL DEFAULT ABS elfDemo.c
  6. 2: 00000000 0 SECTION LOCAL DEFAULT 2
  7. 3: 00000000 0 SECTION LOCAL DEFAULT 4
  8. 4: 00000000 0 SECTION LOCAL DEFAULT 5
  9. 5: 00000000 0 SECTION LOCAL DEFAULT 6
  10. 6: 00000004 4 OBJECT LOCAL DEFAULT 4 local_static_init_var.219
  11. 7: 00000000 4 OBJECT LOCAL DEFAULT 5 local_static_uninit_var.2
  12. 8: 00000000 0 SECTION LOCAL DEFAULT 7
  13. 9: 00000000 0 SECTION LOCAL DEFAULT 9
  14. 10: 00000000 0 SECTION LOCAL DEFAULT 10
  15. 11: 00000000 0 SECTION LOCAL DEFAULT 8
  16. 12: 00000000 0 SECTION LOCAL DEFAULT 1
  17. 13: 00000000 4 OBJECT GLOBAL DEFAULT 4 global_init_var
  18. 14: 00000004 4 OBJECT GLOBAL DEFAULT COM global_uninit_var
  19. 15: 00000000 46 FUNC GLOBAL DEFAULT 2 func
  20. 16: 00000000 0 FUNC GLOBAL HIDDEN 7 __x86.get_pc_thunk.ax
  21. 17: 00000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
  22. 18: 00000000 0 NOTYPE GLOBAL DEFAULT UND printf
  23. 19: 0000002e 74 FUNC GLOBAL DEFAULT 2 main

重定位

重定位是连接符号定义与符号引用的过程。可重定位文件必须具有描述如何修改段内容的信息,从而运行可执行文件和共享对象文件保存进程程序映像的正确信息。

  1. typedef struct
  2. {
  3. Elf32_Addr r_offset; /* Address */
  4. Elf32_Word r_info; /* Relocation type and symbol index */
  5. } Elf32_Rel;
  6. typedef struct
  7. {
  8. Elf64_Addr r_offset; /* Address */
  9. Elf64_Xword r_info; /* Relocation type and symbol index */
  10. Elf64_Sxword r_addend; /* Addend */
  11. } Elf64_Rela;

查看重定位表:

  1. $ readelf -r elfDemo.o
  2. Relocation section '.rel.text' at offset 0x338 contains 9 entries:
  3. Offset Info Type Sym.Value Sym. Name
  4. 00000008 00001002 R_386_PC32 00000000 __x86.get_pc_thunk.ax
  5. 0000000d 0000110a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
  6. 00000019 00000509 R_386_GOTOFF 00000000 .rodata
  7. 00000021 00001204 R_386_PLT32 00000000 printf
  8. 00000040 00001002 R_386_PC32 00000000 __x86.get_pc_thunk.ax
  9. 00000045 0000110a R_386_GOTPC 00000000 _GLOBAL_OFFSET_TABLE_
  10. 00000052 00000d09 R_386_GOTOFF 00000000 global_init_var
  11. 0000005d 00000309 R_386_GOTOFF 00000000 .data
  12. 00000068 00000f02 R_386_PC32 00000000 func
  13. Relocation section '.rel.eh_frame' at offset 0x380 contains 3 entries:
  14. Offset Info Type Sym.Value Sym. Name
  15. 00000020 00000202 R_386_PC32 00000000 .text
  16. 00000044 00000202 R_386_PC32 00000000 .text
  17. 00000070 00000802 R_386_PC32 00000000 .text.__x86.get_pc_thu

参考资料