adr指令与ldr指令,都是加载,而他们的区别在于:
adr指令加载符号时加载运行地址(相对位置),编译器编译后改成add指令或sub指令
ldr指令加载符号时加载链接地址(绝对位置),编译器编译后仍是ldr指令(原来的ldr是伪指令)。
实际测试以下程序:
.global _start
_start:
adr r0, _start
ldr r0, _start
ldr r0, =_start
b . @结尾死循环
反汇编结果如下:
00000000 <_start>:
0: e24f0008 sub r0, pc, #8
4: e51f000c ldr r0, [pc, #-12] ; 0 <_start>
8: e59f0000 ldr r0, [pc, #0] ; 10 <_start+0x10>
c: eafffffe b c <_start+0xc>
10: 00000000 andeq r0, r0, r0
首先看 adr r0, _start
0: e24f0008 sub r0, pc, #8
由于arm有两级流水线,所以当前pc指令指向0x8,所以r0就是0x0。
ldr r0, _start
4: e51f000c ldr r0, [pc, #-12] ; 0 <_start>
可以看出r0的值也指向0x0.
ldr r0, =_start
8: e59f0000 ldr r0, [pc, #0] ; 10 <_start+0x10>
此时注意到r0的值指向了0x10,而0x10地址内存放的正是00000000, _start标号。
从上面的实际测试看出,
adr r0, _start ;这条指令中adr被转化为了add或sub指令,是根据pc当前指向的位置来获取_start标号的位置,是基于pc的偏移量,因此取得的是相对位置
ldr r0, _start ;这条指令也是根据pc当前指向位置来获取_start标号的位置,与adr不同的是使用了ldr指令
ldr r0, _start ;这条指令是用文字池的方式来得到一个地址,而地址里放的内容才是_start标号的地址,取得的是绝对位置
查阅其他资料时得知,adr主要是用作短加载,因转化为add/sub的形式,所以其寻址空间较小。adr的寻址空间只有前后4kb,而且必须在同一个代码段中,只能用于短加载。ldr没有这些要求,ldr指令取得的是绝对地址,可用来长加载或长跳转。
综上:ldr和adr的区别主要就是地址的位置无关性的差别。
补充:指令adrl
adrl //转化成两个ADD,寻址空间是8KB