Linker Scripts3--SECTIONS Command

1.前言

SECTIONS命令告诉链接器如何映射输入段到输出段以及在内存中如何放置输出段,SECTIONS命令的格式如下:

SECTIONS  
{  
  sections-command  
  sections-command  
  ...  
}  

sections-command可能是如下的一种:

  • 一个ENTRY命令(看 Entry Command)
  • 一个符号赋值(看 Assignments)
  • 一个输出段描述
  • 一个叠加描述

为了方便使用位置计数(location counter),ENTRY命令和符号赋值命令被允许放在SECTIONS命令中,这样可以使链接脚本更加容易理解,因为你可以在输出文件中有意义的位置使用它们

输出段描述和叠加描述会在下面介绍

如果你在链接脚本中没有使用SECTIONS命令,那么链接器会将输入段按照顺序放到一个有实际名字的输出段中,

这个输出段就是被解析到的第一个输入文件。如果第一个文件能呈现所有输出段,那么输出文件中段的顺序就和第一个输入文件的一样,第一个段的地址会被设置成0

2. Output Section Description

一个完整的输出段描述像这个样子:

section [address] [(type)] :
[AT(lma)]
[ALIGN(section_align)]
[SUBALIGN(subsection_align)]
[constraint]
{
output-section-command
output-section-command
...
} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]

大部分输出段都不会使用到这里面绝大多数的段选项属性

section旁边的空格是必须的,这样段名才不会混淆。分号和大括号也是必须的。换行和另外的空格就无所谓了

output-section-command可能是如下的一种:

  • 一个符号赋值(看 Assignments)
  • 一个输入段描述(看 Input Section)
  • 一个直接包含的数据值(看 Output Section Data)
  • 一个特殊的输出段关键字(看 Output Section Keywords)

2.1. Output Section Name 

输出段的名字就是section,它是有限制的。比如a.out的格式只限制了几个段名的使用(仅仅允许使用‘.text’、‘.data’、‘.bss’)。

如果输出格式支持任何段名,但是像数字或者不是名字的名称,那么必须要加上引号。

段名可以包含了一些有序字符,但是如果包含了一些比如像逗号这样的不常规字符那么久一定要加引号

‘/DISCARD/’是一个特殊的输出段名,看 Output Section Discarding

2.2.Output Section Address

address是输出section的VMA的表达式。如果没有提供address,链接器将基于当前的region进行设置,否则将基于位置计数器来设置。
如果提供了address,输出section的address将被精准设置。如果既没有提供address也没有提供region,则输出section的address将被设置到位置计数器按输出
section对齐要求的当前值。输出section对齐要求是包含在输出secton中的所有输入section的最严格对齐。

.text . : { *(.text) }

.text : { *(.text) }

是十分不一样的。第一个‘.text’的地址是当前的位置计数值,第二个它的地址是当前位置计数值经过输入段的严格对齐后的值

这个address可以是任意表达式,看 Expressions。比如,你想设定段以0x10个字节来对齐,则section地址的最低四位会是0,可以像这样写

ALIGN会将当前的位置计数值向上扩展到一个合适的值

指定一个段的address会改变当前位置计数的值,当然了,空段会被忽略

2.3.output-section-command 

最常用的output-section-command是一个输入section描述

输入section描述是最基本的链接脚本操作,输出section可以告诉链接器你的程序在内存中如何布局,输入段描述告诉链接器如何映射输入文件到内存布局中

2.3.1 Input Section Basics

输入段描述由一个文件名和一列可选的段名包含在括号里面组成

文件名和段名可以是通配符模式,更详细的内容可以查看 Input Section Wildcards

大部分输入段描述都是包含所有该段名的输入段到输出段中,举例来说,包含所有的‘.text’输入段,你可以这样写:

  *(.text)  

这里的‘*’是一个通配符,它匹配了所有的文件名。为了排除一些被匹配到的通配符文件名,你可以使用EXCLUDE_FILE来排除用它指定的一列文件,例如:

  1. *(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors)  

这个描述会包含所有文件的‘.ctors’段,但是除了crtend.o和otherfile.o

下面有两种方式来包含多于1个段:

  1. *(.text .rdata)  
  2. *(.text) *(.rdata)  

它们的不同点是‘.text’和‘rdata’输入段在输出段中的顺序。第一个例子,它们先把两个段混在一起然后按照链接输入的顺序放置,第二个例子,首先会放置所有文件的‘.text’输入段然后在跟着放所有的‘.rdata'输入段

