最近,进行项目代码的移植,源代码和Makefile都相同,但是到另一个平台就是不能得到期望的效果,后来发现链接脚本文件不同,故推测可能这方面出现问题,所以就在网上找些资料,学习一下。
以下内容多摘自网络:
链接定位是系统级软件开发过程中必不可少的一部分,嵌入式软件开发均属于系统级开发,绝大部分嵌入式软件都涉及到链接定位脚本文件;链接定位脚本使得我们的目标代码组织更加灵活。
连接脚本的一个主要目的是描述输入文件中的节如何被映射到输出文件中,并控制输出文件的内存排布,几乎所有的连接脚本只做这两件事情。但是,在需要的时候,连接脚本还可以指示连接器执行很多其他的操作,这通过下面描述的命令实现。
连接器总是使用连接脚本的,如果你自己不提供,连接器会使用一个缺省的脚本,这个脚本是被编译进连接器生成可执行文件的。
你可以使用'--verbose'命令行选项来显示缺省的连接器脚本的内容。
某些命令行选项,比如'-r'或'-N',会影响缺省的连接脚本。
你可以过使用'-T'命令行选项来提供你自己的连接脚本,当你这么做的时候,你的连接脚本会替换缺省的连接脚本。
你也可以通过把连接脚本作为一个连接器的输入文件来隐式地使用它,就象它们是一个被连接的文件一样.
链接定位过程一般由链接器根据链接定位脚本完成,比较简单的系统可以通过设置链接器开关选项取代链接定位脚本;链接定位的关键是链接定位脚本的编写。
转自
1、基本的链接脚本的概念:
我们需要定义一些基本的概念与词汇以描述连接脚本语言。
连接器把多个输入文件合并成单个输出文件。输出文件和输入文件都以一种叫做'目标文件格式'的数据格式形式存在。每一个文件被叫做'目标文件'。输出文件经常被叫做'可执行文件',但是由于需要,我们也把它叫做目标文件。每一个目标文件中,在其它东西之间,有一个节列表。我们有时把输入文件的节叫做输入节;相似的,输出文件中的一个节经常被叫做输出节。
一个目标文件中的每一个节都有一个名字和一个大小尺寸,大多数节还有一个相关的数据块,称为节内容。某一个节可能被标识为'loadable',含义是在输出文件被执行时,这个节应当被载入到内存中去。一个没有内容的节可能是'allocatable', 含义是内存中必须为这个节开辟一块空间,但是没有实际的内容载入到这里(在某些情况下,这块内存必须被标识为零)。 一个既不是loadable也不是allocatable的节一般含有一些调试信息。
每一个loadable或allocatable的输出节有两个地址. 第一个是'VMA'或称为虚拟内存地址. 这是当输出文件运行时节所拥有的地址. 第二个是"LMA', 或称为载入内存地址. 这个节即将要载入的内存地址. 大多数情况下这两个地址是相同的. 它们两个有可能不同的一个例子是当一个数据节在ROM中时, 当程序启动时,被拷贝到RAM中(这个技术经常被用在基于ROM的系统中进行全局变量的初始化). 在这种情况下, ROM地址就是LMA, 而RAM地址就是VMA.
你可以通过使用带有'-h'选项的'objdump'来察看目标文件中的节.
每一个目标文件还有一个关于符号的列表, 被称为'符号表'. 一个符号可能是定义过了的,也可能是未定义的.
每一个符号有一个名字, 而且每一个定义的符号有一个地址. 如果你把一个C/C++程序编译为一个目标文件,对于每一个定义的函数和全局或静态变量,你会得到一个定义的符号. 每一个在输入文件中只是一个引用而未定义的函数或全局变量会变成一个未定义的符号.
你可以使用'nm'程序来看一个目标文件中的符号, 或者使用'objdump'程序带有'-t'选项.
2、链接脚本的格式:
连接脚本是文本文件.
你写了一系列的命令作为一个连接脚本. 每一个命令是一个带有参数的关键字,或者是一个对符号的赋值. 你可以用分号分隔命令. 空格一般被忽略.
文件名或格式名之类的字符串一般可以被直接键入. 如果文件名含有特殊字符,比如一般作为分隔文件名用的逗号, 你可以把文件名放到双引号中. 文件名中间无法使用双引号.
你可以象在C语言中一样,在连接脚本中使用注释, 用'/*'和'*/'隔开. 就像在C中,注释在语法上等同于空格.
3、简单的链接脚本的语法示例:
许多脚本是相当的简单的.
可能的最简单的脚本只含有一个命令: 'SECTIONS'. 你可以使用'SECTIONS'来描述输出文件的内存布局.
'SECTIONS'是一个功能很强大的命令. 这里我们会描述一个很简单的使用. 让我们假设你的程序只有代码节, 初始化过的数据节, 和未初始化过的数据节. 这些会存在于'.text','.data'和'.bss'节, 另外, 让我们进一步假设在你的输入文件中只有这些节.
对于这个例子, 我们说代码应当被载入到地址'0x10000'处, 而数据应当从0x8000000处开始. 下面是一个实现这个功能的脚本:
SECTIONS
{
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}
你使用关键字'SECTIONS'写了这个SECTIONS命令, 后面跟有一串放在花括号中的符号赋值和输出节描述的内容.
4、简单的链接脚本命令:
1)设置入口点.
在运行一个程序时第一个被执行到的指令称为"入口点". 你可以使用'ENTRY'连接脚本命令来设置入口点.参数是一个符号名:
ENTRY(SYMBOL)
有多种不同的方法来设置入口点.连接器会通过按顺序尝试以下的方法来设置入口点, 如果成功了,就会停止.
* `-e'入口命令行选项;
* 连接脚本中的`ENTRY(SYMBOL)'命令;
* 如果定义了start, 就使用start的值;
* 如果存在,就使用'.text'节的首地址;
* 地址`0'.
2)处理文件的命令.
有几个处理文件的连接脚本命令.
`INCLUDE FILENAME'
在当前点包含连接脚本文件FILENAME. 在当前路径下或用'-L'选项指定的所有路径下搜索这个文件,你可以嵌套使用'INCLUDE'达10层.
`INPUT(FILE, FILE, ...)'
`INPUT(FILE FILE ...)'
'INPUT'命令指示连接器在连接时包含文件, 就像它们是在命令行上指定的一样.
比如,如果你在连接的时候总是要包含文件'subr.o',但是你对每次连接时要在命令行上输入感到厌烦, 你就可以在你的连接脚本中输入'INPUT (subr.o).事实上,如果你喜欢,你可以把你所有的输入文件列在连接脚本中, 然后在连接的时候什么也不需要,只要一个'-T'选项就够了。
在一个'系统根前缀'被配置的情况下, 一个文件名如果以'/'字符打头, 并且脚本也存放在系统根前缀的某个子目录下, 文件名就会被在系统根前缀下搜索. 否则连接器就会企图打开当前目录下的文件. 如果没有发现, 连接器会通过档案库搜索路径进行搜索.
如果你使用了'INPUT (-lFILE)', 'ld'会把文件名转换为'libFILE.a', 就象命令行参数'-l'一样.
当你在一个隐式连接脚本中使用'INPUT'命令的时候, 文件就会在连接时连接脚本文件被包含的点上被包含进来. 这会影响到档案搜索.
`GROUP(FILE, FILE, ...)'
`GROUP(FILE FILE ...)'
除了文件必须全是档案文件之外, 'GROUP'命令跟'INPUT'相似, 它们会被反复搜索,直至没有未定义的引用被创建.
`OUTPUT(FILENAME)'
'OUTPUT'命令命名输出文件. 在连接脚本中使用'OUTPUT(FILENAME)'命令跟在命令行中使用'-o FILENAME'命令是完全等效的. 如果两个都使用了, 那命令行选项优先.
你可以使用'OUTPUT'命令为输出文件创建一个缺省的文件名,而不是常用的'a.out'.
`SEARCH_DIR(PATH)'
`SEARCH_DIR'命令给'ld'用于搜索档案文件的路径中再增加新的路径. 使用`SEARCH_DIR(PATH)'跟在命令行上使用'-L PATH'选项是完全等效的. 如果两个都使用了, 那连接器会两个路径都搜索. 用命令行选项指定的路径首先被搜索.
`STARTUP(FILENAME)'
除了FILENAME会成为第一个被连接的输入文件, 'STARTUP'命令跟'INPUT'命令完全相似, 就象这个文件是在命令行上第一个被指定的文件一样. 如果在一个系统中, 入口点总是存在于第一个文件中,那这个就很有用.
3)处理目标文件格式的命令.
有两个处理目标文件格式的连接脚本命令
`OUTPUT_formAT(BFDNAME)'
`OUTPUT_formAT(DEFAULT, BIG, LITTLE)'
`OUTPUT_formAT'命令为输出文件使用的BFD格式命名. 使用`OUTPUT_formAT(BFDNAME)'跟在命令行上使用'-oformat BFDNAME'是完全等效的. 如果两个都使用了, 命令行选项优先.
你可在使用`OUTPUT_formAT'时带有三个参数以使用不同的基于'-EB'和'-EL'的命令行选项的格式.
如果'-EB'和'-EL'都没有使用, 那输出格式会是第一个参数DEFAULT, 如果使用了'-EB',输出格式会是第二个参数BIG, 如果使用了'-EL', 输出格式会是第三个参数, LITTLE.
比如, 缺省的基于MIPS ELF平台连接脚本使用如下命令:
OUTPUT_formAT(elf32-bigmips, elf32-bigmips, elf32-littlemips)
这表示缺省的输出文件格式是'elf32-bigmips', 但是当用户使用'-EL'命令行选项的时候, 输出文件就会被以`elf32-littlemips'格式创建.
`TARGET(BFDNAME)'
'TARGET'命令在读取输入文件时命名BFD格式. 它会影响到后来的'INPUT'和'GROUP'命令. 这个命令跟在命令行上使用`-b BFDNAME'相似. 如果使用了'TARGET'命令但`OUTPUT_formAT'没有指定, 最后的'TARGET'命令也被用来设置输出文件的格式.
4)其它的连接脚本命令.
还有一些其它的连接脚本命令.
`ASSERT(EXP, MESSAGE)'
确保EXP不等于零,如果等于零, 连接器就会返回一个错误码退出,并打印出MESSAGE.
`EXTERN(SYMBOL SYMBOL ...)'
强制SYMBOL作为一个无定义的符号输入到输出文件中去. 这样做了,可能会引发从标准库中连接一些节外的库. 你可以为每一个EXTERN'列出几个符号, 而且你可以多次使用'EXTERN'. 这个命令跟'-u'命令行选项具有相同的效果.
`FORCE_COMMON_ALLOCATION'
这个命令跟命令行选项'-d'具有相同的效果: 就算指定了一个可重定位的输出文件('-r'),也让'ld'为普通符号分配空间.
`INHIBIT_COMMON_ALLOCATION'
这个命令跟命令行选项`--no-define-common'具有相同的效果: 就算是一个不可重位输出文件, 也让'ld'忽略为普通符号分配的空间.
`NOCROSSREFS(SECTION SECTION ...)'
这个命令在遇到在某些特定的节之间引用的时候会产生一条错误信息.
在某些特定的程序中, 特别是在使用覆盖技术的嵌入式系统中, 当一个节被载入内存时,另外一个节就不会在内存中. 任何在两个节之间的直接引用都会是一个错误. 比如, 如果节1中的代码调用了另一个节中的一个函数,这就会产生一个错误.
`NOCROSSREFS'命令带有一个输出节名字的列表. 如果'ld'遇到任何在这些节之间的交叉引用, 它就会报告一个错误,并返回一个非零退出码. 注意, `NOCROSSREFS'命令使用输出节名,而不是输入节名.
`OUTPUT_ARCH(BFDARCH)'
指定一个特定的输出机器架构. 这个参数是BFD库中使用的一个名字. 你可以通过使用带有'-f'选项的'objdump'程序来查看一个目标文件的架构.
5、为符号赋值: