在分析uboot源码时,遇到adr、ldr指令,却分不清这2者的区别,网上很多解释含糊不清,现对adr与ldr指令做解释。
首先给出一段汇编代码:
ldr r0, _start
adr r0, _start
ldr r0, =_start
nop
mov pc, lr
_start:
nop
设置代码的起始地址为 0x0c008000,用 ADS1.2编译后的反汇编如下所示:
0c008000 <_start-0x14>:
c008000: e59f000c ldr r0, [pc, #12] ; c008014 <_start>
c008004: e28f0008 add r0, pc, #8 ; 0x8
c008008: e59f0008 ldr r0, [pc, #8] ; c008018 <_start+0x4>
c00800c: e1a00000 nop (mov r0,r0)
c008010: e1a0f00e mov pc, lr
0c008014 <_start>:
c008014: e1a00000 nop (mov r0,r0)
c008018: 0c008014 stceq 0, cr8, [r0], -#80
分析:
1、ldr r0, _start;
这里ldr是一个指令,把内存地址 _start(0c008014)对应的值(可能是数据或指令对应的机器码)读入。执行这个后,r0 = 0xe1a00000。
2、adr r0, _start;小范围的地址读取伪指令
adr是一个伪指令,通过指令“add r0, pc, #8 ”把_start标号对应的地址加载到 r0,因为地址是相对程序的,因此adr产生依赖于位置的代码。这里这段代码在 0x0c008000 运行,那么 adr r0, _start 得到 r0 = 0x0c008014;如果在地址 0 运行,那么r0 = 0x00000014。
通过这条伪指令就可以得知当前程序在内存的什么位置运行。这是一条伪指令,但是功能却如此强大,需要相应的编译器功能才能实现。
3、ldr r0, =_start;大范围的地址读取
这里ldr是一个伪指令,用于加载32位的立即数或一个地址值到指定寄存器。这条指令取得的是标号_start 的绝对地址。这个绝对地址是在编译器编译完后进行 link(链接)的时候确定的。看上去这只是一个指令,但是它要占用 2 个 32bit 的空间,一条是指令,另一条是 _start 的数据(因为在编译阶段不能确定 _start 的值(要等到link时才确定),而且也不能用 mov 指令来给 r0 赋一个 32bit 的常量,所以需要多出一个空间存放 _start标号对应的地址数据,这里就是 0x0c008014,r0=0x0c008014)。
参考: