linux 链接脚本的编写

1. 概论
 
--------------------------------------------------------------------------------
 
每一个链接过程都由链接脚本(linker script, 一般以lds作为文件的后缀名)控制. 链接脚本主要用于规定如何把输入文件内的section放入输出文件内, 并控制输出文件内各部分在程序地址空间内的布局. 但你也可以用连接命令做一些其他事情.
 
连接器有个默认的内置连接脚本, 可用ld --verbose查看. 连接选项-r和-N可以影响默认的连接脚本(如何影响?).
 
-T选项用以指定自己的链接脚本, 它将代替默认的连接脚本.你也可以使用<暗含的连接脚本>以增加自定义的链接命令.
 
以下没有特殊说明,连接器指的是静态连接器.
 
2. 基本概念
 
--------------------------------------------------------------------------------
 
链接器把一个或多个输入文件合成一个输出文件.
 
输入文件: 目标文件或链接脚本文件.
输出文件: 目标文件或可执行文件.
目标文件(包括可执行文件)具有固定的格式, 在UNIX或GNU/Linux平台下, 一般为ELF格式. 若想了解更多, 可参考 UNIX/Linux平台可执行文件格式分析
 
有时把输入文件内的section称为输入section(input section), 把输出文件内的section称为输出section(output sectin).
 
目标文件的每个section至少包含两个信息: 名字和大小. 大部分section还包含与它相关联的一块数据, 称为section contents(section内容). 一个section可被标记为“loadable(可加载的)”或“allocatable(可分配的)”.
 
loadable section: 在输出文件运行时, 相应的section内容将被载入进程地址空间中.
 
allocatable section: 内容为空的section可被标记为“可分配的”. 在输出文件运行时, 在进程地址空间中空出大小同section指定大小的部分. 某些情况下, 这块内存必须被置零.
 
如果一个section不是“可加载的”或“可分配的”, 那么该section通常包含了调试信息. 可用objdump -h命令查看相关信息.
 
每个“可加载的”或“可分配的”输出section通常包含两个地址: VMA(virtual memory address虚拟内存地址或程序地址空间地址)和LMA(load memory address加载内存地址或进程地址空间地址). 通常VMA和LMA是相同的.
 
在目标文件中, loadable或allocatable的输出section有两种地址: VMA(virtual Memory Address)和LMA(Load Memory Address). VMA是执行输出文件时section所在的地址, 而LMA是加载输出文件时section所在的地址. 一般而言, 某section的VMA == LMA. 但在嵌入式系统中, 经常存在加载地址和执行地址不同的情况: 比如将输出文件加载到开发板的flash中(由LMA指定), 而在运行时将位于flash中的输出文件复制到SDRAM中(由VMA指定).
 
 
 
可这样来理解VMA和LMA, 假设:
 
(1) .data section对应的VMA地址是0x08050000, 该section内包含了3个32位全局变量, i、j和k, 分别为1,2,3.
(2) .text section内包含由"printf( "j=%d ", j );"程序片段产生的代码.
 
连接时指定.data section的VMA为0x08050000, 产生的printf指令是将地址为0x08050004处的4字节内容作为一个整数打印出来.
 
如果.data section的LMA为0x08050000,显然结果是j=2
 
如果.data section的LMA为0x08050004,显然结果是j=1
 
还可这样理解LMA:
 
.text section内容的开始处包含如下两条指令(intel i386指令是10字节,每行对应5字节):
 
jmp 0x08048285
 
movl $0x1,%eax
 
如果.text section的LMA为0x08048280, 那么在进程地址空间内0x08048280处为“jmp 0x08048285”指令, 0x08048285处为movl $0x1,%eax指令. 假设某指令跳转到地址0x08048280, 显然它的执行将导致%eax寄存器被赋值为1.
 
如果.text section的LMA为0x08048285, 那么在进程地址空间内0x08048285处为“jmp 0x08048285”指令, 0x0804828a处为movl $0x1,%eax指令. 假设某指令跳转到地址0x08048285, 显然它的执行又跳转到进程地址空间内0x08048285处, 造成死循环.
 
符号(symbol): 每个目标文件都有符号表(SYMBOL TABLE), 包含已定义的符号(对应全局变量和static变量和定义的函数的名字)和未定义符号(未定义的函数的名字和引用但没定义的符号)信息.
 
