参考
一、加载域和执行域
镜像的各个域在加载时是被放置在系统存储器中的。 在执行镜像之前,可能必须将一些域移动到它们的执行地址,并创建 ZI 输出节。例如,初始化的 RW 数据可能必须从 ROM 中的加载地址复制到 RAM 中的执行地址。为了可以灵活的处理这种情况,ARM 定义了如下两个视图:
Load view: 根据镜像在加载到内存中时所位于的地址(镜像执行开始前的位置),描述每个镜像域和节。
Execution view: 根据镜像执行过程中所位于的地址,描述每个镜像域和节。
下面是两种视图的对比:
二、Image entry points
镜像中入口点(Image entry points)就是镜像中的一个位置(地址),该位置(地址)会被加载到 PC 寄存器。 它是程序执行开始的位置。 虽然镜像中可以有多个入口点,但在链接时只能指定一个入口点。并非每个 ELF 文件都必须有入口点。 不允许在单个 ELF 文件中存在多个入口点。
对于嵌入式 Cortex-M 核的程序,程序的执行是从复位向量所在的位置(地址)开始执行。复位向量会被加载到 PC 寄存器中,且复位向量的位置(地址)并不固定。 通常,复位向量指向 Reset_Handler 函数。
有两种不同类型的入口点:
1.初始化入口点(Initial entry point): 镜像的初始入口点是存储在 ELF 头文件中的单个值。 对于那些需要由操作系统或引导加载程序加载到 RAM 中的程序,加载程序通过将控制转移到镜像中的初始入口点来启动镜像执行。一个镜像只能有一个初始化入口点。初始入口点可以是 ENTRY 指令设置的入口点之一,但不是必需的。
2.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:一系列数据项的开始,如文字池
补充说明:
1.文字池: 是代码段中存放常量数据的区域。因为没有一条指令可以生成一个 4 字节的常量,因此编译器将这些常量放到文字池中,然后生成从文字池加载这些常量的代码。
2.ARM/Thumb交互(ARM/Thumb interworking): 是指对汇编语言和 C/C++ 语言的 ARM 和 Thumb 代码进行连接的方法,它进行两种状态(ARM 和 Thumb)间的切换。
3.胶合代码(Veneer): 在进行 ARM/Thumb 交互时,有时需使用额外的代码,这些代码被称为 胶合代码(Veneer)。
4.AAPCS: 定义了 ARM 和 Thumb 过程调用的标准。
此外, armlink 还会生成 $d.realdata 映射符号,以告诉 fromelf 该数据是来自非可执行节区。因此, fromelf -z 输出的代码和数据大小与 armlink --info sizes 的输出相同。
四、链接器预定义符号
当链接器创建镜像文件时,它会创建一些 ARM 预定义的与域或者节相关的符号。这些符号就代表了链接器创建创建镜像的依据。
链接器定义了一些 ARM 保留的符号,我们可以在需要时访问这些符号。 这些符号是包含 SS字符序列的符号以及所有其他包含 $$ 字符序列的外部名称。我们可以导入这些符号地址,并将它们作为汇编语言程序的可重定位地址使用,或者将它们作为 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