映像的结构由以下各项定义:
• 映像的组成区和输出节的数量
• 加载映像时这些区和节在内存中的位置
• 执行映像时这些区和节在内存中的位置
描述内存映射时:
• 术语 根区域用于描述加载地址和执行地址相同的区。
• 载入区相当于 ELF 段。
1.1.1 对象和映像的构建块
可执行文件由映像、区、输出节和输入节的层次结构构成:
• 映像由一个或多个区组成。 每个区由一个或多个输出节组成。
• 每个输出节包含一个或多个输入节。
• 输入节是对象文件中的代码和数据信息。
图3-1 显示了区、输出节和输入节之间的关系。
输入节 输入节包含代码、初始化数据,或描述未初始化的或在映像执行前必须设为 0 的内存片断。 这些特性通过RO、RW和 ZI 这样的属性来表示。armlink 使用这些属性,将输入节组织到称为输出节和区的更大的构建块中。
输出节 一个输出节由若干个具有相同 RO、RW 或 ZI 属性的相邻输入节组成。 输出节的属性与组成它的输入节的属性相同。 在输出节中,输入节根据节放置中描述的规则进行排序。
区 一个区由一个、两个或三个相邻的输出节组成。 区中的输出节根据其属性排序。 首先是 RO 输出节,然后是 RW 输出节,最后是 ZI输出节。 区通常映射到物理内存设备,如 ROM、RAM 或外围设备。
1.1.2 映像的加载视图和执行视图
映像区在加载时放入系统内存映射。 在执行映像之前,您可能要将它的一些区移到执行地址并创建ZI 输出节。 例如,必须将已初始化的RW 数据从 ROM中的加载地址复制到 RAM中的执行地址。
映像的内存映射具有以下不同的视图(如图3-2中所示)。
加载视图 根据映像加载到内存时所在的地址(即映像开始执行之前的位置)来描述每个映像的区和节。
执行视图 根据映像执行时所在的地址来描述每个映像的区和节。
1.1.3 指定映像的内存映射
映像可以由任意个区和输出节组成。 所有这些区可以有不同的加载地址和执行地址。 要构建映像的内存映射,armlink 必须有以下各项信息:
分组 如何将输入节分组为输出节和区。
布局 在内存映射中如何安排映像区的位置。
根据映像的内存映射的复杂程度,有两种方法可将此信息传递到 armlink:
使用命令行选项
对于映像只有一个或两个加载区和最多三个执行区的简单情况,
可以使用以下选项:
• --ro-base
• --rw-base
• --ropi
• --rwpi
• --first
• --last
• --split
• --rosplit
1.1.4 映像入口点
必须为程序指定至少一个初始入口点,否则链接器会生成警告。 并非每个源文件都必须具有入口点。 单个源文件中不允许有多个入口点。对于 ROM 起始地址为 0的嵌入式应用程序,可使用 --entry 0x0 (或对于有高位向量的 CPU,可以使用 0xFFFF0000)。
初始入口点必须满足以下条件:
• 映像入口点必须始终在执行区内执行区必须是非重叠的,而且必须是根执行区(加载地址与执行地址相同)。如果不使用 --entry 选项指定初始入口点,则:
• 如果输入对象仅包含一个由 ENTRY 指令设置的入口点,则链接器将使用该入口点作为映像的初始入口点
• 在以下情况时,链接器生成不包含初始入口点的映像:
— 已使用 ENTRY 指令指定多个入口点
— 未使用 ENTRY 指令指定任何入口点。
1.2 使用命令行选项创建简单映像
数个RO、RW和 ZI 类型的输入节可以组成一个简单的映像。 这些输入节整合成RO、RW和 ZI 输出节。 根据载入区和执行区中输出节的排列方式,有三种基础类型的简单映像:
类型 1加载视图中有一个区,执行视图中有三个相邻区。 使用--ro-base选项可创建此类型的映像。有关详细信息,请参阅类型 1,一个加载区和几个连续执行区。
类型 2加载视图中有一个区,执行视图中有三个不连续的区。 使用--ro-base 和 --rw-base 选项可创建此类型的映像。
有关详细信息,请参阅类型 2,一个加载区和几个不连续的执行区。
类型 3加载视图中有两个区,执行视图中有三个不连续的区。 使用--ro-base、--rw-base 和 --split 选项可创建此类型的映像。 您也可以使用--rosplit 选项将缺省载入区分为两个 RO 输出节,一个用于代码,另一个用于数据。
有关详细信息,请参阅类型 3,两个加载区和几个不连续的执行区。
在所有这三种类型的简单映像中,最多允许有三个执行区:
• 第一个执行区包含 RO 输出节
• 第二个执行区包含 RW 输出节(如果有)
• 第三个执行区包含 ZI 输出节(如果有)。
这些执行区称为RO、RW和 ZI 执行区。
也可以用分散加载描述文件创建简单映像。
1.2.1 类型 1,一个加载区和几个连续执行区
此类型的映像由加载视图中的单个载入区和内存映射中相邻放置的三个执行区组成。 此方法适用于将程序加载到RAM 中的系统,例如OS 引导加载程序或桌面系统
也可以使用以下命令创建此类映像:armlink --ro-base 0x8000
加载视图
单个载入区由连续放置的RO 和 RW输出节组成。 RO和 RW执行区都是根区。加载时不存在 ZI输出节。 该节是在执行前使用映像文件中的输出节描述创建的。
执行视图
包含RO、RW 和 ZI输出节的三个执行区相邻排列。 RO和 RW执行区的执行地址与其加载地址相同,因此不需要从加载地址向执行地址移动任何内容。 不过,包含ZI 输出节的 ZI执行区是在运行时创建的。
使用armlink 选项 --ro-base address 可指定包含 RO输出的区的加载和执行地址。缺省地址是 0x8000,如图3-3 中所示。
1.2.2 类型 2,一个加载区和几个不连续的执行区
此类型的映像由单个加载区和执行视图中的三个执行区组成。 RW执行区与 RO执行区是不相邻的。 例如,此方法适用于基于ROM 的嵌入式系统(请参阅图3-4),其中 RW数据在启动时从 ROM复制到 RAM。
使用以下命令创建此类映像:armlink --ro-base 0x0 --rw-base 0xA000
加载视图
在加载视图中,单个加载区由连续放置的 RO 和 RW输出节(例如在 ROM中)组成。 此处的RO 区是根区, RW区是非根区。 加载时不存在ZI 输出节。 该节在运行时创建。
执行视图
在执行视图中,第一个执行区包含 RO 输出节,第二个执行区包含 RW和 ZI输出节。包含 RO输出节的区的执行地址与加载地址相同,因此不必移动 RO输出节。即,它是根区。包含 RW输出节的区的执行地址与加载地址不同,因此 RW输出节从(单载入区)加载地址移到其执行地址(第二个执行区中)。 ZI执行区及其输出节与RW 执行区相邻放置。
使用 armlink 选项 --ro-base address 可指定 RO 输出节的加载地址和执行地址,
使用 --rw-baseexec_address 可指定 RW 输出节的执行地址。 如果未使用
--ro-base 选项指定地址,则 armlink 将使用缺省值 0x8000。 对于嵌入式系统,--ro-base 值通常为 0x0。 如果未使用 --rw-base 选项指定地址,则缺省情况下
将RW 放在紧靠RO 的上方。(如第3-22 页的类型1,一个加载区和几个连续执行区中所述)。RW 和 ZI 输出节的执行区不能与任何加载区重叠。
1.2.3 类型 3,两个加载区和几个不连续的执行区
此类型的映像与类型2 的映像相似,只是单个加载区现在分成了两个根加载区。(请参阅图3-5)。
使用以下命令创建此类映像:armlink --split --ro-base 0x8000 --rw-base 0xE000
加载视图
在加载视图中,第一个载入区由 RO 输出节组成,第二个载入区由 RW输出区组成。 加载时不存在ZI 输出节。 它是在执行前,使用映像文件中包含的输出节描述创建的。
执行视图
在执行视图中,第一个执行区包含 RO 输出节,第二个执行区包含 RW和 ZI输出节。
RO 区的执行地址与其加载地址相同,因此 RO输出节的内容不必从加载地址移动或复制到执行地址。 RO和 RW都是根区。
RW 区的执行地址也与其加载地址相同,因此 RW输出节的内容不必从加载地址移到执行地址。 不过,ZI 输出节在运行时创建,并且紧邻 RW区放置。
使用以下链接器选项指定加载和执行地址:
--split将缺省的单加载区(包含 RO 和 RW 输出节)分成两个根加载区
(一个包含RO 输出节,另一个包含RW 输出节),以便可以使用
--ro-base 和 --rw-base 分别放置这两个加载区。
--ro-base address 指示 armlink 将包含 RO 节的区的加载和执行地址设置在 4 字节对齐的 address处(例如,ROM 中第一个位置的地址)。 如果未使用
--ro-base 选项指定地址,则 armlink 将使用缺省值 0x8000。
--rw-base address 指示 armlink 将包含 RW 输出节的区的执行地址设置在 4 字节对齐的 address处。 如果此选项与 --split 结合使用,则会同时指定 RW区(例如根区)的加载地址和执行地址。
1.3 映像符号
链接器定义了一些包含$$ 字符序列的符号。 这些符号和所有其他包含$$ 序列的外部名称都是 ARM 的保留名称。
汇编语言程序可以导入这些符号地址并将其用作可重定位的地址,或者从 C 或C++ 源代码中将其作为 extern 符号进行引用。 有关详细信息,
注意:仅当代码引用链接器定义的符号时,才会生成这些符号
1.3.1 与区相关的符号
Image$$ 执行区符号
下表显示了链接器为映像中存在的每个执行区生成的符号。 下表中所有符号都在初始化 C库后引用执行地址。
NO. |
Image$$ 执行区符号 |
说明 |
1 |
Image$$region_name$$Base |
区的执行地址。 |
2 |
Image$$region_name$$Length |
执行区长度(以字节为单位),不包括 ZI长度。 |
3 |
Image$$region_name$$Limit |
执行区中非 ZI 部分末尾后面的字节的地址。 |
4 |
Image$$region_name$$RO$$Base |
此区中的 RO 输出节的执行地址。 |
5 |
Image$$region_name$$RO$$Length |
RO 输出节的长度(以字节为单位)。 |
6 |
Image$$region_name$$RO$$Limit |
执行区中 RO 输出节末尾后面的字节的地址。 |
7 |
Image$$region_name$$RW$$Base |
此区中的 RW 输出节的执行地址。 |
8 |
Image$$region_name$$RW$$Length |
RW输出节的长度(以字节为单位)。 |
9 |
Image$$region_name$$RW$$Limit |
执行区中 RW 输出节末尾后面的字节的地址。 |
10 |
Image$$region_name$$ZI$$Base |
此区中的 ZI输出节的执行地址。 |
11 |
Image$$region_name$$ZI$$Length |
ZI 输出节的长度(以字节为单位)。 |
12 |
Image$$region_name$$ZI$$Limit |
执行区中 ZI 输出节末尾后面的字节的地址。 |
Load$$ 执行区符号
下表显示了链接器为映像中存在的每个 Load$$执行区生成的符号。此表中的所有符号都在初始化 C 库之前引用加载地址。 有以下几点需要注意:
• 这些符号是绝对的,因为与节相关的符号只能具有执行地址。
• 这些符号会考虑 RW 压缩。
• 这些符号不包括 ZI 输出节,因为该节在初始化 C 库之前不存在。
• 经过 RW 压缩的执行区中的所有重定位都必须在压缩之前执行,因为链接器无法对压缩数据解析延迟的重定位。
• 如果链接器检测到从经过 RW 压缩的区到依赖于 RW 压缩的链接器定义符的重定位,则链接器对该区禁用压缩。
• 写入到文件的任何零字节都可见。 因此,限制 (Limit) 和长度 (Length) 值必须考虑写入到文件中的零字节。
NO. |
Load$$ 执行区符号 |
说明 |
1 |
Load$$region_name$$Base |
区的加载地址。 |
2 |
Load$$region_name$$Length |
加载区长度(以字节为单位)。 |
3 |
Load$$region_name$$Limit |
执行区末尾后面的字节的地址。 |
4 |
Load$$region_name$$RO$$Base |
此执行区中的 RO 输出节的地址。 |
5 |
Load$$region_name$$RO$$Length |
RO 输出节的长度(以字节为单位)。 |
6 |
Load$$region_name$$RO$$Limit |
执行区中 RO 输出节末尾后面的字节的地址。 |
7 |
Load$$region_name$$RW$$Base |
此执行区中的 RW 输出节的地址。 |
8 |
Load$$region_name$$RW$$Length |
RW 输出节的长度(以字节为单位)。 |
9 |
Load$$region_name$$RW$$Limit |
执行区中 RW 输出节末尾后面的字节的地址。 |
Load$$LR$$ 加载区符号
下表显示了链接器为映像中存在的每个 Load$$LR$$加载区生成的符号。 一个Load$$LR$$ 加载区可以包含许多执行区,因此没有单独的 $$RO 和$$RW 组件。
NO. |
Load$$LR$$加载区符号 |
说明 |
1 |
Load$$LR$$load_region_name$$Base |
加载区的地址。 |
2 |
Load$$LR$$load_region_name$$Length |
加载区的长度。 |
3 |
Load$$LR$$load_region_name$$Limit |
加载区末尾后面的字节的地址。 |
1.3.2 非分散加载时的区名称值
如果未使用分散加载,链接器将使用以下项的 region_name 值:
• ER_RO,适用于只读执行区
• ER_RW,适用于读写执行区
• ER_ZI,适用于零初始化的执行区。
注意
• 映像的 ZI输出节不是静态创建的,而是在运行时自动动态创建的。因此,ZI输出节没有加载地址符号。
• 建议优先使用与区相关的符号,而不是与节相关的符号。
1.3.3 与节相关的符号
如果使用命令行选项创建简单映像,则会生成表4-4 中所示的输出节符号。 简单映像有三个输出节(RO、RW 和 ZI),它们生成三个执行区。 对于映像中存在的每个输入节,链接器将生成第4-8 页的表4-5 中所示的输入符号。
链接器先按 RO、RW 或 ZI 属性对执行区内的节进行排序,然后按名称进行排序。例如,将所有 .text 节放在一个连续块中。 包含相同属性和名称的节的连续块称为合并节。
注意:如果使用分散加载描述文件,则不会定义表4-4中的输出节符号。如果代码访问这些符号,则必须将其视为弱引用。__user_initial_stackheap()的标准实现使用Image$$ZI$$Limit中的值。因此,如果使用分散加载描述文件,则必须手动放置堆栈和堆。可以在分散描述文件中实现此目的,或是通过重新实现__user_initial_stackheap()设置堆和堆栈边界来实现。
1.3.4 导入链接器定义的符号
可以使用两种方法,将链接器定义的符号导入到 C 或 C++ 源代码中。 请使用以下方法之一:
externunsigned int symbol_name;或
externchar symbol_name[];
如果将符号声明为int,则必须使用取址运算符获取正确的值,如示例4-2 中所示。
示例4-2 导入链接器定义的符号
***********************************************************************
externunsigned int Image$$ZI$$Length;
externchar Image$$ZI$$Base[];
memset(Image$$ZI$$Base,0,(unsignedint)&Image$$Length);
***********************************************************************
1.4 使用分散加载描述文件指定堆栈和堆
ARM C 库提供了 __user_initial_stackheap() 函数的多个实现,可以根据分散加载描述文件中给出的信息自动选择正确的函数实现。要选择两个区内存模型,请在分散加载描述文件中定义两个名为 ARM_LIB_HEAP和
ARM_LIB_STACK的特殊执行区。 两个区均具有 EMPTY 属性。 这导致库选择使用以下
符号值的非缺省 __user_initial_stackheap() 实现:
•Image$$ARM_LIB_STACK$$Base
•Image$$ARM_LIB_STACK$$ZI$$Limit
•Image$$ARM_LIB_HEAP$$Base
•Image$$ARM_LIB_HEAP$$ZI$$Limit
只能指定一个 ARM_LIB_STACK 或 ARM_LIB_HEAP 区,并且必须分配大小,例如:
ARM_LIB_HEAP0x20100000 EMPTY 0x100000-0x8000 ; Heap starts at 1MB
; and grows upwards
ARM_LIB_STACK0x20200000 EMPTY -0x8000 ;Stack space starts at the end
; of the 2MB of RAM
; And grows downwards for 32KB
注意:如果使用上面的堆栈函数,则还必须在汇编源代码中包含一个 IMPORT __use_two_region_memory,或在 C/C++源代码中包含一个 #pragmaimport(__use_two_region_memory),因为不会自动选择双区模型。
可以使用 EMPTY 属性定义单个名为ARM_LIB_STACKHEAP的执行区,强制__user_initial_stackheap()使用合并的堆栈/堆区。这导致__user_initial_stackheap()使用符号Image$$ARM_LIB_STACKHEAP$$Base和
Image$$ARM_LIB_STACKHEAP$$ZI$$Limit的值。
1.5 Stack/Heap保留空白区
可以在执行区分散加载描述中使用 EMPTY 属性,为堆栈保留一个空白内存块
该内存块并不构成加载区的一部分,而是在执行时分配使用的。 由于它是作为
虚ZI 区创建的,因此链接器使用以下符号对其进行访问:
• Image$$region_name$$ZI$$Base
• Image$$region_name$$ZI$$Limit
• Image$$region_name$$ZI$$Length.
如果指定的长度为负值,则将该地址作为区结束地址。 它必须是绝对地址,而不是相对地址。 例如,第5-26 页的示例5-15 中说明的执行区定义 STACK 0x800000 EMPTY –0x10000 定义了一个名为 STACK 的区,它的开始地址是0x7F0000,结束地址是 0x800000。
注意:
在运行时,不会将为 EMPTY执行区创建的虚 ZI区初始化为零
如果地址采用相对格式 (+n),并且长度为负值,链接器将生成错误
EMPTY 属性仅适用于执行区。如果在加载区定义中使用 EMPTY属性,链接器将生成警告并忽略该属性。
在此示例中,链接器生成以下符号:
Image$$STACK$$ZI$$Base = 0x7f0000
Image$$STACK$$ZI$$Limit =0x800000
Image$$STACK$$ZI$$Length =0x10000
Image$$HEAP$$ZI$$Base = 0x800000
Image$$HEAP$$ZI$$Limit = 0x810000
Image$$HEAP$$ZI$$Length= 0x10000