========================== 基本概念 ========================================
ELF全称Executable and Linking format,是UNIX发布的作为应用程序二进制接口(ABI)的一部分。了解ELF的内部结构对于我们了解linux程序的运行过程是很有帮助的,在我门编译IMG的时候经常会根据特定的芯片架构来修改这些ELF的内部信息,从而使得应用程序运行的时候内存地址映射之类的都能满足我们的要求。ELF应该是一个很大的协议簇,关于相关的spec我传了一个中文版的在资源上,英文版本的google.com搜一下有很多。觉得真要把ELF完全弄清楚是需要花很长时间的,我在这里只是以自己的思路。 ELF分为三种类型:.o 可重定位文件(relocalble file),可执行文件以及共享库(shared library),三种格式基本上从结构上是一样的,只是具体到每一个结构不同。下面我们就从整体上看看这3种格式从文件内容上存储的方式,spec上有张图是比较经典的:
其实从文件存储的格式来说,上面的两种view实际上是一样的,Segment实际上就是由section组成的,将相应的一些section映射到一起就叫segment了,就是说segment是由0个或多个section组成的,实际上本质都是section。在这里我们首先来仔细了解一下section和segment的概念:section就是相同或者相似信息的集合,比如我们比较熟悉的.text .data .bss section,.text是可执行指令的集合,.data是初始化后数据的集合,.bss是未初始化数据的集合。实际上我们也可以将一个程序的所有内容都放在一起,就像dos一样,但是将可执行程序分成多个section是很有好处的,比如说我们可以将.text section放在memory的只读空间内,将可变的.data section放在memory的可写空间内。从可执行文件的角度来讲,如果一个数据未被初始化那就不需要为其分配空间,所以.data和.bss一个重要的区别就是.bss并不占用可执行文件的大小,它只是记载需要多少空间来存储这些未初始化数据,而不分配实际的空间。 关于segment,segment是只会出现可执行程序和共享库的概念,因为它是为了代码的执行而出现的。ELF会将不同的section映射到各个段,而这些段也有不同的类型,针对不同的段在运行程序的时候kernel会作不同的处理,我们下面来看一个简单的例子readelf -l a.out : $ readelf -l a.out
Elf file type is EXEC (Executable file) Entry point 0x8048310 There are 8 program headers, starting at offset 52
Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x08048034 0x08048034 0x00100 0x00100 R E 0x4 INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1 [Requesting program interpreter: /lib/ld-linux.so.2] LOAD 0x000000 0x08048000 0x08048000 0x004c0 0x004c0 R E 0x1000 LOAD 0x000f0c 0x08049f0c 0x08049f0c 0x00108 0x00110 RW 0x1000 DYNAMIC 0x000f20 0x08049f20 0x08049f20 0x000d0 0x000d0 RW 0x4 NOTE 0x000148 0x08048148 0x08048148 0x00020 0x00020 R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4 GNU_RELRO 0x000f0c 0x08049f0c 0x08049f0c 0x000f4 0x000f4 R 0x1
Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame 03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag 06 07 .ctors .dtors .jcr .dynamic .got 由上面显示的内容的后半部分,我们可以看到我们有8个段,而每个段后面都能看到这些段包含的section。可以这么说section是将程序本身的各个部分进行分类存放,而段就是从代码运行的角度再把各个section进行分类映射。
了解了这两个概念,我们再回头来看看上面的图。linking view说的是.o文件,从文件存储的角度来说,一个.o文件是由ELF header以及可选的program header(一般都没有),然后是各个section,最后就是section header组成的。Executing view是说的可执行文件以及共享库,一个可执行程序或者共享库是由ELF header,必须的program header,然后是各个segment,最后是可选的section header。虽然上面的图是这么画的,但只是一种普遍的格式,实际上除了ELF header必须在文件的头部之外,其他元素的位置都没有固定要求。 不管是section header还是ELF header都有一个固定的数据结构来描述,具体的大家可以去看spec,这里我就不贴了,下面我就从具体的例子来看看这些结构的具体内容。 首先是ELF header,我们分别来看看两种不同的header内容,分别是.o文件和可执行文件的: $ readelf -h helloword.o 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: REL (Relocatable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x0 Start of program headers: 0 (bytes into file) Start of section headers: 224 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 0 (bytes) Number of program headers: 0 Size of section headers: 40 (bytes) Number of section headers: 11 Section header string table index: 8 $ readelf -h a.out
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: 0x8048310
Start of program headers: 52 (bytes into file)
Start of section headers: 5996 (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: 36
Section header string table index: 33
首先Magic用7f 45 4c 46表示这是一个ELF文件,01表示这是32位的,再01表示LSB,后面是一些其他信息;Type表明了ELF类型,此外还有DYN (Shared object file); Entry point address只有linking view才会有,他表示.text段的虚拟地址;Start of program headers表示program header在文件里面相对于文件头的偏移,program header也只有linking view才会有值;Start of section headers表示section header在文件里面相对于文件头的偏移;Size of this header是ELF header的大小;Size of program headers是指program header table里面每个header的大小;Size of section headers是指section header table里面每个header的大小;至于number就不说了,最后的index是指有一个叫.shstrtab的section,它存储着所有的section的名字,他在所有section里面的索引就是这个index了。应该来说这个ELF header对整个ELF的内容进行了一个整体的描述,下面我们着重看看它后面的内容。
首先是section,一般系统有一些默认的section,这些section都是以. 开头的,用户也可以定义自己的section,我们首先来看看.o的section table :
$ readelf -S helloword.o
There are 11 section headers, starting at offset 0xe0:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 000026 00 AX 0 0 4
[ 2] .rel.text REL 00000000 000350 000010 08 9 1 4
[ 3] .data PROGBITS 00000000 00005c 000000 00 WA 0 0 4
[ 4] .bss NOBITS 00000000 00005c 000000 00 WA 0 0 4
[ 5] .rodata PROGBITS 00000000 00005c 00000c 00 A 0 0 1
[ 6] .comment PROGBITS 00000000 000068 000024 00 0 0 1
[ 7] .note.GNU-stack PROGBITS 00000000 00008c 000000 00 0 0 1
[ 8] .shstrtab STRTAB 00000000 00008c 000051 00 0 0 1
[ 9] .symtab SYMTAB 00000000 000298 0000a0 10 10 8 4
[10] .strtab STRTAB 00000000 000338 000017 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)
关于type这里就不说了,需要详细了解的可以去查sepc,我们来关注一些其他的东西,首先是Addr是指section在内存中的虚拟地址,因为.o文件不需要执行,所以这里都是0,off是指section与文件头之间的偏移,size是指在文件里面section占用的size。至于后面的一些FLAG则定义了一些对齐啊,是否占用内存啊,是否可写可执行啊之类的信息。
在这里有一个.rel.text section,.rel.text section只有可能出现在.o文件里面,因为它包含了一些需要重定义的Symbol,在应用程序和共享库里面也可能有.rel.dyn之类的section,他们代表着需要动态链接。我们可以简单看看relocal 的信息readelf -r helloword.o
$ readelf -r helloword.o
Relocation section '.rel.text' at offset 0x350 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
00000014 00000501 R_386_32 00000000 .rodata
00000019 00000902 R_386_PC32 00000000 puts
因为我调用了printf函数,所以它有一个对于puts的重定向。
.strtab定义了字符串表,它里面都是各个Symbol名称的字符串,而.symtab则包含了系统中的的各种symbol,它会引用.strtab里面的内容。关于符号的数据结构大家可以查spec,这里说一下它里面的几个类容:首先是绑定类型,即Symbol是global的还是local的;然后是符号类型,可能这个符号和数据有关,也可能和函数有关;然后是与之相关的section的index,也就是这个Symbol所在的section; 最后是Symbol的值,一般根据.o文件和可执行文件(或者共享库)的不同,分别代表着在所属section内部的偏移以及Symbol的虚拟地址。
下面我们看看一个可执行程序的section table并重点说说这个relocable:
$ readelf -S a.out
There are 36 section headers, starting at offset 0x176c:
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 08048134 000134 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048148 000148 000020 00 A 0 0 4
[ 3] .hash HASH 08048168 000168 000028 04 A 5 0 4
[ 4] .gnu.hash GNU_HASH 08048190 000190 000020 04 A 5 0 4
[ 5] .dynsym DYNSYM 080481b0 0001b0 000050 10 A 6 1 4
[ 6] .dynstr STRTAB 08048200 000200 00004a 00 A 0 0 1
[ 7] .gnu.version VERSYM 0804824a 00024a 00000a 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 08048254 000254 000020 00 A 6 1 4
[ 9] .rel.dyn REL 08048274 000274 000008 08 A 5 0 4
[10] .rel.plt REL 0804827c 00027c 000018 08 A 5 12 4
[11] .init PROGBITS 08048294 000294 000030 00 AX 0 0 4
[12] .plt PROGBITS 080482c4 0002c4 000040 04 AX 0 0 4
[13] .text PROGBITS 08048310 000310 00017c 00 AX 0 0 16
[14] .fini PROGBITS 0804848c 00048c 00001c 00 AX 0 0 4
[15] .rodata PROGBITS 080484a8 0004a8 000014 00 A 0 0 4
[16] .eh_frame PROGBITS 080484bc 0004bc 000004 00 A 0 0 4
[17] .ctors PROGBITS 08049f0c 000f0c 000008 00 WA 0 0 4
[18] .dtors PROGBITS 08049f14 000f14 000008 00 WA 0 0 4
[19] .jcr PROGBITS 08049f1c 000f1c 000004 00 WA 0 0 4
[20] .dynamic DYNAMIC 08049f20 000f20 0000d0 08 WA 6 0 4
[21] .got PROGBITS 08049ff0 000ff0 000004 04 WA 0 0 4
[22] .got.plt PROGBITS 08049ff4 000ff4 000018 04 WA 0 0 4
[23] .data PROGBITS 0804a00c 00100c 000008 00 WA 0 0 4
[24] .bss NOBITS 0804a014 001014 000008 00 WA 0 0 4
[25] .comment PROGBITS 00000000 001014 0000fc 00 0 0 1
[26] .debug_aranges PROGBITS 00000000 001110 000070 00 0 0 8
[27] .debug_pubnames PROGBITS 00000000 001180 000025 00 0 0 1
[28] .debug_info PROGBITS 00000000 0011a5 0001b5 00 0 0 1
[29] .debug_abbrev PROGBITS 00000000 00135a 000083 00 0 0 1
[30] .debug_line PROGBITS 00000000 0013dd 000180 00 0 0 1
[31] .debug_str PROGBITS 00000000 00155d 00008e 01 MS 0 0 1
[32] .debug_ranges PROGBITS 00000000 0015f0 000040 00 0 0 8
[33] .shstrtab STRTAB 00000000 001630 000139 00 0 0 1
[34] .symtab SYMTAB 00000000 001d0c 0004a0 10 35 54 4
[35] .strtab STRTAB 00000000 0021ac 000211 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)
relocalble说穿了就是调用了其他OBJECT中的函数或者数据,但是却不知道这些函数或者数据的地址(应该是虚拟地址?),这里有一个静态连接和动态链接的概念,所谓静态链接实际上在LD的时候,链接器会在其他OBJECT中找到要调用的数据或函数的地址,然后将地址赋值给要调用的地方,如果是静态链接库的话,它会把需要调用的代码与数据生成一个新的object放入生成的文件中,如果是动态链接的话呢程序仅仅保存需要链接的动态库的名字以及需要链接的符号。静态链接相对简单就是将需要连接的代码和数据生成一个新的object放入最后生成的可执行程序一起,我们可以看下面简单的例子:
main(){printf("hello world/n");} 编译器会生成一个包含对printf的引用的.o目标文件,因为我们没有定义printf这个符号,所以它是一个外部引用。可执行代码中将会包含一个指令来调用printf,但是在.o文件中我们并不知道这个function的实际地址,汇编器(assembler)会将printf标记为外部的,并为其生成一个重定向(relocation),这个relocation就是section中的.rel.text,我们可以用readelf -r helloword.o来看,其中最重要的就是第一个offset,它表示必须将printf的地址放在.text段的0x00000019处。当连接这个目标文件的时候,链接器(linker)会找到printf的最终地址,并将地址放在适当的偏移处,这样调用指令就指向真正的printf了。 $ objdump -d helloword.o
helloword.o: file format elf32-i386
Disassembly of section .text:
00000000 <main>:
0: 8d 4c 24 04 lea 0x4(%esp),%ecx
4: 83 e4 f0 and $0xfffffff0,%esp
7: ff 71 fc pushl -0x4(%ecx)
a: 55 push %ebp
b: 89 e5 mov %esp,%ebp
d: 51 push %ecx
e: 83 ec 04 sub $0x4,%esp
11: c7 04 24 00 00 00 00 movl $0x0,(%esp)
18: e8 fc ff ff ff call 19 <main+0x19>
1d: 83 c4 04 add $0x4,%esp
20: 59 pop %ecx
21: 5d pop %ebp
22: 8d 61 fc lea -0x4(%ecx),%esp
25: c3 ret
$ objdump -d a.out
……
080482f4 <puts@plt>:
80482f4: ff 25 08 a0 04 08 jmp *0x804a008
80482fa: 68 10 00 00 00 push $0x10
80482ff: e9 c0 ff ff ff jmp 80482c4 <_init+0x30>
……
080483c4 <main>:
80483c4: 8d 4c 24 04 lea 0x4(%esp),%ecx
80483c8: 83 e4 f0 and $0xfffffff0,%esp
80483cb: ff 71 fc pushl -0x4(%ecx)
80483ce: 55 push %ebp
80483cf: 89 e5 mov %esp,%ebp
80483d1: 51 push %ecx
80483d2: 83 ec 04 sub $0x4,%esp
80483d5: c7 04 24 b0 84 04 08 movl $0x80484b0,(%esp)
80483dc: e8 13 ff ff ff call 80482f4 <puts@plt>
动态链接的过程比较复杂,待会会单独来讲。
了解了section以后我们来看看prgram header 和 segment。还是上面的那个program header的描述,首先是program header的类型,LOAD表示这个段是可以加载进内存的;INTERP包含动态链接器的完整路径;DYNAMIC包含了动态链接的信息;PHDR也是数组,包含了program header本身在文件和内存中的大小和位置。除了type另外offset表示从文件头到这个段的偏移;Viraddr表示这个段在内存中的虚拟地址;后面的地址phyaddr只用在和物理地址相关的系统中;两个size分别说明段在文件和memory里面的大小,memory的大小必须大于等于文件里面的大小。
========================== 基本概念完 ==================================
====================== 动态链接 ========================================== 在将动态链接之前我们首先要着重讲几个关键的section,首先是前面提到的.symtab;然后就是.got section,.got(global offset table)实际上是一个数组,数组指向一些符号的绝对地址,其中第一个指向.dynamic,第二个指向link_map,link_map是一个描述已经被载入的共享object的数据结构,供动态链接器使用,第三个指向动态链接器的地址,后面就是一些需要链接的符号在共享库中的绝对地址(没动态链接前这些值指向下一条指令);第三个就是.plt(procedure linkage table)过程链接表,实际上他也和一个数组类似,第一个指向动态链接器,然后下面依次是需要动态链接的符号。.dynsym和.dynstr记载着我们需要动态链接的符号,.dynamic记载着动态链接相关的信息,应该是给动态链接器来使用的。 了解了这些信息以后我们就可以来看看动态链接的具体过程了,当我们需要动态链接一个符号的时候首先通过.plt来跳转到指定的.got表条目,如果这个.got已经动态连接了则直接跳到相应的地址执行,如果没有链接的话会加载动态链接器进行链接,链接的过程就是动态链接器在共享库中找到相应的函数的地址,然后将地址赋值给.got表:
18 #include <stdio.h> 19 int main (int argc, char * argv[]){ 20 printf("hello world/n"); 21 return 0; 22 }
我们可以看看有哪些符号需要链接:
$ readelf -r a.out Relocation section '.rel.dyn' at offset 0x274 contains 1 entries: Offset Info Type Sym.Value Sym. Name 08049ff0 00000106 R_386_GLOB_DAT 00000000 __gmon_start__ Relocation section '.rel.plt' at offset 0x27c contains 3 entries: Offset Info Type Sym.Value Sym. Name 0804a000 00000107 R_386_JUMP_SLOT 00000000 __gmon_start__ 0804a004 00000207 R_386_JUMP_SLOT 00000000 __libc_start_main 0804a008 00000307 R_386_JUMP_SLOT 00000000 puts 只有R_386_JUMP_SLOT类型的才需要使用.got和.plt进行动态链接
通过查看.dynsym也能看到上面的信息:
$ objdump -T a.out
a.out: file format elf32-i386
DYNAMIC SYMBOL TABLE:
00000000 w D *UND* 00000000 __gmon_start__
00000000 DF *UND* 00000000 GLIBC_2.0 __libc_start_main
00000000 DF *UND* 00000000 GLIBC_2.0 puts
080484ac g DO .rodata 00000004 Base _IO_stdin_used
编译之后用gdb -q a.out
<xie@ 15:30:01 ~/cstudy> $ gdb -q a.out (gdb) disassemble main Dump of assembler code for function main: 0x080483c4 <main+0>: lea 0x4(%esp),%ecx 0x080483c8 <main+4>: and $0xfffffff0,%esp 0x080483cb <main+7>: pushl -0x4(%ecx) 0x080483ce <main+10>: push %ebp 0x080483cf <main+11>: mov %esp,%ebp 0x080483d1 <main+13>: push %ecx 0x080483d2 <main+14>: sub $0x4,%esp 0x080483d5 <main+17>: movl $0x80484b0,(%esp) 0x080483dc <main+24>: call 0x80482f4 <puts@plt> 0x080483e1 <main+29>: mov $0x0,%eax 0x080483e6 <main+34>: add $0x4,%esp 0x080483e9 <main+37>: pop %ecx 0x080483ea <main+38>: pop %ebp 0x080483eb <main+39>: lea -0x4(%ecx),%esp 0x080483ee <main+42>: ret End of assembler dump. (gdb) b *0x080483dc Breakpoint 1 at 0x80483dc (gdb) r Starting program: /home/xie/cstudy/a.out
Breakpoint 1, 0x080483dc in main () Current language: auto; currently asm (gdb) disassemble 0x80482f4 Dump of assembler code for function puts@plt: 0x080482f4 <puts@plt+0>: jmp *0x804a008 0x080482fa <puts@plt+6>: push $0x10 0x080482ff <puts@plt+11>: jmp 0x80482c4 <_init+48> End of assembler dump. (gdb) x/w 0x804a008 0x804a008 <_GLOBAL_OFFSET_TABLE_+20>: 0x080482fa //此时它指向下一条指令的地址 (gdb) x/8w 0x8049ff4 0x8049ff4 <_GLOBAL_OFFSET_TABLE_>: 0x08049f20 0xb78f3670 0xb78e99b0 0x080482da 0x804a004 <_GLOBAL_OFFSET_TABLE_+16>: 0xb7771690 0x080482fa 0x00000000 0x00000000 (gdb) x/50w 0xb78f3670 0xb78f3670: 0x00000000 0xb78eff43 0x08049f20 0xb78f3958 0xb78f3680: 0x00000000 0xb78f3670 0x00000000 0xb78f3948 0xb78f3690: 0x00000000 0x08049f20 0x08049f78 0x08049f70 0xb78f36a0: 0x08049f38 0x08049f48 0x08049f50 0x00000000 0xb78f36b0: 0x00000000 0x00000000 0x08049f58 0x08049f60 0xb78f36c0: 0x08049f28 0x08049f30 0x00000000 0x00000000 0xb78f36d0: 0x00000000 0x08049f90 0x08049f98 0x08049fa0 0xb78f36e0: 0x08049f80 0x08049f68 0x00000000 0x08049f88 0xb78f36f0: 0x00000000 0x00000000 0x00000000 0x00000000 0xb78f3700: 0x00000000 0x00000000 0x00000000 0x00000000 0xb78f3710: 0x00000000 0x00000000 0x08049fb0 0x08049fa8 0xb78f3720: 0x00000000 0x00000000 0x00000000 0x00000000 0xb78f3730: 0x00000000 0x00000000 (gdb) disassemble 0x08049f20 Dump of assembler code for function _DYNAMIC: 0x08049f20 <_DYNAMIC+0>: add %eax,(%eax) 0x08049f22 <_DYNAMIC+2>: add %al,(%eax) 0x08049f24 <_DYNAMIC+4>: adc %al,(%eax) 0x08049f26 <_DYNAMIC+6>: add %al,(%eax) 0x08049f28 <_DYNAMIC+8>: or $0x0,%al 0x08049f2a <_DYNAMIC+10>: add %al,(%eax) 0x08049f2c <_DYNAMIC+12>: xchg %eax,%esp 0x08049f2d <_DYNAMIC+13>: (bad) 0x08049f2e <_DYNAMIC+14>: add $0x8,%al 0x08049f30 <_DYNAMIC+16>: or $0x8c000000,%eax 0x08049f35 <_DYNAMIC+21>: test %al,(%eax,%ecx,1) 0x08049f38 <_DYNAMIC+24>: add $0x0,%al 0x08049f3a <_DYNAMIC+26>: add %al,(%eax) 0x08049f3c <_DYNAMIC+28>: push $0xf5080481 0x08049f41 <_DYNAMIC+33>: (bad) 0x08049f42 <_DYNAMIC+34>: ljmp *-0x70(%edi) 0x08049f45 <_DYNAMIC+37>: addl $0x5,(%eax,%ecx,1) 0x08049f4c <_DYNAMIC+44>: add %al,0x60804(%edx) 0x08049f52 <_DYNAMIC+50>: add %al,(%eax) 0x08049f54 <_DYNAMIC+52>: mov $0x81,%al 0x08049f56 <_DYNAMIC+54>: add $0x8,%al 0x08049f58 <_DYNAMIC+56>: or (%eax),%al 0x08049f5a <_DYNAMIC+58>: add %al,(%eax) 0x08049f5c <_DYNAMIC+60>: dec %edx 0x08049f5d <_DYNAMIC+61>: add %al,(%eax) 0x08049f5f <_DYNAMIC+63>: add %cl,(%ebx) 0x08049f61 <_DYNAMIC+65>: add %al,(%eax) 0x08049f63 <_DYNAMIC+67>: add %dl,(%eax) 0x08049f65 <_DYNAMIC+69>: add %al,(%eax) 0x08049f67 <_DYNAMIC+71>: add %dl,0x58000000 0x08049f6d <_DYNAMIC+77>: ss 0x08049f6e <_DYNAMIC+78>: (bad) 0x08049f6f <_DYNAMIC+79>: mov $0x3,%bh 0x08049f71 <_DYNAMIC+81>: add %al,(%eax) 0x08049f73 <_DYNAMIC+83>: add %dh,%ah ---Type <return> to continue, or q <return> to quit--- 0x08049f75 <_DYNAMIC+85>: lahf 0x08049f76 <_DYNAMIC+86>: add $0x8,%al 0x08049f78 <_DYNAMIC+88>: add (%eax),%al 0x08049f7a <_DYNAMIC+90>: add %al,(%eax) 0x08049f7c <_DYNAMIC+92>: sbb %al,(%eax) 0x08049f7e <_DYNAMIC+94>: add %al,(%eax) 0x08049f80 <_DYNAMIC+96>: adc $0x0,%al 0x08049f82 <_DYNAMIC+98>: add %al,(%eax) 0x08049f84 <_DYNAMIC+100>: adc %eax,(%eax) 0x08049f86 <_DYNAMIC+102>: add %al,(%eax) 0x08049f88 <_DYNAMIC+104>: pop %ss 0x08049f89 <_DYNAMIC+105>: add %al,(%eax) 0x08049f8b <_DYNAMIC+107>: add %bh,0x4(%edx,%eax,4) 0x08049f8f <_DYNAMIC+111>: or %dl,(%ecx) 0x08049f91 <_DYNAMIC+113>: add %al,(%eax) 0x08049f93 <_DYNAMIC+115>: add %dh,0x4(%edx,%eax,4) 0x08049f97 <_DYNAMIC+119>: or %dl,(%edx) 0x08049f99 <_DYNAMIC+121>: add %al,(%eax) 0x08049f9b <_DYNAMIC+123>: add %cl,(%eax) 0x08049f9d <_DYNAMIC+125>: add %al,(%eax) 0x08049f9f <_DYNAMIC+127>: add %dl,(%ebx) 0x08049fa1 <_DYNAMIC+129>: add %al,(%eax) 0x08049fa3 <_DYNAMIC+131>: add %cl,(%eax) 0x08049fa5 <_DYNAMIC+133>: add %al,(%eax) 0x08049fa7 <_DYNAMIC+135>: add %bh,%dh 0x08049fa9 <_DYNAMIC+137>: (bad) 0x08049faa <_DYNAMIC+138>: ljmp *0x54(%edi) 0x08049fad <_DYNAMIC+141>: (bad) 0x08049fae <_DYNAMIC+142>: add $0x8,%al 0x08049fb0 <_DYNAMIC+144>: (bad) 0x08049fb1 <_DYNAMIC+145>: (bad) 0x08049fb2 <_DYNAMIC+146>: ljmp *0x1(%edi) 0x08049fb5 <_DYNAMIC+149>: add %al,(%eax) 0x08049fb7 <_DYNAMIC+151>: add %dh,%al 0x08049fb9 <_DYNAMIC+153>: (bad) 0x08049fba <_DYNAMIC+154>: ljmp *0x4a(%edi) ---Type <return> to continue, or q <return> to quit--- 0x08049fbd <_DYNAMIC+157>: (bad) 0x08049fbe <_DYNAMIC+158>: add $0x8,%al 0x08049fc0 <_DYNAMIC+160>: add %al,(%eax) 0x08049fc2 <_DYNAMIC+162>: add %al,(%eax) 0x08049fc4 <_DYNAMIC+164>: add %al,(%eax) 0x08049fc6 <_DYNAMIC+166>: add %al,(%eax) 0x08049fc8 <_DYNAMIC+168>: add %al,(%eax) 0x08049fca <_DYNAMIC+170>: add %al,(%eax) 0x08049fcc <_DYNAMIC+172>: add %al,(%eax) 0x08049fce <_DYNAMIC+174>: add %al,(%eax) 0x08049fd0 <_DYNAMIC+176>: add %al,(%eax) 0x08049fd2 <_DYNAMIC+178>: add %al,(%eax) 0x08049fd4 <_DYNAMIC+180>: add %al,(%eax) 0x08049fd6 <_DYNAMIC+182>: add %al,(%eax) 0x08049fd8 <_DYNAMIC+184>: add %al,(%eax) 0x08049fda <_DYNAMIC+186>: add %al,(%eax) 0x08049fdc <_DYNAMIC+188>: add %al,(%eax) 0x08049fde <_DYNAMIC+190>: add %al,(%eax) 0x08049fe0 <_DYNAMIC+192>: add %al,(%eax) 0x08049fe2 <_DYNAMIC+194>: add %al,(%eax) 0x08049fe4 <_DYNAMIC+196>: add %al,(%eax) 0x08049fe6 <_DYNAMIC+198>: add %al,(%eax) 0x08049fe8 <_DYNAMIC+200>: add %al,(%eax) 0x08049fea <_DYNAMIC+202>: add %al,(%eax) 0x08049fec <_DYNAMIC+204>: add %al,(%eax) 0x08049fee <_DYNAMIC+206>: add %al,(%eax) End of assembler dump. (gdb) 后面具体的动态链接库怎么解析这些符号,我就不贴了,最后动态链接器会将puts正确的地址赋值给0x804a008,以后就不需要再次动态链接了,会直接跳到指定的地址执行;另外动态链接器只有真正需要使用到这些符号的时候才会进行链接,这就是所谓的动态连接的LAZY MODE。
========================== 动态链接完 =========================================
========================== ELF 程序的加载 ======================================
ELF程序加载的方式大概是下面的过程:
1、首先kernel读取ELF文件的头部,根据头部的信息分别读入各种数据结构,找到可加载(loadable)的段,并使用mmap将这些段内容加载到内存。mmap根据段的标志位来确定映射到内存中是否可读,可写,可执行。
2、kernel从ELF文件中标记为INTERP的段中读到对应的动态链接器的名称,并加载动态链接器,现在linux的动态链接器一般是/lib/ld-linux.so.2。
3、kernel为新进程的堆栈设置一些标记,方便动态链接器操作,然后将将控制传递给动态链接器
4、动态链接器会检查程序对外部共享库的依赖,并在需要的时候对其进行加载,我们可以用ldd来检查程序对动态库的依赖:
$ ldd a.out
linux-gate.so.1 => (0xb76ef000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7575000)
/lib/ld-linux.so.2 (0xb76f0000)
5、然后动态链接器会对程序的外部引用进行重定位,说穿了就是找到程序引用的外部变量/函数的虚拟地址,这些地址在共享库被加载的内存空间里面。
6、然后动态链接器将控制传递给程序,程序从.init section开始执行,最后会跳到.text section,最后是.fini section
[关于上面的究竟动态链接器是先全部解析外部符号还是引用的时候再解析还没弄明白,我觉得应该是在执行到这个Symbol的时候才会解析]
最后说明一下,程序的代码一般是现从.init section里面的_init开始的,然后才会跳到程序的入口地址.text section,在_start里面调用main函数。
========================== ELF程序的加载完 ======================================
参考:
|
|