不能用 LDR 代替 MOVW / MOVT
Posted
技术标签:
【中文标题】不能用 LDR 代替 MOVW / MOVT【英文标题】:Can't replace MOVW / MOVT with LDR 【发布时间】:2021-12-27 17:25:23 【问题描述】:我想用一条ldr
指令替换这个movw / movt
组合:
movw r0, #0x3800
movt r0, #0x4002
|
V
ldr r0, =0x40023800
但是,在此更改之后,代码不再起作用。由于这是我唯一更改的部分,因此它一定有问题。我的印象是这两个代码段是相同的。
汇编代码顶部有以下说明
.syntax unified
.cpu cortex-m4
.thumb
并使用GNU Arm Embedded Toolchain 使用以下命令进行编译:
arm-none-eabi-as -mcpu=cortex-m4 code.s -c -o code.o
arm-none-eabi-gcc -T link.ld -nostartfiles -o result code.o
如果重要,代码的其余部分与this page(底部)托管的代码相同。代码在带有 Arm Cortex-M4 内核的 STM32F411CEU6 上运行。
为什么此更改会破坏我的代码?是否有另一种将 32 位立即数写入寄存器的单行方法?
在汇编结果上运行objdump
会显示这一点(注意<main>
的开头和<loop>
的结尾:
Disassembly of section .text:
00000000 <main>:
0: 4810 ldr r0, [pc, #64] ; (44 <.loop+0x2a>)
2: f240 0101 movw r1, #1
[removed]
18: 6001 str r1, [r0, #0]
0000001a <.loop>:
1a: f240 0100 movw r1, #0
[removed]
44: 40023800 andmi r3, r2, r0, lsl #16
【问题讨论】:
看看组装好的汇编器。最好在调试器中,这样您就可以看到正在发生的事情。 LDR(立即加载)是一条伪指令,因此汇编程序应将其替换为实际指令。 ARM 对立即数进行编码的方式并不简单。我很惊讶汇编程序没有给你警告。 @RealtimeRik 我无法访问调试器,但我确实在问题中添加了一个 objdump。 @KevinKevinski 翻译正确 - 请参阅我的答案。 ARM 指令集并非专为人类编译器而设计。用汇编程序编程几乎没有意义,除了一些非常低级的核心相关的东西(比如主管处理程序中的上下文切换) 是的,代码似乎是正确的,因此它应该使用正确的值加载寄存器。除非您能够通过调试器或其他方式证明实际上加载了错误的值,否则我会怀疑问题出在其他地方,并且恰好是由这种不相关的更改触发的。 @NateEldredge 特别是在故障不精确的情况下。 【参考方案1】:首先,汇编语言特定于汇编程序(armasm、gas 等)而不是目标(cortex-m4),它们可以而且肯定经常是跨工具不兼容的语言。 Ldr 通常用作伪指令,这意味着汇编器决定为您使用什么指令,而不是您要求的指令。
.cpu cortex-m4
.thumb
ldr r0,=0x12345678
ldr r1,=0x00000003
ldr r2,=0xFFFFFFF5
nop
nop
b .
组装和拆卸
00000000 <.text>:
0: 4803 ldr r0, [pc, #12] ; (10 <.text+0x10>)
2: f04f 0103 mov.w r1, #3
6: f06f 020a mvn.w r2, #10
a: 46c0 nop ; (mov r8, r8)
c: 46c0 nop ; (mov r8, r8)
e: e7fe b.n e <.text+0xe>
10: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
首先使用 gnu 汇编器支持 ldr r0,= 语法,不要期望所有 arm/thumb 汇编器都支持该语法。其次,使用 gnu 汇编器(可能还有其他答案中指出的其他工具),如果该工具可以创建实际上不执行 ldr 的优化,它会。
我们可以看到,对于第二个和第三个常量,汇编器使用了非加载指令,并将常量嵌入为立即数。
对于 0x12345678 值,您根本无法将 32 位立即数放入 32 位(或 16 位)指令中,您必须进行加载,它通过找到一个将常量放入的池并进行相对于 pc 的负载。
汇编语言绝对不适合编译器,无论如何只有一些编译器使用汇编语言。如果没有汇编语言,我们就不会有当前或新的处理器供人类用于处理器开发和测试。因此,非人类 asm 意味着没有处理器。我们将无法引导高级编程语言,因此不会有编程语言。不会有编译器,因为您需要通过汇编语言精通指令集的人(即使编译器不编译为 asm)才能成功创建编译器,所以不会有编译器,有还没有因为其他原因。如果人类指令集级编程消失,处理器和所有副作用都会消失。每一代人都有很多人必须扛起火炬,寻找并教导他人。
我很幸运(嗯,这是计划好的),这里的工作没有完成,这个呢:
.cpu cortex-m4
.thumb
ldr r0,=0x12345678
ldr r1,=0x00000003
ldr r2,=0xFFFFFFF5
nop
b .
Disassembly of section .text:
00000000 <.text>:
0: 4803 ldr r0, [pc, #12] ; (10 <.text+0x10>)
2: f04f 0103 mov.w r1, #3
6: f06f 020a mvn.w r2, #10
a: 46c0 nop ; (mov r8, r8)
c: e7fe b.n c <.text+0xc>
e: 56780000 ldrbtpl r0, [r8], -r0
12: Address 0x0000000000000012 is out of bounds.
常量被放置在非单词对齐的边界上。它可能已经组装成功,但 ldr 是未对齐的传输,可能会导致异常并且代码将无法工作。
快速修复:
.cpu cortex-m4
.thumb
ldr r0,=0x12345678
ldr r1,=0x00000003
ldr r2,=0xFFFFFFF5
nop
b .
.align
Disassembly of section .text:
00000000 <.text>:
0: 4803 ldr r0, [pc, #12] ; (10 <.text+0x10>)
2: f04f 0103 mov.w r1, #3
6: f06f 020a mvn.w r2, #10
a: 46c0 nop ; (mov r8, r8)
c: e7fe b.n c <.text+0xc>
e: bf00 nop
10: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
这很奏效。我们仍然在假设池会去哪里,我们可以尝试强制它。
.cpu cortex-m4
.thumb
one:
ldr r0,=0x12345678
ldr r1,=0x00000003
ldr r2,=0xFFFFFFF5
b .
.align
two:
ldr r0,=0x11223344
b .
.align
Disassembly of section .text:
00000000 <one>:
0: 4803 ldr r0, [pc, #12] ; (10 <two+0x4>)
2: f04f 0103 mov.w r1, #3
6: f06f 020a mvn.w r2, #10
a: e7fe b.n a <one+0xa>
0000000c <two>:
c: 4801 ldr r0, [pc, #4] ; (14 <two+0x8>)
e: e7fe b.n e <two+0x2>
10: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
14: 11223344 ; <UNDEFINED> instruction: 0x11223344
.cpu cortex-m4
.thumb
one:
ldr r0,=0x12345678
ldr r1,=0x00000003
ldr r2,=0xFFFFFFF5
b .
.align
.ltorg
two:
ldr r0,=0x11223344
b .
.align
00000000 <one>:
0: 4802 ldr r0, [pc, #8] ; (c <one+0xc>)
2: f04f 0103 mov.w r1, #3
6: f06f 020a mvn.w r2, #10
a: e7fe b.n a <one+0xa>
c: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
00000010 <two>:
10: 4800 ldr r0, [pc, #0] ; (14 <two+0x4>)
12: e7fe b.n 12 <two+0x2>
14: 11223344 ; <UNDEFINED> instruction: 0x11223344
pc相对负载是正向的,所以我们没有完全控制这个:
.cpu cortex-m4
.thumb
one:
ldr r0,=0x12345678
ldr r1,=0x00000003
ldr r2,=0xFFFFFFF5
nop
b .
two:
ldr r0,=0x11223344
b .
.align
.ltorg
00000000 <one>:
0: 4804 ldr r0, [pc, #16] ; (14 <two+0x6>)
2: f04f 0103 mov.w r1, #3
6: f06f 020a mvn.w r2, #10
a: 46c0 nop ; (mov r8, r8)
c: e7fe b.n c <one+0xc>
0000000e <two>:
e: 4802 ldr r0, [pc, #8] ; (18 <two+0xa>)
10: e7fe b.n 10 <two+0x2>
12: bf00 nop
14: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
18: 11223344 ; <UNDEFINED> instruction: 0x11223344
我们不需要在两个之前对齐,两个可以落在非 32 位边界上,所以我们有时会在那里保存一个半字。 .align (汇编语言特定于汇编程序,而不是目标,这是 gnu 汇编程序支持的语言,并为此目标执行特定操作)允许它在 4 字节边界上对齐,因此 ldr 不会出错,并且 . ltorg 实际上并没有改变这里的东西,只是表明这是我们想要的。您还必须了解相对于 pc 的负载可以工作多远,每个指令集(arm、mips、x86 等)的范围各不相同,因此您不能只是在大型项目结束时坚持使用池方式。
为什么这不一定适合您?我们需要看到并理解——您是简单地更改了汇编语言并重新组装,还是侵入了二进制指令?后者有很多问题,包括指令大小以及如何将项目填充到池中。如果它只是代码并且您组装了它,它很可能是未对齐的,您可能会遇到未对齐的访问错误。
但是 ldr rd,= 可以覆盖所有可能的位模式,movw/movt 也可以。但是 gnu 汇编器上的 ldr rd,= 会优化,否则它需要池并且池需要为它做好准备。如果你手动创建 movw/movt,你只需要这两条指令,不需要池。
您需要制作一个更完整的示例,并定义“不再有效”的含义。
【讨论】:
问题或答案中的外部链接可能会过时,并且不会与这些页面并行维护,最好将该代码剪切并粘贴到问题中。我通常原则上从不查看链接,但在这种情况下确实如此,如果您拆卸我怀疑您只是有对齐问题,也许底部的单个 .align 可以解决它。但你必须尝试一下【参考方案2】:ldr
是一个伪指令,转换为从池中加载。
LDR Rd,=const
伪指令生成最高效的 加载任何 32 位数字的单个指令。你可以用这个 生成超出范围的常量的伪指令 MOV 和 MVN 指令。 LDR 伪指令生成 指定立即数的最有效单条指令:如果可以使用单个 MOV 或 MVN 指令构造立即值,则汇编器会生成相应的指令。 如果无法使用单个 MOV 或 MVN 指令构造立即值,则汇编器: 将值放在文字池中(嵌入在代码中以保存常量值的内存部分)。 生成具有 PC 相对地址的 LDR 指令,该指令从文字池中读取常量。 例如:
LDR rn, [pc, #offset to literal pool] ; load register n with one word ; from the address [pc + offset]
您必须确保在汇编器生成的 LDR 指令范围内存在文字池。
https://www.keil.com/support/man/docs/armasm/armasm_dom1359731147386.htm
【讨论】:
以上是关于不能用 LDR 代替 MOVW / MOVT的主要内容,如果未能解决你的问题,请参考以下文章