ARM Cortex-M4上未对齐访问导致错误

10

我有一个对象,它的地址不是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行,即“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]上会导致硬件错误。如果我手动将寄存器R4更改为08738B80,则不会发生硬件错误。

这是生成上述汇编代码的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编译器生成的? - Some programmer dude
如果您告诉我们您正在使用哪个编译器(例如gcc,armcc等),这也可能会有所帮助。 - Mats Petersson
请问您能否发布 BaseArea 的结构,并说明构造函数在哪里被调用? - Mats Petersson
你是如何实例化对象的?也许它是打包结构的一部分或类似于此的东西?否则,我认为这将被注册为工具链错误,因为语言保证对象将被分配到满足其对齐要求的地址(除了超对齐对象,但这不是这里的情况)。无论如何,在创建对象时,您可以使用alignas(C++11)或编译器特定的等效项来强制指定对齐方式。 - Samuel Peter
3个回答

18
在基于ARM的系统上,您经常不能访问未对齐到4字节边界的32位字(正如错误提示所示)。在x86上,您可以访问非对齐数据,但会对性能造成巨大影响。如果ARM部件支持不对齐访问(例如单字正常加载),则会有性能损失和可配置异常陷阱。

以下是在ARM上发生边界错误的示例(此处),简而言之:将指向无符号字符的指针存储,然后尝试将其转换为double指针。

要解决问题,您需要请求一个4字节对齐的内存块,并复制非对齐字节+填充垃圾字节以确保它是4字节对齐的(因此需要手动执行数据结构对齐)。然后,您可以从其新地址将该对象解释为4字节对齐的对象。

从TurboJ的评论中得知,明确的错误为:

Cortex-M3和M4默认允许不对齐访问。但是,它们不允许使用STRD指令进行不对齐访问,因此会出现故障。
您还可以查看此处,了解如何在ARM上强制数据结构对齐,这可能会对您有所帮助。

在x86上,性能损失只会在您的访问越过64B行边界(或-天佑我-页面边界)时发生。仅仅访问一些行内未对齐的字节并不重要,您只需获取整个缓存行即可。 - Leeor
@Leeor,我把我的初始答案作为社区维基,因为ARM不是我太熟悉的领域(让其他人为一个集体、强大的答案做出贡献)。我相信我忽略或未说明的细节还有很多,欢迎相应地编辑原帖。 - Jacob Pollack
谢谢!问题在于未对齐的地址源自包括 sizeof() 的多个计算,而该地址对齐到 2 字节。我将修改代码的这部分以对齐到 4 字节。 - rdrmntn
@Leeor:OP表示这是由C++编译器生成的代码,所以我不确定你回答中最后一个链接的相关性。 - idoby
3
Cortex-M3和M4默认允许非对齐访问。 但是,它们不允许使用“STRD”指令进行非对齐访问,因此会引发错误。 - Turbo J
显示剩余4条评论

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整除。

1
看起来对于Cortex-M3,至少在使用LDR和STR指令时,非字对齐地址支持非对齐访问,并且只有当配置控制寄存器中的UNALIGN_TRP位为“1”时才会生成对齐错误。 - Uchia Itachi

0

正如您所发现的那样,Cortex-M4支持4字节未对齐访问但不支持8字节未对齐访问。后者在UFSR.UNALIGNED位的文档中有所解释:

UNALIGNED - 表示发生了未对齐访问操作。 访问未按8字节对齐的uint64_t等未对齐的多字访问将始终生成此异常。除Cortex-M0 MCUs之外,是否配置未对齐访问低于4个字节会产生异常。

8字节访问可以是STR指令(就像您的示例一样),也可以简单地访问uint64_t


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接