读书人

程序的装入跟链接

发布时间: 2013-04-12 18:33:12 作者: rapoo

程序的装入和链接

操作系统概念

问题引入:在多道程序设计环境下,要使程序运行,必须为之创建进程,以便将程序何数据装入内存。

源程序------compiler(编译程序)--------》目标模块----------Linker(链接程序)-------》装入模块。

这里要搞清楚一个问题,是先有逻辑地址,再有物理地址,也就是说逻辑地址是在链接阶段形成的,物理地址是在装入阶段形成的。

装入方式有:绝对装入(Absolute Loading Mode),可重定位装入(Relocation Loading Mode),动态运行时装入—ynamic Run—time Loading)。

一下结合<<编译原理>>进行分析一下整个程序的编译过程:

链接器和装入器的基本工作原理
一个程序要想在内存中运行,除了编译之外还要经过链接和装入这两个步骤。从程序员的角度来看,引入这两个步骤带来的好处就 是可以直接在程序中使用printf和errno这种有意义的函数名和变量名,而不用明确指明printf和errno在标准C库中的地址。当然,为 了将程序员从早期直接使用地址编程的梦魇中解救出来,编译器和汇编器在这当中做出了革命性的贡献。编译器和汇编器的出现使 得程序员可以在程序中使用更具意义的符号来为函数和变量命名,这样使得程序在正确性和可读性等方面都得到了极大的提高。但 是随着C语言这种支持分别编译的程序设计语言的流行,一个完整的程序往往被分割为若干个独立的部分并行开发,而各个模块间 通过函数接口或全局变量进行通讯。这就带来了一个问题,编译器只能在一个模块内部完成符号名到地址的转换工作,不同模块间 的符号解析由谁来做呢?比如前面所举的例子,调用printf的用户程序和实现了printf的标准C库显然就是两个不同的模块。实际上, 这个工作是由链接器来完成的。
了解决不同模块间的链接问题,链接器主要有两个工作要做——符号解析和重定位:
符号解析:当一个模块使用了在该模块中没有定义过的函数或全局变量时,编译器生成的符号表会标记出所有这样的函数或全局变量,而链接器的责任就是要到别的模块中去查找它们的定义,如果没有找到合适的定义或者找到的合适的定义不唯一,符号解析都无法正常完成。
重定位:编译器在编译生成目标文件时,通常都使用从零开始的相对地址。然而,在链接过程中,链接器将从一个指定的地址开始,根据输入的目标文件的顺序以段为单位将它们一个接一个的拼装起来。除了目标文件的拼装之外,在重定位的过程中还完成了两个任务:一是生成最终的符号表;二是对代码段中的某些位置进行修改,所有需要修改的位置都由编译器生成的重定位表指出。
举个简单的例子,上面的概念对读者来说就一目了然了。假如我们有一个程序由两部分构成,m.c中的main函数调用f.c中实现的函数sum()

$ gcc m.o f.o$ objdump -dj .text a.out | lessDisassembly of section .text:……080482c4 <main>:……80482d6:       e8 0d 00 00 00call   80482e8 <sum>80482db:       83 c4 08add    $0x8,%esp……080482e8 <sum>:……

可以看到经过重定位之后,call指令中的偏移量修改成0x0000000d了,简单的计算告诉我们:0x080482e8-0x80482db=0xd。这样,经过重定位之后最终的可执行程序就生成了。


可执行程序生成后,下一步就是将其装入内存运行。Linux下的编译器(C语言)是cc1,汇编器是as,链接器是ld,但是并没有一个实际的程序对应装入器这个概念。实际上,将 可 执行程序装入内存运行的功能是由execve(2)这一系统调用实现的。简单来讲,程序的装入主要包含以下几个步骤:

读书人网 >其他相关

热点推荐