1.4.2 代码搬运
当程序执行时,必须把代码搬运到链接时所指定的运行地址空间,以保证程序在执行过程中对变量、函数等符号的正确引用。在带有操作系统的嵌入式系统中,这个过程由操作系统负责完成。而在裸机环境下,镜像文件的运行地址由程序员根据具体平台指定,加载地址又与处理器的设计密切相关。通常情况下,启动代码最先执行一段位置无关码,这段代码实现程序从加载地址到运行地址的重定位,或者将程序从外部存储介质直接拷贝至其运行地址。
1.位置无关码
位置无关码必须具有位置无关的跳转、位置无关的常量访问等特点,不能访问静态变量,都是相对pc的偏移量来进行函数的跳转或者常量的访问。在ARM 体系中,使用相对跳转指令b/bl实现程序跳转。指令中所跳转的目标地址用基于当前 PC 的偏移量来表示,与链接时分配给地址标号的绝对地址值无关,因而代码可以在任何位置正确的跳转,实现位置无关性。
使用ldr伪指令将一个常量读取到非pc的其他通用寄存器中,可实现位置无关的常量访问。例如:
ldr r0, =WATCHDOG
如果使用ldr伪指令将一个函数标号读取到pc,这是一条与位置有关的跳转指令,执行的结果是跳转到函数的运行地址处。
2.运行地址与加载地址
试想一下,当系统上电复位的时候,如果test.bin刚好位于0x5000_0000地址(flag的初值12位于0x5000_00dc),PC指向0x5000_0000地址,那么这段代码按照上述flag变量的读取步骤,能够准确无误地得到结果。但是,如果test.bin位于0地址(flag的初值12位于0xdc,LED不亮时的情况),PC指向0地址,程序依然从0x5000_00dc地址读取flag变量,实际上它的初值位于0xdc。这时从C语言的角度看,出现一个flag不等于它的初值的现象(期间没有改变flag)。出现错误的原因是在程序中使用了位置相关的变量,但运行地址与加载地址不一致(加载地址为 0,运行地址为0x5000_0000)。由此,能够容易理解运行地址和加载地址的含义:
加载地址是系统上电启动时,程序被加载到可直接执行的存储器的地址,也就是程序在RAM或者 Flash ROM 中的地址。因为有些存储介质只能用来存储数据不能执行程序,例如 SD 卡和NAND Flash 等,必须把程序从这些存储介质加载到可以执行的地址处。运行地址就是程序在链接时候确定的地址,比如fortest.lds链接脚本指定了程序的运行地址为0x5000_0000,那么链接器在为变量、函数等分配地址的时候就会以0x5000_0000作为参考。当加载地址和运行地址不相等时,必须使用与位置无关码把程序代码从它的加载地址搬运至运行地址,然后使用“ldr pc, =label”指令跳转到运行地址处执行。