一、准备知识
1.运行地址和链接地址
链接地址:链接时指定的地址(指定方式为:Makefile中用-Ttext,或者链接脚本),理论上程序运行时所处的地址。在编译时,编译器会根据链接地址来翻译位置有关码。
其实链接地址就是告诉编译器我这一段程序将来这一段代码会运行在什么地方,然后编译器会根据这个链接地址来对汇编代码进行编译。但是加载地址并不一定等于链接地址,所以说链接地址是理论上程序锁运行的地址。
运行地址(又叫做加载地址):程序实际运行时地址(指定方式:由实际运行时被加载到内存的哪个位置说了算)
2.位置有关码和位置无关码
位置无关编码(PIC,position independent code):CPU取指时,总是相对于本条执行指令的相对地址去取指。比如指行一个ADD指令时,PC要取下一指令的地址,就在原来的基础上+4。这就不管你代码放在存储器的任何位置,只要他们的相对地址没有改变,就能正常执行程序。一般上电复位那几条语句就必须是位置无关码指令。
位置有关编码:CPU每次取指都从绝对位置去取,而不是上面的相对位置。这样,就要求你在加载程序时,必须给连接脚本所规定的一样,把代码放到指定位置。
小结:运行地址不一定等于链接地址。当运行地址不等于链接地址的时,采用位置有关编码的指令就有可能出错。
举例如下:
当链接地址跟运行地址不同的时候,假如链接地址是0x1000,运行地址(加载地址)是0x0000,链接脚本指定函数A将来是要存放到(基地址+偏移量)=0x1000+0x0001=0x1001地址的,但是程序在下载的时候却把这个程序下载到0x0000,所以函数A的地址实际上是存放在(基地址+偏移量)=0x0000+0x0001=0x0001这个地址的。当程序运行到一行位置有关码例如:ldr PC, =main,编译器首先就会按照链接脚本指定的A的那个地址0x1001寻找A函数,但是因为加载地址跟链接地址不同的原因,实际上A函数已经被放到了0x0001,所以执行就会出错。所以,当这两个地址不同的时候,执行一段位置有关码的时候就会发生不可预估的错误。
一段反汇编代码如下:
ldr r0, =SMRDATA /* 获取标号地址,位置有关 */
33f80014: e59f0018ldrr0, [pc, #24]; 33f80034 <SMRDATA+0x8>1、加载地址0:r0 = 0x14 + 8 + 24 = 0x34 处的值 33f8002c 与标号实际地址(2c)不符合,不正确
2、加载地址0x3ff80000:r0 = 0x3ff80014 + 8 + 24 = 0x33f80034 处的值 33f8002c 正确
二、什么时候才会出现链接地址和运行地址不同
拿2440芯片举例,我们的代码如果比较庞大,比如说跑的OS,那么肯定想要我们的程序跑在外部的SDRAM上(内部的内存只有4K),也就是说我们会将链接地址设定在外部的SDRAM,但是外部SDRAM不能直接使用,必须经过一系列的初始化才能使用。所以在外部SDRAM没有初始化之前,我们的程序开始只能在内部的SRAM运行,也就是说我们的加载地址设定为内部的SRAM(0X00)。具体的内容参见:2440的启动过程
三、什么是重定位
重定位实际就是在运行地址处执行一段位置无关码PIC,让这段PIC(也就是重定位代码)从运行地址处把整个程序镜像拷贝一份到链接地址处,完了之后使用一句长跳转指令从运行地址处直接跳转到链接地址处去执行,这样就实现了重定位之后的无缝连接。
四、为什么需要重定位
链接地址跟运行地址不同可以有两种解决方案:
①全部使用位置无关码。
②进行重定位让这两个地址相同。
我们知道,如果是一个小代码,使用①时可以的,但是一个大的代码文件很难保证全部都使用位置无关码的,这也是不现实的,所以必须使用重定位解决这个问题。
https://blog.csdn.net/lizuobin2/article/details/52049892
https://blog.csdn.net/gyyu32g/article/details/78508406
http://wiki.100ask.org/%E7%AC%AC013%E8%AF%BE_%E4%BB%A3%E7%A0%81%E9%87%8D%E5%AE%9A%E4%BD%8D