未对齐的访问导致 ARM Cortex-M4 上的错误
Posted
技术标签:
【中文标题】未对齐的访问导致 ARM Cortex-M4 上的错误【英文标题】:Unaligned access causes error on ARM Cortex-M4 【发布时间】:2013-08-16 08:39:15 【问题描述】:我有一个地址不是 4 字节对齐的对象。当有 STR 指令保存 2 个寄存器时,这会导致 cpu 中出现 HardFault 错误。
这是生成的代码:
00000000 <_ZN8BaseAreaC1EPcmm>:
0: b510 push r4, lr
2: 4604 mov r4, r0
4: 6042 str r2, [r0, #4]
6: e9c4 3102 strd r3, r1, [r4, #8]
a: 2001 movs r0, #1
c: 7420 strb r0, [r4, #16]
e: b921 cbnz r1, 1a <_ZN8BaseAreaC1EPcmm+0x1a>
这些是在“4: 6042...”行时的寄存器
R0 08738B82 R8 0
R1 08738BAE R9 0
R2 0 R10 082723E0
R3 2FCC R11 0
R4 08738B82 R12 0
R5 20007630 R13 2000CB38
正如所见,STR 指令的目标寄存器未在 4 字节上对齐。指令STR r2, [r0, #4]
执行良好。但它在下一个 STRD r3, r1, [r4, #8]
上出现 HardFaults。如果我手动将寄存器 R4 更改为 08738B80
它不会出现硬故障。
这是生成上述 asm 的 C++ 代码:
BaseArea::BaseArea(char * const pAddress, unsigned long startOffset, unsigned long endOffset) :
m_pAddress(pAddress), m_start(startOffset), m_end(endOffset), m_eAreaType(BASE_AREA)
而m_start
是类中的第一个变量,与this
(0x08738B82
) 具有相同的地址,m_end
紧跟在0x08738B86
之后。
如何让对象按 4 字节对齐? 有人对此有其他解决方案吗?
【问题讨论】:
您实际上是在用汇编程序编程,还是这个代码是由例如生成的。 C 编译器? 如果您告诉我们您使用的编译器(例如 gcc、armcc 等)也可能会有所帮助 能否请您发布BaseArea
的结构,以及构造函数被调用的位置?
你是如何实例化对象的?也许它是打包结构的一部分或类似的东西?否则,我认为这将注册为工具链错误,因为该语言保证将在满足其对齐要求的地址分配对象(过度对齐的对象除外,但此处并非如此)。在任何情况下,您都可以在创建对象时使用 alignas (C++11) 或编译器特定的等效项强制进行特定对齐。
【参考方案1】:
在基于 ARM 的系统上,您经常无法处理未与 4 字节边界对齐的 32 位字(正如您的错误告诉您的那样)。在 x86 上,您可以访问未对齐的数据,但是对性能有很大影响。如果 ARM 部件确实支持非对齐访问(例如,单字正常加载),则会有性能损失并且应该有一个可配置的异常陷阱。
ARM (here) 上的边界错误示例,TLDR:存储指向 unsigned char
的指针,然后尝试将其转换为 double *
(双指针)。
要解决您的问题,您需要请求一个 4 字节对齐的内存块并复制未对齐的字节 + 用垃圾字节填充它以确保它是 4 字节对齐的(因此执行数据结构对齐手动)。然后,您可以将该对象解释为与其新地址对齐的 4 字节。
来自 cmets 中的 TurboJ,显式错误:
Cortex-M3 和 M4 默认允许非对齐访问。但是它们不允许使用 STRD 指令进行单边访问,因此出现了错误。
您可能还会发现查看 this 以在 ARM 上强制数据结构对齐很有帮助。
【讨论】:
在 x86 上,只有当您的访问跨越 64B 行边界(或 - 上帝禁止 - 页面边界)时,才会影响性能。仅仅访问一行中的几个未对齐的字节并不重要,您只需将整行缓存起来。 @Leeor,我将我最初的答案作为一个社区 wiki,因为 ARM 不是我太熟悉的领域(让其他人为一个集体的、强有力的答案做出贡献)。有很多细节我确定我忽略或没有说明,请随时分别编辑原始帖子。 谢谢!问题是未对齐的地址源自包括 sizeof() 在内的许多计算,并且地址对齐到 2 字节。我将修改那部分代码以对齐 4 字节。 我仍然很好奇为什么第一个带有一个寄存器的 STR 可以工作,而带有 2 个寄存器的 STR 却不行。有什么想法吗?这些对齐问题是否有任何编译器选项? Cortex-M3 和 M4 默认允许非对齐访问。但是它们确实不允许使用STRD
指令进行无限制访问,因此是错误的。【参考方案2】:
以下内容至少适用于 ARM 架构(已在 cortex M0 上验证):
在使用加载和存储指令时,我们访问的内存必须能被我们尝试从/向内存访问的字节数整除,否则我们会遇到硬故障异常。
例如:
LDR r0, = 0x1001
LDR r1, [r0]
上面代码中的第二行将给出硬错误,因为试图读取 4 个字节但内存地址不能被 4 整除
如果我们把上面代码的第二行改成下面这样
LDRB r1, [r0];
//从地址加载1字节
上述行不会产生硬故障,因为我们正在尝试访问 1 个字节(可以从任何内存位置访问 1 个字节)
还要注意下面的例子;
LDR r0,= 0x1002
LDRH r1,[r0]; //Load half word from 0x1002
上述行不会产生硬故障,因为内存访问是 2 个字节,地址可以被 2 整除。
【讨论】:
看起来对于 Cortex-M3,至少具有 LDR 和 STR 指令的非字对齐地址支持未对齐访问,并且仅当配置控制寄存器中的 UNALIGN_TRP 位为“1”时才会生成对齐错误。以上是关于未对齐的访问导致 ARM Cortex-M4 上的错误的主要内容,如果未能解决你的问题,请参考以下文章
为啥 GCC 会为 ARM Cortex-A9 产生非法未对齐访问