我刚读了一本ARM指令书,我看到了一条我无法解释的指令。
它说LDR
将一个32位常量加载到r0
寄存器中:
LDR r0, [pc, #const_number-8-{pc}]
..........
const_number
DCD 0xff00ffff
我无法理解[pc, #const_number-8-{pc}]
的含义。具体做法是:
#
是什么意思?{}
)是什么意思?pc
?r0
如何具有值0xff00ffff?
答案 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不会改变它是你设置的任何东西。