不能用 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的主要内容,如果未能解决你的问题,请参考以下文章

MIPS 32位立即数

不能在手机上的“范围按钮”上使用onmousemove,有啥可以用相同的功能代替它吗?

条码扫描枪输入为啥不能用键盘输入代替

Arm汇编学习总结

BL和LDR——位置无关码和位置相关码

Swift中什么时候不能用 () 代替 Void 来使用