1、1,目标文件及链接,2,C/C+源文件,cc1/g+,头文件,汇编文件,as,目标文件,生成库,连接命令文件,可重定位模块,ld,ar,用户库,库列表,可执行程序,3,4,目标文件是什么样的 ?,目标文件中的内容至少有编译后的机器指令代码、数据。没错,除了这些内容以外,目标文件中还包括了链接时所须要的一些信息,比如符号表、调试信息、字符串等。,5,目标文件的格式(ABI) ?,符号修饰标准、变量内层布局、函数调用方式等这些跟可执行代码二进制兼容性相关的内容称为ABI(Application Binary Interface)。我们常见的ABI格式:,A.out,PE,ELF,一般目标文件将这些
2、信息按不同的属性,以“节”(Section)的形式存储,有时候也叫“段”(Segment)。,6,struct exec unsigned long a_midmag;unsigned long a_text;unsigned long a_data;unsigned long a_bss;unsigned long a_syms;unsigned long a_entry;unsigned long a_trsize;unsigned long a_drsize;,a.out是早期unix系统使用的可执行文件格式,由AT&T设计,由其格式和头部结构可以看出,a.out格式非常紧凑,只包含程序
3、运行的必须信息(代码、数据),每个节的顺序是固定的,这种结构这种结构缺乏扩展性,如不能包含“现代”可执行文件中常见的调试信息 。现在基本上已被ELF格式取代。,A.Out 目标文件的格式,7,COFF 目标文件的格式,8,PE 目标文件的格式,9,ELF 目标文件的格式,10,int global_init_var = 84;int global_uninit_var;void func1( int i ) printf( %dn, i );int main(void) static int static_var = 85; static int static_var2; int a = 1;
4、 int b; func1( static_var + static_var2 + a + b ); return a; ,SimpleSection.c,11,SimpleSection.o各段信息,$ objdump -h SimpleSection.oSimpleSection.o: file format elf32-i386Sections:Idx Name Size VMA LMA File off Algn 0 .text 0000005b 00000000 00000000 00000034 2*2 CONTENTS, ALLOC, LOAD, RELOC, READONLY,
5、 CODE1 .data 00000008 00000000 00000000 00000090 2*2 CONTENTS, ALLOC, LOAD, DATA2 .bss 00000004 00000000 00000000 00000098 2*2 ALLOC 3 .rodata 00000004 00000000 00000000 00000098 2*0 CONTENTS, ALLOC, LOAD, READONLY, DATA4 .comment 0000002a 00000000 00000000 0000009c 2*0 CONTENTS, READONLY5 .note.GNU
6、-stack 00000000 00000000 00000000 000000c6 2*0 CONTENTS, READONLY,12,$ objdump -s -d SimpleSection.o 00000000 : 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 83 ec 08 sub $0x8,%esp 6: 8b 45 08 mov 0x8(%ebp),%eax 9: 89 44 24 04 mov %eax,0x4(%esp) d: c7 04 24 00 00 00 00 movl $0x0,(%esp) 14: e8 fc ff ff f
7、f call 15 19: c9 leave 1a: c3 ret 0000001b : 1b: 8d 4c 24 04 lea 0x4(%esp),%ecx 1f: 83 e4 f0 and $0xfffffff0,%esp 22: ff 71 fc pushl -0x4(%ecx) 25: 55 push %ebp 26: 89 e5 mov %esp,%ebp 28: 51 push %ecx 29: 83 ec 14 sub $0x14,%esp 2c: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%ebp) 33: 8b 15 04 00 00 00 mo
8、v 0x4,%edx 39: a1 00 00 00 00 mov 0x0,%eax 3e: 8d 04 02 lea (%edx,%eax,1),%eax 41: 03 45 f4 add -0xc(%ebp),%eax 44: 03 45 f8 add -0x8(%ebp),%eax 47: 89 04 24 mov %eax,(%esp) 4a: e8 fc ff ff ff call 4b 4f: 8b 45 f4 mov -0xc(%ebp),%eax 52: 83 c4 14 add $0x14,%esp 55: 59 pop %ecx 56: 5d pop %ebp 57: 8d
9、 61 fc lea -0x4(%ecx),%esp 5a: c3 ret,SimpleSection.o反汇编代码,13,ELF文件结构描述,14,ELF文件头,typedef struct unsigned char e_ident16; Elf32_Half e_type;/ Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry;/ Elf32_Off e_phoff; Elf32_Off e_shoff; Elf32_Word e_flags; Elf32_Half e_ehsize; Elf32_Half e_p
10、hentsize; Elf32_Half e_phnum; Elf32_Half e_shentsize; Elf32_Half e_shnum; Elf32_Half e_shstrndx; Elf32_Ehdr;,描述了字的大小产生此文件的系统的字节次序目标文件的类型机器类型节头表的位置其它,15,段头表,段头表目标文件中各节的位置和大小处于目标文件的末尾,16,text节 被编译程序的机器代码rodata节 诸如printf语句中的格式串和switch语句的跳转表等只读数据data节 已初始化的全局变量bss节(.comm 节) 未初始化的全局变量 在目标文件中不占实际的空间,17,ty
11、pedef struct Elf32_Word sh_name; Elf32_Word sh_type; Elf32_Word sh_flags;Elf32_Addr sh_addr;Elf32_Off sh_offset;Elf32_Word sh_size; Elf32_Word sh_link; Elf32_Word sh_info; Elf32_Word sh_addralign; Elf32_Word sh_entsize; Elf32_Shdr;,段描述符(Section header Descriptor),每个段描述符都对应一个段,18,SimpleSection.o段表信息,
12、$ readelf -S SimpleSection.oThere are 11 section headers, starting at offset 0x118:Section Headers:Nr NameType Addr Off Size ES Flg Lk Inf Al0 NULL 00000000 000000 000000 00 0 0 01 .text PROGBITS 00000000 000034 00005b 00 AX 0 0 42 .rel.textREL 00000000 000428 000028 089 1 43 .dataPROGBITS 00000000
13、000090 000008 00 WA 0 0 44 .bss NOBITS 00000000 000098 000004 00 WA 0 0 45 .rodataPROGBITS 00000000 000098 000004 00 A 0 0 16 .commentPROGBITS 00000000 00009c 00002a 00 0 0 17 .note.GNU-stackPROGBITS 00000000 0000c6 000000 00 0 0 18 .shstrtab STRTAB 00000000 0000c6 000051 00 0 0 19 .symtab SYMTAB 00
14、000000 0002d0 0000f0 10 10 10 410 .strtab STRTAB 00000000 0003c0 000066 00 0 0 1,19,SimpleSection.o的段表及所有段的位置和长度,20,符号表&字符串表,typedef struct Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Half st_shndx; Elf32_Sym;,21,模块之间组装,一个程序要想在内存中
15、运行,除了编译之外还要经过链接和装入这两个步骤。从程序员的角度来看,引入这两个步骤带来的好处就是可以直接在程序中使用printf和errno这种有意义的函数名和变量名,而不用明确指明printf和errno在标准C库中的地址。当然,为了将程序员从早期直接使用地址编程的梦魇中解救出来,编译器和汇编器在这当中做出了革命性的贡献。编译器和汇编器的出现使得程序员可以在程序中使用更具意义的符号来为函数和变量命名,这样使得程序在正确性和可读性等方面都得到了极大的提高。但是随着C语言这种支持分别编译的程序设计语言的流行,一个完整的程序往往被分割为若干个独立的部分并行开发,而各个模块间通过函数接口或全局变量进
16、行通讯。这就带来了一个问题,编译器只能在一个模块内部完成符号名到地址的转换工作,不同模块间的符号解析由谁来做呢?,22,链接是一个收集、组织程序所需的不同代码和数据的过程,以便程序能被装入内存并被执行。链接过程分为两步:-空间与地址分配扫描所有的输入目标文件,获得它们的各个段的长度、属性和位置,并且将输入目标文件中的符号定义和符号引用收集起来,统一放到一个全局符号表。这一步中,链接器将能获得所有输入目标文件的段长度,并且将它们合并,计算出输出文件中各个段合并后的长度与位置,并建立映射关系。-符号解析与重定位 使用上面第一步中收集到的所有信息,读取输入文件中段的数据、重定位信息,并且进行符号解析
17、与重定位、调整代码中的地址等。事实上第二步是链接过程的核心,特别是重定位过程。,链接,23,/*a.c*/extern int shared;int main() int a = 100; swap(,/*b.c*/int shared = 1;void swap(int *a, int *b) *a = *b = *a = *b;,看一个链接的例子,24,链接前后各个段的属性,$objdump h a.osections:Idx NameSize VMA LMA File off Algn0 .text00000034 00000000 00000000 00000034 2*2 CONTE
18、NTS, ALLOC, LOAD, RELOC, READONLY, CODE1 .data00000000 00000000 00000000 00000068 2*2 CONTENTS, ALLOC, LOAD,DATA2 .bss 00000000 00000000 00000000 00000068 2*2$objdump h b.osections:Idx NameSize VMA LMA File off Algn0 .text0000003e 00000000 00000000 00000034 2*2 CONTENTS, ALLOC, LOAD, READONLY, CODE1
19、 .data00000004 00000000 00000000 00000074 2*2 CONTENTS, ALLOC, LOAD,DATA2 .bss 00000000 00000000 00000000 00000078 2*2 ALLOC$objdump h absections:Idx NameSize VMA LMA File off Algn0 .text00000072 08048094 08048094 00000094 2*2 CONTENTS, ALLOC, LOAD, READONLY, CODE1 .data00000004 08049108 08049108 00
20、000108 2*2 CONTENTS, ALLOC, LOAD,DATA,25,目标文件、可执行文件与进程空间,26,符号解析,将每个符号引用正确地与某可重定位模块的符号表中的一个符号定义相关联,从而确定各个符号引用的位置在所有输入模块中都找不到被引用符号的定义,则打印错误消息并结束链接需要定义解析规则,解析规则函数和已初始化的全局变量称为强符号;未初始化的全局变量称为弱符号不允许有多重的强符号定义出现一个强符号定义和多个弱符号定义时,选择强符号的定义出现多个弱符号定义时,选择任意一个弱符号的定义,27,符号重定位,$ objdump -d a.o 00000000 : 0: 8d 4c 2
21、4 04 lea 0x4(%esp),%ecx 4: 83 e4 f0 and $0xfffffff0,%esp 7: ff 71 fc pushl 0xfffffffc(%ecx) a: 55 push %ebp b: 89 e5 mov %esp,%ebp d: 51 push %ecx e: 83 ec 24 sub $0x24,%esp11:c7 45 f8 64 00 00 00 movl $0x64,0xfffffff8(%ebp)18:c7 44 24 04 00 0000 movl 0x0,0x4(%esp)1f: 00 20:8d 45 f8 lea 0xfffffff8(%
22、ebp),%eax23:89 04 24 mov %eax,(%esp)26:e8 fc ff ff ff call 27 2b:83 c4 24 add $0x24,%esp2e:59 pop %ecx2f: 5d pop %ebp30:8d 61 fc lea 0xfffffffc(%ecx),%esp33:c3 ret,/*a.c*/extern int shared;int main() int a = 100; swap(,28,符号重定位,$ objdump -r a.o RELOCATION RECORDS FOR.text:OFFSET TYPE VALUE0000001C R
23、_386_32shared00000027 R_386_PC32 swap,A=保存在修正位置的值P=被修正的位置(相对于段开始的偏移量或者虚拟地址),注意该值可以通过r_offset计算得到S=符号的实际地址,即由r_info的高24位指定的符号的实际地址,重定位表结构typedef struct Elf32_Addr r_offset;Elf32_Word r_info; Elf32_Sym;,29,现在让我们假设在将a.o和b.o链接成最终可执行文件后,main函数的虚拟地址为0x1000,swap函数的虚拟地址为0x2000,shared变量的虚拟地址为0x3000。那么我们的链接器将
24、如何修正a.o里面的这两个重定位入口呢?相对寻址修正:S是符号shared的实际地址,即0x3000A是被修正位置的值,即0x00000000Shared修正后的地址为0x3000 + 0x00000000 = 0x3000绝对寻址修正: S是符号swap的实际地址,为0x2000 A是被修正位置的值:0xfffffffC(-4) P为被修正的位置,当链接成可执行文件时,这个值应该是被修正位置的虚拟地址,为0x1000+0x27 swap修正后的地址为0x2000 + (-4) + (0x1000 + 0x27) = 0xfd5,30,可执行目标文件及装入,可执行目标文件与可重定位目标文件格式
25、类似可执行目标文件的装入由装载器完成,31,典型的ELF可执行目标文件,32,Linux运行时的内存映像,33,这里描述的装入过程从概念上来说是正确的若需要了解装入过程真正是怎样工作的,必须在理解了进程、虚拟内存和内存分页等概念以后,34,处理目标文件的一些工具,ar 创建静态库,插入、删除、罗列和提取成strings 列出包含在目标文件中的所有可打印strip 从一个目标文件中删除符号表信息nm 列出一个目标文件的符号表中定义的符号size 列出目标文件中各段的名字和大小readelf 显示目标文件的完整结构,包括编码在ELF 头中的所有信息。它包括了size和nm的功能objdump可以显示目标文件中的所有信息。其最有用 的功能是反汇编.text节中的二进制指令ldd 列出可执行目标文件在运行时需要的共享库,