系列文章目录
官方 Linker Scripts 语法和规则解析(1)
官方 Linker Scripts 语法和规则解析(2)
官方 Linker Scripts 语法和规则解析(3)
链接脚本(Linker Scripts)语法和规则解析(自官方手册)
7.9. 链接脚本
链接脚本的作用及格式
程序员的自我修养:链接、装载与库阅读
Linux Section
Gcc 链接文件
文章目录
- 系列文章目录
- 3 Linker Scripts
- 3.6 SECTIONS Command
- 3.6.1 Output Section Description
- 3.6.2 Output Section Name
- 3.6.3 Output Section Address
- 3.6.4 Input Section Description
- 3.6.4.1 Input Section Basics
- 3.6.4.2 Input Section Wildcard Patterns
- 3.6.4.3 Input Section for Common Symbols
- 3.6.4.4 Input Section and Garbage Collection
- 3.6.4.5 Input Section Example
- 3.6.5 Output Section Data
- 3.6.6 Output Section Keywords
- 3.6.7 Output Section Discarding
- 3.6.8 Output Section Attributes
- 3.6.8.1 Output Section Type
- 3.6.8.2 Output Section LMA
- 3.6.8.3 Forced Output Alignment
- 3.6.8.4 Forced Input Alignment
- 3.6.8.5 Output Section Constraint
- 3.6.8.6 Output Section Region
- 3.6.8.7 Output Section Phdr
- 3.6.8.8 Output Section Fill
- 3.6.9 Overlay Description
为了便于与英文原文对照学习与理解(部分翻译可能不准确),本文中的每个子章节标题和引用使用的都是官方手册英文原称。命令及命令行选项统一使用斜体书写。高频小节会用蓝色字体标出。
3 Linker Scripts
Basic Script Concepts
: 链接器脚本的基本概念Script Format
: 链接器脚本的格式Simple Example
: 简单的链接器脚本例子Simple Commands
: 简单的链接器脚本命令Assignments
: 为符号指定数值SECTIONS
: 段命令MEMORY
: 内存命令PHDRS
: PHDRS 命令VERSION
: 版本命令Expressions
: 链接脚本的表达式Implicit Linker Scripts
: 隐式链接脚本
3.6 SECTIONS Command
SECTIONS
命令告诉链接器如何将输入段映射到输出段,以及如何将输出段放在内存中。
SECTIONS 命令的格式为:
SECTIONS
{sections-commandsections-command…
}
每个 sections-command
命令可能是下面之一:
- ENTRY 命令(请参阅 Entry command)
- 符号赋值(请参阅 Assigning Values to Symbols)
- 输出段的描述
- overlay 描述
为了方便在这些命令中使用位置计数器,在 SECTIONS
命令中允许使用 ENTRY
命令和符号赋值。 这也可以使链接描述文件更容易理解,因为你可以在更有意义的地方使用这些命令来控制输出文件的布局。
输出段的描述
和 覆盖描述
在下面将会分析。
如果在链接脚本中未使用 SECTIONS
命令,则链接器将会照输入文本的顺序,将每个输入部段放置到名称相同的输出段中。例如,如果所有输入段出现在第一个文件中,输出文件的段的顺序将会与第一个输入文件保持一致。第一个段被放在地址 0 。
Output Section Description
输出段描述Output Section Name
输出段名称Output Section Address
输出段地址Input Section
输入段描述Output Section Data
输出段数据Output Section Keywords
输出段关键字Output Section Discarding
输出段忽略的内容Output Section Attributes
输出段属性Overlay Description
Overlay description
3.6.1 Output Section Description
输出段的完整描述如下所示:
section [address] [(type)] :[AT(lma)][ALIGN(section_align) | ALIGN_WITH_INPUT][SUBALIGN(subsection_align)][constraint]{output-section-commandoutput-section-command...} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp] [,]
大部分的可选段属性在多数输出段不需要使用。
SECTION
边上的空格是必须的,这样段名就没有歧义了。冒号和花括号也是必需的。如果使用了 fillexp
,并且下一个 section-command 看起来像是表达式的延续,则可能需要在末尾使用逗号。换行符和其他空格是可选的。
当 fillexp 使用且接下来的 sections-command 看起来像是表达式的延续的时候,可能需要在后面加上逗号。
每个 output-section-command
可以是下列命令之一:
- 符号赋的值(参见 Assigning Values to Symbols)
- 输入段描述(参见 Input Section Description)
- 直接包引用的数据值(参见 Output Section Data)
- 特殊的输出段关键字(参见 Output Section Keywords)
3.6.2 Output Section Name
输出段的名字是 section
。section 必须满足输出格式的规定。在只支持有限段数目的格式中,例如 a.out ,名称必须是该格式所支持的名称之一(例如 a.out ,只允许 ‘.text’,‘.data’,‘.bss’)。如果输出格式支持任意数量的段,但是只有数字而不是名称(Oasys 就是这种情况),则名称应该以带引号的数字字符串的形式提供。一个段的名字可以由任意字符序列组成,但一个含有许多特殊字符(如逗号)的名称必须用引号括起来。
名称为 ‘/DISCARD/’ 的输出段 ,有特殊含义; 参考 Output Section Discarding 。
3.6.3 Output Section Address
address
是输出段 VMA
(虚拟内存地址)的表达式。此地址是可选参数,但如果提供了该地址,则输出地址就会被精确的设置为指定的值。
如果没有指定输出地址,那么则依照下面的几种方式尝试选择一个地址。此地址将被调整以适应输出段的对齐要求。输出段的对齐要求是所有输入节中含有的对齐要求中最严格的一个。
输出段地址探索如下:
-
如果为该段设置了一个输出内存区域,那么它将被添加到该区域中,其地址将是该区域中的下一个空闲地址。
-
如果使用
MEMORY
命令创建内存区域列表,那么将选择具有与该段兼容属性的第一个区域来包含该区域。该部分的输出地址将是该区域中的下一个空闲地址;MEMORY Command 。 -
如果没有指定内存区域,或者没有与段匹配的内存区域,则输出地址将基于位置计数器的当前值。
例如:
.text . : { *(.text) }
和
.text : { *(.text) }
有着细微的不同。 第一个将 ‘.text’ 输出段的地址设置为位置计数器的当前值。 第二个参数会将其设置为位置计数器的当前值,但是该值与所有 ‘.text’ 输入段中最严格的对齐方式对齐。
address
可以是任意表达式; 例如,如果要在 0x10 字节(16字节)边界上对齐段,以使节地址的最低四位为零,则可以执行以下操作:
.text ALIGN(0x10) : { *(.text) }
之所以这样做,是因为 ALIGN
返回的当前位置计数器向上对齐到指定的值。
为段指定地址将会改变位置计数器的值,前提是该段是非空的(空的段被忽略)。
3.6.4 Input Section Description
最常见的输出段命令(output-section-command)是输入段描述(input section description
)。
输入段描述是链接脚本最基本的操作。 您可以使用输出段来告诉链接器如何在内存中布置程序。 您可以使用输入段描述来告诉链接器如何将输入文件映射到您的内存布局中。
Input Section Basics
基本的输入段Input Section Wildcards Patterns
输入段通配符模板Input Section Common Symbols
普通符号的输入段Input Section and Garbage Collection
输入段与垃圾回收Input Section Example
输入段例子
3.6.4.1 Input Section Basics
输入段说明由一个文件名和一个括号中的段名列表(可选)组成。
文件名和段名可以是通配符,我们将在下面进一步描述(请参阅 Input Section Wildcards)。
最常见的输入段描述是在输出段中包括所有具有特定名称的输入段。 例如,把所有输入段放入 ‘.text’ 段,可以这么写:
*(.text)
这里的 '*'
是一个通配符,它可以用来匹配任何文件名。要排除与文件名通配符匹配的文件列表,可以使用 EXCLUDE_FILE
来匹配除 EXCLUDE_FILE 列表中指定的文件以外的所有文件。例如:
EXCLUDE_FILE (*crtend.o *otherfile.o) *(.ctors)
将导致包括除 crtend.o
和 otherfile.o
以外的所有文件的所有 .ctors
段。EXCLUDE_FILE 也可以放在段的列表中,例如:
*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors)
其结果与前面的示例相同。如果段列表包含多个段,则支持 EXCLUDE_FILE 的两个语法非常有用,如下所述。
有两种方法可以包含多个段:
*(.text .rdata)
*(.text) *(.rdata)
两种方法的区别是输入段的 '.text'
和 '.rdata'
段出现在输出段中的顺序。第一个例子里,他们将被混合在一起,按照链接器找到它们的顺序存放。另一个例子中,所有 ‘.text’ 输入段将会先出现,后面是 ‘.rdata’ 输入段。
将 EXCLUDE_FILE 与多个段一起使用时,这个排除命令仅仅对紧随其后的段有效,例如:
*(EXCLUDE_FILE (*somefile.o) .text .rdata)
将导致包含除 somefile.o 以外的所有文件的所有 ‘.text’ 段,而包括 somefile.o 在内的所有文件的所有 ‘.rdata’ 段都将被包含。要从 somefile.o 中排除 ‘.rdata’ 段部分,可以将示例修改为:
*(EXCLUDE_FILE (*somefile.o) .text EXCLUDE_FILE (*somefile.o) .rdata)
或者,将 EXCLUDE_FILE
放在段列表之外(在选择输入文件之前),将导致排除操作对所有段有效。因此,前一示例可以重写为:
EXCLUDE_FILE (*somefile.o) *(.text .rdata)
你可以指定一个文件名来包含特定文件的段。如果一个或者多个你的文件需要被放在内存中的特定位置,你可能需要这么做。例如:
data.o(.data)
如果想使用段标志来选择输入文件的段,可以使用 INPUT_SECTION_FLAGS
。
下面是一个为 ELF 段使用段头标志的简单示例:
SECTIONS {.text : { INPUT_SECTION_FLAGS (SHF_MERGE & SHF_STRINGS) *(.text) }.text2 : { INPUT_SECTION_FLAGS (!SHF_WRITE) *(.text) }
}
在本例中,输出段 ‘.text’ 将被由那些与 *(.text)
能匹配的段(名字)且段头部标志设置了SHF_MERGE 和 SHF_STRINGS 的段构成。输出段 ‘.text2’ 由那些与 *(.text)
能匹配的段(名字)且段头部标志未设置 SHF_WRITE 的段构成。
你也可以指出特别的关联库名称的文件,命令是[ 库匹配模板:与文件匹配的模式 ]
,冒号两边不能有空格。
'archive:file'
在库中寻找能够匹配的文件'archive:'
匹配整个库':file'
匹配文件但不匹配库
‘archive’ 和 ‘file’ 中的一个或两个都可以包含 shell 通配符。在基于 DOS 的文件系统上,链接器会假定一个单字跟着一个冒号是一个特殊的驱动符,因此 ‘c:myfile.o’ 是一个文件的特殊使用,而不是关联库 ‘c’ 的 ‘myfile.o’ 文件。‘archive:file’:可以使用在 EXCLUDE_FILE 列表中,但不能出现在其他链接脚本内部。例如,你不能使用 ‘archive:file’ 从 INPUT 命令中取出一个库相关的文件。
如果你使用一个文件名而不指出段列表,则所有的输入文件的段将被放入输出段。通常不会这么做,但有些场合比较有用,例如:
data.o
当你使用一个文件名且不是 ‘archive:file’ 特殊命令,并且不含任何通配符,链接器将先查看你是否在命令行上或者在 INPUT 命令里指定了该文件。如果没有这么做,链接器尝试将文件当作输入文件打开,就像文件出现在了命令行一样。注意与 INPUT 命令有区别,因为链接器不会在库文件路径搜索文件。
3.6.4.2 Input Section Wildcard Patterns
在输入段描述中,文件名和段名都可以使用通配符模式。
在许多示例中看到的文件名 ‘*’
是一个简单的文件名通配符模式。
通配符模式类似于 Unix shell 使用的那些模式。
'*'
匹配任意数量字符'?'
匹配任意单字'[chars]'
匹配任何字符的单个实例;'-'
字符可被用来指出一个字符的范围,例如 ‘[a-z]’ 可以用来匹配所有小写字母'\'
引用后面的字符
当文件名与通配符匹配时,通配符将不匹配 '/'
字符(在 Unix 上用于分隔目录名)。由单个 '*'
字符组成的模式是除外;它将始终匹配任何文件名,无论它是否包含 ‘/’ 。在段名称中,通配符将匹配 ‘/’ 字符。
文件名通配符模式只匹配在命令行或输入命令中显式指定的文件。链接器不会搜索目录以扩展通配符。
如果一个文件名匹配多个通配符,或者一个文件名被显示指定了,且又被通配符匹配了,则链接器将使用链接器脚本中的第一个匹配项。例如,下面的输入段描述可能有错误,因为 data.o 的规则不会被应用:
.data : { *(.data) }
.data1 : { data.o(.data) }
通常情况下,链接器将按照链接过程中出现通配符的顺序放置文件和段。您可以通过使用 SORT_BY_NAME
关键字来更改此行为,该关键字出现在括号中的通配符模式之前(例如,SORT_BY_NAME(.text*))。当使用 SORT_BY_NAME 关键字时,链接器将按名称按升序对文件或段进行排序,然后将它们放入输出文件中。
SORT_BY_ALIGNMENT
对齐方式类似于 SORT_BY_NAME 。SORT_BY_ALIGNMENT 将在将段放入输出文件之前,按对齐方式的降序对段进行排序。大的对齐被放在小的对齐前面可以减少所需的填充量。
SORT_BY_INIT_PRIORITY
与 SORT_BY_NAME 相似,区别是 SORT_BY_INIT_PRIORITY 把段按照 GCC 的嵌入在段名称的 init_priority 数字属性值升序排列后放入输出文件。.init_array.NNNNN 和 .fini_array.NNNNN, NNNNN 是 init_priority 。 .ctors.NNNNN 和 .dtors.NNNNN, NNNNN 是65535减去 init_priority 。
SORT
是 SORT_BY_NAME
的别名。
当链接器脚本中有嵌套的段排序命令时,段排序命令最多可以有1个嵌套级别。
SORT_BY_NAME (SORT_BY_ALIGNMENT (wildcard section pattern))
。它将首先按名称对输入部分进行排序,如果两个部分同名,则按对齐方式排序。SORT_BY_ALIGNMENT (SORT_BY_NAME (wildcard section pattern))
。它将首先按对齐方式对输入段进行排序,如果两个段具有相同的对齐方式,则按名称排序。SORT_BY_NAME (SORT_BY_NAME (wildcard section pattern))
与SORT_BY_NAME (wildcard section pattern)
相同。SORT_BY_ALIGNMENT (SORT_BY_ALIGNMENT (wildcard section pattern))
与SORT_BY_ALIGNMENT (wildcard section pattern)
相同。- 除此之外,其它所有嵌套段排序命令都是无效的。
当同时使用命令行段排序选项和链接器脚本段排序命令时,段排序命令总是优先于命令行选项。
如果链接器脚本中的段排序命令不是嵌套的,那么命令行选项将使段排序命令被视为嵌套的排序命令。
SORT_BY_NAME (wildcard section pattern )
与–sort-sections alignment
连用等价于SORT_BY_NAME (SORT_BY_ALIGNMENT (wildcard section pattern))
。SORT_BY_ALIGNMENT (wildcard section pattern)
与–sort-section name
连用等价于
SORT_BY_ALIGNMENT (SORT_BY_NAME (wildcard section pattern))
。
如果链接器脚本中的段排序命令是嵌套的,那么命令行选项将被忽略。
SORT_NONE
通过忽略命令行部段排序选项来禁用段排序。
如果您对输入段的去向感到困惑, 可以使用 ‘-M’ 链接器选项来生成映射文件 。映射文件精确地显示了如何将输入段映射到输出段。
下面这个示例展示了通配符如何被用来分隔文件。这个链接脚本指引链接器把所有 ‘.text’ 段放在 '.text'
里,以及所有 ‘.bss’ 段放到 '.bss'
中。链接器将会把所有以大写字母开头的文件的 ‘.data’ 段放入 '.DATA'
,其他文件的 ‘.data’ 段放入 '.data'
。
SECTIONS {.text : { *(.text) }.DATA : { [A-Z]*(.data) }.data : { *(.data) }.bss : { *(.bss) }
}
3.6.4.3 Input Section for Common Symbols
普通符号需要一个特别的标记,因为很多目标文件格式中没有特定的普通符号输入段。链接器把普通符号当作位于一个名为 'COMMON'
的输入段中。
像使用其它文件名与段一样,你也可以使用文件名与 ‘COMMON’ 段的组合。通过这种方法把一个特定文件的普通符号放入一个段内,同时把其它输入文件的普通符号放入另一个段内。
大多数情况下,输入文件的普通符号会被放到输出文件的 ‘.bss’ 段里面。例如:
.bss { *(.bss) *(COMMON) }
有些目标文件格式含有多种普通符号的类型。例如,MIPS EL F目标文件把标准普通符号和小型普通符号区分开来。在这种情况下,链接器会为另一个类型的普通符号使用其它的特殊段名称。在 MIPS ELF 中,链接器为普通符号使用 ‘COMMON’ 以及为小型普通符号使用 ‘.scommon’ 。这样就可以把不同类型的普通符号映射到内存中的不同位置。
有时在老的链接脚本中能看见 ‘[COMMON]’ 。这个标记现在已废弃。它等价于 ‘*(COMMON)’ 。
3.6.4.4 Input Section and Garbage Collection
使用了链接时垃圾收集('–gc-sections'
)的功能,在把段标记为不应被消除非常常用。此功能通过把一个输入段的通配符入口使用 KEEP() 实现,类似于 KEEP((.init))
或 KEEP(SORT_BY_NAME()(.ctors))
。
3.6.4.5 Input Section Example
下面是一个完整的链接脚本的例子。它告诉链接器从 all.o 读取所有段,把它们放到输出段 'outputa'
的开头位置,‘outputa’ 的起始地址为 ‘0x10000’ 。所有文件 foo.o 中的 ‘.input1’ 段紧跟其后。所有文件 foo.o 中的 ‘input2’ 段放入输出文件的 'outputb'
中,跟着是 foo1.o 中的 ‘input1’ 段。所有其它的 '.input1” 和 ‘.input2’ 段被放入输出段 'outputc'
。
SECTIONS {outputa 0x10000 :{all.ofoo.o (.input1)}outputb :{foo.o (.input2)foo1.o (.input1)}outputc :{*(.input1)*(.input2)}
}
如果输出段的名称与输入段的名称相同,并且可以表示为 C 标识符,那么链接器将自动看到 PROVIDE
两个符号:余下的 __start_SECNAME
和 __stop_SECNAME
,其中 SECNAME
是段的名称。它们分别指示输出段的开始地址和结束地址。注意:大多数段名不能表示为 C 标识符,因为它们包含 '.'
字符。
3.6.5 Output Section Data
你可以通过使用输出段命令 BYTE
, SHORT
, LONG
, QUAD
, 或者 SQUAD
在输出段显式的包含几个字节的数据。每个关键字后面跟着一个括号包裹的表达式指出需要存储的数值(参照 Expressions in Linker Scripts)。表达式的值被存储在当前位置计数器值的地方。
BYTE
, SHORT
, LONG
, QUAD
命令分别存储 1,2,4,8 字节。在存储字节后,位置计数器会按照存储的字节数增加。
例如,下面将会存储一个单字节数据 1,然后存储一个符号为 ‘addr’ 四字节数据的值:
BYTE(1)
LONG(addr)
当使用 64 位主机或目标时,QUAD
和 SQUAD
是相同的; 它们都存储一个 8 字节或 64 位的值。主机和目标都是 32 位时,表达式被当作 32 位计算。在这种情况下 QUAD
存储一个 32 位的值,并使用 0 扩展到 64 位,SQUAD
保存 32 位值并使用符号位扩展到 64 位。
如果输出文件的目标文件格式显式的指定 endiannes,在正常的情况下,值将按照大小端存储。当对象文件格式没有显式的指定 endianness,例如,S-records,值将被按照第一个输入目标文件的大小端存储。
注意 —— 这些命令仅在段描述内部工作,因此下面的例子会使链接器产生错误:
SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } }
而下面这是可行的:
SECTIONS { .text : { *(.text) ; LONG(1) } .data : { *(.data) } }
您可以使用 FILL
命令设置当前段的填充模式。该命令后面跟着一个括号包裹的表达式。所有其它没有被特别指定段的内存区域(例如因为对齐需要而留出来的缝隙)按照表达式的值填充,如果有必要可以重复填充。一个 FILL 语句仅会覆盖它本身在段定义中出现的位置后面的所有内存区域;通过使用不同的 FILL 声明,你可以在一个输出段中使用不同的填充模板。
这个例子显示了如何使用 ‘0x90’ 填充未定义内存区域:
FILL(0x90909090)
FILL 命令类似 '=fillexp'
输出段属性,但其仅影响 FILL 命令后面的段,而不是整个段。如果同时使用,FILL 命令为高优先级。参考 Output Section Fill 获取更多填充细节。
3.6.6 Output Section Keywords
这里有两个关键字可以作为输出段的命令:
-
CREATE_OBJECT_SYMBOLS
此命令告诉链接器为每个输入文件创建一个符号。每个符号的名字为对应输入文件的名字。每个符号出现的位置位于包含CREATE_OBJECT_SYMBOLS
命令的输出段中。
这个命令常常是 a.out 目标文件格式特有的。 它一般不为其它的目标文件格式所使用。 -
CONSTRUCTORS
当链接时使用 a.out 目标文件的格式,链接器使用一个特殊构造集来支持 C++ 全局构造函数和析构函数。在链接不支持任意段的文件格式时,例如 ECOFF 和 XCOFF ,链接器将会通过名字自动识别 C++ 全局构造函数和析构函数。对于这些格式的目标文件,CONSTRUCTORS
命令告诉链接器把构造函数信息放到出现CONSTRUCTORS
命令的输出段中。其它文件格式中 CONSTRUCTORS 命令被忽略。
符号__CTOR_LIST__
标记全局构造函数的开始,符号__CTOR_END__
标记结束。同样的,__DTOR_LIST__
和__DTOR_END__
分别标记全局析构函数的开始和结束。第一个列表中的字是入口的数量,后面是每个构造函数或者析构函数的地址,最后是一个全零的字。编译器必须安排实际运行代码。对于这些目标文件格式,GNU C++ 通常从一个__main
子程序中调用构造函数,而对__main
的调用自动被插入到 main 的启动代码中。GNU C++ 通常使用atexit
运行析构函数,或者直接从函数 exit 中退出。
对于 COFF 或者 ELF 等支持任意段名字的目标文件格式,GNU C++ 通常把全局构造函数和析构函数放入.ctors
和.dtors
段。把下面的代码放入你的链接脚本,将会创建 GUN C++ 运行时期望的表。__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__ = .;
如果你正在使用 GUN C++ 支持的初始化优先级,初始化优先级提供了一些对全局构造函数运行顺序的控制,则你必须在链接时对构造函数排序以保证它们以正确的顺序执行。当你使用
CONSTRUCTORS
命令,使用 ‘SORT_BY_NAME(CONSTRUCTORS)’ 替换它。当使用.ctors
和.dtors
段,使用 ‘(SORT_BY_NAME(.ctors))’ 和’ (SORT_BY_NAME(.dtors))’ 取代 ‘(.ctors)’ 和’ ‘(.dtors)’ 。
通常编译器和链接器会自动处理这些问题,您不需要关心它们。但是,在你自己写链接脚本且正在使用 C++ 的时候,你可能需要考虑这些。
3.6.7 Output Section Discarding
链接器通常不会创建没有内容的输出段。这是为了方便引用那些有可能出现或者不出现任何输入文件中的段。例如:
.foo : { *(.foo) }
只有在至少有一个输入文件含有 '.foo'
段且 ‘.foo’ 段不为空的时候才会在输出文件创建一个 '.foo'
段。其它链接脚本指出在一个段中间分配空间也会创建输出段。赋值也一样即使赋值没有创建空间,除了 '. = 0'
, '. = . + 0'
, '. = sym'
, '. = . + sym'
和 '. = ALIGN (. != 0, expr, 1)'
其中 ‘sym’ 是一个值为 0 的已定义绝对符号。因此你可以强制一个空的输出段使用 '. = .'
。
链接器将忽略为丢弃的输出段进行地址赋值(请参见 Output Section Address),除非链接器脚本在输出段中定义符号。在这种情况下,链接器将遵守地址赋值,有可能更新 ‘.’ 的值,即便段被抛弃了。
特殊输出段名称 '/DISCARD/'
可被用来抛弃输入段。一个被分派到名为 ‘/DISCARD/’ 的输出段的输入段将不会被包含在输出文件中。
3.6.8 Output Section Attributes
我们在前面展示了输出部分的完整描述如下:
section [address] [(type)] :[AT(lma)][ALIGN(section_align) | ALIGN_WITH_INPUT][SUBALIGN(subsection_align)][constraint]{output-section-commandoutput-section-command...} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
我们已经描述了section, address, and output-section-command 命令。在本节中,我们将描述其余的段属性。
Output Section Type
: 输出段类型Output Section LMA
: 输出段LMA —— 加载地址Forced Output Alignment
: 强制输出对齐Forced Input Alignment
: 强制输入对齐Output Section Constraint
: 输出段限制Output Section Region
: 输出段区域Output Section Phdr
: 输出段 phdrOutput Section Fill
: 输出段填充
3.6.8.1 Output Section Type
每个输出段可以有一个类型。类型是圆括号中的关键字。定义了以下类型:
-
NOLOAD
此段应标记为不可加载,以便在程序运行时不会将其加载到内存中。 -
DSECT
/COPY
/INFO
/OVERLAY
支持这些类型名称是为了向后兼容,而且很少使用。它们都具有相同的效果: 该段应该标记为不可分配,以便在程序运行时不会为该段分配内存。
链接器通常根据映射到输出段的输入段设置输出段的属性。您可以使用 section 类型来覆盖它。例如,在下面的脚本示例中,’ ROM’ 部分位于内存位置 ’ 0 ',在程序运行时不需要加载它。
SECTIONS {ROM 0 (NOLOAD) : { ... }...
}
3.6.8.2 Output Section LMA
每个段有一个虚拟地址 (VMA
) 和一个加载地址 (LMA
) ; 参见 Basic Linker Script Concepts 。虚拟地址由前面描述的 Output Section Address 指定。加载地址由 AT
或 AT>
关键字指定。指定加载地址是可选的。
AT
关键字把一个表达式当作自己的参数。这将指定段的实际加载地址 。关键字 AT>
使用内存区域的名字作为参数。参考 MEMORY Command 。段的加载地址被设置为该区域的当前空闲位置,并且按照段对齐要求对齐。
如果没有为可分配段使用 AT 和 AT>,链接器会使用下面的方式尝试来决定加载地址:
- 如果段有一个特定的 VMA 地址,则 LMA 也使用该地址。
- 如果段为不可分配的则 LMA 被设置为它的 VMA 。
- 否则如果可以找到符合当前段的一个内存区域,且此区域至少包含了一个段,则设置 LMA 在那里。如此 VMA 和 LMA 的区别类似于 VMA 和 LMA 在该区域的上一个段的区别。
- 如果没有声明内存区域且默认区域覆盖了整个地址空间,则采用前面的步骤。
- 如果找不到合适的区域或者没有前面存在的段,则 LMA 被设置为等于 VMA 。
这些功能旨在使构建 ROM 映像变得容易。例如,以下链接器脚本创建三个输出段:一个名为 “.text”
,从 0x1000 开始;一个名为 “.mdata”
,即使其 VMA 为 0x2000,也加载在 “.text” 节的末尾;另一个名为 “.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;
3.6.8.3 Forced Output Alignment
你可以使用 ALIGN
增加输出段的对齐。作为替换,你可以通过 ALIGN_WITH_INPUT
属性强制 VMA 与 LMA 自始至终保持它们之间的区别。
您可以使用 ALIGN
来增加输出段的对齐方式。作为一种替代方法,您可以使用 ALIGN_WITH_INPUT
属性在整个输出段保持 VMA 和 LMA 之间的差异。
3.6.8.4 Forced Input Alignment
您可以使用 SUBALIGN
来强制输出段中的输入段对齐。指定的值将覆盖输入段提供的任何对齐方式,无论比原来大还是小。
3.6.8.5 Output Section Constraint
通过分别使用关键字 ONLY_IF_RO
和 ONLY_IF_RW
,可以指定只有在所有输入段都是只读或所有输入段都是读写的情况下才创建输出段。
3.6.8.6 Output Section Region
可以使用 '>region'
把一个段指定到此前设置的内存区域内。参见 MEMORY Command 。
下面是一个例子:
MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 }
SECTIONS { ROM : { *(.text) } >rom }
3.6.8.7 Output Section Phdr
您可以使用 ':phdr'
将一个段分配给先前定义的程序段。参见 PHDRS Command 。如果一个段被分配给一个或多个段,那么所有后续分配的段也将被分配给这些段,除非它们显式地使用 :phdr
修饰符。您可以使用 :NONE
来告诉链接器根本不要将该段放在任何段中。
这里有一个简单的例子:
PHDRS { text PT_LOAD ; }
SECTIONS { .text : { *(.text) } :text }
3.6.8.8 Output Section Fill
你可以使用 '=fillexp'
为整个段设置填充模板。fillexp
是一个表达式(参考 Expressions in Linker Scripts)。任何其它的未被特殊指定的输出段的内存区域(例如,因为对其输入段产生的缝隙)将会被用 fillexp 的值填充,如果有需要可以重复填充。如果表达式是一个简单的 hex 数字,例如一个十六进制数字由 ‘0x’ 开头且结尾没有 ‘k’ 或 ‘M’ ,则一个任意长的十六进制数字可以被用来给填充模板赋值,前面的 0 同样成为模板的一部分。在其它情况中,包含额外的括号或者一个一元 + ,填充模板为表达式值的最低 4 个有意义的字节。在所有情况中,数字总是大端的。
你也可以使用 FILL
命令设置填充值(参考 Output Section Data)。
这里有一个简单的例子:
SECTIONS { .text : { *(.text) } =0x90909090 }
3.6.9 Overlay Description
覆盖描述提供了一种简单的方法来描述将作为单个内存映像的一部分加载但将在相同内存地址上运行的段。在运行时,某种类型的覆盖管理器将根据需要从运行时内存地址复制覆盖的段,可能通过简单地操作寻址位来实现。这种方法可能很有用,例如,当某个内存区域比另一个区域更快时。
覆盖描述使用 OVERLAY
命令。OVERLAY 命令和 SECTIONS 命令一起使用,就像一个输出段描述符。完整的 OVERLAY 命令的语义如下:
OVERLAY [start] : [NOCROSSREFS] [AT ( ldaddr )]{secname1{output-section-commandoutput-section-command...} [:phdr...] [=fill]secname2{output-section-commandoutput-section-command...} [:phdr...] [=fill]...} [>region] [:phdr...] [=fill] [,]
除了 OVERLAY
(关键字),以及每个段都必须有一个名字(上面的 secname1 和 secname2 ),所有的部分都是可选的。除了 OVERLAY 中不能为段定义地址和内存区域,使用 OVERLAY 结构定义的段类似于那些普通的 SECTIONS 中的结构(参考 SECTIONS)。
结尾的逗号可能会被使用,如果使用了 fill 且下一个 sections-command 看起来像是表达式的延续。
所有的段都使用同样的开始地址定义。所有段的载入地址都被排布,使它们在内存中从整个 ‘OVERLAY’ 的载入地址开始都是连续的(就像普通的段定义,载入地址是可选的,缺省的就是开始地址;开始地址也是可选的,缺省是当前的位置计数器的值)。
如果使用了关键字 NOCROSSREFS
,并且在任何段间有互相引用,链接器将会产生一个错误报告。因为所有的段运行在同样的地址,直接引用其它的段通常没有任何意义。参考 NOCROSSREFS 。
每个伴随 OVERLAY 的段,链接器自动提供两个符号。符号 __load_start_secname
被定义为段的起始地址。符号 __load_stop_secname
被定义为段结束地址。任何不符合 C 定义的伴随 secname 的字符都将被移除。C(或者汇编)代码可以使用这些符号在需要时搬移复盖代码。
覆盖之后,位置计数器的值设置为覆盖的起始值加上最大段的长度。
下面是例子,请记住这应该放在 SECTIONS 结构内。
OVERLAY 0x1000 : AT (0x4000){.text0 { o1/*.o(.text) }.text1 { o2/*.o(.text) }}
这将把 '.text0'
和 '.text1'
的起始地址设置为地址 0x1000
。'.text0'
的加载地址为 0x4000
,‘.text1’ 会加载到 ‘.text0’ 后面。下面的符号如果被引用则会被定义: __load_start_text0
, __load_stop_text0
, __load_start_text1
, __load_stop_text1
。
C 代码拷贝覆盖 .text1 到覆盖区域可能像下面的形式。
extern char __load_start_text1, __load_stop_text1;memcpy ((char *) 0x1000, &__load_start_text1,&__load_stop_text1 - &__load_start_text1);
注意 ‘OVERLAY’ 命令只是为了语法上的便利,因为它所做的所有事情都可以用更加基本的命令加以代替。上面的例子可以用下面的写法:
.text0 0x1000 : AT (0x4000) { o1/*.o(.text) }PROVIDE (__load_start_text0 = LOADADDR (.text0));PROVIDE (__load_stop_text0 = LOADADDR (.text0) + SIZEOF (.text0));.text1 0x1000 : AT (0x4000 + SIZEOF (.text0)) { o2/*.o(.text) }PROVIDE (__load_start_text1 = LOADADDR (.text1));PROVIDE (__load_stop_text1 = LOADADDR (.text1) + SIZEOF (.text1));. = 0x1000 + MAX (SIZEOF (.text0), SIZEOF (.text1));