您的位置:首页 > 其它

:程序是怎样被链接和加载的?

2015-06-08 07:31 232 查看
个真实的例子

我们通过一个简小的链接实例来结束对链接过程的介绍。图 3 所示为一对 C 语言源代码

文件,m.c 中的主程序调用了一个名为 a 的例程,而调用了库例程 strlen 和 write 的 a 例程

bbs.theithome.com

在 a.c 中。

---------------------------------------------------------------------------------------------

图 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));

}

---------------------------------------------------------------------------------------------

如图 4 所示,主程序 m.c 在我的 Pentium 机器上用 gcc 编译成一个典型 a.out 目标代码

格式长度为 165 字节的目标文件。该目标文件包含一个固定长度的头部,16 个字节的“文本

”段,包含只读的程序代码,16 个字节的数据段,包含字符串。其后是两个重定位项,其

中一个标明 pushl 指令将字符串 string 的地址放置在栈上为调用例程 a 作准备,另一个标明

call 指令将控制转移到例程 a。符号表分别导出和导入了_main 与_a 的定义,以及调试器需

要的其它一系列符号(每一个全局符号都会以下划线做为前缀,第 5 章中将会讲述原因)。

注意由于和字符串 string 在同一个文件中,pushl 指令引用了 string 的临时地址 0x10,而

由于_a 的地址是未知的所以 call 指令引用的地址为 0x0。

---------------------------------------------------------------------------------------------

图 1-4 m.o 的目标代码

Sections:

Idx Name Size VMA LMA File off Algn

0 .text 00000010 00000000 00000000 00000020 2**3

1 .data 00000010 00000010 00000010 00000030 2**3

Disassembly of section .text:

00000000 <_main>:

0: 55 pushl %ebp

1: 89 e5 movl %esp,%ebp

bbs.theithome.com

3: 68 10 00 00 00 pushl $0x10

4: 32 .data

8: e8 f3 ff ff ff call 0

9: DISP32 _a

d: c9 leave

e: c3 ret

...

---------------------------------------------------------------------------------------------

如图 5 所示,子程序文件 a.c 编译成一个长度为 160 字节的目标文件,包括头部, 28

字节的文本段,无数据段。两个重定位项标记了对 strlen 和 write 的 call 指令,符号表中

导出_a 并导入了_strlen 和_write。

---------------------------------------------------------------------------------------------

图 1-5 a.c 的目标代码

Sections:

Idx Name Size VMA LMA File off Algn

0 .text 0000001c 00000000 00000000 00000020 2**2

CONTENTS, ALLOC, LOAD, RELOC, CODE

1 .data 00000000 0000001c 0000001c 0000003c 2**2

CONTENTS, ALLOC, LOAD, DATA

Disassembly of section .text:

00000000 <_a>:

0: 55 pushl %ebp

1: 89 e5 movl %esp,%ebp

3: 53 pushl %ebx

4: 8b 5d 08 movl 0x8(%ebp),%ebx

7: 53 pushl %ebx

8: e8 f3 ff ff ff call 0

9: DISP32 _strlen

d: 50 pushl %eax

e: 53 pushl %ebx

f: 6a 01 pushl $0x1

11: e8 ea ff ff ff call 0

12: DISP32 _write

16: 8d 65 fc leal -4(%ebp),%esp

19: 5b popl %ebx

1a: c9 leave

1b: c3 ret

---------------------------------------------------------------------------------------------

bbs.theithome.com

为了产生一个可执行程序,链接器将这两个目标文件,以及一个标准的 C 程序启动初始

化例程,和必要的 C 库例程整合到一起,产生一个部分如图 6 所示的可执行文件。

---------------------------------------------------------------------------------------------

图 1-6 可执行程序的部分代码

Sections:

Idx Name Size VMA LMA File off Algn

0 .text 00000fe0 00001020 00001020 00000020 2**3

1 .data 00001000 00002000 00002000 00001000 2**3

2 .bss 00000000 00003000 00003000 00000000 2**3

Disassembly of section .text:

00001020 <start-c>:

...

1092: e8 0d 00 00 00 call 10a4 <_main>

...

000010a4 <_main>:

10a4: 55 pushl %ebp

10a5: 89 e5 movl %esp,%ebp

10a7: 68 24 20 00 00 pushl $0x2024

10ac: e8 03 00 00 00 call 10b4 <_a>

10b1: c9 leave

10b2: c3 ret

...

000010b4 <_a>:

10b4: 55 pushl %ebp

10b5: 89 e5 movl %esp,%ebp

10b7: 53 pushl %ebx

10b8: 8b 5d 08 movl 0x8(%ebp),%ebx

10bb: 53 pushl %ebx

10bc: e8 37 00 00 00 call 10f8 <_strlen>

10c1: 50 pushl %eax

10c2: 53 pushl %ebx

10c3: 6a 01 pushl $0x1

10c5: e8 a2 00 00 00 call 116c <_write>

10ca: 8d 65 fc leal -4(%ebp),%esp

10cd: 5b popl %ebx

10ce: c9 leave

bbs.theithome.com

10cf: c3 ret

...

000010f8 <_strlen>:

...

0000116c <_write>:

...

---------------------------------------------------------------------------------------------

链接器将每个输入文件中相应的段合并在一起,故只存在一个合并后的文本段,一个

合并后的数据段和一个 bss 段(两个输入文件不会使用的,被初始化为 0 的数据段)。由于

每个段都会被填充为 4K 对齐以满足 x86 的页尺寸,因此文本段为 4K(减去文件中 20 字节长

度的 a.out 头部,逻辑上它并不属于该段),数据段和 bss 段每个同样也是 4K 字节。

合并后的文本段包含名为 start-c 的库启动代码,由 m.o 重定位到 0x10a4 的代码,重

定位到 0x10b4 的 a.o,以及被重定位到文本段更高地址从 C 库中链接来的例程。数据段,没

有显示在这里,按照和文本段相同的顺序包含了合并后的数据段。由于_main 的代码被重定

位到地址 0x10a4,所以这个代码要被修改到 start-c 代码的 call 指令中。在 main 例程内部,

对字符串 string 的引用被重定位到 0x2024,这是 string 在数据段最终的位置,并且 call

指令中地址修改为 0x10b4,这是_a 最终确定的地址。在_a 内部,对_strlen 和_write 的 cal

l 指令也要修改为这两个例程的最终地址。

可执行程序中仍然有很多其它的 C 库例程,没有显示在这里,它们由启动代码和_write

(在稍后例子中的出错处理例程)直接或间接的调用。由于可执行程序的文件格式不是可以重

链接的,且操作系统从已知的固定位置加载它,因此它不包含重定位数据。它带有一个有助

于调试器(debugger)工作的符号表,尽管这个程序没有使用这个符号表并且可以将其删除

以节省空间。

在这个例子中,从库中链接的代码明显要多于程序本身的代码。这是很正常的,尤其

当程序使用大的图形库或窗口库,这就促进了共享库的出现,详见第 9 章和第 10 章。这个

链接好的程序大小为 8K,但若使用共享库链接则同样的程序大小仅为 264 字节。当然这是一

个像玩具一样的例子,但真实程序经常也会采用同样的方法节省空间。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: