最近在看u-boot、osekOS的启动代码,其中涉及到lds文件,经过参考其余网友的文章,但愿对lds文件有个明晰的认识,为了巩固及加深影响,特将相关博客内容重写一遍。html
原始文章: http://linux.chinaunix.net/techdoc/beginner/2009/08/12/1129972.shtml。原始文章讲解更清楚,不懂的地方能够返回参考。linux
1、概念c++
1.1 lds文件函数
lds文件与scatter文件类似,定义了整个程序编译以后的链接过程,都是决定一个可执行程序的各个段的存储位置,以及入口地址,这也是连接定位的做用。网站
每个连接过程都由连接脚本(linker script, 通常以lds做为文件的后缀名)控制。连接器经过连接脚本中指定的规则把一个或多个输入文件(能够是目标文件或连接脚本文件)的section合成一个输出文件(能够是目标文件或可执行文件)。spa
1.2 section.net
为了区分不一样文件的section,目标文件中的每一个section须要至少包含名字和大小,大部分还包含与其相关联的一块数据section contents。一个section可被标记为“loadable” (可加载的,在输出文件运行时,该section被载入进程地址空间)或“allocatable” (可分配的,输出文件运行时,在进程地址空间中预留section大小的部分)。命令行
在目标文件中, loadable或allocatable的输出section有两种地址: VMA(virtual Memory Address,进程执行时使用的地址)和LMA(Load Memory Address,加载到进程地址空间的地址)。通常而言, 某section的VMA = LMA. 但在嵌入式系统中, 常常存在加载地址和执行地址不一样的状况: 好比将输出文件加载到开发板的flash中(由LMA指定), 而在运行时将位于flash中的输出文件复制到SDRAM中(由VMA指定)。unix
1.3 符号指针
每一个目标文件都有符号表(SYMBOL TABLE), 包含已定义的符号(对应全局变量和static变量和定义的函数的名字)和未定义符号(未定义的函数的名字和引用但没定义的符号)信息。每一个符号对应一个地址, 即符号值(这与c程序内变量的值不同, 某种状况下能够把它当作变量的地址)。
2、语法
2.1 脚本格式
连接脚本由一系列命令组成, 每一个命令由一个关键字(通常在其后紧跟相关参数)或一条对符号的赋值语句组成。命令由分号‘;’分隔开。文件名或格式名内若是包含分号’;'或其余分隔符, 则要用引号‘”’将名字全称引用起来,没法处理含引号的文件名。
/* */之间的是注释。
GNU官方网站上对.lds文件形式的完整描述: 脚本命令; 脚本命令; … SECTIONS { ... secname start BLOCK(align) (TYPE) : AT ( ldadr ) { contents }[>REGION] [AT>LMA_REGION] [:PHDR HDR ...] [=FILLEXP] ... }
说明:secname和contents是必须的,前者用来命名这个段,后者用来肯定代码中的什么部分放在这个段,如下是对这个描述中的一些关键字的解释。
一、脚本命令:参见2.3节,能够设置内存段、输出格式、入口地址等。
二、secname:输出section段名。
三、start BLOCK:是段的重定位地址,本段链接(运行)的地址,若是代码中有位置无关指令,程序运行时这个段必须放在这个地址上。start能够用任意一种描述地址的符号来描述。设置了输出section的VMA地址,其中align将定位符号的值调整到知足输出section对齐要求后的值。
四、TYPE:输出section类型,指明是否在程序运行时载入内存。
五、AT(ldadr):定义本段存储(加载)的地址,默认状况下,LMA等于VMA,但能够经过关键字AT()指定LMA。用关键字AT()指定,括号内包含表达式,表达式的值用于设置LMA。若是不用AT()关键字,那么可用AT>LMA_REGION表达式设置指定该section加载地址的范围。经过这个选项能够控制各段分别保存于输出文件中不一样的位置。
六、contents:决定哪些内容放在本段,能够是整个目标文件,也能够是目标文件中的某段(代码段、数据段等),详情参考2.4节。
七、region:若是start没有定义,则以region地址为VMA,若是region也没定义,以定位符号‘.’的值肯定VMA。
八、phdr:没在具体的实例中看到过。
九、FILLEXP:指明空闲空间(如按align对齐后的空闲部分)的填充字段。
2.2 符号及表达式
2.2.1 符号
没有被引号“”包围的符号,以字母、下划线或‘.’开头,可包含字母、下划线、’.'和’-'。当符号名被引号包围时,符号名能够与关键字相同。
(1). 是一个特殊的符号,它是定位器,一个位置指针,指向程序地址空间内的某位置(或某section内的偏移,若是它在SECTIONS命令内的某section描述内),该符号只能在SECTIONS命令内使用。
(2)PROVIDE关键字:用于定义在目标文件内被引用,但没有在任何目标文件内被定义的符号。
2.2.2 表达式
(1)赋值
在目标文件内定义的符号能够在连接脚本内被赋值,但只有对全局变量赋值才有效,并且此处的赋值是更改这个符号对应的地址,而不是变量的值。赋值语句能够出如今链接脚本的三处地方:SECTIONS命令内,SECTIONS命令内的section描述内和全局位置;经常使用赋值操做有:
SYMBOL = EXPRESSION ; SYMBOL += EXPRESSION ; SYMBOL -= EXPRESSION ; SYMBOL *= EXPRESSION ; SYMBOL /= EXPRESSION ; SYMBOL >= EXPRESSION ; SYMBOL &= EXPRESSION ; SYMBOL |= EXPRESSION ;
注意:除了第一类表达式外, 使用其余表达式须要SYMBOL被定义于某目标文件。
(2)运算符优先级
优先级 结合顺序 操做符 1 left ! – ~ (1) 2 left * / % 3 left + - 4 left >> = 6 left & 7 left | 8 left && 9 left || 10 right ? : 11 right &= += -= *= /= (2)
(3)内建函数
ABSOLUTE(EXP) :转换成绝对值 ADDR(SECTION) :返回某section的VMA值。 ALIGN(EXP) :返回定位符’.'的修调值,对齐后的值,(. + EXP – 1) & ~(EXP – 1) BLOCK(EXP) :如同ALIGN(EXP),为了向前兼容。 DEFINED(SYMBOL) :若是符号SYMBOL在全局符号表内,且被定义了,那么返回1,不然返回0。 LOADADDR(SECTION) :返回三SECTION的LMA MAX(EXP1,EXP2) :返回大者 MIN(EXP1,EXP2) :返回小者 NEXT(EXP) :返回下一个能被使用的地址,该地址是EXP的倍数,相似于ALIGN(EXP)。除非使用了MEMORY命令定义了一些非连续的内存块,不然NEXT(EXP)与ALIGH(EXP)必定相同。 SIZEOF(SECTION) :返回SECTION的大小。当SECTION没有被分配时,即此时SECTION的大小还不能肯定时,链接器会报错。 SIZEOF_HEADERS : sizeof_headers :返回输出文件的文件头大小(仍是程序头大小),用以肯定第一个section的开始地址(在文件内)。
2.3 脚本命令
2.3.1 简单命令
命令 |
说明 |
备注 |
ENTRY(SYMBOL) |
将符号SYMBOL的值设置成入口地址(进程执行的第一条用户空间的指令在进程地址空间的地址)。 |
进程入口地址优先级:ld命令行的-e选项>链接脚本的ENTRY(SYMBOL)命令>若是定义了start符号, 使用start符号值>若是存在.text section, 使用.text section的第一字节的位置值>使用值0。 |
INCLUDE filename |
包含其余名为filename的连接脚本 |
能够嵌套使用, 最大深度为10。 |
INPUT(files) |
将括号内的文件作为连接过程的输入文件。 |
ld首先在当前目录下寻找该文件, 若是没有, 则在由-L指定的搜索路径下搜索 |
GROUP(files) |
指定须要重复搜索符号定义的多个输入文件 |
file必须是库文件,被ld重复扫描,直到不在有新的未定义的引用出现。 |
OUTPUT(FILENAME) |
定义输出文件的名字 |
|
SEARCH_DIR(PATH) |
定义搜索路径 |
|
STARTUP(filename) |
指定filename为第一个输入文件 |
连接过程当中, 每一个输入文件是有顺序的,此命令设置filename为第一个输入文件。 |
TARGET(BFDNAME) |
设置输入文件的BFD格式。 |
|
OUTPUT_FORMAT(BFDNAME) |
设置输出文件使用的BFD格式。 |
|
OUTPUT_FORMAT(DEFAULT,BIG,LITTLE) |
定义三种输出文件的格式(大小端)。 |
如有命令行选项-EB, 则使用第2个BFD格式; -EL,则使用第3个BFD格式.不然默认第一个。 |
ASSERT(EXP, MESSAGE) |
若是EXP不为真,终止链接过程
|
|
EXTERN(SYMBOL SYMBOL …) |
在输出文件中增长未定义的符号 |
|
FORCE_COMMON_ALLOCATION |
为common symbol(通用符号)分配空间,即便用了-r链接选项也为其分配 |
|
NOCROSSREFS(SECTION SECTION …) |
检查列出的输出section,若是发现他们之间有相互引用,则报错。 |
对于某些系统,特别是内存较紧张的嵌入式系统,某些section是不能同时存在内存中的,因此他们之间不能相互引用。 |
OUTPUT_ARCH(BFDARCH) |
设置输出文件的machine architecture(体系结构) |
BFDARCH为被BFD库使用的名字之一。 |
man -S 1 ld |
查看ld的联机帮助, 里面也包括了对以上命令的介绍. |
|
2.3.2 其它命令
(1)内存区域命令
在默认情形下,链接器能够为section分配任意位置的存储区域。你也能够用MEMORY命令定义存储区域,并经过输出section描述的> REGION属性显示地将该输出section限定于某块存储区域,当存储区域大小不能知足要求时,链接器会报告该错误。
MEMORY命令的文法以下: MEMORY { NAME1 [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN2 NAME2 [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2 … }
说明以下:
NAME :存储区域的名字,这个名字能够与符号名、文件名、section名重复,由于它处于一个独立的名字空间。
ATTR :定义该存储区域的属性,在讲述SECTIONS命令时提到,当某输入section没有在SECTIONS命令内引用时,链接器会把该输入 section直接拷贝成输出section,而后将该输出section放入内存区域内。若是设置了内存区域设置了ATTR属性,那么该区域只接受知足该属性的section(怎么判断该section是否知足?输出section描述内好象没有记录该section的读写执行属性)。
ATTR属性内能够出现如下7个字符:
R 只读section W 读/写section X 可执行section A ‘可分配的’section I 初始化了的section L 同I ! 不知足该字符以后的任何一个属性的section
ORIGIN :关键字,区域的开始地址,可简写成org或o
LENGTH :关键字,区域的大小,可简写成len或l
(2)PHDRS命令
在链接脚本内不指定PHDRS命令时,链接器可以很好的建立程序头,可是有时须要更精确的描述程序头,那么PAHDRS命令就派上用场了。
注意:一旦在链接脚本内使用了PHDRS命令,那么链接器**仅会**建立PHDRS命令指定的信息,因此使用时须谨慎。
PHDRS命令文法以下:
PHDRS
{
NAME TYPE [ FILEHDR ] [ PHDRS ] [ AT ( ADDRESS ) ]
[ FLAGS ( FLAGS ) ] ;
}
其中FILEHDR、PHDRS、AT、FLAGS为关键字。
NAME :为程序段名,此名字能够与符号名、section名、文件名重复,由于它在一个独立的名字空间内。此名字只能在SECTIONS命令内使用。
一个程序段能够由多个‘可加载’的section组成。经过输出section描述的属性:PHDRS能够将输出section加入一个程序段,: PHDRS中的PHDRS为程序段名。在一个输出section描述内能够屡次使用:PHDRS命令,也便可以将一个section加入多个程序段。
若是在一个输出section描述内指定了:PHDRS属性,那么其后的输出section描述将默认使用该属性,除非它也定义了:PHDRS属性。显然当多个输出section属于同一程序段时可简化书写。
在TYPE属性后存在FILEHDR关键字,表示该段包含ELF文件头信息;存在PHDRS关键字,表示该段包含ELF程序头信息。
TYPE能够是如下八种形式: PT_NULL 0表示未被使用的程序段 PT_LOAD 1表示该程序段在程序运行时应该被加载 PT_DYNAMIC 2表示该程序段包含动态链接信息 PT_INTERP 3表示该程序段内包含程序加载器的名字,在linux下常见的程序加载器是ld-linux.so.2 PT_NOTE 4表示该程序段内包含程序的说明信息 PT_SHLIB 5一个保留的程序头类型,没有在ELF ABI文档内定义 PT_PHDR 6表示该程序段包含程序头信息。 EXPRESSION 表达式值
以上每一个类型都对应一个数字,该表达式定义一个用户自定的程序头。
AT(ADDRESS)属性定义该程序段的加载位置(LMA),该属性将**覆盖**该程序段内的section的AT()属性。
默认状况下,链接器会根据该程序段包含的section的属性(什么属性?好象在输出section描述内没有看到)设置FLAGS标志,该标志用于设置程序段描述的p_flags域。
(3)版本号命令
请参看原文介绍
2.4 SECTIONS详解
SECTIONS命令告诉ld如何把输入文件的sections映射到输出文件的各个section: 如何将输入section合为输出section; 如何把输出section放入程序地址空间(VMA)和进程地址空间(LMA),格式以下:
SECTIONS { ENTRY命令/符号赋值语句/一个输出section的描述(output section description)/一个section叠加描述(overlay description) }
其中,一个输出section描述的格式为:
SECTION [ADDRESS] [(TYPE)] : [AT(LMA)] { 符号赋值语句/一个输入section描述/直接包含的数据值/一个特殊的输出section关键字 } [>REGION] [AT>LMA_REGION] [:PHDR HDR ...] [=FILLEXP]
说明以下:
(1)输出section名字(SECTION):
输出section名字必须符合输出文件格式要求,好比:a.out格式的文件只容许存在.text、.data和.bss section名。而有的格式只容许存在数字名字,那么此时应该用引号将全部名字内的数字组合在一块儿;另外,还有一些格式容许任何序列的字符存在于 section名字内,此时若是名字内包含特殊字符(好比空格、逗号等),那么须要用引号将其组合在一块儿。
(2)输出section地址(ADDRESS):
ADDRESS是一个表达式,它的值用于设置VMA。若是没有该选项且有REGION选项,那么链接器将根据REGION设置VMA;若是也没有 REGION选项,那么链接器将根据定位符号‘.’的值设置该section的VMA,将定位符号的值调整到知足输出section对齐要求后的值,输出 section的对齐要求为:该输出section描述内用到的全部输入section的对齐要求中最严格的。
注意:设置ADDRESS值,将更改定位符号的值。
(3)TYPE :
每一个输出section都有一个类型,若是没有指定TYPE类型,那么链接器根据输出section引用的输入section的类型设置该输出section的类型。它能够为如下五种值,
NOLOAD :该section在程序运行时,不被载入内存。
DSECT,COPY,INFO,OVERLAY :这些类型不多被使用,为了向后兼容才被保留下来。这种类型的section必须被标记为“不可加载的”,以便在程序运行不为它们分配内存。
(4)输出section的LMA :
默认状况下,LMA等于VMA,但能够经过关键字AT()指定LMA。
用关键字AT()指定,括号内包含表达式,表达式的值用于设置LMA。若是不用AT()关键字,那么可用AT>LMA_REGION表达式设置指定该section加载地址的范围。
这个属性主要用于构件ROM境象。
(5)输入section描述:
最多见的输出section描述命令是输入section描述。输入section描述是最基本的链接脚本描述。
1)输入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
2)字符串模式内可存在如下通配符:
* :表示任意多个字符 ? :表示任意一个字符 [CHARS] :表示任意一个CHARS内的字符,可用-号表示范围,如:a-z :表示引用下一个紧跟的字符 SORT():对知足字符串模式的全部名字进行递增排序,如SORT(.text*)。
在文件名内,通配符不匹配文件夹分隔符/,但当字符串模式仅包含通配符*时除外。另外,任何一个文件的任意section只能在SECTIONS命令内出现一次。
3)通用符号(common symbol)的输入section
在许多目标文件格式中,通用符号并无占用一个section。链接器认为:输入文件的全部通用符号在名为COMMON的section内。
4)输入section和垃圾回收
在链接命令行内使用了选项–gc-sections后,链接器可能将某些它认为没用的section过滤掉,此时就有必要强制链接器保留一些特定的 section,可用KEEP()关键字达此目的。如KEEP(*(.text))或KEEP(SORT(*)(.text))。
5)在输出section存放数据命令
可以显示地在输出section内填入你想要填入的信息(这样是否是能够本身经过链接脚本写程序?固然是简单的程序)。
BYTE(EXPRESSION) 1 字节 SHORT(EXPRESSION) 2 字节 LOGN(EXPRESSION) 4 字节 QUAD(EXPRESSION) 8 字节 SQUAD(EXPRESSION) 64位处理器的代码时,8 字节 注意,这些命令只能放在输出section描述内,其余地方不行。 错误:SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } } 正确:SECTIONS { .text : { *(.text) LONG(1) } .data : { *(.data) } }
6)FILL(EXPRESSION)和=FILEEXP属性
在当前输出section内可能存在未描述的存储区域(好比因为对齐形成的空隙),能够用FILL(EXPRESSION)命令决定这些存储区域的内容, EXPRESSION的前两字节有效,这两字节在必要时能够重复被使用以填充这类存储区域。如FILE(0×9090)。在输出section描述中能够 有=FILEEXP属性,它的做用如同FILE()命令,可是FILE命令只做用于该FILE指令以后的section区域,而=FILEEXP属性做用 于整个输出section区域,且FILE命令的优先级更高!!!
7)输出section内命令的关键字
CREATE_OBJECT_SYMBOLS :为每一个输入文件创建一个符号,符号名为输入文件的名字。每一个符号所在的section是出现该关键字的section。
CONSTRUCTORS :与c++内的(全局对象的)构造函数和(全局对像的)析构函数相关。具体介绍可参看原文 http://linux.chinaunix.net/techdoc/beginner/2009/08/12/1129972.shtml。
8)输出section的丢弃
例子,.foo { *(.foo) },若是没有任何一个输入文件包含.foo section,那么链接器将不会建立.foo输出section。可是若是在这些输出section描述内包含了非输入section描述命令(如符号 赋值语句),那么链接器将老是建立该输出section。
有一个特殊的输出section,名为/DISCARD/,被该section引用的任何输入section将不会出如今输出文件内,
(6)覆盖图(overlay)描述
覆盖图描述使两个或多个不一样的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大小的最大值。
2.5 暗含的连接脚本
输入文件能够是目标文件,也能够是链接脚本,此时的链接脚本被称为暗含的链接脚本。若是链接器不认识某个输入文件,那么该文件被看成链接脚本被解析。一个暗含的链接脚本不会替换默认的链接脚本,仅仅是增长新的链接而已。在链接命令行中,每一个输入文件的顺序都被固定好了,暗含的链接脚本在链接命令行内占住一个位置,这个位置决定了由该链接脚本指定的输入文件在链接过程当中的顺序。
3、实例
例1:
如下脚本将输出文件的text section定位在0×10000, data section定位在0×8000000:
SECTIONS { . = 0×10000; .text : { *(.text) } . = 0×8000000; .data : { *(.data) } .bss : { *(.bss) } }
解释一下上述的例子:
. = 0×10000 : 把定位器符号置为0×10000 (若不指定, 则该符号的初始值为0).
.text : { *(.text) } : 将全部(*符号表明任意输入文件)输入文件的.text section合并成一个.text section, 该section的地址由定位器符号的值指定, 即0×10000.
. = 0×8000000 :把定位器符号置为0×8000000
.data : { *(.data) } : 将全部输入文件的.data section合并成一个.data section, 该section的地址被置为0×8000000.
.bss : { *(.bss) } : 将全部输入文件的.bss section合并成一个.bss section,该section的地址被置为0×8000000+.data section的大小.
链接器每读完一个section描述后, 将定位器符号的值*增长*该section的大小. 注意: 此处没有考虑对齐约束。
例2
SECTIONS{ … OVERLAY 0×1000 : AT (0×4000) { .text0 { o1/*.o(.text) } .text1 { o2/*.o(.text) } } … }
.text0 section和.text1 section的VMA地址是0×1000,.text0 section加载于地址0×4000,.text1 section紧跟在其后。
程序代码,拷贝.text1 section代码:
extern char __load_start_text1, __load_stop_text1; memcpy ((char *) 0×1000, &__load_start_text1, &__load_stop_text1 – &__load_start_text1);
例3
PHDRS { headers PT_PHDR PHDRS ; interp PT_INTERP ; text PT_LOAD FILEHDR PHDRS ; data PT_LOAD ; dynamic PT_DYNAMIC ; } SECTIONS { . = SIZEOF_HEADERS; .interp : { *(.interp) } :text :interp .text : { *(.text) } :text .rodata : { *(.rodata) } /* defaults to :text */ … . = . + 0×1000; /* move to a new page in memory */ .data : { *(.data) } :data .dynamic : { *(.dynamic) } :data :dynamic … }
例4
MEMORY { rom (rx) : ORIGIN = 0, LENGTH = 256K ram (!rx) : org = 0×40000000, l = 4M }