ldr和adr的区别

本文整理于博文ARM指令ldr和adr的区别_ldr adr,有修改。

一、使用格式

我们先弄清楚ldr与adr的使用格式。adr是伪指令,而ldr根据使用的格式可分为ldr指令与ldr伪指令。

1、adr伪指令

格式:adr{cond} Rd,addr

功能:将基于PC相对偏移的地址值或者基于寄存器相对偏移的地址值,读取到目标寄存器Rd中。

2、ldr指令

格式:ldr{cond} Rd,addr

功能:将addr这个存储器地址对应的存储单元中的数据,加载到目标寄存器Rd中。

3、ldr伪指令

格式:ldr{cond} Rd,=addr

功能:将一个32位的立即数,或者一个地址值,加载到目标寄存器Rd中。

从上面的描述中可以看出,ldr作为加载指令时,Rd这个寄存器中存储的是addr这个地址对应的存储单元中的数据(指针所指向的内容)。而ldr作为伪指令时,它与adr伪指令一样,Rd这个寄存器中存储的就是addr这个地址(即指针)。这也是ldr伪指令、adr伪指令又叫作(大范围/小范围)地址读取伪指令的原因,因为它们就是用来获取一个地址(至于为什么有前缀“大范围”“小范围”,是因为它们获取地址的方式不一样,导致能够获取的地址范围不同。这有点废话,不过确实如此。见接下来的描述)。

二、两者的区别 

比如有一个汇编文件test_adr.S,其内容如下:

.text             //伪操作
.globl _start     //伪操作,将_start这个标号导出,即作为全局变量

_start: //这是一个标号,这个标号表示一个地址,就好比门牌号
	ldr r0,test   @ 指令 ,表示r0寄存器存储的是test这个地址对应的存储单元中的数据
	adr r0,test   //伪指令,表示r0寄存器存储的是test这个地址(或者说标号,标号即地址) 
	ldr r0,=test  //伪指令,表示r0寄存器存储的是test这个地址(或者说标号,标号即地址)
	nop
test:
	nop

 另外有一个Makefile文件,其内部对test_adr.S进行汇编、链接与反汇编: 

all:test_adr.S
    #汇编:将汇编文件test_adr.S转换成目标文件test_adr.o
	@ arm-linux-gcc -c -o test_adr.o test_adr.S 

    #链接:将目标文件的集合,组合成可执行程序test_adr.elf
	@ arm-linux-ld -Ttext 0x00000000 -g test_adr.o -o test_adr.elf

    #复制:将可执行程序test_adr.elf从一种二进制格式(elf)转换成另外一种格式(bin)
	@ arm-linux-objcopy -O binary -S test_adr.elf test_adr.bin 

    #反汇编:将可执行文件test_adr.elf反汇编,并将结果输出到test_adr.dis文件(否则输出至终端)
	@ arm-linux-objdump -d -m arm test_adr.elf > test_adr.dis

clean:
	@ rm -f test_adr.dis test_adr.bin test_adr.elf *.o

注意到 adr r0,test 与 ldr r0,=test 都是将test这个标号所在的地址存储到r0寄存器中,它们有什么区别呢?

我们看一下反汇编生成的test_adr.dis文件的内容:

xjh@ubuntu:~/iot/tmp$ cat test_adr.dis 

test_adr.elf:     file format elf32-littlearm


Disassembly of section .text:
//因为链接地址是00000000,所以这里显示为00000000
00000000 <_start>:
   0:	e59f0008 	ldr	r0, [pc, #8]	; 10 <test>
   4:	e28f0004 	add	r0, pc, #4
   8:	e59f0004 	ldr	r0, [pc, #4]	; 14 <test+0x4>
   c:	e1a00000 	nop			        ; (mov r0, r0)

00000010 <test>:
  10:	e1a00000 	nop			        ; (mov r0, r0)
  14:	00000010 	.word	0x00000010
xjh@ubuntu:~/iot/tmp$ 

首先要明白,为了提高处理的效率,ARM采用了流水线技术,使用最广泛的是三级流水线,这意味着取指操作与执行操作相差两个周期,因为每个周期4个字节,所以差8个字节。因为PC总是指向取指的指令,而非指向正在执行的指令或者正在译码的指令,所以PC值=当前程序执行位置+8

(1)由反编译结果可知,代码 ldr r0,test 等价于 ldr r0, [pc, #8] 指令。ldr r0, [pc, #8] 这条指令对应的二进制码e59f0008,它存放在地址0对应的存储单元中,也就是执行到这指令时,当前程序执行位置是0,则PC值=0+8(即指向了指令ldr r0, [pc, #4])。而[pc,#8]表示PC的值再加上8,所以[pc,#8]表示0+8+8=0d16=0x10这个地址所对应的存储单元中的数据([  ]表示间接寻址)。我们到0x10这个地址,看到它对应的存储单元中的数据是e1a00000,这个数据其实是指令nop对应的二进制码。

(2)由反编译结果可知,代码 adr r0,test 等价于 add r0, pc, #4 指令。注意这条指令的位置是4,也就是执行到这条指令时,当前执行位置是4,那么PC值=4+8(即指向了第一个指令nop)。add r0, pc, #4 指令表示将PC的值再加上4,然后存储到r0寄存器中,那么r0中存储的内容就是4+8+4=0d16=0x10。这个0x10表示的是一个地址,也就是test这个标号所在的地址。总结之,adr r0,test 就是把标号test所在的地址取出来,存储到r0寄存器中。 

(3)由反编译结果可知,代码 ldr r0,=test 等价于 ldr r0, [pc, #4] 指令。注意这条指令的位置是8,也就是执行到这条指令时,当前执行位置是8,那么PC值=8+8。[pc, #4]表示PC的值再加上4,所以[pc, #4]表示8+8+4=0d20=0x14这个地址所对应的存储单元中的数据。我们到0x14这个地址,看到它对应的存储单元中的数据是00000010,这个数据表示test这个标号所在的地址。总结之,ldr r0,=test就是把标号test所在的地址取出来,存储到r0寄存器中。 

总结

从上面的描述可知,adr伪指令中r0寄存器所存储的数据,我们(根据反汇编代码)可以明确地知道它是一个地址。标号所在的地址=(标号所在的地址-当前执行指令的地址)+当前执行指令的地址。当前执行指令的地址是PC的值-8,虽然不直接就是PC,但依然是一个与PC有关的值。也就是说,使用adr伪指令获取某个标号的地址时,这个标号的地址是与运行地址(体现为PC)有关的,或者说,它是基于PC相对偏移的地址值。

ldr伪指令获取某个标号的地址时,这个标号的地是与链接地址有关的。链接脚本写好并执行链接之后,可执行文件中各条指令、各个标号的地址就已经定下来了。

当链接地址与运行地址一致时,使用adr伪指令与使用ldr伪指令,它们获取的标号地址是一样的。

当链接地址与运行地址不同时,使用adr伪指令获取的是程序实际运行时标号所在的地址,而使用ldr伪指令获取的是链接脚本指导下的该标号所在的地址。

我们可以利用这两个伪指令取址时的差异,判断是否需要重定位。见博客https://xiefor100.blog.csdn.net/article/details/70135880

运行地址与链接地址,除了影响上面提到的取址操作,也影响跳转指令。比如b和bl跳转指令,使用格式是“b lable”(表示短跳转),此时的lable是一个标号,它的实际值是相对当前PC值的一个偏移量。而使用“ldr PC,=lable”(表示长跳转)时,这个lable是与链接地址有关的。

猜你喜欢

转载自blog.csdn.net/oqqHuTu12345678/article/details/129523790
LDR