未对齐的访问导致 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 产生非法未对齐访问

Arm Cortex-M4 LDRD 指令导致硬故障

[ARM] Cortex-M Startup.s启动文件相关代码解释

如何捕获未对齐的内存访问?

带你深入了解对齐与非对齐访问(ARM指令集)

ARMClang6.1编译优化导致的访问不对齐异常