分类: LINUX
2011-03-24 23:57:35
========================== 基本概念 ========================================
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
了解了这两个概念,我们再回头来看看上面的图。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
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了。
了解了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
编译之后用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)