符号值: 每个符号对应一个地址, 即符号值(这与c程序内变量的值不一样, 某种情况下可以把它看成变量的地址). 可用nm命令查看它们. (nm的使用方法可参考本blog的GNU binutils笔记)
 
3. 脚本格式
--------------------------------------------------------------------------------
 
链接脚本由一系列命令组成, 每个命令由一个关键字(一般在其后紧跟相关参数)或一条对符号的赋值语句组成. 命令由分号‘;’分隔开.
 
文件名或格式名内如果包含分号';'或其他分隔符, 则要用引号‘"’将名字全称引用起来. 无法处理含引号的文件名.
 
/* */之间的是注释.
 
4. 简单例子
--------------------------------------------------------------------------------
 
在介绍链接描述文件的命令之前, 先看看下述的简单例子:
 
以下脚本将输出文件的text section定位在0x10000, data section定位在0x8000000:
 
SECTIONS
 
{
 
. = 0x10000;
 
.text : { *(.text) }
 
. = 0x8000000;
 
.data : { *(.data) }
 
.bss : { *(.bss) }
 
}
 
解释一下上述的例子:
 
. = 0x10000 : 把定位器符号置为0x10000 (若不指定, 则该符号的初始值为0).
 
.text : { *(.text) } : 将所有(*符号代表任意输入文件)输入文件的.text section合并成一个.text section, 该section的地址由定位器符号的值指定, 即0x10000.
 
. = 0x8000000 :把定位器符号置为0x8000000
 
.data : { *(.data) } : 将所有输入文件的.text section合并成一个.data section, 该section的地址被置为0x8000000.
 
.bss : { *(.bss) } : 将所有输入文件的.bss section合并成一个.bss section,该section的地址被置为0x8000000+.data section的大小.
 
连接器每读完一个section描述后, 将定位器符号的值*增加*该section的大小. 注意: 此处没有考虑对齐约束.
 
5. 简单脚本命令
--------------------------------------------------------------------------------
 
ENTRY(SYMBOL) : 将符号SYMBOL的值设置成入口地址.
 
入口地址(entry point): 进程执行的第一条用户空间的指令在进程地址空间的地址)
 
ld有多种方法设置进程入口地址, 按一下顺序: (编号越前, 优先级越高)
 
1, ld命令行的-e选项
 
2, 连接脚本的ENTRY(SYMBOL)命令
 
3, 如果定义了start符号, 使用start符号值
 
4, 如果存在.text section, 使用.text section的第一字节的位置值
 
5, 使用值0
 
INCLUDE filename : 包含其他名为filename的链接脚本.

相当于c程序内的的#include指令, 用以包含另一个链接脚本.
 
脚本搜索路径由-L选项指定. INCLUDE指令可以嵌套使用, 最大深度为10. 即: 文件1内INCLUDE文件2, 文件2内INCLUDE文件3... , 文件10内INCLUDE文件11. 那么文件11内不能再出现 INCLUDE指令了.
 
INPUT(files): 将括号内的文件做为链接过程的输入文件.

ld首先在当前目录下寻找该文件, 如果没找到, 则在由-L指定的搜索路径下搜索. file可以为 -lfile形式,就象命令行的-l选项一样. 如果该命令出现在暗含的脚本内, 则该命令内的file在链接过程中的顺序由该暗含的脚本在命令行内的顺序决定.
 
GROUP(files) : 指定需要重复搜索符号定义的多个输入文件.
 
file必须是库文件, 且file文件作为一组被ld重复扫描,直到不在有新的未定义的引用出现.
 
OUTPUT(FILENAME) : 定义输出文件的名字.
 
同ld的-o选项, 不过-o选项的优先级更高. 所以它可以用来定义默认的输出文件名. 如a.out
 
SEARCH_DIR(PATH) :定义搜索路径,
 
同ld的-L选项, 不过由-L指定的路径要比它定义的优先被搜索.
 
STARTUP(filename) : 指定filename为第一个输入文件

在链接过程中, 每个输入文件是有顺序的. 此命令设置文件filename为第一个输入文件.

OUTPUT_FORMAT(BFDNAME) : 设置输出文件使用的BFD格式
 
同ld选项-o format BFDNAME, 不过ld选项优先级更高.
 
OUTPUT_FORMAT(DEFAULT,BIG,LITTLE) : 定义三种输出文件的格式(大小端).
 
