分类: LINUX
2010-07-22 09:27:56
ELF 文件格式分析
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
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
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
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
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
动态链接的过程比较复杂,待会会单独来讲。
了解了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
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
$ gdb -q a.out
(gdb) disassemble main
Dump of assembler code for function
main:
0x080483c4
0x080483c8
0x080483cb
0x080483ce
0x080483cf
0x080483d1
0x080483d2
0x080483d5
0x080483dc
0x080483e1
0x080483e6
0x080483e9
0x080483ea
0x080483eb
0x080483ee
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
0x080482fa
0x080482ff
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
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
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程序的加载完 ======================================