分类:
2008-07-16 07:39:36
每个链接都由链接脚本控制着,脚本由链接器命令语言组成。脚本的主要目的是描述如何把输入文件中的节(sections)映射到输出文件中,并控制输出文件的存储布局。大多数的链接脚本就是做这些事情的,但在有必要时,脚本也可以指导链接器执行一些其他的操作。
链接器总是使用链接器脚本,如果你没有提供一个你自己的脚本文件的话,编译器会使用一个缺省的脚本,而它被编译进链接器。你可以使用"-verbose"命令行参数来显示缺省的链接脚本。而某些命令行选项,像"-r","-N"会影响缺省的链接脚本。
在命令行选项中,通过参数"-T"你可以提供你自己的链接器脚本,这样就会替换缺省的脚本了。
你还可以隐式地使用脚本,只要给一个脚本文件命名,并作为输入文件提交给链接器,就像是它们都是要被链接的文件一样。
这里我们会定义一些基本的概念和一些词汇,来描述链接器脚本语言。
链接器把一些输入文件联合在一起,生成输出文件。输出的文件和输入文件都是object文件格式,每个文件都被称为对象文件(object file),而且,输出文件还经常被称为可执行文件。但这里我们依然称之为对象文件。每个对象文件在其中都包含有一个节(section)列表,我们有时称输入文件中的节(section)为输入节(input section),同样,输出文件中的节称为输出节(output section)。
对象文件中的每一个节都有名字和大小。大多数的节还有一个相连的数据块,就是有名的"section contents"。一个被标记为可加载(loadable)的节,意味着在输出文件运行时,contents可以被加载到内存中。没有contents的节也可以被加载,实际上处了一个数组被设置外,没有其他的东西被加载(在一些情况下,存储器必须被清0)。而既不是可加载的又不是可分配的(allocatable)节,通常包含了某些调试信息。
每个可加载或可分配的输出节(output section)都有2个地址。第一个是虚拟存储地址VMA(virtual memory address),这是在输出文件执行时该节所使用的地址。第二个是加载存储地址LMA(load memory address),这是该节被加载是的地址。在大多数情况下,这两个地址是相同的。有个例子说明不同时的情况:当一个数据节(data section)加载在ROM中,后来在程序开始执行时又拷贝到RAM中(在基于ROM的系统中,这种技术经常用在初始化全局变量中)。在这种基于ROM的系统情况下,这时,ROM地址是LMA,而内存地址是VMA。
要查看一个对象文件中各个节,可以使用objdump,并使用"-h"参数。
每个对象文件也有一个符号(symbles)列表,这就是著名的符合表(symble table)。一个符号可以是"已定义"(defined)或"无定义"(undefined)的。每个符号有名字,并且每个定义了的符号还有地址。在你编译一个c/c++程序成对象文件时,每个定义的函数,全局变量,静态变量,都可以有一个"已定义"的符号。输入文件中引用的每个没有定义的函数和全局变量则变成"无定义"的符号。
使用nm可以查看对象文件中的符合,objdump并使用"-t"选项也可以。
链接器脚本是一个文本文件,包括一系列命令序列,每个命令是一个关键字,可能还带着参数,又或者是对一个符号的赋值。你可以使用分号来隔开命令,而空格则通常被忽略。像文件名,格式名等字符串通常直接输入,如果文件名包含有像用于分割文件名的逗号等有其他用处的字符的话,你可以用双引号把文件名括起来。当然没有办法在文件名中使用双引号了。
你可以使用注释,就像在C中,定界符是"/*"和"*/",和C中一样,注释在语法上等同于空格。
很多的了解脚本都比较简单。可能最简单的链接器脚本只有一个命令: 'SECTIONS'。使用'SECTIONS'命令描述输出文件的内存布局。
'SECTIONS'命令功能强大。这里描述一个简单的使用。我们假设你的只有代码(code),初始数据(initialized data)和未初始化的数据(uninitialized data)。它们要分别被放到'.text', '.data', '.bss'节中。更进一步假定它们是输入文件中的所有的节。
这个例子中,代码要加载到地址0x10000,数据要从地址0x8000000开始。链接脚本如下:
SECTIONS { . = 0x10000; .text : { *(.text) } . = 0x8000000; .data : { *(.data) } .bss : { *(.bss) } } |
'SECTIONS'命令的关键字是'SECTIONS',接着是一系列的符号(symbol)赋值,输出节(output section)描述被大括号包括着。
上面例子中,在'SECTIONS'命令里面,第一行设置一个值到一个特殊的符号'.',它是位置计数器(location counter),(像程序计数器PC)。如果你没有以某种其他的方式指定输出节(output section)的地址,地址就会是位置计数器中设置的当前值。而后,位置计数器就会以输出节的大小增加其值。在'SECTIONS'命令的开始,位置计数器是0。
第2行定义'.text'输出节。冒号是必须的语法。在大括号里,输出节名字之后,你要列出要输入节的名字,它们会放入输出节中。通配符"*"匹配任何文件名,表达式"*(.text)"意味着所有的输入文件中的输入节".text"。
因为在输出节".text"定义时,位置计数器是0x10000,所以链接器会设置输出文件中的".text"节的地址为0x10000。
剩下的行定义输出文件中的".data"和".bss"节。链接器会把输出节".data"放置到地址0x8000000。之后,链接器把输出节".data"的大小加到位置计数器的值0x8000000, 并立即设置".bss"输出节,效果是在内存中,".bss"节会紧随".data"之后。
链接器会确保每个输出节都有必要的对齐,它会在需要是增加位置计数器的值。在上面的例子中,指定的".text"和".data"节的地址都是符合对齐条件的,但是链接器可能会在".data"和".bss"间生成一个小间隙。
注:
GNU ld的详细用法及linker script的详细格式,请参考有Red Hat出品的USING系列之USING LD(另一个是USING AS)。
参考文献:
1.USING LD
2.USING AS
3.