ELF format -- How programs look from the inside
2010-12-21 18:02
531 查看
ELF format -- How programs look from the inside
ELF 是用于Linux系统下一种文件格式,包括目标文件,二进制文件,共享库和core dump。
它非常简单,而且具有好的输出格式。
ELF针对不同的架构具有相同的布局,但是排列次序(endianness)和字长可能不同;重定位类型,符号类型和可能具有平台相关的值,当然也包括平台相关的代码。
一个ELF文件对它包含的数据提供两种视图,链接视图和执行视图。这两个视图能够被两个头访问:Section Header Table(SHT)和Program Header Table(PHT).
Linking View: Section Header Table(SHT)
SHT提供对ELF文件所有section的概述。特别令人关注的是REL section(relocations),SYMTAB/DYNSYM(symbol tables),VERSYM/VERDEF/VERNEED sections(symbol versioning information)。
[john@localhost objmodel]$ readelf -S /bin/bash
There are 32 section headers, starting at offset 0xd5ea8:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08047134 000134 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08047148 000148 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08047168 000168 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0804718c 00018c 003600 04 A 5 0 4
[ 5] .dynsym DYNSYM 0804a78c 00378c 0083c0 10 A 8 1 4
[ 6] .gnu.liblist GNU_LIBLIST 08052b4c 00bb4c 000050 14 A 8 0 4
[ 7] .gnu.conflict RELA 08052b9c 00bb9c 000240 0c A 5 0 4
[ 8] .dynstr STRTAB 08053b3a 00cb3a 008076 00 A 0 0 1
[ 9] .gnu.version VERSYM 0805bbb0 014bb0 001078 02 A 5 0 2
[10] .gnu.version_r VERNEED 0805cc28 015c28 0000b0 00 A 8 2 4
[11] .rel.dyn REL 0805ccd8 015cd8 000040 08 A 5 0 4
[12] .rel.plt REL 0805cd18 015d18 000638 08 A 5 14 4
[13] .init PROGBITS 0805d350 016350 000030 00 AX 0 0 4
[14] .plt PROGBITS 0805d380 016380 000c80 04 AX 0 0 4
[15] .text PROGBITS 0805e000 017000 0886ac 00 AX 0 0 16
[16] .fini PROGBITS 080e66ac 09f6ac 00001c 00 AX 0 0 4
[17] .rodata PROGBITS 080e66e0 09f6e0 01905e 00 A 0 0 32
[18] .eh_frame_hdr PROGBITS 080ff740 0b8740 003aa4 00 A 0 0 4
[19] .eh_frame PROGBITS 081031e4 0bc1e4 014b84 00 A 0 0 4
[20] .ctors PROGBITS 08118000 0d1000 000008 00 WA 0 0 4
[21] .dtors PROGBITS 08118008 0d1008 000008 00 WA 0 0 4
[22] .jcr PROGBITS 08118010 0d1010 000004 00 WA 0 0 4
[23] .dynamic DYNAMIC 08118014 0d1014 0000d8 08 WA 8 0 4
[24] .got PROGBITS 081180ec 0d10ec 000004 04 WA 0 0 4
[25] .got.plt PROGBITS 081180f0 0d10f0 000328 04 WA 0 0 4
[26] .data PROGBITS 08118420 0d1420 004390 00 WA 0 0 32
[27] .dynbss PROGBITS 0811c7c0 0d57c0 000044 00 WA 0 0 32
[28] .bss NOBITS 0811c804 0d5804 004e08 00 WA 0 0 32
[29] .gnu_debuglink PROGBITS 00000000 0d5804 000010 00 0 0 4
[30] .gnu.prelink_undo PROGBITS 00000000 0d5814 00056c 01 0 0 4
[31] .shstrtab STRTAB 00000000 0d5d80 000127 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
Execution view: Program Header Table (PHT)
PHT包含一些信息,让内核知道怎么启动程序。LOAD指令决定ELF文件的那部分需要映射到内存。INTERP指令指定一个ELF解释器, 在Linux系统下一般是/lib/ld-linux.so.2。
DYNAMIC 入口点指向.dynamic段,包含用于ELF解释器设置二进制的信息。
[john@localhost objmodel]$ readelf -l /bin/bash
Elf file type is EXEC (Executable file)
Entry point 0x805e000
There are 8 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08047034 0x08047034 0x00100 0x00100 R E 0x4
INTERP 0x000134 0x08047134 0x08047134 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08047000 0x08047000 0xd0d68 0xd0d68 R E 0x1000
LOAD 0x0d1000 0x08118000 0x08118000 0x04804 0x0960c RW 0x1000
DYNAMIC 0x0d1014 0x08118014 0x08118014 0x000d8 0x000d8 RW 0x4
NOTE 0x000148 0x08047148 0x08047148 0x00044 0x00044 R 0x4
GNU_EH_FRAME 0x0b8740 0x080ff740 0x080ff740 0x03aa4 0x03aa4 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .gnu.liblist .gnu.conflict .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .dynbss .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
把它们放在一起:ELF头
STH和PTH都没有固定的位置,他们可以放在ELF文件的任何地方。 使用ELF头来找到它们,ELF头定位在文件的开始处。
第一个字节包含ELF magic(?)"/x7fELF",接着是ClassID(32/64bit ELF文件),数据格式ID(小端/大端),机器类型,等等。
在ELF头的结尾处有指向SHT和PHT的指针。
[john@localhost objmodel]$ readelf -h /bin/bash
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x805e000
Start of program headers: 52 (bytes into file)
Start of section headers: 876200 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 8
Size of section headers: 40 (bytes)
Number of section headers: 32
Section header string table index: 31
Relocation Table
重定位表指定哪里需要重定位,使程序能够运行起来。在程序中正常都是符号重定位,例如动态链接器需要用符号名来决议,然后把符号地址写到指定的重定位入口点。
重定位类型是架构相关的,这儿通常有很多相关的。在i386上最重要的是R_386_COPY 类型,意思是"只是拷贝符号地址到另一个地址", R_386_JUMP_SLOT,用于一般的PLT/GOT函数调用重定位机制。
符号值自身的决议是通过动态链接库(包含在/lib/ld-linux.so.2,通常用到ELF解释器),这是个很复杂的过程。基本上链接器搜索所有加载的ELF对象(二进制文件本身和加载的动态库),当搜索到第一个符号定义时就使用它。
[john@localhost objmodel]$ readelf -r /bin/bash | more
Relocation section '.gnu.conflict' at offset 0xbb9c contains 48 entries:
Offset Info Type Sym.Value Sym. Name + Addend
081181a8 0000002a R_386_IRELATIVE 006c00f0
081181b8 0000002a R_386_IRELATIVE 007386b0
08118278 0000002a R_386_IRELATIVE 006c0700
08118280 0000002a R_386_IRELATIVE 006bee80
081182f8 0000002a R_386_IRELATIVE 006c6bc0
08118338 0000002a R_386_IRELATIVE 006c81f0
08118358 0000002a R_386_IRELATIVE 006c0030
081183a4 0000002a R_386_IRELATIVE 006bf100
081183b0 0000002a R_386_IRELATIVE 006bf460
081183d0 0000002a R_386_IRELATIVE 00738700
081183ec 0000002a R_386_IRELATIVE 006be880
00448a88 00000001 R_386_32 0811c7c0
...
输出的符号(exported symbols)
当动态链接器通过浏览动态符号表.dynsym来搜索符号时,所有的能被其他程序使用的符号都会出现(换句话说:exported and in case of a library, part of the ABI),Application Binary Interface.
实际上处理过程是很复杂的(在.hash段中调用hashes),但是最后的结果和描述的是一样的。
[john@localhost objmodel]$ readelf -D -s /bin/bash | more
Symbol table of `.gnu.hash' for image:
Num Buc: Value Size Type Bind Vis Ndx Name
199 1: 0811f46c 4 OBJECT GLOBAL DEFAULT 28 prog_completes
200 1: 080c4780 406 FUNC GLOBAL DEFAULT 15 sh_regmatch
201 1: 0807d590 90 FUNC GLOBAL DEFAULT 15 fatal_error
202 1: 080965b0 34 FUNC GLOBAL DEFAULT 15 set_sigwinch_handler
203 2: 080b6f50 465 FUNC GLOBAL DEFAULT 15 set_shellopts
204 2: 08074730 194 FUNC GLOBAL DEFAULT 15 execute_command
205 2: 08060c30 354 FUNC GLOBAL DEFAULT 15 history_delimiting_chars
206 2: 08118ae8 20 OBJECT GLOBAL DEFAULT 26 it_builtins
...
A more detailed look at versionsort64
机警的读者也许已经注意到versionsort64已经在上文动态符号表中提到过两次,这两个符号具有不同的值。
原因很简单,libc.so.6使用符号版本,这里有两个versionsort64版本是有效的。很不幸,binutils(二进制工具集)readelf 不能显示符号版本,eu-readelf 来至于elfutils包
[john@localhost objmodel]$ readelf -D -s /lib/libc.so.6 | grep versionsort64
1127 9: 006e19a0 31 FUNC GLOBAL DEFAULT 12 versionsort64
1126 9: 00766a40 31 FUNC GLOBAL DEFAULT 12 versionsort64
1126 498: 00766a40 31 FUNC GLOBAL DEFAULT 12 versionsort64
1127 498: 006e19a0 31 FUNC GLOBAL DEFAULT 12 versionsort64
内核载入程序
ELF文件本身不是特别有趣。ELF文件是怎样被载入内存的,在程序能够执行自己核心之前会发生什么。
一个程序的执行开始与内核的内部,也就是在系统调用中开始。查找文件类型,调用合适的处理程序。binfmt-elf处理程序加载ELF头和PHT, 紧接着是大量完整性检查。
然后内核在PHT的LOAD指令下部分的载入到内存。假如INTERP进入点已经存在,解释器也会被加载。静态链接的二进制可以不需要解释器,动态链接的程序总是需要/lib/ld-linux.so来解释,因为它包含许多启动代码,loads shared libraries needed by the binary, and performs relocation?
最后控制权会转移到程序中,(到解释器中,如果存在,否则就在二进制本身)to the interpreter, if present, otherwise to the binary itself。
[john@localhost objmodel]$ readelf -l /bin/bash
Elf file type is EXEC (Executable file)
Entry point 0x805e000
There are 8 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08047034 0x08047034 0x00100 0x00100 R E 0x4
INTERP 0x000134 0x08047134 0x08047134 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08047000 0x08047000 0xd0d68 0xd0d68 R E 0x1000
LOAD 0x0d1000 0x08118000 0x08118000 0x04804 0x0960c RW 0x1000
DYNAMIC 0x0d1014 0x08118014 0x08118014 0x000d8 0x000d8 RW 0x4
NOTE 0x000148 0x08047148 0x08047148 0x00044 0x00044 R 0x4
GNU_EH_FRAME 0x0b8740 0x080ff740 0x080ff740 0x03aa4 0x03aa4 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .gnu.liblist .gnu.conflict .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .dynbss .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
动态链接和ELF解释器
In case of a statically linked binary that's pretty much it, however with dynamically linked binaries a lot more magic has to go on
首先动态链接器(包含在解释器中)查看.dynamic段,它的地址存储在PHT中。
当发现NEEDED进入点是,在程序运行之前决定哪个库要不加载,×REL× 进入点包含重定位表的地址,×VER*进入点包含符号版本信息,等等。
所以动态链接器加载必要的库,执行重定位(无论是直接在程序启动时或之后,尽快重定位符号是必要的,根据不同的重定位类型。
最后控制权转交给二进制中符号_start的地址。通常许多gcc/glibc启动代码就在这里,最后调用main()。
[john@localhost objmodel]$ readelf -d /bin/bash
Dynamic section at offset 0xd1014 contains 26 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libtinfo.so.5]
0x00000001 (NEEDED) Shared library: [libdl.so.2]
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000c (INIT) 0x805d350
0x0000000d (FINI) 0x80e66ac
0x6ffffef5 (GNU_HASH) 0x804718c
0x00000005 (STRTAB) 0x8053b3a
0x00000006 (SYMTAB) 0x804a78c
0x0000000a (STRSZ) 32867 (bytes)
0x0000000b (SYMENT) 16 (bytes)
0x00000015 (DEBUG) 0x0
0x00000003 (PLTGOT) 0x81180f0
0x00000002 (PLTRELSZ) 1592 (bytes)
0x00000014 (PLTREL) REL
0x00000017 (JMPREL) 0x805cd18
0x00000011 (REL) 0x805ccd8
0x00000012 (RELSZ) 64 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x6ffffffe (VERNEED) 0x805cc28
0x6fffffff (VERNEEDNUM) 2
0x6ffffff0 (VERSYM) 0x805bbb0
0x6ffffef9 (GNU_LIBLIST) 0x8052b4c
0x6ffffdf7 (GNU_LIBLISTSZ) 80 (bytes)
0x6ffffef8 (GNU_CONFLICT) 0x8052b9c
0x6ffffdf6 (GNU_CONFLICTSZ) 576 (bytes)
0x00000000 (NULL) 0x0
动态链接器的符号查找(symbol lookup by the dynamic linker)
在上面提到,符号查找是一个复杂的过程,我将会给一个简单的描述。
对于每个加载的对象,RTLD(Runtime dynamic linker)保持一个加载对象的列表,叫做“查找范围(lookup scope)”,每个范围包含指向所有加载对象的指针(二进制和所有加载的库),但是不同的范围对象的顺序是不同的。可是在每个scope中binary总是第一个对象。
当RTLD决议一个符号时,它第一步检查那个对象需要执行重定位。是查找的二进制文件本身或者一个加载的库。然后它给出那个对象的查找范围,迭代之中的每一个对象。
对于每一个对象,它在动态符号表中查找需要的符号。当匹配上后,它用符号值来做重定位,否则继续查找这个scope中的下一个对象。
符号查找规则的重要性(consequences of the symbol lookup rules)
Libs不能够直接跳转到它输出的函数上(它们当然知道它们自己的函数在哪里),但是也必须通过描述的符号查找机制。
总有这样一个事实,二进制总是第一个在每个查找范围中查找,意味着符号定义在二进制中总是覆盖符号定义在库中。
特意采用这样的方式,是允许二进制覆盖它不喜欢的库中的函数。如果没有发生,就将采用库中的函数,也不是由库本身来调用。
通常这是很好的,但是这可能带来一个问题,假如二进制不留意定义了一个符号,这个符号也在许多加载的库使用(think program uses GTK, which pulls in different theme and input plugins depending on the user's system config)。
假如程序定义一个函数void print_error(int error_code, char* str);同时许多插件也定义了同样名字的函数,但是另一个署名,例如int print_error(char* str), 这也许是有问题的。假如插件没有输出print_error符号,这里就没有问题,因为在插件中的代码只是直接调用合适的函数,并不需要查找符号表。
然而假如插件输出符号了,它就要自己查找符号表(因为那是由SystemV ABI规范所要求的,同时也有LSB).那么print_error将会被二进制中的符号干预,那是个不兼容的签名,将会引发一个崩溃。
解决方案(solutions for this)
正确的解决方案是不要输出那些你不期望其它地方用到的符号。这是TheRightWay的,因为1,加速库/插件(没有符号查找,只是在函数内部使用),你不必污染名称空间,你避免了上面所提到的可能出现的问题,最后,符号不是你ABI的一部分,那意味这你可以任意的修改,也不会弄坏任何依赖的程序。
这里有一个快速的方法来避免上述的问题。当在连接的的命令行中加入-Bsymbolic来连接一个库时,库的查找范围改变了,库本身就在第一现场,然后才是二进制。
这不能带给你任何优势,程序也不能干预你的符号,即使它想那么做。因此,-Bsymbolic应当避免,除非你真的很懒很懒!;-)
ELF 是用于Linux系统下一种文件格式,包括目标文件,二进制文件,共享库和core dump。
它非常简单,而且具有好的输出格式。
ELF针对不同的架构具有相同的布局,但是排列次序(endianness)和字长可能不同;重定位类型,符号类型和可能具有平台相关的值,当然也包括平台相关的代码。
一个ELF文件对它包含的数据提供两种视图,链接视图和执行视图。这两个视图能够被两个头访问:Section Header Table(SHT)和Program Header Table(PHT).
Linking View: Section Header Table(SHT)
SHT提供对ELF文件所有section的概述。特别令人关注的是REL section(relocations),SYMTAB/DYNSYM(symbol tables),VERSYM/VERDEF/VERNEED sections(symbol versioning information)。
[john@localhost objmodel]$ readelf -S /bin/bash
There are 32 section headers, starting at offset 0xd5ea8:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08047134 000134 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08047148 000148 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08047168 000168 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0804718c 00018c 003600 04 A 5 0 4
[ 5] .dynsym DYNSYM 0804a78c 00378c 0083c0 10 A 8 1 4
[ 6] .gnu.liblist GNU_LIBLIST 08052b4c 00bb4c 000050 14 A 8 0 4
[ 7] .gnu.conflict RELA 08052b9c 00bb9c 000240 0c A 5 0 4
[ 8] .dynstr STRTAB 08053b3a 00cb3a 008076 00 A 0 0 1
[ 9] .gnu.version VERSYM 0805bbb0 014bb0 001078 02 A 5 0 2
[10] .gnu.version_r VERNEED 0805cc28 015c28 0000b0 00 A 8 2 4
[11] .rel.dyn REL 0805ccd8 015cd8 000040 08 A 5 0 4
[12] .rel.plt REL 0805cd18 015d18 000638 08 A 5 14 4
[13] .init PROGBITS 0805d350 016350 000030 00 AX 0 0 4
[14] .plt PROGBITS 0805d380 016380 000c80 04 AX 0 0 4
[15] .text PROGBITS 0805e000 017000 0886ac 00 AX 0 0 16
[16] .fini PROGBITS 080e66ac 09f6ac 00001c 00 AX 0 0 4
[17] .rodata PROGBITS 080e66e0 09f6e0 01905e 00 A 0 0 32
[18] .eh_frame_hdr PROGBITS 080ff740 0b8740 003aa4 00 A 0 0 4
[19] .eh_frame PROGBITS 081031e4 0bc1e4 014b84 00 A 0 0 4
[20] .ctors PROGBITS 08118000 0d1000 000008 00 WA 0 0 4
[21] .dtors PROGBITS 08118008 0d1008 000008 00 WA 0 0 4
[22] .jcr PROGBITS 08118010 0d1010 000004 00 WA 0 0 4
[23] .dynamic DYNAMIC 08118014 0d1014 0000d8 08 WA 8 0 4
[24] .got PROGBITS 081180ec 0d10ec 000004 04 WA 0 0 4
[25] .got.plt PROGBITS 081180f0 0d10f0 000328 04 WA 0 0 4
[26] .data PROGBITS 08118420 0d1420 004390 00 WA 0 0 32
[27] .dynbss PROGBITS 0811c7c0 0d57c0 000044 00 WA 0 0 32
[28] .bss NOBITS 0811c804 0d5804 004e08 00 WA 0 0 32
[29] .gnu_debuglink PROGBITS 00000000 0d5804 000010 00 0 0 4
[30] .gnu.prelink_undo PROGBITS 00000000 0d5814 00056c 01 0 0 4
[31] .shstrtab STRTAB 00000000 0d5d80 000127 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
Execution view: Program Header Table (PHT)
PHT包含一些信息,让内核知道怎么启动程序。LOAD指令决定ELF文件的那部分需要映射到内存。INTERP指令指定一个ELF解释器, 在Linux系统下一般是/lib/ld-linux.so.2。
DYNAMIC 入口点指向.dynamic段,包含用于ELF解释器设置二进制的信息。
[john@localhost objmodel]$ readelf -l /bin/bash
Elf file type is EXEC (Executable file)
Entry point 0x805e000
There are 8 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08047034 0x08047034 0x00100 0x00100 R E 0x4
INTERP 0x000134 0x08047134 0x08047134 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08047000 0x08047000 0xd0d68 0xd0d68 R E 0x1000
LOAD 0x0d1000 0x08118000 0x08118000 0x04804 0x0960c RW 0x1000
DYNAMIC 0x0d1014 0x08118014 0x08118014 0x000d8 0x000d8 RW 0x4
NOTE 0x000148 0x08047148 0x08047148 0x00044 0x00044 R 0x4
GNU_EH_FRAME 0x0b8740 0x080ff740 0x080ff740 0x03aa4 0x03aa4 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .gnu.liblist .gnu.conflict .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .dynbss .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
把它们放在一起:ELF头
STH和PTH都没有固定的位置,他们可以放在ELF文件的任何地方。 使用ELF头来找到它们,ELF头定位在文件的开始处。
第一个字节包含ELF magic(?)"/x7fELF",接着是ClassID(32/64bit ELF文件),数据格式ID(小端/大端),机器类型,等等。
在ELF头的结尾处有指向SHT和PHT的指针。
[john@localhost objmodel]$ readelf -h /bin/bash
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x805e000
Start of program headers: 52 (bytes into file)
Start of section headers: 876200 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 8
Size of section headers: 40 (bytes)
Number of section headers: 32
Section header string table index: 31
Relocation Table
重定位表指定哪里需要重定位,使程序能够运行起来。在程序中正常都是符号重定位,例如动态链接器需要用符号名来决议,然后把符号地址写到指定的重定位入口点。
重定位类型是架构相关的,这儿通常有很多相关的。在i386上最重要的是R_386_COPY 类型,意思是"只是拷贝符号地址到另一个地址", R_386_JUMP_SLOT,用于一般的PLT/GOT函数调用重定位机制。
符号值自身的决议是通过动态链接库(包含在/lib/ld-linux.so.2,通常用到ELF解释器),这是个很复杂的过程。基本上链接器搜索所有加载的ELF对象(二进制文件本身和加载的动态库),当搜索到第一个符号定义时就使用它。
[john@localhost objmodel]$ readelf -r /bin/bash | more
Relocation section '.gnu.conflict' at offset 0xbb9c contains 48 entries:
Offset Info Type Sym.Value Sym. Name + Addend
081181a8 0000002a R_386_IRELATIVE 006c00f0
081181b8 0000002a R_386_IRELATIVE 007386b0
08118278 0000002a R_386_IRELATIVE 006c0700
08118280 0000002a R_386_IRELATIVE 006bee80
081182f8 0000002a R_386_IRELATIVE 006c6bc0
08118338 0000002a R_386_IRELATIVE 006c81f0
08118358 0000002a R_386_IRELATIVE 006c0030
081183a4 0000002a R_386_IRELATIVE 006bf100
081183b0 0000002a R_386_IRELATIVE 006bf460
081183d0 0000002a R_386_IRELATIVE 00738700
081183ec 0000002a R_386_IRELATIVE 006be880
00448a88 00000001 R_386_32 0811c7c0
...
输出的符号(exported symbols)
当动态链接器通过浏览动态符号表.dynsym来搜索符号时,所有的能被其他程序使用的符号都会出现(换句话说:exported and in case of a library, part of the ABI),Application Binary Interface.
实际上处理过程是很复杂的(在.hash段中调用hashes),但是最后的结果和描述的是一样的。
[john@localhost objmodel]$ readelf -D -s /bin/bash | more
Symbol table of `.gnu.hash' for image:
Num Buc: Value Size Type Bind Vis Ndx Name
199 1: 0811f46c 4 OBJECT GLOBAL DEFAULT 28 prog_completes
200 1: 080c4780 406 FUNC GLOBAL DEFAULT 15 sh_regmatch
201 1: 0807d590 90 FUNC GLOBAL DEFAULT 15 fatal_error
202 1: 080965b0 34 FUNC GLOBAL DEFAULT 15 set_sigwinch_handler
203 2: 080b6f50 465 FUNC GLOBAL DEFAULT 15 set_shellopts
204 2: 08074730 194 FUNC GLOBAL DEFAULT 15 execute_command
205 2: 08060c30 354 FUNC GLOBAL DEFAULT 15 history_delimiting_chars
206 2: 08118ae8 20 OBJECT GLOBAL DEFAULT 26 it_builtins
...
A more detailed look at versionsort64
机警的读者也许已经注意到versionsort64已经在上文动态符号表中提到过两次,这两个符号具有不同的值。
原因很简单,libc.so.6使用符号版本,这里有两个versionsort64版本是有效的。很不幸,binutils(二进制工具集)readelf 不能显示符号版本,eu-readelf 来至于elfutils包
[john@localhost objmodel]$ readelf -D -s /lib/libc.so.6 | grep versionsort64
1127 9: 006e19a0 31 FUNC GLOBAL DEFAULT 12 versionsort64
1126 9: 00766a40 31 FUNC GLOBAL DEFAULT 12 versionsort64
1126 498: 00766a40 31 FUNC GLOBAL DEFAULT 12 versionsort64
1127 498: 006e19a0 31 FUNC GLOBAL DEFAULT 12 versionsort64
内核载入程序
ELF文件本身不是特别有趣。ELF文件是怎样被载入内存的,在程序能够执行自己核心之前会发生什么。
一个程序的执行开始与内核的内部,也就是在系统调用中开始。查找文件类型,调用合适的处理程序。binfmt-elf处理程序加载ELF头和PHT, 紧接着是大量完整性检查。
然后内核在PHT的LOAD指令下部分的载入到内存。假如INTERP进入点已经存在,解释器也会被加载。静态链接的二进制可以不需要解释器,动态链接的程序总是需要/lib/ld-linux.so来解释,因为它包含许多启动代码,loads shared libraries needed by the binary, and performs relocation?
最后控制权会转移到程序中,(到解释器中,如果存在,否则就在二进制本身)to the interpreter, if present, otherwise to the binary itself。
[john@localhost objmodel]$ readelf -l /bin/bash
Elf file type is EXEC (Executable file)
Entry point 0x805e000
There are 8 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08047034 0x08047034 0x00100 0x00100 R E 0x4
INTERP 0x000134 0x08047134 0x08047134 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08047000 0x08047000 0xd0d68 0xd0d68 R E 0x1000
LOAD 0x0d1000 0x08118000 0x08118000 0x04804 0x0960c RW 0x1000
DYNAMIC 0x0d1014 0x08118014 0x08118014 0x000d8 0x000d8 RW 0x4
NOTE 0x000148 0x08047148 0x08047148 0x00044 0x00044 R 0x4
GNU_EH_FRAME 0x0b8740 0x080ff740 0x080ff740 0x03aa4 0x03aa4 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .gnu.liblist .gnu.conflict .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .dynbss .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
动态链接和ELF解释器
In case of a statically linked binary that's pretty much it, however with dynamically linked binaries a lot more magic has to go on
首先动态链接器(包含在解释器中)查看.dynamic段,它的地址存储在PHT中。
当发现NEEDED进入点是,在程序运行之前决定哪个库要不加载,×REL× 进入点包含重定位表的地址,×VER*进入点包含符号版本信息,等等。
所以动态链接器加载必要的库,执行重定位(无论是直接在程序启动时或之后,尽快重定位符号是必要的,根据不同的重定位类型。
最后控制权转交给二进制中符号_start的地址。通常许多gcc/glibc启动代码就在这里,最后调用main()。
[john@localhost objmodel]$ readelf -d /bin/bash
Dynamic section at offset 0xd1014 contains 26 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libtinfo.so.5]
0x00000001 (NEEDED) Shared library: [libdl.so.2]
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000c (INIT) 0x805d350
0x0000000d (FINI) 0x80e66ac
0x6ffffef5 (GNU_HASH) 0x804718c
0x00000005 (STRTAB) 0x8053b3a
0x00000006 (SYMTAB) 0x804a78c
0x0000000a (STRSZ) 32867 (bytes)
0x0000000b (SYMENT) 16 (bytes)
0x00000015 (DEBUG) 0x0
0x00000003 (PLTGOT) 0x81180f0
0x00000002 (PLTRELSZ) 1592 (bytes)
0x00000014 (PLTREL) REL
0x00000017 (JMPREL) 0x805cd18
0x00000011 (REL) 0x805ccd8
0x00000012 (RELSZ) 64 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x6ffffffe (VERNEED) 0x805cc28
0x6fffffff (VERNEEDNUM) 2
0x6ffffff0 (VERSYM) 0x805bbb0
0x6ffffef9 (GNU_LIBLIST) 0x8052b4c
0x6ffffdf7 (GNU_LIBLISTSZ) 80 (bytes)
0x6ffffef8 (GNU_CONFLICT) 0x8052b9c
0x6ffffdf6 (GNU_CONFLICTSZ) 576 (bytes)
0x00000000 (NULL) 0x0
动态链接器的符号查找(symbol lookup by the dynamic linker)
在上面提到,符号查找是一个复杂的过程,我将会给一个简单的描述。
对于每个加载的对象,RTLD(Runtime dynamic linker)保持一个加载对象的列表,叫做“查找范围(lookup scope)”,每个范围包含指向所有加载对象的指针(二进制和所有加载的库),但是不同的范围对象的顺序是不同的。可是在每个scope中binary总是第一个对象。
当RTLD决议一个符号时,它第一步检查那个对象需要执行重定位。是查找的二进制文件本身或者一个加载的库。然后它给出那个对象的查找范围,迭代之中的每一个对象。
对于每一个对象,它在动态符号表中查找需要的符号。当匹配上后,它用符号值来做重定位,否则继续查找这个scope中的下一个对象。
符号查找规则的重要性(consequences of the symbol lookup rules)
Libs不能够直接跳转到它输出的函数上(它们当然知道它们自己的函数在哪里),但是也必须通过描述的符号查找机制。
总有这样一个事实,二进制总是第一个在每个查找范围中查找,意味着符号定义在二进制中总是覆盖符号定义在库中。
特意采用这样的方式,是允许二进制覆盖它不喜欢的库中的函数。如果没有发生,就将采用库中的函数,也不是由库本身来调用。
通常这是很好的,但是这可能带来一个问题,假如二进制不留意定义了一个符号,这个符号也在许多加载的库使用(think program uses GTK, which pulls in different theme and input plugins depending on the user's system config)。
假如程序定义一个函数void print_error(int error_code, char* str);同时许多插件也定义了同样名字的函数,但是另一个署名,例如int print_error(char* str), 这也许是有问题的。假如插件没有输出print_error符号,这里就没有问题,因为在插件中的代码只是直接调用合适的函数,并不需要查找符号表。
然而假如插件输出符号了,它就要自己查找符号表(因为那是由SystemV ABI规范所要求的,同时也有LSB).那么print_error将会被二进制中的符号干预,那是个不兼容的签名,将会引发一个崩溃。
解决方案(solutions for this)
正确的解决方案是不要输出那些你不期望其它地方用到的符号。这是TheRightWay的,因为1,加速库/插件(没有符号查找,只是在函数内部使用),你不必污染名称空间,你避免了上面所提到的可能出现的问题,最后,符号不是你ABI的一部分,那意味这你可以任意的修改,也不会弄坏任何依赖的程序。
这里有一个快速的方法来避免上述的问题。当在连接的的命令行中加入-Bsymbolic来连接一个库时,库的查找范围改变了,库本身就在第一现场,然后才是二进制。
这不能带给你任何优势,程序也不能干预你的符号,即使它想那么做。因此,-Bsymbolic应当避免,除非你真的很懒很懒!;-)
相关文章推荐
- [Salesforce] How to determine the login environment from Apex
- How can i specify the resource to get from R.res.drawable dynamically?
- Cannot nest 'PRJ/src/main/resource' inside 'PRJ/src'. To enable the nesting exclude 'main/' from 'PR
- How to Get Help With a Command from the Linux Terminal
- ZZ: How to remove 'Open in Windows Explorer' from the 'Actions Menu'
- How to convert from the color camera space to the depth camera space in Kinect For Windows
- From Intern To CEO: How 3 Execs Climbed To The Top
- How to capture video frames from the camera as images using AV Foundation
- How to Delete an Address from the Outlook Auto-Complete List
- 关于JAVA界面风格(How to Set the Look and Feel)
- How to get the "connection string" from web.config file
- How to sovle the VSS error -- Error reading from file
- [转]How to remove an assembly from the Cache if it is locked by Microsoft Installer
- How to monitor Nginx web server from the command line in real time
- How to prevent the Program Compatibility Assistant from appearing on Windows Vista
- How to access Master page controls from the Content page
- How to get the data from a cell when I click on the GridButtonColumn of the same row
- How to Find the Offending SQL from a ORA-600 or ORA-7445 Trace File (文档 ID 154170.1) 转到底部 In this
- How the bgp deterministic-med Command Differs from the bgp always-compare-med Command
- How can I get my public IP address from the command line, if I am behind a router?