LDR指令如何将常量加载到寄存器中?

时间:2017-06-28 11:53:52

标签: assembly arm

我刚读了一本ARM指令书,我看到了一条我无法解释的指令。

它说LDR将一个32位常量加载到r0寄存器中:

LDR r0, [pc, #const_number-8-{pc}]
..........
const_number
DCD 0xff00ffff

我无法理解[pc, #const_number-8-{pc}]的含义。具体做法是:

  1. #是什么意思?
  2. 花括号({})是什么意思?
  3. 为什么这个例子减去8和pc
  4. r0如何具有值0xff00ffff?

2 个答案:

答案 0 :(得分:2)

ldr将32位数据从内存加载到寄存器中。第一个操作数是要加载的寄存器,第二个操作数是灵活的第二个操作数,包含要加载的地址。 pc程序计数器,即要执行的下一条指令的地址。操作数[pc, #const_number-8-{pc}]描述了const_number相对于我们当前所处位置的地址。因此,

LDR r0, [pc, #const_number-8-{pc}]

const_number位置的任何内容加载到r0。由于0xff00ffff似乎位于该位置,因此会加载0xff00ffff

请注意,使用单个指令mvn r0, 0x00ff0000可以更轻松地加载此值。

答案 1 :(得分:2)

nop
nop
nop
nop
ldr r0,hello
nop
nop
nop
nop
b .
hello: .word 0x12345678


00000000 <hello-0x28>:
   0:   e1a00000    nop         ; (mov r0, r0)
   4:   e1a00000    nop         ; (mov r0, r0)
   8:   e1a00000    nop         ; (mov r0, r0)
   c:   e1a00000    nop         ; (mov r0, r0)
  10:   e59f0010    ldr r0, [pc, #16]   ; 28 <hello>
  14:   e1a00000    nop         ; (mov r0, r0)
  18:   e1a00000    nop         ; (mov r0, r0)
  1c:   e1a00000    nop         ; (mov r0, r0)
  20:   e1a00000    nop         ; (mov r0, r0)
  24:   eafffffe    b   24 <hello-0x4>
  28:   12345678
对我来说听起来很简单。地址0x10的[pc,#16]如何导致0x28? 0x28-0x10 = 0x18或24这是8到大,挂在第二个...(我刚刚回答的新答案)

在ARM文档的其他地方,它讨论了程序计数器提前两个或可能错误地记录为前面的8个字节。它实际上是两个指令,因此在拇指模式下为4个字节,在arm模式下为8个字节。当涉及thumb2扩展时,根据下一条指令对lr进行整理。但看起来对于pc相对的东西,拇指领先4个(传统上是两个指令)和8个领先手(前面两个)。这并不意味着pc真的处于这个值,可能是在橡子时代的arm1,但现在它是纯粹合成的,因为堆栈更深。

因此,在进行数学运算以计算立即数时,您将获取目标当前指令-8 所以在这种情况下0x28-0x10-8 - 16。

注意#16#只是意味着这是一个常数/立即就像逗号和括号一样,使得解析更容易一些(并且在一些情况下,没有#的数字意味着至少一般的其他东西也许不适合武装gnu汇编)。

如上所示,指令中编码的立即值是目标地址 - 在ARM模式下为pc-8。

正如您所希望的那样(但在某些情况下不一定期望)立即获得符号扩展

00000000 <hello-0xc>:
   0:   e1a00000    nop         ; (mov r0, r0)
   4:   e1a00000    nop         ; (mov r0, r0)
   8:   e1a00000    nop         ; (mov r0, r0)

0000000c <hello>:
   c:   12345678    eorsne  r5, r4, #120, 12    ; 0x7800000
  10:   e1a00000    nop         ; (mov r0, r0)
  14:   e1a00000    nop         ; (mov r0, r0)
  18:   e1a00000    nop         ; (mov r0, r0)
  1c:   e1a00000    nop         ; (mov r0, r0)
  20:   e1a00000    nop         ; (mov r0, r0)
  24:   e51f0020    ldr r0, [pc, #-32]  ; c <hello>

相同的数学(手臂模式)

0xC - 0x24 - 8 = -0x20

00000000 <hello-0x14>:
   0:   46c0        nop         ; (mov r8, r8)
   2:   46c0        nop         ; (mov r8, r8)
   4:   46c0        nop         ; (mov r8, r8)
   6:   46c0        nop         ; (mov r8, r8)
   8:   4802        ldr r0, [pc, #8]    ; (14 <hello>)
   a:   46c0        nop         ; (mov r8, r8)
   c:   46c0        nop         ; (mov r8, r8)
   e:   46c0        nop         ; (mov r8, r8)
  10:   46c0        nop         ; (mov r8, r8)
  12:   e7fe        b.n 12 <hello-0x2>
  14:   12345678

完成故事0x14 - 0x8 - 4(拇指模式)= 8

完成故事将包括thumb2扩展。

00000000 <hello-0x18>:
   0:   bf00        nop
   2:   bf00        nop
   4:   bf00        nop
   6:   bf00        nop
   8:   4803        ldr r0, [pc, #12]   ; (18 <hello>)
   a:   eba0 0001   sub.w   r0, r0, r1
   e:   bf00        nop
  10:   bf00        nop
  12:   bf00        nop
  14:   bf00        nop
  16:   e7fe        b.n 16 <hello-0x2>

00000018 <hello>:
  18:   12345678    eorsne  r5, r4, #120, 12    ; 0x7800000

好的,所以它使用4,当处理预取中止时,指令的大小进入计划,而不是pc相对负载,所以这很好。

当你有一个返回地址时,它是以拇指模式正确地完成两个前提。

那是做什么的

ldr r0,[pc,#16]

处理器采用程序计数器(合成非实数),即指令地址加8,然后在此处添加#符号标记的立即数值,如果该指令位于地址0x1234,则需要0x1234 + 8 + 16 = 0x124C。这是括号内的一个间接层,因此需要地址0x124C读取那里的32位值(ldr vs ldrb vs ldrh vs ldrd),然后在这种情况下将结果放在指定的目标寄存器r0中。在你的情况下,你有一个标签,它只是一个地址,并且汇编器会立即提供正确的指令,这样当执行时r0在该地址获得0xFF00FFFF。

这称为pc相对寻址,非常重要的寻址模式,尤其适用于RISC机器。但对CISC也很有用

在同一指令集中

ldr r0,[r1,#16]

技术上没有什么不同r1加上16是使用的地址,pc是特殊的,因为“pc中的任何值”根据模式(arm / thumb)和指令所在的地址而变化但是对于a [r1,#16] r1不会改变它是你设置的任何东西。