关于ARM中的重定位引入:
要想弄明白重定义的问题,首先我们需要引入4个概念:链接地址 / 运行地址 / 位置无关码 / 位置有关码
这里我们先简单回顾一下三星S5PV210芯片的启动过程(如果想详细了解,请翻我之前的文章):
由于三星芯片设计时IROM为64Kb(存放BL0的位置),ISRAM为96Kb(也就是加载BL1和BL2的地方,并且前16KB预留给了BL1),所以按照三星推荐的启动方式,bootloader必须大于16KB小于96KB,我们先假设bootloader为80Kb。给Soc上电后,BL0先运行,然后BL0会加载外部启动设备中bootloader的16KB(BL1)到芯片内部的SRAM中执行,BL1再继续加载BL2(bootloader中剩余的80kb-16kb=64kb)到内部SRAM(前16KB存放BL1,BL2内容内容接着16Kb的结束地址存放)执行,然后初始化外部的内存(DDR)并将OS从外部启动设备中搬运到DDR并将运行地址跳转到DDR上继续执行,整个启动过程完成。
现在来说一下关于链接地址和运行地址:
运行地址: 程序实际运行时地址,也就是当前执行程序中运行指令所对应的地址。
链接地址: 程序在编译器编译成可执行文件时(四个过程:我之前的文章也说过,预处理、编译、汇编、链接),在链接过程中我们指定的地址,如果不显式指定,编译器会默认为0x00地址。
链接地址就是我们写Makefile时用-Ttext指定的地址。
运行地址就是我们用dnw下载程序到芯片上指定的地址。
关于位置无关码和位置有关码:
位置无关码: PIC(Position independent code),在程序被编译成二进制可执行文件后,其指令的运行与内存地址无关。
位置有关码:
和位置无关码相反,也就是编译后指令的运行和内存地址有关。
像我之前,如果只看这个解释肯定会懵逼,举个例子应该就好理解了,顺便还能拓展一下知识:
adr和ldr伪指令: 这里我说的ldr是伪指令而不是普通的加载指令(详细了解,可以翻我之前的文章)ldr是长加载,adr是短加载。这里我们通过反汇编代码来说这俩个指令的运行过程。
(写到这里我发现今天这个电脑没有条件用Linux反汇编,悲剧,我就直接口述算了,虽然我很想把代码什么的贴上来,那样会很容易就懂了)
我们假设_start是下载程序到s5pv210上的起始运行地址0xd0020000。而我们编写程序时的指定的链接地址是0xd0024000。
1.adr r0, _start
2.ldr r1, =_start
经过反汇编后这俩个代码就会变成:
1.sub r0,pc,#xx
2.ldr r1,[pc,#xx]
那么问题来了,
r0=pc+xx:pc就是当前程序运行时指向的地址,r0存放的就是pc指针向前或者向后偏移xx的地址。所以adr加载的地址只跟运行地址有关,和链接地址没有半毛关系,它是位置无关码。
r1=[pc+xx]:r1存放的是pc+xx这个内存地址所存放的内容,由于这个内存地址经过编译器链接后生成的,和链接时指定的地址有关,根据反汇编可以看到其实这个内存地址存放的是0xd0024000,它是位置有关码。
综上:加载同样一个标号,但是加载的内容却不相同,这是因为链接地址和运行地址不相同的缘故,我们可以看到实质adr加载的是运行地址(0xd0020000),ldr加载的是链接地址(0xd0024000)。
所以我们可以了解到,如果程序的运行地址和链接地址相同时,那么所谓的位置有关码和位置无关码执行的结果都是一样的。如果程序的运行地址和链接地址不同时,问题就来了,比如说,由于程序员写程序时的代码编译出来的可执行文件中指令相应的内存地址是和链接地址有关的,比如我们遇到了上面的ldr伪指令,需要在编译后的程序的某个内存地址读取内容,如果下载时程序的运行地址和链接地址不同时,理论上这个内存地址读出来的内容是错误的,所以就会导致程序出错甚至不能继续运行。这个时候我们就需要重定位,也就是说重定位可以解决我们运行地址和链接地址不用时引起的位置有关码先关的问题。
所以什么时候我们需要重定位呢?
那就是当我们写程序时链接地址和运行地址有时候必须不相同,而且还不能全部用位置无关码来写程序时,我们可以用重定位来解决问题。
比如说上面举的这个例子用重定位解决:
我们可以在程序开始的几句代码,用位置无关码来编写,来实现将0xd0020000开始的程序内容复制一份到0xd0024000处,然后程序可以跳到0xd0024000合适的地方执行,这样就可以解决位置有关码出现的问题了。