现在的位置: 首页 > 操作系统 > 正文

linux 内存链接脚本

2018年12月17日 操作系统 ⁄ 共 2407字 ⁄ 字号 评论关闭

可能的最简单的脚本只含有一个命令: 'SECTIONS'. 你可以使用'SECTIONS'来描述输出文件的内存布局.

输入文件: 目标文件或链接脚本文件.
输出文件: 目标文件或可执行文件.
最早的计算机程序是由机器语言编写的。程序员也可先编写符号形式的汇编程序,然后手工汇编为机器码,
再交付给计算机执行。程序员在手工汇编时需要自己确定符号地址;这样做的弊端是,一旦程序稍有改动,
相关的符号地址都必须进行修正。
       产生这种弊端的原因是过早地将符号(变量和地址标号)与其地址绑定在一起。于是出现了assembler,
当程序完成后,由assembler来完成符号的地址翻译工作。
       代码库的出现使得地址分派进一步复杂化。库文件是一些有用的小程序的集合。程序员可以调用其中的
子程序来构成一个完整的程序。实际上,库的历史比assembler更久远。库中的子程序都假定是从地址0开始的,
当它被链接到一个特定的主函数中时,由重定位加载器(relocating loader)来确定其实际的地址。
       随着操作系统的出现,relocating loader从链接器(linker)中分离出来。这是因为,没有操作系统时,
一个程序可以占有机器的整个存储空间,因而程序可以被汇编和链接到固定的内存地址;而操作系统出现后,
程序则要与操作系统和其他程序共享计算机的内存,某个程序执行时的实际地址在操作系统把它加载到内存
之前是无法预知的,这样就把最终的地址绑定从链接时推后到加载时。于是linkers和loaders进行了分工,
linkers作部分地址绑定,指派各个程序内部的相对地址;loaders则在最后的重定位阶段指派实际地址。
       程序变得越来越庞大,甚至超过了系统的内存容量,因此linkers又提供了覆盖(overlay)的功能,
使得程序员可以为同一个程序的不同部分安排共享空间,根据需要在调用时进行覆盖加载。
Overlay自1960年磁盘出现直到1970年虚拟内存技术的发展,广泛应用于大型主机上;
20世纪80年代早期又重现于微型计算机,到90年代虚拟内存技术应用于PC机后又消逝。
这一技术仍然被用于存储空间受限的嵌入式领域。
       随着硬件重定位和虚拟内存的出现,linkers和loaders实际上没有这么复杂了,因为每个程序又可以占
有整个地址空间了。程序可以链接到要加载的固定地址,并使用硬件方法而非软件重定位来进行加载时的
重定位。但计算机通常会运行某个程序的多个实例,每个实例中某些部分是与其他实例相同的(只读的代码部分),
某些部分则是各不相同的(可读写的数据部分),于是compiler和assembler又改进为将程序分段(section)
处理,例如分为代码段和数据段,这样在运行多个实例时,就可以只要一分代码段的拷贝,以节省内存空间。
地址仍在链接时指派,但更多工作则推迟到linker为所有的sections指派地址。
       即使运行在同一计算机上的不同程序,也会共享部分代码,这就是库,分为静态共享库和动态共享库。
静态共享库在建库时就被绑定到特定地址,linker则在链接时把程序中对库函数的引用也绑定到特定地址;
静态库的方便性和灵活性欠缺,因为一旦库发生改变,所有的程序必须重新链接,建立静态共享库的过程
也很繁琐。而动态链接库则不同,库中的段和符号直到使用该库的程序运行时(或实际调用该库时)
才会被绑定到实际地址。

Linking vs. loading
linkers和loaders执行几个相关的但是概念上分离的操作:
(1)       Program loading:将程序从二级存储器(辅存如disk)拷贝到主存以准备运行。某些情况下

还包括分配存储单元,设置保护位,或安排虚拟内存映射到磁盘页面的虚拟地址空间。
(2)       Relocation:Compilers和assemblers通常建立起始地址为0的目标文件,但极少数计算机允
许你将程序加载到地址0。Relocation就是为程序的各个部分指派加载地址,调整程序中的代码和数据到指派
地址的过程。多数系统上,relocation通常不止一次。linker会把多个子程序链接为一个程序,并建立一个链接
好的起始地址为0的输出程序,这个过程实际上包括了把各个子程序重定位(relocate)到不同地址的过程。
当程序加载时,系统则选择实际的加载地址,并把这个链接好的程序再次重定位(relocate)到加载地址。
(3)       Symbol resolution:多个子程序之间的引用是通过符号完成的。linker使用符号的地址来对其进行引用。
尽管链接和加载之间有很多相交之处,我们仍然有理由把进行程序加载的定义为loader,把进行符号解析的
定义为linker。它们都能进行重定位,甚至还存在同时具备上述三种功能的linking loaders。
  
在现代体系结构上,产生PIC可执行代码并非难事。Jumps和Branches只要是PC-relative或是基于运行时所
设置的基址寄存器的即可,这样就不需要在加载时对程序进行重定位。为题在于数据寻址。代码不能直接使用
数据地址寻址,否则需要将数据重定位到特定地址,这样就不是PIC了;通常的解决办法是建立一个数据地址表,
并使用一个寄存器指向该表,这样代码就可以使用该基址寄存器对数据进行索引——即使如此,还有一个问题是
该基址寄存器如何获取第一个数据地址。
对于ELF格式的可执行文件,通常是代码段后面跟着数据段,并且两者之间的偏移量(offset)是固定的,
因此只要代码段中的代码将其自身的地址加载到一个寄存器中,数据段的地址与该地址就是一个确知的距离。
linker会为可执行文件所寻址的全局数据建立一个GOT(global offset table),该表中包含了所有的数据指针。
动态链接器(ld.so)则对GOT中的指针进行解析和重定位。

抱歉!评论已关闭.