目录
本文主要是对 浅析 Keil 中的 sct 文件 一文做进一步的补充和说明。
一、加载域和执行域
镜像的各个域在加载时是被放置在系统存储器中的。 在执行镜像之前,可能必须将一些域移动到它们的执行地址,并创建 ZI 输出节。例如,初始化的 RW 数据可能必须从 ROM 中的加载地址复制到 RAM 中的执行地址。为了可以灵活的处理这种情况,ARM 定义了如下两个视图:

Load view: 根据镜像在加载到内存中时所位于的地址(镜像执行开始前的位置),描述每个镜像域和节。Execution view: 根据镜像执行过程中所位于的地址,描述每个镜像域和节。
下面是两种视图的对比:
| Load | Description | Execution | Description |
|---|---|---|---|
| 加载地址 | 在包含分节或域的镜像开始执行之前,要加载到内存中的节或者域的地址。 节或者非根域的加载地址和他们执行地址可以不同 |
执行地址 | 当包含某个节或域的镜像被执行时,该节或域所在的地址 |
| 加载域 | 加载域描述在加载地址空间中连续内存块的布局 | 执行域 | 执行域描述在执行地址空间中的连续内存块的布局 |
二、Image entry points
镜像中入口点(Image entry points)就是镜像中的一个位置(地址),该位置(地址)会被加载到 PC 寄存器。 它是程序执行开始的位置。 虽然镜像中可以有多个入口点,但在链接时只能指定一个入口点。并非每个 ELF 文件都必须有入口点。 不允许在单个 ELF 文件中存在多个入口点。
对于嵌入式 Cortex-M 核的程序,程序的执行是从复位向量所在的位置(地址)开始执行。复位向量会被加载到 PC 寄存器中,且复位向量的位置(地址)并不固定。 通常,复位向量指向 Reset_Handler 函数。
有两种不同类型的入口点:
- 初始化入口点(Initial entry point): 镜像的初始入口点是存储在 ELF 头文件中的单个值。 对于那些需要由操作系统或引导加载程序加载到 RAM 中的程序,加载程序通过将控制转移到镜像中的初始入口点来启动镜像执行。一个镜像只能有一个初始化入口点。初始入口点可以是 ENTRY 指令设置的入口点之一,但不是必需的。
- ENTRY 指令指定的入口点: ENTRY 指令可以为镜像从多个可能的入口点中选择其中一个。每个镜像只能有一个入口点。您可以在汇编程序文件中使用 ENTRY 指令在对象中创建入口点。 在嵌入式系统中,该指令的典型用途是标记进入处理器异常向量(例如 RESET,IRQ 和 FIQ)的代码。该指令使用 ENTRY 关键字标记输出代码部分,该关键字指示链接器在执行未使用的部分消除时不删除该部分。对于 C/C++ 程序,C 库中的
__main就是入口点。
如果加载程序要使用嵌入式的镜像,则它必须在标头中指定一个初始入口点。 使用 --entry 命令行选项选择入口点。
三、映射符号
映射符号由编译器和汇编器生成,以识别文字池边界处的代码和数据之间的内联转换,以及 ARM 代码和 Thumb 代码之间的内联转换。例如 ARM/Thumb 交互操作胶合代码。其必须由 armlink 的参数 --list_mapping_symbols 和 --no_list_mapping_symbols 分别来控制显示与不显示。在默认情况下为 `–no_list_mapping_symbols,即不显示这部分符号。映射符号有如下这些:
$a:一系列ARM指令的开始$t:一系列Thumb指令的开始$t.x:一系列ThumbEE指令的开始$d:一系列数据项的开始,如文字池
补充说明:
- 文字池:是代码段中存放常量数据的区域。因为没有一条指令可以生成一个 4 字节的常量,因此编译器将这些常量放到文字池中,然后生成从文字池加载这些常量的代码。
- ARM/Thumb交互(ARM/Thumb interworking):是指对汇编语言和 C/C++ 语言的 ARM 和 Thumb 代码进行连接的方法,它进行两种状态(ARM 和 Thumb)间的切换。
- 胶合代码(Veneer):在进行 ARM/Thumb 交互时,有时需使用额外的代码,这些代码被称为 胶合代码(Veneer)。
- AAPCS:定义了 ARM 和 Thumb 过程调用的标准。
此外, armlink 还会生成 $d.realdata 映射符号,以告诉 fromelf 该数据是来自非可执行节区。因此, fromelf -z 输出的代码和数据大小与 armlink --info sizes 的输出相同。
四、链接器预定义符号
当链接器创建镜像文件时,它会创建一些 ARM 预定义的与域或者节相关的符号。这些符号就代表了链接器创建创建镜像的依据。
链接器定义了一些 ARM 保留的符号,我们可以在需要时访问这些符号。 这些符号是包含 $$ 字符序列的符号以及所有其他包含 $$ 字符序列的外部名称。我们可以导入这些符号地址,并将它们作为汇编语言程序的可重定位地址使用,或者将它们作为 C 或 C++ 源代码中的 extern 符号来引用。
如果使用
--strict编译器命令行选项,则编译器不接受包含$的符号名称。要重新启用支持,请在编译器命令行中包含--dollar选项。
- 链接器定义的符号只有在代码引用它们时才会生成。
- 如果存在仅执行(XO)节,则链接器定义的符号受以下约束:
- 不能对没有 XO 节的域或者空域定义 XO 连接器定义符号
- 不能对仅包含 RO 节的域定义 XO 连接器定义符号
- 对于仅包含 XO 节的域,不能定义 RO 连接器定义符号
1、将符号引入到程序中
1.1 引入到 C/C++
可以通过 值引用 或 地址引用 这两种方式将链接器定义的符号导入到的 C 或 C++ 源代码中来供我们使用:
- 值引用:
extern unsigned int symbol_name; - 地址引用:
extern void *symbol_name;
注意,如果将符号声明为 int 类型的值引用,则必须使用寻址操作符(&)来获得正确的值,如下例所示:
// Importing a linker-defined symbol
extern unsigned int Image$$ZI$$Limit;
config.heap_base = (unsigned int) &Image$$ZI$$Limit;
// Importing symbols that define a ZI output section
extern unsigned int Image$$ZI$$Length;
extern char Image$$ZI$$Base[];
memset(Image$$ZI$$Base, 0, (unsigned int)&Image$$ZI$$Length);
1.2 引入到汇编
可以使用指令 IMPORT 将连接器定义的符号引入到 ARM 汇编文件中来供我们使用:
IMPORT |Image$$ZI$$Limit|
...
zi_limit DCD |Image$$ZI$$Limit|
LDR r1, zi_limit
2、域相关的符号
链接器为镜像文件中的每个域生成不同类型的与域相关的符号,我们可以根据需要访问这些符号。域相关的符号主要有以下两种:
Image$$或者Load$$开头的符号,用于各执行域Load$$LR$$开头的符号,用于各加载域
如果未使用分散加载文件,则会以默认的 region 名称来生成域相关的符号。链接器默认的域名称如下:
ER_XO:用于仅执行属性的执行域(如果存在)。ER_RO:用于只读执行域。ER_RW:用于可读写执行域。ER_ZI:用于零初始化的执行域。
可以将这些名称插入 Image$$ 和 Load$$ 中以获取所需的地址,例如:Load$$ER_RO$$Base 就是只读域的基地址。
使用分散加载时,连接器将使用分散加载文

最低0.47元/天 解锁文章
1万+