若有命令行选项-EB, 则使用第2个BFD格式; 若有命令行选项-EL,则使用第3个BFD格式.否则默认选第一个BFD格式.
 
TARGET(BFDNAME):设置输入文件的BFD格式.

同ld选项-b BFDNAME. 若使用了TARGET命令, 但未使用OUTPUT_FORMAT命令, 则最用一个TARGET命令设置的BFD格式将被作为输出文件的BFD格式.
 
另外还有一些:
 
ASSERT(EXP, MESSAGE):如果EXP不为真,终止连接过程
 
EXTERN(SYMBOL SYMBOL ...):在输出文件中增加未定义的符号,如同连接器选项-u
 
FORCE_COMMON_ALLOCATION:为common symbol(通用符号)分配空间,即使用了-r连接选项也为其分配
 
NOCROSSREFS(SECTION SECTION ...):检查列出的输出section,如果发现他们之间有相互引用,则报错.对于某些系统,特别是内存较紧张的嵌入式系统,某些section是不能同时存在内存中的,所以他们之间不能相互引用.
 
OUTPUT_ARCH(BFDARCH):设置输出文件的machine architecture(体系结构),BFDARCH为被BFD库使用的名字之一.可以用命令objdump -f查看.
 
可通过 man -S 1 ld查看ld的联机帮助, 里面也包括了对这些命令的介绍.
 
6. 对符号的赋值
--------------------------------------------------------------------------------
 
在目标文件内定义的符号可以在链接脚本内被赋值. (注意和C语言中赋值的不同!) 此时该符号被定义为全局的. 每个符号都对应了一个地址, 此处的赋值是更改这个符号对应的地址.
 
e.g. 通过下面的程序查看变量a的地址:
 
/* a.c */
 
#include <stdio.h>
 
int a = 100;
 
int main(void)
 
{
 
    printf( "&a=0x%p ", &a );
 
    return 0;
 
}
 
/* a.lds */
 
a = 3;
 
$ gcc -Wall -o a-without-lds a.c
 
&a = 0x8049598
 
$ gcc -Wall -o a-with-lds a.c a.lds
 
&a = 0x3
 
注意: 对符号的赋值只对全局变量起作用!
 
一些简单的赋值语句

能使用任何c语言内的赋值操作:
 
SYMBOL = EXPRESSION ;
 
SYMBOL += EXPRESSION ;
 
SYMBOL -= EXPRESSION ;
 
SYMBOL *= EXPRESSION ;
 
SYMBOL /= EXPRESSION ;
 
SYMBOL <<= EXPRESSION ;
 
SYMBOL >>= EXPRESSION ;
 
SYMBOL &= EXPRESSION ;
 
SYMBOL |= EXPRESSION ;
 
除了第一类表达式外, 使用其他表达式需要SYMBOL被定义于某目标文件.
 是一个特殊的符号,它是定位器,一个位置指针,指向程序地址空间内的某位置(或某section内的偏移,如果它在SECTIONS命令内的某section描述内),该符号只能在SECTIONS命令内使用.
 
注意:赋值语句包含4个语法元素:符号名、操作符、表达式、分号;一个也不能少.
 
被赋值后,符号所属的section被设值为表达式EXPRESSION所属的SECTION(参看11. 脚本内的表达式)
 
赋值语句可以出现在连接脚本的三处地方:SECTIONS命令内,SECTIONS命令内的section描述内和全局位置;如下,
 
floating_point = 0; /* 全局位置 */
 
SECTIONS
 
{
 
.text :
 
{
 
*(.text)
 
_etext = .; /* section描述内 */
 
}
 
_bdata = (. + 3) & ~ 4; /* SECTIONS命令内 */
 
.data : { *(.data) }
 
}
 
PROVIDE关键字
 
该关键字用于定义这类符号:在目标文件内被引用,但没有在任何目标文件内被定义的符号.
 
例子:
 
SECTIONS
{
 .text :
 {
   *(.text)
   _etext = .;
   PROVIDE(etext = .);
 
  }
}
 
当目标文件内引用了etext符号,确没有定义它时,etext符号对应的地址被定义为.text section之后的第一个字节的地址.
 
7. SECTIONS命令
--------------------------------------------------------------------------------
 
SECTIONS命令告诉ld如何把输入文件的sections映射到输出文件的各个section: 如何将输入section合为输出section; 如何把输出section放入程序地址空间(VMA)和进程地址空间(LMA).该命令格式如下:

