linker and loader 读书笔记 一 用Helloworld 程序示例链接过程
一: 几个概念
重定位: 编译器和汇编器为每个源文件创建的目标文件的地址往往从0开始,但是没有计算机会从地址0对子程序进行加载。如果一个程序由很多子程序组成,那么所有的子程序都会被加载到位置不重叠的地址上。重定位的功能就是为程序不同的部分分配加载地址。重定位在链接及加载的过程中不仅仅发生一次。一种普遍的情形是由多个程序构建成一个大程序,并生成一个链接好的起始地址为0的输出程序,各个子程通过重定位找到自己的位置。但程序被加载时,系统会选择一个加载地址,这样链接好的程序会被整体重定位到新的加载地址。
在链接和加载过程中,不止一次的发生过重定位的动作。
符号解析:当通过多个子程序来构建一个程序时,子程序的相互引用是通过符号进行的;主程序可能会调用sqrt这个函数,(在数学库中),链接器遇到这个符号会用在数学库中sqrt这个地址来解析,然后修改目标代码使得call指令引用该地址。
程序加载:将主程序从辅助存储器拷贝到主存上准备运行。在某些情况下,加载仅仅意味着将数据从磁盘拷贝到内存中;在其他情况下,还包括分配存储空间,设置保护位或通过虚拟内存将虚拟地址映射到磁盘空间中。
重定位和符号解析的划分界限是模糊的。由于链接器已经可以用来做解析符号的引用,一种处理重定位的方法是为程序的每一个部分分配一个指向基址的符号 然后重定位地址认为是对该基址的引用。其实概念是概念,实现方法是实现方法,只不过在有些时候两者可以统一。
链接器和加载器共有的一个重要特性就是他们都会修改目标代码,他们也许是唯一比调试程序在这方面应用更为广泛的程序。这是一个独特而强大的特性,而且细节非常依赖于
机器的规格,如果做错的话就会引发令人困惑的bug。
机器的规格,如果做错的话就会引发令人困惑的bug。
二:Hello World 示例
例子的源代码:
---------------------------------------------------------------------------------------------
图1-3 源程序
源程序m.c
extern void a(char *);
int main(int ac, char **av)
{
static char string[] = "Hello, world!\n";
a(string);
}
源程序a.c
#include <unistd.h>
#include <string.h>
void a(char *s)
{
write(1, s, strlen(s));
}
---------------------------------------------------------------------------------------------
图1-3 源程序
源程序m.c
extern void a(char *);
int main(int ac, char **av)
{
static char string[] = "Hello, world!\n";
a(string);
}
源程序a.c
#include <unistd.h>
#include <string.h>
void a(char *s)
{
write(1, s, strlen(s));
}
---------------------------------------------------------------------------------------------
该程序是由两个文件组成,a.c 和 main.c 。在main.c调用了a 函数。
用GCC在Cygwin的环境上查看a.o ,main.o 的目标文件内容,及反编译的内容。
$ objdump -h a.o
a.o: file format pe-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000002c 00000000 00000000 0000008c 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 00000000 00000000 00000000 2**2
ALLOC, LOAD, DATA
2 .bss 00000000 00000000 00000000 00000000 2**2
ALLOC
a.o: file format pe-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000002c 00000000 00000000 0000008c 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 00000000 00000000 00000000 2**2
ALLOC, LOAD, DATA
2 .bss 00000000 00000000 00000000 00000000 2**2
ALLOC
$ objdump -D a.o
a.o: file format pe-i386
Disassembly of section .text:
00000000 <_a>: 每个文件都会编译成以0为首地址的目标文件。
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 18 sub $0x18,%esp
6: 8b 45 08 mov 0x8(%ebp),%eax
9: 89 04 24 mov %eax,(%esp)
c: e8 00 00 00 00 call 11 <_a+0x11> 这个11做为strelen函数的临时地址会在以后被重定位的方式替换,
11: 89 44 24 08 mov %eax,0x8(%esp)
15: 8b 45 08 mov 0x8(%ebp),%eax
18: 89 44 24 04 mov %eax,0x4(%esp)
1c: c7 04 24 01 00 00 00 movl $0x1,(%esp)
23: e8 00 00 00 00 call 28 <_a+0x28> 这个28 做为write函数的临时地址会在以后被重定位的方式替换
28: c9 leave 至于为什么这里用具体的数字而不是0来代替,不是很清楚!
29: c3 ret
2a: 90 nop
2b: 90 nop
{
红色为可执行文件的反汇编代码:
0040118c <_a>:
40118c: 55 push %ebp
40118d: 89 e5 mov %esp,%ebp
40118f: 83 ec 18 sub $0x18,%esp
401192: 8b 45 08 mov 0x8(%ebp),%eax
401195: 89 04 24 mov %eax,(%esp)
401198: e8 9b 00 00 00 call 401238 <_strlen>
40119d: 89 44 24 08 mov %eax,0x8(%esp)
4011a1: 8b 45 08 mov 0x8(%ebp),%eax
4011a4: 89 44 24 04 mov %eax,0x4(%esp)
4011a8: c7 04 24 01 00 00 00 movl $0x1,(%esp)
4011af: e8 8c 00 00 00 call 401240 <_write>
4011b4: c9 leave
4011b5: c3 ret
4011b6: 90 nop
4011b7: 90 nop
4011b8: 66 90 xchg %ax,%ax
4011ba: 66 90 xchg %ax,%ax
4011bc: 66 90 xchg %ax,%ax
4011be: 66 90 xchg %ax,%ax
401195: 89 04 24 mov %eax,(%esp)
401198: e8 9b 00 00 00 call 401238 <_strlen>
40119d: 89 44 24 08 mov %eax,0x8(%esp)
4011a1: 8b 45 08 mov 0x8(%ebp),%eax
4011a4: 89 44 24 04 mov %eax,0x4(%esp)
4011a8: c7 04 24 01 00 00 00 movl $0x1,(%esp)
4011af: e8 8c 00 00 00 call 401240 <_write>
4011b4: c9 leave
4011b5: c3 ret
4011b6: 90 nop
4011b7: 90 nop
4011b8: 66 90 xchg %ax,%ax
4011ba: 66 90 xchg %ax,%ax
4011bc: 66 90 xchg %ax,%ax
4011be: 66 90 xchg %ax,%ax
}
$ objdump -h main.o
main.o: file format pe-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000001c 00000000 00000000 0000008c 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000010 00000000 00000000 000000a8 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 00000000 00000000 00000000 2**2
ALLOC
$ objdump -D main.o
main.o: file format pe-i386
Disassembly of section .text:
00000000 <_main>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 e4 f0 and $0xfffffff0,%esp
6: 83 ec 10 sub $0x10,%esp
9: e8 00 00 00 00 call e <_main+0xe> e会被重定位
e: c7 04 24 00 00 00 00 movl $0x0,(%esp)
15: e8 00 00 00 00 call 1a <_main+0x1a> 1a 会被重定位
1a: c9 leave
1b: c3 ret
{
红色为main反汇编代码在可执行文件之中。
$ objdump -h main.o
main.o: file format pe-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000001c 00000000 00000000 0000008c 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000010 00000000 00000000 000000a8 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 00000000 00000000 00000000 2**2
ALLOC
$ objdump -D main.o
main.o: file format pe-i386
Disassembly of section .text:
00000000 <_main>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 e4 f0 and $0xfffffff0,%esp
6: 83 ec 10 sub $0x10,%esp
9: e8 00 00 00 00 call e <_main+0xe> e会被重定位
e: c7 04 24 00 00 00 00 movl $0x0,(%esp)
15: e8 00 00 00 00 call 1a <_main+0x1a> 1a 会被重定位
1a: c9 leave
1b: c3 ret
{
红色为main反汇编代码在可执行文件之中。
00401170 <_main>:
401170: 55 push %ebp
401171: 89 e5 mov %esp,%ebp
401173: 83 e4 f0 and $0xfffffff0,%esp
401176: 83 ec 10 sub $0x10,%esp
401179: e8 b2 00 00 00 call 401230 <___main>
40117e: c7 04 24 00 20 40 00 movl $0x402000,(%esp)
401185: e8 02 00 00 00 call 40118c <_a>
40118a: c9 eave
40118b: c3 ret
401170: 55 push %ebp
401171: 89 e5 mov %esp,%ebp
401173: 83 e4 f0 and $0xfffffff0,%esp
401176: 83 ec 10 sub $0x10,%esp
401179: e8 b2 00 00 00 call 401230 <___main>
40117e: c7 04 24 00 20 40 00 movl $0x402000,(%esp)
401185: e8 02 00 00 00 call 40118c <_a>
40118a: c9 eave
40118b: c3 ret
至于符号<___main>
00401230 <___main>:
401230: ff 25 7c 50 40 00 jmp *0x40507c 这里会不会是因为动态链接库的原因???。
401236: 90 nop
401237: 90 nop
401230: ff 25 7c 50 40 00 jmp *0x40507c 这里会不会是因为动态链接库的原因???。
401236: 90 nop
401237: 90 nop
}
Disassembly of section .data:
00000000 <_string.1219>:
0: 48 dec %eax
1: 65 gs
2: 6c insb (%dx),%es:(%edi)
3: 6c insb (%dx),%es:(%edi)
4: 6f outsl %ds:(%esi),(%dx)
5: 20 77 6f and %dh,0x6f(%edi)
8: 72 6c jb 76 <_string.1219+0x76>
a: 64 21 0a and %ecx,%fs:(%edx)
d: 00 00 add %al,(%eax)
...
链接的输入:
00000000 <_string.1219>:
0: 48 dec %eax
1: 65 gs
2: 6c insb (%dx),%es:(%edi)
3: 6c insb (%dx),%es:(%edi)
4: 6f outsl %ds:(%esi),(%dx)
5: 20 77 6f and %dh,0x6f(%edi)
8: 72 6c jb 76 <_string.1219+0x76>
a: 64 21 0a and %ecx,%fs:(%edx)
d: 00 00 add %al,(%eax)
...
链接的输入:
链接器将一系列的的目标文件,库及可能的命令文件作为它的输入,然后就将目标文件作为输出结果,此外也可能有诸如加载映射信息或调试符号的文件的副产品。
下图很形象的说明了输入输出。
两遍链接过程:
两遍链接过程:
当链接器运行时,会首先对输入文件进行扫描,得到各个段的大小,并收集对所有符号的定义和引用。它会创建一个列出输入文件中定义的所有段的段表,和包含所有导出、导
入符号的符号表。利用第一遍扫描得到的数据,链接器可以为符号分配数字地址,决定各个段在输出地址空间中的大小和位置,并确定每一部分在输出文件中的布局。
第二遍扫描会利用第一遍扫描中收集的信息来控制实际的链接过程。它会读取并重定位目标代码,为符号引用替换数字地址,调整代码和数据的内存地址以反映重定位的段地址,并将重定位后的代码写入到输出文件中。通常还会再向输出文件中写入文件头部信息,重定位的段和符号表信息。如果程序使用了动态链接,那么符号表中还要包含运行时链接器解析动态符号时所需的信息。在很多情况下,链接器自己将会在输出文件中生成少量代码或数据,例如用来调用覆盖中或动态链接库中的例程的“胶水代码”,或在程序启动时需要被调用的指向各初始化例程的函数指针数组。
入符号的符号表。利用第一遍扫描得到的数据,链接器可以为符号分配数字地址,决定各个段在输出地址空间中的大小和位置,并确定每一部分在输出文件中的布局。
第二遍扫描会利用第一遍扫描中收集的信息来控制实际的链接过程。它会读取并重定位目标代码,为符号引用替换数字地址,调整代码和数据的内存地址以反映重定位的段地址,并将重定位后的代码写入到输出文件中。通常还会再向输出文件中写入文件头部信息,重定位的段和符号表信息。如果程序使用了动态链接,那么符号表中还要包含运行时链接器解析动态符号时所需的信息。在很多情况下,链接器自己将会在输出文件中生成少量代码或数据,例如用来调用覆盖中或动态链接库中的例程的“胶水代码”,或在程序启动时需要被调用的指向各初始化例程的函数指针数组。
不论程序是否使用了动态链接,文件中都会保存一个程序本身不会使用的重链接或调试用符号表,但是这些信息可以被处理输出文件的其它程序所使用。有些目标代码的格式是可以重链接的,也就是一次链接器运行的输出文件可以作为下次链接器运行的输入。这要求输出文件要包含一个像输入文件中那样的符号表,以及其它会出现在输入文件中的辅助信息。几乎所有的目标代码格式都预备有调试符号,这样当程序在调试器控制下运行时,调试器可以使用这些符号让程序员通过源代码中的行号或名字来控制程序。根据目标代码格式细节的不同,调试符号可能会与链接器需要的符号混合在一个符号表中,也可能独立于链接器需要的符号表为链接器建立单独建立一个有些许冗余的符号表。
有很少的一些链接器可以在一次扫描中完成工作。他们是通过在链接过程中将输入文件的部分或全部缓冲在内存或磁盘中,并稍后读取被缓冲的信息的方法来实现的。由于这是
一个并不影响链接过程两边扫描实质的实现技巧,因此这里我们不再赘述。
以下是可执行文件信息:
有很少的一些链接器可以在一次扫描中完成工作。他们是通过在链接过程中将输入文件的部分或全部缓冲在内存或磁盘中,并稍后读取被缓冲的信息的方法来实现的。由于这是
一个并不影响链接过程两边扫描实质的实现技巧,因此这里我们不再赘述。
以下是可执行文件信息:
$ objdump -h test
test: file format pei-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000778 00401000 00401000 00000400 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE, DATA
1 .data 000000a4 00402000 00402000 00000c00 2**5
CONTENTS, ALLOC, LOAD, DATA
2 .eh_frame 00000004 00403000 00403000 00000e00 2**2
CONTENTS, ALLOC, LOAD, DATA
3 .bss 00000110 00404000 00404000 00000000 2**5
ALLOC
4 .idata 000001dc 00405000 00405000 00001000 2**2
CONTENTS, ALLOC, LOAD, DATA
5 .debug_aranges 00000160 00406000 00406000 00001200 2**0
CONTENTS, READONLY, DEBUGGING
6 .debug_pubnames 00000245 00407000 00407000 00001400 2**0
CONTENTS, READONLY, DEBUGGING
7 .debug_pubtypes 0000055f 00408000 00408000 00001800 2**0
CONTENTS, READONLY, DEBUGGING
8 .debug_info 00004c76 00409000 00409000 00001e00 2**0
CONTENTS, READONLY, DEBUGGING
9 .debug_abbrev 000008d3 0040e000 0040e000 00006c00 2**0
CONTENTS, READONLY, DEBUGGING
10 .debug_line 00000b54 0040f000 0040f000 00007600 2**0
CONTENTS, READONLY, DEBUGGING
11 .debug_frame 0000029c 00410000 00410000 00008200 2**2
CONTENTS, READONLY, DEBUGGING
12 .debug_str 00000038 00411000 00411000 00008600 2**0
CONTENTS, READONLY, DEBUGGING
test: file format pei-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000778 00401000 00401000 00000400 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE, DATA
1 .data 000000a4 00402000 00402000 00000c00 2**5
CONTENTS, ALLOC, LOAD, DATA
2 .eh_frame 00000004 00403000 00403000 00000e00 2**2
CONTENTS, ALLOC, LOAD, DATA
3 .bss 00000110 00404000 00404000 00000000 2**5
ALLOC
4 .idata 000001dc 00405000 00405000 00001000 2**2
CONTENTS, ALLOC, LOAD, DATA
5 .debug_aranges 00000160 00406000 00406000 00001200 2**0
CONTENTS, READONLY, DEBUGGING
6 .debug_pubnames 00000245 00407000 00407000 00001400 2**0
CONTENTS, READONLY, DEBUGGING
7 .debug_pubtypes 0000055f 00408000 00408000 00001800 2**0
CONTENTS, READONLY, DEBUGGING
8 .debug_info 00004c76 00409000 00409000 00001e00 2**0
CONTENTS, READONLY, DEBUGGING
9 .debug_abbrev 000008d3 0040e000 0040e000 00006c00 2**0
CONTENTS, READONLY, DEBUGGING
10 .debug_line 00000b54 0040f000 0040f000 00007600 2**0
CONTENTS, READONLY, DEBUGGING
11 .debug_frame 0000029c 00410000 00410000 00008200 2**2
CONTENTS, READONLY, DEBUGGING
12 .debug_str 00000038 00411000 00411000 00008600 2**0
CONTENTS, READONLY, DEBUGGING
链接器将每个输入文件中相应的段合并在一起,故只存在一个合并后的文本段,一个合并后的数据段和一个bss段(两个输入文件不会使用的,被初始化为0的数据段)。由于
每个段都会被填充为 4K对齐以满足x86的页尺寸(这里是在64位机win7的cygwin中编译,不过从VMA 上可以看出401000->402000->403000可以看出每个段的大小为0x1000(4K)),因此文本段为4K(减去文件中20字节长度的a.out头部,逻辑上它并不属于该段),数据段和bss段每个同样也是4K字节。
每个段都会被填充为 4K对齐以满足x86的页尺寸(这里是在64位机win7的cygwin中编译,不过从VMA 上可以看出401000->402000->403000可以看出每个段的大小为0x1000(4K)),因此文本段为4K(减去文件中20字节长度的a.out头部,逻辑上它并不属于该段),数据段和bss段每个同样也是4K字节。