嵌入式Linux学习:重定位(Relocation)

2019-07-12 22:44发布

引用了http://blog.csdn.net/zhaocj/article/details/6636175博文的图片 引用了《嵌入式Linux应用开发完全手册》P260的图片 可以参考:http://blog.csdn.net/skyflying2012/article/details/37660265; 这个链接里包含了一个简单的实例!
笔者也是嵌入式新手,以下博文只是我对uboot初浅的认识,有任何问题欢迎指正 --------------------------------------------正文开始----------------------------------------
什么是重定位?
上面解释了两个概念:加载地址和运行地址,这里在简单总结一下,就是程序员将所有的代码编译完以后按照一定的顺序进行存放到FLASH里,而且在存放的过程中设置了连接地址,这个连接地址就是后期我们希望这个代码能够运行的地址(而不是SRAM或者在FLASH中XIP,一般就在SDRAM中),上电完成后程序一般运行在FLASH或者SRAM,然后通过适当的复制程序将原本在FLASH中的数据复制到上述指定的连接地址,然后再跳到SDRAM中继续运行。这个是旧版本的uboot中的策略。
这样的策略最终让(一般连接地址就是SDRAM的0x33F80000)uboot处于SDRAM的中低端空间,再在其0x34000000后面放置kernal的代码;(这样的策略直接简单,一刀切的进行了SDRAM的空间划分,使得SDRAM的利用率偏低);
为了最尽可能的利用SDRAM资源,在上电完成后,运行在FALSH或者SRAM中的程序将整个uboot程序复制到了SDRAM的顶端开始,依次开始对SDARM进行空间划分。这样的结果就是uboot放置在SDRAM中的实际位置和连接地址会存在一个偏差,这种偏差会导致程序跑飞! 图 1 两种策略的在uboot进入第二阶段时的SDRAM分布 从上面的图可以看到这两种策略所引起的不同,左图的uboot起始位置确定为0x33F80000,而右图的uboot的起始位置实际上是不确定的,因为uboot的.text和.bss段实际的大小是在连接时才最终确定,所以这里的连接地址的意义已经名存实亡,因为它和最终的运行地址已经存在一个offset;因为存在这么一个offset,所以uboot的程序需要重定位! 图 2 策略1将uboot数据复制到了SDRAM的TEXT_BASE后,两者执行的转跳图 先看看策略1,加入现在已经完成了程序的复制工作(即将FLASH中相应的数据复制到了指定的TEXT_BASE位置),如果这个时候程序若还在FLASH(或SRAM)中运行:执行ldr pc,=lable,实际上就是读取offset处(FLASH中的offset)的数据,将这个数据赋值给pc,见上图左虚线部分就是取读取offset处的数据,但是这个地址实际上是指向SDRAM中的lable(并不是指向FLASH中的lable,连接地址的作用),所以这个时候就完成了从FLASH(或者SRAM)到SDRAM的转跳; 图 3 跳到SDRAM后的运行情况 当完成FLASH到SDRAM的转跳后,程序已经运行在了SDRAM,那么后续的情况会如何呢,如此时要执行ldr pc,=lable2,实际上就是读取offset2处(SDRAM中的offset2)的数据,将这个数据赋值给pc,见上图右虚线部分就是取读取offset2处的数据,这个地址实际上仍指向SDRAM中的lable2(并不是指向FLASH中的lable2,连接地址的作用),所以这个时候程序正常转跳,且还在SDRAM中运行;
通过上面的解释,我们就明白了策略1的连接地址和后续需要复制的SDRAM的偏移地址就应该是一样的;那么如果此时将uboot复制到另外一个地方,情况会怎样? 图 4简单的将uboot复制到SDRAM2中,而不是复制到相应的SDRAM1(TEXT_BASE所指向)
有上图可知,CPU在FLASH中运行时,执行ldr pc,=lable,实际上就是读取offset处(FLASH中的offset)的数据,将这个数据赋值给pc,见上图左虚线部分就是取读取offset处的数据,程序按照我们的预设跳到了SDRAM1(TEXT_BASE所指向)后面的某处,但是这个地址上并没有数据(因为我们并没有把数据复制到这个地方!),此时程序跑飞!
而如果不经过重定位,我们强行将程序运行在SDRAM2上会如何? 图 5 若强行运行结果会怎么样? 同样的,才是CPU运行在SDRAM2中,执行ldr pc,=lable2,实际上就是读取offset2处(SDRAM2中的offset2)的数据,将这个数据赋值给pc,见上图右虚线部分就是取读取offset2处的数据,这个时候程序仍旧是转跳到了SDRAM1中,程序仍然跑飞!
解决办法就是重定位!
通过上面的实例我们知道了,实际上如果此时强行将CPU指针指向SDRAM2,那么为了能够正常的执行我们的程序,我们需要做的就是修改SDRAM2中offset2处所保留的数据,因为这个数据保留了接下来CPU要转跳的地址,那么靠的就是rel_dyn段!上面的图片中的FLASH都有rel_dyn段,但是他们都没有被复制到SDRAM,那么他们的作用如何实现,接下来仍旧用图片来解决问题; 图 6 重定位的过程 首先在FLASH的,有一个数据段叫做rel_dyn,其中保存了很多地址,这些地址都指向了SDRAM1中的虚拟offset(为什么叫虚拟呢,因为我们uboot数据被复制到了SDRAM2中,SDRAM1中相应地方的数据为空),而虚拟offset中的数据就是虚拟lable的地址;我们再来重新解释下上面没有重定位的SDRAM2为什么运行失败:那是因为我们SDRAM2中的offset2中保留的数据,实际上指向了SDRAM1中的虚拟lable!
继续讲重定位;
假如,我们获得了REL_OFFSET,就是SDRAM2余SDRAM1的偏移;rel_dyn中有一个数据RD1,把这个数据作为地址,它指向的恰好就是SDRAM1的虚拟offset2(上图棕 {MOD}实线),那么只要将rel_dyn加上REL_OFFSET,那么RD1作为地址就可以指向SDRAM2中的offset2(上图棕 {MOD}虚线);
这样我们就获取到了SDRAM2中的offset2,它里面所包含的数据作为地址,实际上指向了SDRAM1中的虚拟lable2(上图黑 {MOD}实线),那么只要将SDRAM2中的offset2里的数据也加上REL_OFFSET就可以指向正确的SDRAM2中的lable2(上图黑 {MOD}虚线),这样即使CPU在SDRAM2中也就能够正常运行了,而不会跑飞;
那么关键就是修改SDRAM2中offset2中的数据(修改还分两种,绝对和相对,这里暂不展开),而这个恰恰就是重定位所需要完成的任务;而如何获得offset2的地址,靠的就是rel_dyn中所保留的原始数据!而rel_dyn中所保留的数据是由gcc的某个编译选项获得