SECTIONS
{
 SECTIONS-COMMAND
 SECTIONS-COMMAND
 ...
}
 
SECTION-COMMAND有四种:
 
(1) ENTRY命令
 
(2) 符号赋值语句
 
(3) 一个输出section的描述(output section description)
 
(4) 一个section叠加描述(overlay description)
 
如果整个连接脚本内没有SECTIONS命令, 那么ld将所有同名输入section合成为一个输出section内, 各输入section的顺序为它们被连接器发现的顺序.
 
如果某输入section没有在SECTIONS命令中提到, 那么该section将被直接拷贝成输出section.
 
输出section描述
 
输出section描述具有如下格式:
 
SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
{
 OUTPUT-SECTION-COMMAND
 OUTPUT-SECTION-COMMAND
 ...
} [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP]
 
[ ]内的内容为可选选项, 一般不需要.
 
SECTION:section名字
SECTION左右的空白、圆括号、冒号是必须的,换行符和其他空格是可选的.

每个OUTPUT-SECTION-COMMAND为以下四种之一,
符号赋值语句
 
一个输入section描述
 
直接包含的数据值
 
一个特殊的输出section关键字
 
输出section名字(SECTION):
 
输出section名字必须符合输出文件格式要求,比如:a.out格式的文件只允许存 在.text、.data和.bss section名.而有的格式只允许存在数字名字,那么此时应该用引号将所有名字内的数字组合在一起;另外,还有一些格式允许任何序列的字符存在于 section名字内,此时如果名字内包含特殊字符(比如空格、逗号等),那么需要用引号将其组合在一起.
 
输出section地址(ADDRESS):
 
ADDRESS是一个表达式,它的值用于设置VMA.如果没有该选项且有REGION选项, 那么连接器将根据REGION设置VMA;如果也没有 REGION选项,那么连接器将根据定位符号‘.’的值设置该section的VMA,将定位符号的值调整到满足输出section对齐要求后的值,输出 section的对齐要求为:该输出section描述内用到的所有输入section的对齐要求中最严格的.
 
例子:
 
.text . : { *(.text) }
 

 
.text : { *(.text) }
 
这两个描述是截然不同的,第一个将.text section的VMA设置为定位符号的值,而第二个则是设置成定位符号的修调值,满足对齐要求后的.
 
ADDRESS可以是一个任意表达式,比如ALIGN(0x10)这将把该section的VMA设置成定位符号的修调值,满足16字节对齐后的.
 
注意:设置ADDRESS值,将更改定位符号的值.
 
输入section描述:
 
最常见的输出section描述命令是输入section描述.
 
输入section描述是最基本的连接脚本描述.
 
输入section描述基础:
 
基本语法:FILENAME([EXCLUDE_FILE (FILENAME1 FILENAME2 ...) SECTION1 SECTION2 ...)
 
FILENAME文件名,可以是一个特定的文件的名字,也可以是一个字符串模式.
 
SECTION名字,可以是一个特定的section名字,也可以是一个字符串模式
 
例子是最能说明问题的,
 
*(.text) :表示所有输入文件的.text section
 
(*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors)) :表示除crtend.o、otherfile.o文件外的所有输入文件的.ctors section.
 
data.o(.data) :表示data.o文件的.data section
 
data.o :表示data.o文件的所有section
 
*(.text .data) :表示所有文件的.text section和.data section,顺序是:第一个文件的.text section,第一个文件的.data section,第二个文件的.text section,第二个文件的.data section,...
 
*(.text) *(.data) :表示所有文件的.text section和.data section,顺序是:第一个文件的.text section,第二个文件的.text section,...,最后一个文件的.text section,第一个文件的.data section,第二个文件的.data section,...,最后一个文件的.data section
 
下面看连接器是如何找到对应的文件的.
 
当FILENAME是一个特定的文件名时,连接器会查看它是否在连接命令行内出现或在INPUT命令中出现.
 
当FILENAME是一个字符串模式时,连接器仅仅只查看它是否在连接命令行内出现.
 
注意:如果连接器发现某文件在INPUT命令内出现,那么它会在-L指定的路径内搜寻该文件.
 
字符串模式内可存在以下通配符:
 
* :表示任意多个字符
 
? :表示任意一个字符
 
[CHARS] :表示任意一个CHARS内的字符,可用-号表示范围,如:a-z
 
:表示引用下一个紧跟的字符
 
