armv7-m 裸机 ldr/str 符号内存
Posted
技术标签:
【中文标题】armv7-m 裸机 ldr/str 符号内存【英文标题】:armv7-m bare metal ldr/str symbolic memory 【发布时间】:2020-06-22 20:26:45 【问题描述】:所以我知道关于 ldr/str on arm 的问题数不胜数。也许这是另一个转折(不太可能)或者我只是遗漏了一些东西(更有可能)。
所以这是裸机,我想在内存中加载/存储一些变量。因为我坚持要给它起个名字。我可以天真地写:
.section .bss
var: .word 0
.section .text
str r0, var
(有一个自定义链接器脚本,将 .bss 放在 ram 中,将 .text 放在 flash 中)
这当然行不通,因为指令是 32 位的,并且只能用于一些较小的立即数。我正在谈论的指令存在于闪存中,即 0x8000000+x 并且变量将存储在内存中,该内存位于 0x20000000+y 的某个位置。
手动我知道很多方法可以解决这个问题:
将变量地址存储在常量中 (varaddr: .word 0x2001234; ldr r1, [pc,#varaddr]; str r0, [r1]
)
在寄存器中加载 ram-base 并相对寻址 (ldr r1, #0x20000000; str r0, [r1,#varoffset]
)
通过算术构造地址 (mov r1, #0x2000000; add r1, #offset / orr / movw / movt something
)
当然还有更多
这些变体中的每一个都有效,但这些变体都不允许我使用我真正想要使用的标签。
那么我在这里错过了什么。我对链接器脚本和标签的想法是假的吗?是否有一些我没有看到的汇编程序功能?完全不同的东西?
【问题讨论】:
使用ldr r1, =var
获取地址,然后使用它。或者,用 C 语言编写相同的程序,看看编译器做了什么,然后做同样的事情。是的,当您实际修改变量时,您将无法使用标签名称。如果您想要一个符号名称,我建议为寄存器号制作一个宏。
ARMv7 可以使用movw
(宽立即数)+movk
或类似的东西在寄存器中构造任意 32 位值(例如地址)。在针对某些 ARM CPU 进行优化时,编译器有时会使用它而不是来自附近常量池的 PC 相关负载。
@Peter:这就是我的第三个要点的意思。
@fuz:编译器完成了我在第一个要点中所做的工作。它将内存地址存储在闪存中并使用它。 ldr r1, =var
一样。这是最好的吗?
您可以做的一件事是将所有变量并排放置在一个结构中,并使用该结构的基地址加载一个寄存器。然后,您可以使用符号表示结构开头的偏移量,以符号方式访问所有变量。
【参考方案1】:
在静态存储中为变量使用符号名称的一种方法是为变量定义一个结构。这允许您将结构的基地址加载到寄存器中,然后使用相对于基地址的符号名称访问结构成员。例如,您可以这样做:
.struct 0 @ start a new structure
foo: .skip 4 @ length of foo
bar: .skip 4 @ length of bar
baz: .skip 4 @ length of baz
len: @ total length of the structure
.section .bss @ switch to the BSS (uninitialised data) section
.balign 4 @ align to 4 bytes
variables:
.space len @ reserve space for your variables
.section .text @ switch to the text (code) section
...
ldr r0, =variables @ load r0 with the base address of your variables
ldr r1, [r0+#foo] @ access foo
str r2, [r0+#bar] @ access bar
ldr r3, [r0+#baz] @ access baz
这几乎是最接近静态存储中变量的符号名称的方法。如果变量在堆栈上,您可以使用类似的方法,使用帧指针(或堆栈指针)作为基地址。 .struct
的操作数是结构的基地址,您可以为其选择任何您喜欢的值。
至于movw
和movt
。这些在某些微架构上比ldr ..., =...
提供了微小的性能优势,因为它们不需要将数据提取到文本部分。据我所知,这对 armv7-m 目标没有影响;此外,movw
和 movt
消耗两个额外的字节,而 ldr
使用 =
操作数。因此,我建议您坚持使用 ldr
和 =
操作数。 movw
和movt
的用法是这样的:
movw r0, :lower16:foo @ load lower 16 bit of foo's address into r0
movt r0, :upper16:foo @ or higher 16 bit of foo's address into r0
这两个必须按此特定顺序发出,因为movw
会清除高 16 位。前缀 :lower16:
和 :upper16:
选择适当的重定位类型,仅指符号地址的低 16 位和高 16 位。您可以制作一个宏以使其更易于键入:
.macro addr reg, sym
movw \reg, :lower16:\sym
movt \reg, :upper16:\sym
.endm
这让你可以写
addr r0, foo
生成上述movw
和movt
对。
【讨论】:
看起来很不错。您对这些指令有参考/教程/手册/更好的文档/阅读建议吗?我发现的很薄。 @Scheintod 阅读GNU assembler manual。 这就是我所说的瘦。你的例子告诉我的远不止这些:sourceware.org/binutils/docs/as/Struct.html#Struct @Scheintod 我完全使用了那个参考,仅此而已。.struct
允许您方便地定义绝对符号(例如,如果您使用 .equ
),而无需手动计算地址。老实说,那个网站上的例子有点误导,但我也没有找到一个好的例子。似乎很少有人使用这个指令。
除此之外,学习如何做事的最佳技巧是用 C 编写代码,将其编译为汇编并检查输出。也有 ABI 文档,但它们通常很长且难以理解。以上是关于armv7-m 裸机 ldr/str 符号内存的主要内容,如果未能解决你的问题,请参考以下文章