我不打算将其作为例外答案,但它确实提供了更多洞察力,并且还为仅使用一条 ldr 指令提供了一种不方便的解决方案。
当使用这两个阶段的 ldr 方法时,汇编器实际上在您的代码之后添加了另外 4 个字节的数据!甚至在 .text 部分,这 4 个字节是 .data 变量的实际地址。然后第一条 ldr 指令实际指向该地址,然后您使用下一条 ldr 来使用实际地址。正如 tangrs 所讨论的那样,这个双指针可能是一种确保您的变量/常量可访问的方法,尤其是在 .data 部分距离更远的情况下(我上次运行时距离为 64k)。
查看一些正确方法的示例代码:
.text
.global _start
_start:
ldr r0, =x
ldr r0, [r0]
mov r7, #1
swi #0
nop
.data
x: .word 0xf0f0f0f0
汇编器实际上产生了这个:
00010074 <_start>:
10074: e59f000c ldr r0, [pc, #12] ; 10088 <_start+0x14>
10078: e5900000 ldr r0, [r0]
1007c: e3a07001 mov r7, #1
10080: ef000000 svc 0x00000000
10084: e1a00000 nop ; (mov r0, r0)
10088: 0002008c andeq r0, r2, ip, lsl #1
Disassembly of section .data:
0002008c <x>:
2008c: f0f0f0f0 ; <UNDEFINED> instruction: 0xf0f0f0f0
第一个 ldr 指向程序计数器之后的 12 个字节(考虑到当前指令 + 8 个以上)。这指向地址 0x10088(如 objdump 所述),它指向 andeq 指令(在此上下文中不是真正的指令)。它实际上是一个地址,0x0002008c,它指向我们在变量 x 的 .data 部分中的正确地址。现在我们在 r0 中有了变量的地址,我们可以在该地址上使用 ldr 来获取实际值。值得注意的是,尽管这两条 ldr 指令的源文件中的第二个操作数看起来非常不同,但机器编码是针对相同的 ldr 编码的;它们都是 LDR 立即数(虽然第一个 ldr 变体也被认为是 LDR 字面量,但它只是 LDR 立即数,将“Rn”硬编码为“1111”,无论如何这只是 pc 寄存器)。
考虑到所有这些,虽然不方便,但我们可以想办法只使用一次 LDR 立即数(文字)形式。我们所要做的就是确保获得与我们的真实数据相对应的正确立即值(偏移量)。做起来比说的容易:
.text
.global _start
_start:
ldr r0, [pc, #8]
mov r7, #1
swi #0
nop
x: .word 0xf0f0f0f0
除了只需要使用一条 LDR 指令来获得相同的结果之外,此版本的源代码还有另一个细微差别:没有 .data 部分。这可以通过数据部分来完成,但它会将我们的数据放在更高的地址中,使我们的偏移量大得多,以至于我们可能不得不使用额外的指令来获得正确的偏移量。另一个注意事项是由于它位于 .text (rx) 部分中,默认情况下您不能在其上使用 str 。这是一个非常小的障碍,只需对 ld 使用 -N 选项,您的 .text 部分现在是 rwx。我敢肯定最后一个建议会激怒stackoverflow之神,来找我;)