在文件名内,通配符不匹配文件夹分隔符/,但当字符串模式仅包含通配符*时除外.
 
任何一个文件的任意section只能在SECTIONS命令内出现一次.看如下例子,
 
SECTIONS {
 .data : { *(.data) }
 .data1 : { data.o(.data) }
 
}
 
data.o文件的.data section在第一个OUTPUT-SECTION-COMMAND命令内被使用了,那么在第二个OUTPUT-SECTION-COMMAND命令内 将不会再被使用,也就是说即使连接器不报错,输出文件的.data1 section的内容也是空的.
 
再次强调:连接器依次扫描每个OUTPUT-SECTION-COMMAND命令内的文件名,任何一个文件的任何一个section都只能使用一次.
 
读者可以用-M连接命令选项来产生一个map文件,它包含了所有输入section到输出section的组合信息.
 
再看个例子,
 
SECTIONS {

 .text : { *(.text) }
 
 .DATA : { [A-Z]*(.data) }
 
 .data : { *(.data) }
 
 .bss : { *(.bss) }
}
 
这个例子中说明,所有文件的输入.text section组成输出.text section;所有以大写字母开头的文件的.data section组成输出.DATA section,其他文件的.data section组成输出.data section;所有文件的输入.bss section组成输出.bss section.
 
可以用SORT()关键字对满足字符串模式的所有名字进行递增排序,如SORT(.text*).
 
通用符号(common symbol)的输入section:
 
在许多目标文件格式中,通用符号并没有占用一个section.连接器认为:输入文件的所有通用符号在名为COMMON的section内.
 
例子,
 
.bss { *(.bss) *(COMMON) }
 
这个例子中将所有输入文件的所有通用符号放入输出.bss section内.可以看到COMMOM section的使用方法跟其他section的使用方法是一样的.
 
有些目标文件格式把通用符号分成几类.例如,在MIPS elf目标文件格式中,把通用符号分成standard common symbols(标准通用符号)和small common symbols(微通用符号,不知道这么译对不对?),此时连接器认为所有standard common symbols在COMMON section内,而small common symbols在.scommon section内.
 
在一些以前的连接脚本内可以看见[COMMON],相当于*(COMMON),不建议继续使用这种陈旧的方式.
 
输入section和垃圾回收:
 
在连接命令行内使用了选项--gc-sections后,连接器可能将某些它认为没用的 section过滤掉,此时就有必要强制连接器保留一些特定的 section,可用KEEP()关键字达此目的.如KEEP(*(.text))或KEEP(SORT(*)(.text))
 
最后看个简单的输入section相关例子:
 
SECTIONS {
 outputa 0x10000 :
 {
  all.o
  foo.o (.input1)
 }
 outputb :
 {
  foo.o (.input2)
  foo1.o (.input1)
 }
 outputc :
 {
  *(.input1)
  *(.input2)
 }
}
 
本例中,将all.o文件的所有section和foo.o文件的所有(一个文件内可以有多 个同名section).input1 section依次放入输出outputa section内,该section的VMA是0x10000;将foo.o文件的所有.input2 section和foo1.o文件的所有.input1 section依次放入输出outputb section内,该section的VMA是当前定位器符号的修调值(对齐后);将其他文件(非all.o、foo.o、foo1.o)文件的. input1 section和.input2 section放入输出outputc section内.
 
在输出section存放数据命令:
 
能够显示地在输出section内填入你想要填入的信息(这样是不是可以自己通过连接脚本写程序?当然是简单的程序).
 
BYTE(EXPRESSION) 1 字节
 
SHORT(EXPRESSION) 2 字节
 
LOGN(EXPRESSION) 4 字节
 
QUAD(EXPRESSION) 8 字节
 
SQUAD(EXPRESSION) 64位处理器的代码时,8 字节
 
输出文件的字节顺序big endianness 或little endianness,可以由输出目标文件的格式决定;如果输出目标文件的格式不能决定字节顺序,那么字节顺序与第一个输入文件的字节顺序相同.
 
如:BYTE(1)、LANG(addr).
 
注意,这些命令只能放在输出section描述内,其他地方不行.
 
错误:SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } }
 
正确:SECTIONS { .text : { *(.text) LONG(1) } .data : { *(.data) } }
 
