首先看两行汇编代码:
1: adr r0, _start
2: ldr r1, =_start
同样是加载一个标号的地址值,adr和ldr有什么区别呢?注意这里的ldr不是命令ldr,而是伪指令ldr,若想区分它们请参看我的一篇博文《adr adrl ldr mov总结整理》。
要区分它们,就需要引入4个概念:
1、运行时地址起始位置:它芯片公司指定的一开始运行代码的位置。这个位置和芯片本身有关,不可改动。对于2440来说一般就是片内SRAM的首地址0x0;对于210来说就是片内SRAM中的地址0xD0020010。
2、链接地址起始位置:它是由程序员指定的,或者说是有链接脚本设定。是可以变动的。但是这个位置在程序链接之后,就会确定下来。
3、运行时地址:就在从运行时地址起始位置(包括起始位置)往后排都是运行时地址。
4、链接地址:就是从链接地址起始位置(包括起始位置)往后排都是链接地址。
说明了以上4点内容之后,我需要铺垫一些前提内容,adr r0, _start ; ldr r1, =_start
这两句代码是从朱老师的一个实验程序里直接截取出来,这实验的目的是演示重定位。之后这段代码我会贴到文章的最后。因为开发板是210的板子所以运行时地址是从0xd0020010开始的,链接地址设置为0xd0024000开始的。
整个程序编译之后,在进行反编译,我们查找adr r0, _start ; ldr r1, =_start 对应的反汇编内容:
1、adr r0, _start 对应的是:
d002401c: e24f0024 sub r0, pc, #36 ; 0x24
2、ldr r1, =_start对应的是:
d0024020: e59f1048 ldr r1, [pc, #72] ; d0024070
同样是加载_start的地址,反汇编之后却是截然不同的命令。首先我们需要会看反汇编,最左边的是链接地址,第二个是机器码,第三个是反汇编得到的内容,最右边分号之后的是反汇编编译器额外帮我们注释了一些内容方便我们阅读。
我们发现反汇编之后,有一个地方很不同,就是pc指针。ldr r1, =_start对于的反汇编pc指针被放到的了[]里面,而另一条反汇编没有。我们知道对于汇编而言,放到[]里面代表是取得寄存器的值并且将寄存器的值当作地址,来访问地址中存储的值。
而对于pc而言,当你直接读取pc的值时访问的是运行时地址,而当你读取[pc]的值时访问的是链接地址。
反观adr r0, _start 和ldr r1, =_start它们都是伪指令,意思也分别是读取运行时地址和读取链接地址。和反汇编意义吻合。
我们现在来验证,我们前面分析的是否正确。首先_start作为程序的最开始,所以_start如果对应运行时地址,那么读取的_start的值应该是运行时地址起始位置及0xd0020010。
观察反汇编及对应的汇编
1、adr r0, _start
d002401c: e24f0024 sub r0, pc, #36 ; 0x24
由于此时该句代码的链接地址是d002401c链接地址的起始位置设定的是0xd0024000,偏移量是0x1c,根据这个便宜量可以算出该句代码的运行时地址为0xd0020010 +0x1c = d002002C,前面提到pc的值对应的就是运行时地址所以pc = d002002C。
d002002C - (36 十进制)+ 8 (流水线)= D002 0010 ;正好得到了_start的运行时地址完全没错。
再看链接地址是否算错,首先_start作为程序的最开始,所以_start如果对应链接地址,那么读取的_start的值应该是链接地址起始位置及之前设定的0xd0024000。
2、ldr r1, =_start
d0024020: e59f1048 ldr r1, [pc, #72] ; d0024070
根据偏移量,这句的运行时地址是d0020030,如果说是运行时地址 + 偏移量(72十进制),得到的是D002 0078,再加8(流水线)等于D002 0080,显然不对。
明显这里的[pc]的值得到的是当前语句对应的链接地址,d0024020 + 偏移量(72十进制)+ 8 才等于D002 4070(这个值也正好是注释里的值)大家是不是奇怪,为啥值不是0xd0024000?是不是算错了?其实不是,你到D002 4070这个链接地址看看就会发现这里存放的值正好就是D002 4070。
代码如下:d0024070: d0024000 andle r4, r2, r0
这里符合ldr r1, [pc, #72]这句指令的本意,他访问的就是这个值代表的地址中的值。(这种跳转的方法其实就是为了应对非法立即数,导致在一个机器码里放不下命令和数据的情况)
/*
* 文件名:led.s
* 作者:朱老师
* 描述:演示重定位(在SRAM内部重定位)
*/
#define WTCON0xE2700000
#define SVC_STACK0xd0037d80
.global _start// 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
// 第1步:关看门狗(向WTCON的bit5写入0即可)
ldr r0, =WTCON
ldr r1, =0x0
str r1, [r0]
// 第2步:设置SVC栈
ldr sp, =SVC_STACK
// 第3步:开/关icache
mrc p15,0,r0,c1,c0,0;// 读出cp15的c1到r0中
//bic r0, r0, #(1<<12)// bit12 置0 关icache
orr r0, r0, #(1<<12)// bit12 置1 开icache
mcr p15,0,r0,c1,c0,0;
// 第4步:重定位
// adr指令用于加载_start当前运行地址
adr r0, _start // adr加载时就叫短加载
// ldr指令用于加载_start的链接地址:0xd0024000
ldr r1, =_start // ldr加载时如果目标寄存器是pc就叫长跳转,如果目标寄存器是r1等就叫长加载
// bss段的起始地址
ldr r2, =bss_start// 就是我们重定位代码的结束地址,重定位只需重定位代码段和数据段即可
cmp r0, r1// 比较_start的运行时地址和链接地址是否相等
beq clean_bss// 如果相等说明不需要重定位,所以跳过copy_loop,直接到clean_bss
// 如果不相等说明需要重定位,那么直接执行下面的copy_loop进行重定位
// 重定位完成后继续执行clean_bss。
// 用汇编来实现的一个while循环
copy_loop:
ldr r3, [r0], #4 // 源
str r3, [r1], #4// 目的 这两句代码就完成了4个字节内容的拷贝
cmp r1, r2// r1和r2都是用ldr加载的,都是链接地址,所以r1不断+4总能等于r2
bne copy_loop
// 清bss段,其实就是在链接地址处把bss段全部清零
clean_bss:
ldr r0, =bss_start
ldr r1, =bss_end
cmp r0, r1// 如果r0等于r1,说明bss段为空,直接下去
beq run_on_dram// 清除bss完之后的地址
mov r2, #0
clear_loop:
str r2, [r0], #4// 先将r2中的值放入r0所指向的内存地址(r0中的值作为内存地址),
cmp r0, r1// 然后r0 = r0 + 4
bne clear_loop
run_on_dram:
// 长跳转到led_blink开始第二阶段
ldr pc, =led_blink// ldr指令实现长跳转
// 从这里之后就可以开始调用C程序了
//bl led_blink// bl指令实现短跳转
// 汇编最后的这个死循环不能丢
b .