SECTIONS 命令
该命令告诉编译器如何将输入section映射为输出section,同时告诉编译器如何放置输出section
下面是这个命令的格式
SECTIONS
{
sections-command
sections-command
...
}
每个sections-command可以是以下之一:
- 一个ENTRY命令
- 一个符号赋值
- 一个输出section的描述
- 一个overlay描述
为了方便在这些命令中使用位置计数器,SECTIONS命令中允许使用ENTRY命令和符号赋值。
这也可以使链接脚本文件更容易理解,因为您可以在输出文件布局中的有意义的位置使用这些命令。
输出section描述和overlay描述如下
如果您在链接脚本文件中未使用SECTIONS命令,则链接器会将每个输入section放置在名称相同的输出section中,顺序则按先找到,则放前面的原则。
例如,如果所有输入section都位于第一个文件中,则输出文件中的section顺序将与第一个输入文件中的顺序匹配。 第一section将位于地址零处。
输出section描述
输出部分的完整描述如下所示:
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使用很少的可选section属性。
section两边必须有空白,以便section名称不混淆。 还需要冒号和花括号。 换行符和其他空格是可选的。
每个output-section-command可以是以下之一:
- 一个符号赋值
- 一个输出section 描述
- 要直接包含的数据值
- 特殊的输出section关键字
输出section名字
输出section的名称是"section"。"section"必须符合您的输出格式的约束。在仅支持有限section的格式中,例如a.out,名称必须是该格式支持的名称之一(例如a.out仅允许’.text’,’.data’或’的.bss’)
如果输出格式支持任意数量的section,但是带有数字而不是名称(Oasys就是这种情况),则应使用带引号的数字字符串来提供名字。
section名可以由任何字符序列组成,但是包含任何特殊字符(例如逗号)的名称必须加引号。输出section名称“ / DISCARD /”是特殊的…
输出section地址
该地址是输出section的VMA(虚拟内存地址)的表达式。
如果不提供地址,则链接器将根据region(如果存在)或其他情况根据位置计数器的当前值进行设置。
如果提供地址,则输出部分的地址将精确设置为提供的值。
如果既不提供地址,也不提供region,则输出部分的地址将设置为与输出部分的对齐要求对齐的位置计数器的当前值。
输出部分的对齐要求是输出部分中包含的所有输入部分的最严格对齐。
例如:
.text . : { *(.text) }
和
.text : { *(.text) }
他们略有不同。
第一个将“ .text”输出section的地址设置为位置计数器的当前值。
第二个将其设置为位置计数器的当前值,该值与“ .text”输入节的最严格对齐方式对齐
地址可以是任意表达式;例如,如果要在0x10字节边界上对齐节,以使节地址的最低四位为零,则可以执行以下操作:
.text ALIGN(0x10) : { *(.text) }
之所以可以这样,是因为ALIGN会将当前位置计数器向上对齐到指定值的位置并返回。
指定section的地址将更改位置计数器的值,前提是该section为非空。 (将忽略空的部分)。
输入section描述
最常见的输出section命令是输入section描述。
输入section描述是最基本的链接描述脚本操作。您可以使用输出section来告诉链接器如何在内存中布置程序。您使用输入section描述来告诉链接器如何将输入文件映射到您的内存布局中
输入section基础知识
输入section描述由文件名组成,可以选择在文件名后加上括号中的section列表。
文件名称和section名称可能是通配符模式,我们将在下面进一步介绍。
最常见的输入section描述是在输出section中包括所有具有特定名称的输入section。
例如,要包括所有输入的“ .text”部分,您应编写:
*(.text)
此处的“ *”是与任何文件名匹配的通配符。 要从匹配文件名称通配符中排除文件列表,可以使用EXCLUDE FILE来匹配除EXCLUDE FILE列表中指定的文件以外的所有文件。例如:
*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors)
将导致所有文件中的所有.ctors部分(“ crtend.o”和“ otherfile.o”除外)被包括在内。
包含多个部分的方法有两种:
*(.text .rdata)
*(.text) *(.rdata)
两者之间的区别是“ .text”和“ .rdata”输入section在输出section中出现的顺序。
在第一个示例中,它们将混合在一起,以与在链接器输入中找到的顺序相同的顺序出现。
在第二个示例中,所有“ .text”输入section将首先显示,然后是所有“ .rdata”输入section。
您可以指定文件名,以包括来自特定文件的section。 如果一个或多个文件包含需要位于内存中特定位置的特殊数据section,则可以执行此操作。 例如:
data.o(.data)
您还可以通过以下方式指定档案文件中的文件:编写与档案文件匹配的模式,然后冒号,然后编写与文件匹配的模式,冒号周围没有空格。例如:
‘archive:file’
匹配archive中的file文件
‘archive:’
匹配整个archive
‘:file’
匹配file
“archive”和“file”之一或两者都可以包含通配符。
在基于DOS的文件系统上,链接程序将假定单个字母后跟冒号是驱动符,因此’c:myfile.o’是一个简单的文件指定,而不是名为’c’的档案中的’myfile.o’文件。'archive:file’也可以在EXCLUDE_FILE列表中使用,但可能不会出现在其他链接描述文件上下文中。
例如,您不能通过在INPUT命令中使用“ archive:file”从存档中提取文件。
如果使用不带section列表的文件名,则输入文件中的所有section将包含在输出section中。例如:
data.o
当您使用不是“ archive:file”说明符的文件名且不包含任何通配符时,链接器将首先查看您是否还在链接器命令行或INPUT命令中指定了文件名。
如果您没有这样做,则链接器将尝试将其作为输入文件打开,就像它出现在命令行中一样。
请注意,这与INPUT命令不同,因为链接器不会在归档搜索路径中搜索文件。
输入section 通配符
在输入section描述中,文件名或section名或两者都可以是通配符模式。
在许多示例中看到的文件名“ *”是文件名的简单通配符模式。 通配符模式类似于Unix shell使用的通配符模式。
- “ *”匹配任意数量的字符
- ‘?’匹配任何单个字符
- “[chars]”匹配任何一个char的单个实例; '-‘字符可用于指定字符范围,如’[a-z]'一样以匹配任何小写字母
- “ \”引用后面字符
当文件名与通配符匹配时,通配符将不匹配’/'字符(在Unix上用于分隔目录名)。
由单个“ *”字符组成的模式是一个例外; 它将始终与任何文件名匹配,无论它是否包含“ /”
在section名称中,通配符将与“ /”字符匹配。
文件名通配符模式仅与在命令行或INPUT命令中明确指定的文件匹配。链接器不会搜索目录以扩展通配符。
如果文件名与多个通配符模式匹配,或者文件名显式出现并且也由通配符模式匹配,则链接器将使用链接器脚本中的第一个匹配项。
例如,此输入section描述序列可能是错误的,因为将不使用“ data.o”规则:
.data : { *(.data) }
.data1 : { data.o(.data) }
通常,链接器将按通配符在链接期间看到的顺序放置文件和section.
您可以使用SORT_BY_NAME关键字更改此排序(例如SORT_BY_NAME(.text *))。
当使用SORT_BY_NAME关键字时,链接器会将文件或section按名称升序排列,然后将它们放入输出文件中。
SORT_BY_ALIGNMENT与SORT_BY_NAME非常相似。 区别在于SORT_BY_ALIGNMENT在将section放置到输出文件中之前,将通过对齐将section按升序排序。
SORT是SORT_BY_NAME的别名。
当链接脚本中有嵌套的section排序命令时,section排序命令最多可以有1个嵌套级别。
-
SORT_BY_NAME(SORT_BY_ALIGNMENT(通配符部分模式))。 首先,它将按名称对输入section进行排序,如果2个section的名称相同,则按对齐方式对输入section进行排序
-
SORT_BY_ALIGNMENT(SORT_BY_NAME(通配符部分模式))。 它将首先按对齐方式对输入部分进行排序,如果2个部分具有相同的对齐方式,则将按名称进行排序。
-
SORT_BY_NAME(SORT_BY_NAME(通配符部分模式))与SORT_BY_NAME(通配符部分模式)相同
-
SORT_BY_ALIGNMENT(通配符部分模式)(SORT_BY_ALIGNMENT)与SORT_BY_ALIGNMENT(通配符部分模式)相同。
-
所有其他嵌套section排序命令均无效
当同时使用命令行section排序选项和链接脚本section排序命令时,链接器section排序命令始终优先于命令行选项。
如果链接脚本中的section排序命令未嵌套,则命令行选项会将section排序命令视为嵌套排序命令
- 命令行指定“ --sort-sections 对齐方式”,同时脚本中出现SORT_BY_NAME(通配符部分模式)。等效于SORT_BY_NAME(SORT_BY_ALIGNMENT(通配符部分模式))
- 命令行选项指定“ --sort-section name”,同时脚本中出现SORT_BY_ALIGNMENT(通配符部分模式)等效于SORT_BY_ALIGNMENT(SORT_BY_NAME(通配符部分模式))。
如果链接脚本中的section排序命令是嵌套的,则命令行选项将被忽略。
如果您对输入section的去向感到困惑,请使用“ -M”链接器选项来生成映射文件。 映射文件准确显示了输入section如何映射到输出section
本示例说明了如何使用通配符模式对文件进行分区。此链接脚本指示链接器将所有“ .text”部分放置在“ .text”中,并将所有“ .bss”部分放置在“ .bss”中。
链接器会将所有以大小字母开头的文件的“ .data”部分放在“ .DATA”中; 对于所有其他文件,链接器会将“ .data”部分放在“ .data”中
SECTIONS {
.text : { *(.text) }
.DATA : { [A-Z]*(.data) }
.data : { *(.data) }
.bss : { *(.bss) }
}
输入section的通用符号
需要注意的是:通用符号需要特殊的标记,因为在许多obj文件格式中,通用符号没有特定的输入section。
链接器将通用符号视为在“ COMMON”的输入section中。
您可以在“ COMMON”section中使用文件名,就像在其他任何输入section中一样。
您可以使用此命令将特定输入文件中的通用符号放置在一个section中,而将其他输入文件中的通用符号放置在另一section中。
在大多数情况下,输入文件中的通用符号将放置在输出文件中的“ .bss” section。 例如:
.bss { *(.bss) *(COMMON) }
某些obj文件格式具有多种类型的通用符号。例如,MIPS ELF obj文件格式区分标准通用符号和小型通用符号。在这种情况下,链接器将使用不同的特殊section名称。
对于MIPS ELF,链接器将“ COMMON”用于标准通用符号,将“ .scommon”用于小型通用符号。
这使您可以将不同类型的通用符号映射到不同位置的内存中。
有时您会在旧的链接程序脚本中看到“ [COMMON]”。 现在,该符号被认为已过时。 等同于“ *(COMMON)”。
输入section和垃圾收集
使用链接垃圾收集(“ --gc-sections”)时,标记不应删除的section通常很有用。
这可以通过在KEEP(*(.init))或KEEP(SORT_BY_NAME(*(.ctors)))中用KEEP()包围输入节的通配符条目来实现。
输入section例子
以下示例是一个完整的链接脚本。它告诉链接器从文件’all.o’中读取所有section,并将它们放在输出section’outputa’的开头,该部分的起始位置为’0x10000’。文件“ foo.o”中".input1"紧随其后。
“ foo.o”中的“ .input2” section全部进入输出section“ outputb”,然后是“ foo1.o”中的“ .input1” section。
来自所有文件的其余的“ .input1”和“ .input2” section均被写入输出section“ outputc”。
SECTIONS {
outputa 0x10000 :
{
all.o
foo.o (.input1)
}
outputb :
{
foo.o (.input2)
foo1.o (.input1)
}
outputc :
{
*(.input1)
*(.input2)
}
}
输出section 数据
通过将BYTE,SHORT,LONG,QUAD或SQUAD用作输出section命令,可以在输出section中包含指定的字节数据。
每个关键字后跟一个括号中的表达式,该表达式提供要存储的值。
表达式的值存储在位置计数器的当前值。BYTE,SHORT,LONG和QUAD命令分别存储一个,两个,四个和八个字节
存储字节后,位置计数器将增加所存储的字节数。
例如,这将存储字节1,后跟符号“ addr”的四个字节值:
BYTE(1)
LONG(addr)
当使用64位主机或目标时,QUAD和SQUAD相同。它们都存储一个8字节或64位的值。
当主机和目标均为32位时,表达式将计算为32位。 在这种情况下,QUAD存储将32位扩展为64位,零填充,而SQUAD存储将32位扩展为64位,符号位填充。
如果输出文件文件格式有显著的字节序,则将以该字节序存储值。
当obj文件格式没有明确的字节序时,例如S-records,该值将以第一个输入obj文件的字节序存储。
注意:这些命令仅在section描述中起作用,而在它们之间不起作用,因此以下内容将在链接器中产生错误:
SECTIONS { .text : { *(.text) } LONG(1) .data : { *(.data) } }
而这将工作:
SECTIONS { .text : { *(.text) ; LONG(1) } .data : { *(.data) } }
您可以使用FILL命令为当前section设置fill模式,其后是括号中的表达式。
该section中任何其他未指定的内存区域(例如,由于输入节的所需对齐而留下的间隙)将以表达式的值填充,并根据需要重复。
FILL语句覆盖在section之后的内存位置; 通过包含多个FILL语句,您可以在输出section的不同部分中使用不同的FLL模式。
本示例说明如何使用0x90填充未指定的内存区域。
FILL(0x90909090)
FILL命令类似于“ = fillexp”输出section的属性,但是它只会影响FILL命令后section,而不是所有section。 如果两者都使用,则FILL命令优先。
输出节关键字
有几个关键字可以作为输出section命令出现。
CREATE_OBJECT_SYMBOLS
该命令告诉链接器为每个输入文件创建一个符号。每个符号的名称将是相应输入文件的名称。然后将这个符号放在,这个命令出现的输出section中。
这对于a.out目标文件格式是常用。 但是对于其他格式不常用。
CONSTRUCTORS
使用a.out obj文件格式进行链接时,链接器使用构造集合来支持C ++全局构造函数和析构函数。
当链接不支持任意section名的文件格式(例如ECOFF和XCOFF)时,链接器将按名称自动识别C ++全局构造函数和析构函数。
对于这些obj文件格式,CONSTRUCTORS命令告诉链接器将构造函数信息放置在CONSTRUCTORS命令出现的输出section中。
对于其他obj文件格式,将忽略CONSTRUCTORS命令。
符号__CTOR_LIST__表示全局构造函数的开始,符号__CTOR_END__表示结束。同样,__ DTOR_LIST__和__DTOR_END__表示全局析构函数的开始和结束。
列表中的第一个字是条目数,其次是每个构造函数或析构函数的地址,然后是0字。
对于这些obj文件格式,gnu C ++通常从__main调用构造函数。 对__main的调用会自动插入到main的启动代码中。
gnu C ++通常通过使用atexit或直接从函数exit运行析构函数。
对于支持任意section名称的obj文件格式(例如COFF或ELF),gnu C ++通常会安排将全局构造函数和析构函数的地址放入.ctors和.dtors section中。
将以下序列放入您的链接器脚本中,将构建gnu 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__ = .;
如果您正在使用gnu C ++对初始化优先级的支持,它对全局构造函数的运行顺序提供了一些控制,则必须在链接时对构造函数进行排序,以确保它们以正确的顺序执行。
使用CONSTRUCTORS命令时,请改用“ SORT_BY_NAME(CONSTRUCTORS)”。 使用.ctors和.dtors部分时,请使用’*(SORT_BY_NAME(.ctors))‘和’*(SORT_BY_NAME(.dtors))’,而不仅仅是’*(.ctors)‘和’*(.dtors)’。
通常,编译器和链接器将自动处理这些问题,并且您无需担心它们。但是,如果您使用的是C ++并编写自己的链接描述文件,则可能需要考虑这一点
输出section 丢弃
链接器将不会创建没有内容的输出section。当引用的输入section不存在于任何输入文件时,例如:
.foo : { *(.foo) }
仅当存在输入foo section,并且不为空时,才会创建一个foo输出section
在输出section中分配空间的其他链接脚本指令也将创建输出section。
链接器对忽略的section上面的地址分配也将忽略,除非链接器脚本在输出部分中定义符号。在这种情况下,链接器将遵守地址分配,即使该段被丢弃。
特殊输出section名称“ / DISCARD /”可用于丢弃输入节。 任何分配给名为“ / DISCARD /”的输出节的输入节都不包含在输出文件中
输出section属性
上面所述的输出节的完整描述如下:
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。 在本节中,我们将描述其余的节属性。
输出section 类型
每个输出节可以具有一个类型。 类型是括号中的关键字。 定义了以下类型:
NOLOAD
该section应标记为不可加载,以便在程序运行时不会将其加载到内存中。
DSECT
COPY
INFO
OVERLAY
支持这些类型名称以实现向后兼容,并且很少使用。
它们都具有相同的效果:该section应标记为不可分配,以便在程序运行时不为该部分分配内存。
链接器通常根据映射到输出section中的输入section来设置输出section的属性。您可以使用section类型来覆盖它。
例如,在下面的脚本示例中,“ ROM”部分位于内存位置“ 0”处,并且在程序运行时无需加载。“ ROM”部分的内容将照常显示在链接器输出文件中。
SECTIONS {
ROM 0 (NOLOAD) : { ... }
...
}
输出section LMA
每个部分都有一个虚拟地址(VMA)和一个加载地址(LMA);可能出现在输出section描述中的地址表达式会修改VMA。AT关键字后面的表达式lma指定该部分的加载地址。
另外,您也可以使用“AT> lma_region”表达式为该部分的加载地址指定一个存储区域。请注意,如果没有为该节分配VMA,则链接器也将lma_region用作VMA。
如果未为可分配section指定AT或AT>,则链接器将设置LMA,以使该节的VMA和LMA之间的差异与同一区域中的先前输出section相同。
如果没有前面的输出section或该section不可分配,则链接器会将LMA设置为等于VMA.
此功能旨在简化构建ROM映像的过程。例如,以下链接脚本创建了三个输出section:一个名为“ .text”的section从0x1000开始,一个名为“ .mdata”的section被加载到“ .text”部分的末尾,即使其VMA为0x2000 ,以及一个名为“ .bss”的section,用于将未初始化的数据保存在地址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;
强制输出对齐
您可以使用ALIGN来增加输出部分的对齐方式。
强制输入对齐
您可以使用SUBALIGN强制输入section在输出section中对齐。指定的值会覆盖输入部分给出的任何对齐方式,无论该对齐方式是大是小。
输出section约束
您可以通过分别使用关键字ONLY_IF_RO和ONLY_IF_RW来指定仅在输出section的所有输入section均为只读或所有输入section为读写的情况下才创建输出section。
输出section区域
您可以使用“> region”将section分配给先前定义的内存区域。
这是一个简单的示例:
MEMORY { rom : ORIGIN = 0x1000, LENGTH = 0x1000 }
SECTIONS { ROM : { *(.text) } >rom }
输出section Phdr
您可以使用“:phdr”将section分配给先前定义的程序段。如果将一个section分配给一个或多个段,则所有后续分配的section也将分配给那些段,除非它们使用显式的:phdr修饰符来修改。
您可以使用“:NONE”告诉链接器不要将该section完全放在任何段中
例如:
PHDRS { text PT_LOAD ; }
SECTIONS { .text : { *(.text) } :text }
输出section填充
您可以使用“ = fillexp”为整个部分设置填充模式。fillexp是一个表达式
输出seciton中任何其他未指定的内存区域(例如,由于所需的输入section对齐而留下的间隙)将被填充这个表达式的值,并根据需要重复该值。
如果fillexp表达式是一个简单的十六进制数,即,一串以“ 0x”开头但不以“ k”或“ M”结尾的十六进制数字,然后可以使用任意长的十六进制数字序列来指定fillexpr.
对于所有其他情况,包括多余的括号或正号+,填充值是表达式值的四个最低有效字节。 在所有情况下,数字都是大端。
您也可以在输出section命令中使用FILL命令来更改填充。例如:
SECTIONS { .text : { *(.text) } =0x90909090 }
overlay说明
overlay提供了一种简单的方式,这种方式用来描述section从不同内存区域中加载,但是同一个内存地址执行。
在运行时,某种overlay管理器可能会根据需要,通过简单地操作寻址位,将overlay seciton复制到运行时内存地址中或从中复制出来。
例如,当某个内存区域比另一个内存区域快时,此方法可能会很有用。
使用OVERLAY命令来描述overlay。 OVERLAY命令在SECTIONS命令中使用,就像输出section描述一样。 OVERLAY命令的完整语法如下:
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]
除了OVERLAY关键字以外,其他都是可选的。每个section都必须要有名字,如上面的secname1和secname2。OVERLAY中的section定义和常规的section定义相同,除了在OVERLAY中没有为sectioin定义地址和区域。
这些section均以相同的起始地址定义。section的加载地址的排列方式使它们在内存中是连续的,从整个OVERLAY使用的加载地址开始。(与普通section定义一样,加载地址是可选的,默认为起始地址; 起始地址也是可选的,默认为位置计数器的当前值)
如果使用了NOCROSSREFS关键字,并且各section之间有任何引用,则链接器将报告错误。由于这些section都在同一地址运行,因此通常一个section直接引用另一section是没有意义的。
对于OVERLAY中的每个部分,链接器会自动提供两个符号。符号__load_start_secname定义为该section的起始加载地址。
符号__load_stop_secname定义为该section的最终加载地址。
secname中被C标识为不合法的任何字符都将被删除。C(或汇编)代码可以根据需要使用这些符号来移动overlay section。
在overlay的末尾,位置计数器的值设置为overlay的起始地址加上最大部分的大小。例如:
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。
将overlay .text1复制到overlay区域的C代码如下所示。
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));
下面的章节,详细说明MEMORY,PHDRS,VERSION指令