在当前输出section内可能存在未描述的存储区域(比如由于对齐造成的空隙),可以用 FILL(EXPRESSION)命令决定这些存储区域的内容, EXPRESSION的前两字节有效,这两字节在必要时可以重复被使用以填充这类存储区域.如FILE(0x9090).在输出section描述中可以 有=FILEEXP属性,它的作用如同FILE()命令,但是FILE命令只作用于该FILE指令之后的section区域,而=FILEEXP属性作用 于整个输出section区域,且FILE命令的优先级更高!!!
 
输出section内命令的关键字:
 
CREATE_OBJECT_SYMBOLS :为每个输入文件建立一个符号,符号名为输入文件的名字.每个符号所在的section是出现该关键字的section.
 
CONSTRUCTORS :与c++内的(全局对象的)构造函数和(全局对像的)析构函数相关,下面将它们简称为全局构造和全局析构.
 
对于a.out目标文件格式,连接器用一些不寻常的方法实现c++的全局构造和全局析构.当 连接器生成的目标文件格式不支持任意section名字时,比如说ECOFF、XCOFF格式,连接器将通过名字来识别全局构造和全局析构,对于这些文件 格式,连接器把与全局构造和全局析构的相关信息放入出现 CONSTRUCTORS关键字的输出section内.
 
符号__CTORS_LIST__表示全局构造信息的的开始处,__CTORS_END__表示全局构造信息的结束处.
 
符号__DTORS_LIST__表示全局构造信息的的开始处,__DTORS_END__表示全局构造信息的结束处.
 
这两块信息的开始处是一字长的信息,表示该块信息有多少项数据,然后以值为零的一字长数据结束.
 
一般来说,GNU C++在函数__main内安排全局构造代码的运行,而__main函数被初始化代码(在main函数调用之前执行)调用.是不是对于某些目标文件格式才这样???
 
对于支持任意section名的目标文件格式,比如COFF、ELF格式,GNU C++将全局构造和全局析构信息分别放入.ctors section和.dtors section内,然后在连接脚本内加入如下,
 
__CTOR_LIST__ = .;
 
LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)
 
*(.ctors)
 
LONG(0)
 
__CTOR_END__ = .;
 
__DTOR_LIST__ = .;
 
LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2)
 
*(.dtors)
 
LONG(0)
 
__DTOR_END__ = .;
 
如果使用GNU C++提供的初始化优先级支持(它能控制每个全局构造函数调用的先后顺序),那么请在连接脚本内把CONSTRUCTORS替换成SORT (CONSTRUCTS),把*(.ctors)换成*(SORT(.ctors)),把*(.dtors)换成*(SORT(.dtors)).一般来 说,默认的连接脚本已作好的这些工作.
 
输出section的丢弃:
 
例子,.foo { *(.foo) },如果没有任何一个输入文件包含.foo section,那么连接器将不会创建.foo输出section.但是如果在这些输出section描述内包含了非输入section描述命令(如符号 赋值语句),那么连接器将总是创建该输出section.
 
有一个特殊的输出section,名为/DISCARD/,被该section引用的任何输入section将不会出现在输出文件内,这就是DISCARD的意思吧.如果/DISCARD/ section被它自己引用呢?想想看.
 
输出section属性:
 
终于讲到这里了,呵呵.
 
我们再回顾以下输出section描述的文法:
 
SECTION [ADDRESS] [(TYPE)] : [AT(LMA)]
{
 OUTPUT-SECTION-COMMAND
 OUTPUT-SECTION-COMMAND
 ...
} [>REGION] [AT>LMA_REGION] [:PHDR :PHDR ...] [=FILLEXP]

前面我们浏览了SECTION、ADDRESS、OUTPUT-SECTION-COMMAND相关信息,下面我们将浏览其他属性.
 
TYPE :每个输出section都有一个类型,如果没有指定TYPE类型,那么连接器根据输出section引用的输入section的类型设置该输出section的类型.它可以为以下五种值,
 
NOLOAD :该section在程序运行时,不被载入内存.
 
DSECT,COPY,INFO,OVERLAY :这些类型很少被使用,为了向后兼容才被保留下来.这种类型的section必须被标记为“不可加载的”,以便在程序运行不为它们分配内存.
 
输出section的LMA :默认情况下,LMA等于VMA,但可以通过关键字AT()指定LMA.
 
用关键字AT()指定,括号内包含表达式,表达式的值用于设置LMA.如果不用AT()关键字,那么可用AT>LMA_REGION表达式设置指定该section加载地址的范围.
 
