下一讲介绍可执行程序的装载,也就是为可执行文件创建内存映像。在这之前我们要先了解可执行文件的格式,在Windows下可执行文件的格式一般为PE,而在Linux下可执行文件的格式为ELF。ELF文件的全称是Executable and Linkable Format,意为可执行的、可连接的格式。
ELF文件分为三类:
1.可重定位(relocabtable)文件,保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件。
2.可执行(executable)文件,保存着一个用来执行的程序,该文件指出了exec(BA_OS)如何来创建程序进程映像。
3.共享object文件,保存着代码和合适的数据,用来被下面的两个链接器链接。第一个是链接编辑器(静态链接),可以和其他的可重定位和共享object文件一起来创建object文件;第二个是动态链接器,联合一个可执行文件和其他的共享object文件来创建一个进程印象。
注意,ELF文件中是二进制兼容的文件(ABI,应用程序二进制接口),也就是说ELF文件已经是适应到某一种CPU体系结构的二进制文件了。可以这样来理解:ELF文件是经过编译或链接生成的文件,而编译或链接必须指定具体的CPU体系结构,故ELF文件是针对某一种CPU体系结构的二进制文件。
有位大神已经对ELF文件做了详细的介绍,我就不班门弄斧了,直接转载过来,并结合我以上的分析以供其他童鞋参考。
转载自:点击打开链接
各种讲解elf文件格式一上来就是各种数据类型,看了半天却不知道这些数据类型是干啥的,所以咱就先找个例子直接上手,这样对elf文件格式有个具体而生动的了解。
然后再去看那些手册,就完全不惧了~。
我们使用一个汇编程序max.s并对其进行编译链接产生的两个elf文件来对比分析elf文件。
例子程序max.s来自《Linux C 一站式编程》。
ps:这是一本看完可以真正可以深入理解C语言精华的书,涵盖面极广,上到数据结构、linux系统、网络通信,下到编译链接、汇编语言、内存寻址。真的很好的哦亲。
汇编程序max.s用于取一组正整数的最大值,使用的是AT&T语法,程序源代码如下
- .section .data
- data_items:
- .long 3,67,34,222,45,75,54,34,44,33,22,11,66,0
- .section .text
- .globl _start
- _start:
- movl $0, %edi
- movl data_items(,%edi,4), %eax # data_items+ 4*(edi) --> eax
- movl %eax, %ebx # (eax) --> ebx
- start_loop: # ebx store the max value
- cmpl $0, %eax
- je loop_exit
- incl %edi
- movl data_items(,%edi,4), %eax # data_items+ 4*(edi) --> eax
- cmpl %ebx, %eax
- jle start_loop # eax <= ebx
- movl %eax, %ebx # eax > ebx
- jmp start_loop
- loop_exit:
- movl $1, %eax # exit system call.
- int $0x80
程序解释:
在源代码中定义了2个section,一个是section名字叫.data,另一个section叫.text, 声明了_start为全局的符号。
在.data section中定义了一个符号data_items,在.text section中定义了3个符号_start 、 start_loop、loop_exit。其中 _start符号被定义为全局符号。
程序逻辑也很简单,依次遍历数组并比较就得出了最大值,将最大值存储在ebx中,最后使用系统调用退出。
编译
$as -o max.o max.s
链接
$ld -o max max.o
执行并测试程序
$./max
$echo $?
222
222就是max.s运行返回的最大值。
下面先来分析编译出的max.o文件
- $ du -b max.o
- 704 max.o #此elf文件大小为704B
- $ readelf -a max.o #读取elf文件
- 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: 200 (bytes into file) #section headers table在文件中的偏移
- Flags: 0x0
- Size of this header: 52 (bytes) #elf header在文件中占了52个字节
- Size of program headers: 0 (bytes)
- Number of program headers: 0 #文件中无program headers
- Size of section headers: 40 (bytes) #section headers table 中的每个section header descriptor有40B
- Number of section headers: 8 #文件中有8个section headers
- Section header string table index: 5
- 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 00002a 00 AX 0 0 4 #这是我们在max.s中定义的section, .text section
- [ 2] .rel.text REL 00000000 0002b0 000010 08 6 1 4
- [ 3] .data PROGBITS 00000000 000060 000038 00 WA 0 0 4 #这是我们在max.s中定义的section, .data section,section size 为 0x38B,即56B(14*4B)
- [ 4] .bss NOBITS 00000000 000098 000000 00 WA 0 0 4
- [ 5] .shstrtab STRTAB 00000000 000098 000030 00 0 0 1 #.shstrtab 存放各section的名字,比如".text" ".data"
- [ 6] .symtab SYMTAB 00000000 000208 000080 10 7 7 4 #.symtab 存放所有section中定义的的符号名字,比如 "data_items","start_loop"
- [ 7] .strtab STRTAB 00000000 000288 000028 00 0 0 1
- Key to Flags:
- W (write), A (alloc), X (execute), M (merge), S (strings)
- I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
- O (extra OS processing required) o (OS specific), p (processor specific)
- There are no section groups in this file.
- There are no program headers in this file.
- Relocation section '.rel.text' at offset 0x2b0 contains 2 entries: #.rel.text 告诉链接器指令哪些地方需要定位,这里表示的是.text section中需要改动的地方,在section中的偏移是8和17
- Offset Info Type Sym.Value Sym. Name
- 00000008 00000201 R_386_32 00000000 .data
- 00000017 00000201 R_386_32 00000000 .data
- There are no unwind sections in this file.
- Symbol table '.symtab' contains 8 entries: #符号就是为一个内存地址起了一个名字。
- Num: Value Size Type Bind Vis Ndx Name #Ndx表示 符号所在的的section编号见Section Headers 中的[Nr]列
- 0: 00000000 0 NOTYPE LOCAL DEFAULT UND #Value 表示此符号在相应section中的偏移
- 1: 00000000 0 SECTION LOCAL DEFAULT 1
- 2: 00000000 0 SECTION LOCAL DEFAULT 3
- 3: 00000000 0 SECTION LOCAL DEFAULT 4
- 4: 00000000 0 NOTYPE LOCAL DEFAULT 3 data_items
- 5: 0000000e 0 NOTYPE LOCAL DEFAULT 1 start_loop
- 6: 00000023 0 NOTYPE LOCAL DEFAULT 1 loop_exit
- 7: 00000000 0 NOTYPE GLOBAL DEFAULT 1 _start #这里_start 符号是GLOBAL的, 因为源代码中使用.globl _start 标明此符号为全局的
- No version information found in this file.
这是max.o文件详细的区域信息
结合readelf读出的信息,可以看到,在max.o的这个elf文件中,有3种类型的数据"区域",分别是elf header、section、section headers。
[1] elf header描述了这个elf文件的一些信息,如数据格式是big-endian 或者 little-endian、运行平台、section header 的个数等。
[2] section headers是一个表,表中的每个条目描述了一个section,如section在文件中的偏移,大小等。
[3] section中就是elf文件中“真正”的信息了。
下面来依次解释max.o中的各个section。
.data 和.text 属于PROGBITS类型的section,这是将来要正常运行的程序和代码。
.shstrtab和.strtab属于STRTAB类型的section,可以在文件中看到,它们都存着字符串,shstrtab存的是section的名字,而.strtab存的是符号的名字(符号表示一个固定的内存地址)。
.symtab是属于SYMTAB类型的section,它描述了.strtab中的符号在"内存"中对应的"内存地址",当然这里的还不是真正的内存地址,只是一个偏移量,等到链接之后就是真正的了。
.rel.text是属于REL类型的section,它为链接器正确链接提供了信息,在下面会详细解释。
$objdump -d max.o
- max.o: file format elf32-i386
- Disassembly of section .text:
- 00000000 <_start>:
- 0: bf 00 00 00 00 mov $0x0,%edi
- 5: 8b 04 bd 00 00 00 00 mov 0x0(,%edi,4),%eax
- c: 89 c3 mov %eax,%ebx
- 0000000e <start_loop>:
- e: 83 f8 00 cmp $0x0,%eax
- 11: 74 10 je 23 <loop_exit>
- 13: 47 inc %edi
- 14: 8b 04 bd 00 00 00 00 mov 0x0(,%edi,4),%eax
- 1b: 39 d8 cmp %ebx,%eax
- 1d: 7e ef jle e <start_loop>
- 1f: 89 c3 mov %eax,%ebx
- 21: eb eb jmp e <start_loop>
- 00000023 <loop_exit>:
- 23: b8 01 00 00 00 mov $0x1,%eax
- 28: cd 80 int $0x80
看一下链接之后的代码
$ld -o max max.o
$objdump -d max
- max: file format elf32-i386
- Disassembly of section .text:
- 08048074 <_start>:
- 8048074: bf 00 00 00 00 mov $0x0,%edi
- 8048079: 8b 04 bd a0 90 04 08 mov 0x80490a0(,%edi,4),%eax
- 8048080: 89 c3 mov %eax,%ebx
- 08048082 <start_loop>:
- 8048082: 83 f8 00 cmp $0x0,%eax
- 8048085: 74 10 je 8048097 <loop_exit>
- 8048087: 47 inc %edi
- 8048088: 8b 04 bd a0 90 04 08 mov 0x80490a0(,%edi,4),%eax
- 804808f: 39 d8 cmp %ebx,%eax
- 8048091: 7e ef jle 8048082 <start_loop>
- 8048093: 89 c3 mov %eax,%ebx
- 8048095: eb eb jmp 8048082 <start_loop>
- 08048097 <loop_exit>:
- 8048097: b8 01 00 00 00 mov $0x1,%eax
- 804809c: cd 80 int $0x80
经过链接,.text代码可以真正的正确运行了,可以看到:
1.跳转指令中的跳转地址由文件偏移改成了实际的内存地址。
2.注意从.data section中取数的这句,max.o中是mov 0x0(,%edi,4),%eax ,链接后被换成了正确的mov 0x80490a0(,%edi,4),%eax。
链接后的文件max区域结构如图所示
可以看到,max文件中多了一个program headers区域,以及2个segment section。
program headers 是一张表,用于描述segment section。
segment section就是真正拷贝到内存并运行的代码。
映射图如下
再使用readelf查看经过链接后的elf文件
- $ readelf -a max
- 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: 0x8048074 #elf文件的内存入口地址由0变为0x8048074了
- Start of program headers: 52 (bytes into file) #program headers table 在文件中的偏移
- Start of section headers: 256 (bytes into file) #section headers table 在文件中的偏移
- Flags: 0x0
- Size of this header: 52 (bytes)
- Size of program headers: 32 (bytes) #program headers
- Number of program headers: 2 #多了2个program headers
- Size of section headers: 40 (bytes)
- Number of section headers: 6 #少了2个section headers
- Section header string table index: 3
- Section Headers: #与max.o文件对比可以发现少了.bss 和 .rel.text两个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 08048074 000074 00002a 00 AX 0 0 4
- [ 2] .data PROGBITS 080490a0 0000a0 000038 00 WA 0 0 4
- [ 3] .shstrtab STRTAB 00000000 0000d8 000027 00 0 0 1
- [ 4] .symtab SYMTAB 00000000 0001f0 0000a0 10 5 6 4
- [ 5] .strtab STRTAB 00000000 000290 000040 00 0 0 1
- Key to Flags:
- W (write), A (alloc), X (execute), M (merge), S (strings)
- I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
- O (extra OS processing required) o (OS specific), p (processor specific)
- There are no section groups in this file.
- Program Headers: #此2个program headers 将被装入至内存中分别的2个物理页中
- Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
- LOAD 0x000000 0x08048000 0x08048000 0x0009e 0x0009e R E 0x1000 #装入至物理页0x8048000~0x8049000
- LOAD 0x0000a0 0x080490a0 0x080490a0 0x00038 0x00038 RW 0x1000 #装入至物理页0x8049000~0x804a000
- Section to Segment mapping:
- Segment Sections...
- 00 .text
- 01 .data
- There is no dynamic section in this file.
- There are no relocations in this file.
- There are no unwind sections in this file.
- Symbol table '.symtab' contains 10 entries:
- Num: Value Size Type Bind Vis Ndx Name
- 0: 00000000 0 NOTYPE LOCAL DEFAULT UND
- 1: 08048074 0 SECTION LOCAL DEFAULT 1
- 2: 080490a0 0 SECTION LOCAL DEFAULT 2
- 3: 080490a0 0 NOTYPE LOCAL DEFAULT 2 data_items
- 4: 08048082 0 NOTYPE LOCAL DEFAULT 1 start_loop
- 5: 08048097 0 NOTYPE LOCAL DEFAULT 1 loop_exit
- 6: 08048074 0 NOTYPE GLOBAL DEFAULT 1 _start
- 7: 080490d8 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
- 8: 080490d8 0 NOTYPE GLOBAL DEFAULT ABS _edata
- 9: 080490d8 0 NOTYPE GLOBAL DEFAULT ABS _end
- No version information found in this file.