你也可以用一个文件名指定包含一个文件中的段,如果一个或多个文件中包含了特殊数据,你可以就会这样做,例如:

  1. data.o(.data)  

为了重新定义出输出段标志(section header flag),INPUT_SECTION_FLAGS可以被使用

下面一个简单的例子,它使用了ELF格式的段标志:

  1. SECTIONS {  
  2.   .text : { INPUT_SECTION_FLAGS (SHF_MERGE & SHF_STRINGS) *(.text) }  
  3.   .text2 :  { INPUT_SECTION_FLAGS (!SHF_WRITE) *(.text) }  
  4. }  

在上面的例子中,输出段‘.text’由所有文件的‘.text’输入段组成且它会设置上SHF_MERGE和SHF_STRINGS段标志,输出段‘.text2’也是所有文件的‘.text’组成但它会去掉SHF_WRITE段标志

你也可以指定在归档包中的文件,可以这样写,一个归档,一个冒号,然后是被匹配的文件,,冒号旁边不能有空格

  1. `archive:file'  
  2.           matches file within archive   
  3. `archive:'  
  4.           matches the whole archive   
  5. `:file'  
  6.           matches file but not one in an archive  

‘archive’和‘file’都可以包含通配符。在DOS文件系统中,通过一个简单的字母后面跟一个冒号来指定一个驱动器,所以‘c:myfile.o’就是指向了一个驱动器上的文件而不是归档名叫‘c’中的‘myfile.o’文件。

‘archive:file’这样的文件格式可以在EXCLUDE_FILE列表中使用但不能在其他链接脚本上下文中使用,比如你不能在INPUT命令中使用‘archive:file’从归档包中解压这个文件

如果你的文件名后面没有跟一列段,那么这个输入文件中的所有段都会被包含到输出段中。这不是一个很常见的做法,但它可能在某些情况下非常有用,例如:

data.o  

如果你使用的文件名不是‘archive:file’也没有任何通配符字符,那么链接器会首先去链接命令行和INPUT命令找该文件,

否则,链接器会尝试去像在命令行中的输入文件一样去打开这个文件。注意,这种方式和INPUT命令不同,因为链接器并不会去归档库路径搜索该文件

2.3.2 Input Section Wildcard Patterns

TODO

2.3.3 Input Section for Common Symbols

一个特殊的符号是通用符号(common symbol),因为在许多目标文件(object file)格式中,它都没有一个特殊的输入段。

链接器对待它们就像它们在名为‘COMMON’的输入段中一样

你可以同其他输入段一样,使用带有‘COMMON’的文件名,你可以将一个特定的输入文件的通用符号放入一个段中,而其他输入文件的通用符号放入另一个段中

大多数情况,输入文件的通用符号都会被放入‘.bss’段中,例如:

  1. .bss { *(.bss) *(COMMON) }  

不过有些目标文件格式不止一种通用符号。比如,MIPS ELF目标文件格式。

为了区分标准的通用符号和小的通用符号,链接器会使用不同的特殊段名。

对于像MIPS ELF目标文件格式,链接器使用‘COMMON’作为标准的通用符号,使用‘.scommon’作为小的通用符号,这样就允许你将不同的通用符号放到内存的不同位置

有时候在老的链接脚本中会看到‘[COMMON]’,这个符号现在已经被废弃了,它和‘*(COMMON)’是一样的

2.3.4 Input Section and Garbage Collection 

当link-time垃圾收集使用时(‘--gc-sections’),通常通过标记sections不被删除是很有用的。这是通过使用KEEP()来完成的,就像KEEP (*(.init))或者KEEP (SORT_BY_NAME (*)(.ctors))

2.3.5 Input Section Example

下面是一个完整的链接脚本的例子。它让链接器把文件all.o的所有输入段放入一个以0x10000地址开始的‘outputa’输出段中,

紧接着把foo.o文件的'.input1'输入段放入相同的输出段中,foo.o的输入段‘input2’放入‘outputb’输出段中,

紧接着把foo1.o的输入段‘.input1’放入,最后将所以文件剩下的'.input1'和'.input2'输入段放入'outputc'输出段中

SECTIONS {
outputa 0x10000 :
{
all.o
foo.o (.input1)
}
outputb :
{
foo.o (.input2)
foo1.o (.input1)
}
outputc :
{
*(.input1)
*(.input2)
}
}

 

2.3.6 Output Section Data 

你可以在output-section-command中直接使用BYTE, SHORT, LONG, QUAD, SQUAD关键字来包含字节数据,

每个关键字都会用括号包含一个表达式,它存储了值(看 Expressions),这个表达式的值被存放在当前的位置计数器的位置

BYTE, SHORT, LONG, QUAD, SQUAD命令分别存储1,2,4,8个字节的值,在把字节值存储后,位置计数会增加相应的字节数

举个例子,下面存储了一个字节的值后紧接着又存储了‘addr’符号的值:

  1. BYTE(1)  
  2. LONG(addr)  

对于使用64位的值,QUAD和SQUAD是一样的,它们都使用8个字节或64位的值。当使用32位的值,表达式就是用32位来计算。

如果QUAD存储的是一个32位的值,那它会用0扩展为64位,而SQUAD则符号扩展到64位

如果输出文件的目标格式显示的指出了字节序,那么这个值就会按照指定的字节序存储,反之,这个值会按照第一个输入目标文件的字节序来存储

注意,这些命令只能写在输出段描述的里面,如果你按照下面的写法就错了:

  1. SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } }  

下面这样才是正确的:

  1. SECTIONS { .text : { *(.text) ; LONG(1) } .data : { *(.data) } }  

你可以使用FILL命令来设定当前段的填充模式(fill pattern),它会用一个括号包含一个表达式

这样的话,任何一个在这个段的没有指定的内存区域(比如,由于输入段必须的对齐导致留下的间隙)都会用这个表达式的值重复的填充。

它会填充FILL命令定义之后的内存位置,如果你在一个输出段中包含了几个FILL命令,那么还可以有不同填充模式

下面的例子展示了如何给未指定的内存区域填充值‘0x90’:

  1. FILL(0x90909090)  

这个FILL命令和输出段属性'=fillexp'类似,但它仅仅影响的是该段内FILL命令之后的部分,而不是整个段。如果都使用的话,FILL命令优先考虑,更详细的信息,去看看Output Section Fill小节

2.3.7 Output Section Keywords

todo

2.3.8 Output Section Discarding

链接器不会去创建一个没有内容的输出段。如果引用的输入section没有在任何一个输入文件中出现,那么这么做是方便的,例如:

  1. .foo : { *(.foo) }  

如果这个‘.foo’输入段至少在一个输入文件存在就会在输出文件中创建一个‘.foo’输出段。如果这个输入段为空,那么链接脚本也会指明,对于需要分配空间时也会去创建一个输出段

对于一个被抛弃的的输出段,链接器将会忽略给该段指定的地址,除非在输出段中有符号定义,那样的话,即使这个输出段被抛弃,链接器依然会遵守该段地址的指定

一个特殊的输出段名‘/DSICARD/’可以用来给抛弃输入段使用,任何被放在名字为‘/DISCARD/’输出段中的输入段都不会被包含到输出文件中

2.4  Output Section Attributes

对于一个完整的输出段描述:

section [address] [(type)] :  
  [AT(lma)]  
  [ALIGN(section_align)]  
  [SUBALIGN(subsection_align)]  
  [constraint]  
  {  
    output-section-command  
    output-section-command  
    ...  
  } [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp] 

到目前,我们已经介绍了section、address和output-section-command。在这个小节中,我们将介绍剩下的段属性

  • Output Section Type: Output section type
  • Output Section LMA: Output section LMA
  • Forced Output Alignment: Forced Output Alignment
  • Forced Input Alignment: Forced Input Alignment
  • Output Section Constraint: Output section constraint
  • Output Section Region: Output section region
  • Output Section Phdr: Output section phdr
  • Output Section Fill: Output section fill

2.4.1 Output Section Type

每个输出段都可以有一个类型,它被放在括号里面。有如下定义:

  • NOLOAD

这个段被标记为不能被加载,也就是说在程序运行的时候,它不会被加载到内存中

  • DSECT
  • COPY
  • INFO
  • OVERLAY

这些类型名是向后兼容的,不过很少使用。它们都是一个作用:这个段被标记为不能分配,也就是说当程序运行的时候,它不会被分配内存

通常,链接器会基于输入section给一个输出section设定属性,你可以通过使用section type覆盖整个。

举例来说,下面的脚本中,这个‘ROM’输出段在内存中的地址是0,但是在程序运行的时候,它不会被加载进内存,'ROM' section的内容通常将出现在链接输出文件里。

SECTIONS {
ROM 0 (NOLOAD) : { ... }
...
}

 

2.4.2 Output Section LMA

每个段都有一个虚拟地址(VMA)和一个加载地址(LMA),看一下 Basic Script Concepts 小节。

出现在输出section描述中的address表达式设置了VMA(虚拟地址的介绍可以看看早前的 Output Section Address 小节。

加载地址的指定是用 AT 和 AT>关键字,它们是可选项:

--AT关键字把参数包含在括号里面,它明确指定了段的加载地址。

‘--AT>lma_region把内存区域作为参数,可以看看 MEMORY 小节,段的加载地址就是这个区域内的下一个空闲地址,当然这个地址是要段对齐的

注:如果段没有指定一个VMA,则使用lma region 作为VMA区域

如果对于一个可分配的(allocatable )段没有AT和AT>关键字,那么链接器会通过以下方式推算出来:

(1)链接器将设置LMA,VMA和LMA的差将与同个region的前一个输出section的相同

(2)如果同个region前面没有输出section或者输出section不可分配,链接器将设置LMA与VMA相同,参考See Section 3.6.8.6 [OutputSection Region], page 58

这种特征很容易被设计来构建一个ROM映像。举例来说,下面的脚本创建了3个输出段:

‘.text’段它的起始地址为0x1000,

‘.mdata’段它被加载到‘.text’段之后但它的虚拟地址为0x2000,

‘.bss’存储着没有初始化的数据起始地址为0x3000。

符号_data的值是0x2000,它显示的是位置计数器的值即VMA的值而不是LMA

SECTIONS  
  {  
  .text 0x1000 : { *(.text) _etext = . ; }  
  .mdata 0x2000 :  
    AT ( ADDR (.text) + SIZEOF (.text) )  
    { _data = . ; *(.data); _edata = . ;  }  
  .bss 0x3000 :  
    { _bstart = . ;  *(.bss) *(COMMON) ; _bend = . ;}  
}  

运行初始化可以使用一段脚本产生的程序将ROM映像中的初始化数据拷贝到运行时的地址空间,可以看到在脚本中定义符号的好处

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;  

2.4.3 Forced Output Alignment

你可以使用ALIGN来增加输出段的对齐方式

2.4.4 Forced Input Alignment

你可以使用SUBALIGN来强制输出段中的输入段对齐方式,这个值会覆盖掉原来输入段的对齐方式不管是大还是小

2.4.5 Output Section Constraint

你可以使用关键字ONLY_IF_RO和ONLY_IF_RW指定,如果所有输入段是只读或者所有输入段是读写,则输出段应该要被创建

2.4.6 Output Section Region

你可以使用‘>region’将一个段分配到之前定义好的一个内存区域中,see MEMORY.

下面是一个简单的例子:

  1. MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 }  
  2. SECTIONS { ROM : { *(.text) } >rom }  

2.4.7 Output Section Phdr

TODO

你可以使用‘:phdr’给一个段分配一个之前定义过的程序段(segment),具体看PHDRS。

如果一个段会被分配给多个segment,那么通过显示的指明‘:phdr’可以改变可分配段被分配给segment的顺序。

使用:NONE告诉链接器不要将该段分配给任何segment

下面一个简单的例子:

  1. PHDRS { text PT_LOAD ; }  
  2. SECTIONS { .text : { *(.text) } :text }  

2.4.8 Output Section Fill

TODO

你可以使用‘=fillexp’来设定填充模式,fillexp是一个表达式(see Expressions)。

对于在输出段中没有被定义的内存空隙(补充:如果使用内存区域则和memory region发生歧义)(例如,输入段对齐后残留下来的空隙)可以用这个值来重复填充。

如果表达式是一个简单的十六进制数,那就是一串以'0x'开头和一长串十六进制数字组成的不以'k'或者‘M’结尾的字符,前面的0也是表达式的一部分。

其他的表达式,需要在外面加上括号或者+且至少有4个有意思的字节。对于所有的表达式,数字都用大端字节序

你也可以使用FILL命令来改变输出段的填充值,(see Output Section Data)

下面是一个例子:

  1. SECTIONS { .text : { *(.text) } =0x90909090 }  

2.4.9 Overlay Description

略.

参考文献

[1] http://blog.csdn.net/han22647/article/details/64920623

[2] http://blog.csdn.net/huiyuyang_fish/article/details/16884593

 

posted @ 2018-01-23 11:58  jasonactions  阅读(368)  评论(0编辑  收藏  举报