为什么 SP(显然)存储在 Cortex-M3 的异常条目上?
Why is SP (apparently) stored on exception entry on Cortex-M3?
我正在使用带有 SysTick 中断的 TI LM3S811(较旧的 Cortex-M3)以 10Hz 触发。这是 ISR 的主体:
void SysTick_Handler(void)
{
__asm__ volatile("sub r4, r4, #32\r\n");
}
这将生成以下带有 -O0
和 -fomit-frame-pointer
以及 gcc-4.9.3 的程序集。 STKALIGN
位为 0,因此堆栈是 4 字节对齐的。
00000138 <SysTick_Handler>:
138: 4668 mov r0, sp
13a: f020 0107 bic.w r1, r0, #7
13e: 468d mov sp, r1
140: b401 push {r0}
142: f1ad 0420 sub.w r4, r4, #32
146: f85d 0b04 ldr.w r0, [sp], #4
14a: 4685 mov sp, r0
14c: 4770 bx lr
14e: bf00 nop
我不明白上面列表中的 r0
是怎么回事。具体来说:
1) 看起来我们正在清除SP 的低3 位并将其存储在堆栈中。那是为了保持8字节对齐吗?还是别的原因?
2) 异常退出程序同样令人费解。根据我对 ARM 程序集的有限理解,它会做这样的事情:
SP = SP + 4;
R0 = SP;
接着存回SP。这似乎在这个阶段之前撤销了操作。
3) 为什么在无条件分支之后(在0x14E
处)有一条nop
指令?
由于您使用的是 -O0
,因此您应该预料到会有很多冗余和无用的代码。编译器工作的一般方式是生成具有程序中任何地方可能使用的所有内容的全部通用性的代码,然后依靠优化器去除不需要的东西。
- 是的,这是在进行 8 字节对齐。它还分配一个堆栈帧来保存局部变量,即使你有 none.
- 出口是相反的,释放栈帧。
- 末尾的 nop 是为了在代码中保持 4 字节对齐,因为您可能希望 link 在某些时候使用非 thumb 代码。
如果启用优化,它将消除堆栈帧(因为不需要)并且代码会变得更加简单。
ARM 过程调用标准和 C ABI 要求堆栈对齐为 8 字节(64 位)。由于在 pushing/poping 个字后可能会发生中断,因此无法保证堆栈在中断入口处正确对齐。
STKALIGN
位(如果设置)(默认设置)强制硬件通过有条件地将额外(虚拟)字推入堆栈来自动对齐堆栈。
函数上的 interrupt
属性告诉 gcc,OTOH 堆栈可能未对齐,因此它添加了此 pre-/postamble 以强制对齐。
所以,两者实际上做的是一样的;一个在硬件上,一个在软件上。如果您只能使用字对齐堆栈,则应从函数声明中删除 interrupt
属性并清除 STKALIGN
位。
确保这样的 "missaligned" 堆栈没有问题(我不希望有任何问题,因为这是纯 32 位 CPU)。 OTOH,你应该保持原样,除非你真的需要保护额外的条件(!)时钟和字(非常不可能)。
警告:根据 ARM 体系结构参考手册,不推荐设置 STKALIGN == 0。简而言之:不要将此位设置为 0
!
我正在使用带有 SysTick 中断的 TI LM3S811(较旧的 Cortex-M3)以 10Hz 触发。这是 ISR 的主体:
void SysTick_Handler(void)
{
__asm__ volatile("sub r4, r4, #32\r\n");
}
这将生成以下带有 -O0
和 -fomit-frame-pointer
以及 gcc-4.9.3 的程序集。 STKALIGN
位为 0,因此堆栈是 4 字节对齐的。
00000138 <SysTick_Handler>:
138: 4668 mov r0, sp
13a: f020 0107 bic.w r1, r0, #7
13e: 468d mov sp, r1
140: b401 push {r0}
142: f1ad 0420 sub.w r4, r4, #32
146: f85d 0b04 ldr.w r0, [sp], #4
14a: 4685 mov sp, r0
14c: 4770 bx lr
14e: bf00 nop
我不明白上面列表中的 r0
是怎么回事。具体来说:
1) 看起来我们正在清除SP 的低3 位并将其存储在堆栈中。那是为了保持8字节对齐吗?还是别的原因?
2) 异常退出程序同样令人费解。根据我对 ARM 程序集的有限理解,它会做这样的事情:
SP = SP + 4;
R0 = SP;
接着存回SP。这似乎在这个阶段之前撤销了操作。
3) 为什么在无条件分支之后(在0x14E
处)有一条nop
指令?
由于您使用的是 -O0
,因此您应该预料到会有很多冗余和无用的代码。编译器工作的一般方式是生成具有程序中任何地方可能使用的所有内容的全部通用性的代码,然后依靠优化器去除不需要的东西。
- 是的,这是在进行 8 字节对齐。它还分配一个堆栈帧来保存局部变量,即使你有 none.
- 出口是相反的,释放栈帧。
- 末尾的 nop 是为了在代码中保持 4 字节对齐,因为您可能希望 link 在某些时候使用非 thumb 代码。
如果启用优化,它将消除堆栈帧(因为不需要)并且代码会变得更加简单。
ARM 过程调用标准和 C ABI 要求堆栈对齐为 8 字节(64 位)。由于在 pushing/poping 个字后可能会发生中断,因此无法保证堆栈在中断入口处正确对齐。
STKALIGN
位(如果设置)(默认设置)强制硬件通过有条件地将额外(虚拟)字推入堆栈来自动对齐堆栈。
函数上的 interrupt
属性告诉 gcc,OTOH 堆栈可能未对齐,因此它添加了此 pre-/postamble 以强制对齐。
所以,两者实际上做的是一样的;一个在硬件上,一个在软件上。如果您只能使用字对齐堆栈,则应从函数声明中删除 interrupt
属性并清除 STKALIGN
位。
确保这样的 "missaligned" 堆栈没有问题(我不希望有任何问题,因为这是纯 32 位 CPU)。 OTOH,你应该保持原样,除非你真的需要保护额外的条件(!)时钟和字(非常不可能)。
警告:根据 ARM 体系结构参考手册,不推荐设置 STKALIGN == 0。简而言之:不要将此位设置为 0
!