这个属性主要用于构件ROM境象.
 
例子,
 
SECTIONS
{
 .text 0x1000 : { *(.text) _etext = . ; }
 .mdata 0x2000 :
 AT ( ADDR (.text) + SIZEOF (.text) )
 { _data = . ; *(.data); _edata = . ; }
 .bss 0x3000 :
 { _bstart = . ; *(.bss) *(COMMON) ; _bend = . ;}
}
 
程序如下,
 
extern char _etext, _data, _edata, _bstart, _bend;
 
char *src = &_etext;
 
char *dst = &_data;
 
/* ROM has data at end of text; copy it. */
 
while (dst < &_edata) {
 
 *dst++ = *src++;
 
}
 
/* Zero bss */
 
for (dst = &_bstart; dst< &_bend; dst++)
 
*dst = 0;
 
此程序将处于ROM内的已初始化数据拷贝到该数据应在的位置(VMA地址),并将为初始化数据置零.
 
读者应该认真的自己分析以上连接脚本和程序的作用.
 
输出section区域:可以将输出section放入预先定义的内存区域内,例子,
 
MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 }
 
SECTIONS { ROM : { *(.text) } >rom }
 
输出section所在的程序段:可以将输出section放入预先定义的程序段 (program segment)内.如果某个输出section设置了它所在的一个或多个程序段,那么接下来定义的输出section的默认程序段与该输出 section的相同.除非再次显示地指定.例子,
 
PHDRS { text PT_LOAD ; }
 
SECTIONS { .text : { *(.text) } :text }
 
可以通过:NONE指定连接器不把该section放入任何程序段内.详情请查看PHDRS命令
 
输出section的填充模版:这个在前面提到过,任何输出section描述内的未指定的内存区域,连接器用该模版填充该区域.用法:=FILEEXP,前两字节有效,当区域大于两字节时,重复使用这两字节以将其填满.例子,
 
SECTIONS { .text : { *(.text) } =0x9090 }
 
覆盖图(overlay)描述:
 
覆盖图描述使两个或多个不同的section占用同一块程序地址空间.覆盖图管理代码负责将 section的拷入和拷出.考虑这种情况,当某存储块的访问速度比其他存储块要快时,那么如果将section拷到该存储块来执行或访问,那么速度将会 有所提高,覆盖图描述就很适合这种情形.文法如下,
 
SECTIONS {
...
OVERLAY [START] : [NOCROSSREFS] [AT ( LDADDR )]
{
 
 SECNAME1
 {
  OUTPUT-SECTION-COMMAND
  OUTPUT-SECTION-COMMAND
  ...
 } [:PHDR...] [=FILL]
 
 SECNAME2
 {
  OUTPUT-SECTION-COMMAND
  OUTPUT-SECTION-COMMAND
  ...
 } [:PHDR...] [=FILL]
 ...
 } [>REGION] [:PHDR...] [=FILL]
 ...
}
 
由以上文法可以看出,同一覆盖图内的section具有相同的VMA.SECNAME2的 LMA为SECTNAME1的LMA加上SECNAME1的大小,同理计算SECNAME2,3,4...的LMA.SECNAME1的LMA由 LDADDR决定,如果它没有被指定,那么由START决定,如果它也没有被指定,那么由当前定位符号的值决定.
 
NOCROSSREFS关键字指定各section之间不能交叉引用,否则报错.
 
对于OVERLAY描述的每个section,连接器将定义两个符号__load_start_SECNAME和__load_stop_SECNAME,这两个符号的值分别代表SECNAME section的LMA地址的开始和结束.
 
连接器处理完OVERLAY描述语句后,将定位符号的值加上所有覆盖图内section大小的最大值.
 
看个例子吧,
 
SECTIONS{
 ...
 OVERLAY 0x1000 : AT (0x4000)
 {
 .text0 { o1/*.o(.text) }
 .text1 { o2/*.o(.text) }
 }
 ...
}
 
.text0 section和.text1 section的VMA地址是0x1000,.text0 section加载于地址0x4000,.text1 section紧跟在其后.
 
程序代码,拷贝.text1 section代码,
 
extern char __load_start_text1, __load_stop_text1;
 
memcpy ((char *) 0x1000, &__load_start_text1,
 
&__load_stop_text1 - &__load_start_text1);

你可能感兴趣的:(linux,list,脚本,File,include,DST)