Discussion on stack 8-byte alignment

1. Why to ensure stack 8-byte alignment
  AAPCS rules require the stack to maintain 8-byte alignment. If it is not aligned, it is no problem to call the general function. But errors may occur when calling functions that require strict adherence to AAPCS rules.
  For example, when calling sprintf to output a floating-point number, the stack must be 8-byte aligned, otherwise the result may be wrong.

Experimental verification:
#include "stdio.h"
#include "string.h"
float fff=1.234;
char buf[128];
int main(void)
{
    sprintf(buf,"%.3f\n\r",fff);//A
    while(1);
}
1. Set a breakpoint at A and let the program run to A at full speed
2. Modify the value of MSP in MDK to make MSP meet 8-byte alignment
3. Run the program at full speed and observe that the character in buf is 1.234 and the result is correct
4. Go back Step 2, modify the MSP so that it only satisfies 4-byte alignment but not 8-byte alignment
5. Run the program at full speed and observe that the character in buf is -2.000. The result is wrong.

This experiment proves that calling sprintf to output a floating point number must be guaranteed The stack is 8-byte aligned.


2. What the compiler does for us
Let's look at an experiment
#include "stdio.h"
#include "string.h"
float fff=1.234;
char buf[128];

void fun(int a,int b,int c,int d)
{
    int v;
    v=v;
}
void test(void)
{}
int main(void)
{    
    fun(1,2,3,4);    
    test();       //A
//  sprintf(buf,"%.3f\n\r",fff);
    while(1);
}
0. Ensure that the stack is 8-byte aligned initially;
1. Set a breakpoint at A;
2. Run to A at full speed, observe MSP=0x2000025c, no 8-byte alignment;
3. Slightly modify the main function code as follows , other parts of the code remain unchanged;
int main(void)
{
    fun(1,2,3,4);
//  test();
    sprintf(buf,"%.3f\n\r",fff);//A
    while(1);
}
4. Also set a breakpoint at A;
5. Run to A at full speed, observe MSP=0x200002d8, this time 8 bytes are aligned;
this experiment shows that if the compiler finds that a function needs to call the floating-point library, it will automatically Adjust the compiled assembly code to ensure that the stack is 8-byte aligned when calling these floating-point library functions. In other words, if we guarantee that the stack is initially 8-byte aligned, the compiler can guarantee that the stack is still 8-byte aligned when calling the floating-point library later.

     

3. How to set the task stack under os
    From the above discussion, we need to ensure that the stack is 8-byte aligned when assigning a stack to a task, otherwise, any function that calls sprintf in this task will make an error.
  Because the stack is not aligned to begin with.

4. Does the stack alignment problem in the interrupt
 ensure that the stack is initially 8-byte aligned and everything is fine? no! Please look at a special case:
#include "stdio.h"
#include "string.h"
float fff=1.234;
char buf[128];
void fun(int a,int b,int c,int d)
{
    int v;
    v=v;
}
int main(void)
{
    fun(1,2,3,4);
    while(1);
}
void SVC_Handler(void)
{
    sprintf(buf,"%.3f\n\r",fff);//B
}
mian函数的反汇编如下:
0x080001DC B500 PUSH {lr}
0x080001DE 2304 MOVS r3,#0x04 ;A
0x080001E0 2203 MOVS r2,#0x03
0x080001E2 2102 MOVS r1,#0x02
0x080001E4 2001 MOVS r0,#0x01
0x080001E6 F7FFFFF5 BL.W fun (0x080001D4)
0x080001EA BF00 NOP
0x080001EC E7FE B 0x080001EC
0.保证初始的时候堆栈是8字节对齐的;
1.在A处设置断点;
2.全速运行至A,观察此时MSP=0x200002e4 未对齐;
3.在MDK中将SVC的挂起位置1;
4.在B处设置断点;
5.全速运行至B,观察此时MSP=0x200002b4 未对齐;
6.继续全速执行,观察buf中的字符为:-2.000 出错了;

   这个实验说明了即使保证栈初始是8字节对齐的,编译器也只能保证在调用sprintf那个时刻栈是8字节对齐的, 但不能保证任意时刻栈都是8字节对齐的,如果恰巧在MSP没有8字节对齐的时刻发生了中断,而中断中又调用 了sprintf, 这种情况下仍会出错。

五.Cortex-M3内核为我们做了什么
  Cortex-M3内核提供了一种硬件机制来解决上述这种中断中栈不对齐问题。
  CM3中可以把NVIC配置控制寄存器的STKALIGN置位,来保证中断中的栈8字节对齐,具体实现过程如下:
    当发生中断时由硬件自动检测MSP是否8字节对齐,如果对齐了,则不进行任何操作,如果没有对齐,
则自动将MSP减4这样便对齐了,同时将xPSR的第9位置位来记录这个 MSP的非正常的变化, 在中断返回若发现xPSR的第9位是置位的则自动将MSP加4调整 回原来的值。

实验验证:
#include "stdio.h"
#include "string.h"
float fff=1.234;
char buf[128];
void fun(int a,int b,int c,int d)
{
    int v;
    v=v;
}
int main(void)
{
    fun(1,2,3,4);
    while(1);
}
void SVC_Handler(void)
{
    sprintf(buf,"%.3f\n\r",fff);//B
}
mian函数的反汇编如下:
0x080001DC B500 PUSH {lr}
0x080001DE 2304 MOVS r3,#0x04 ;A
0x080001E0 2203 MOVS r2,#0x03
0x080001E2 2102 MOVS r1,#0x02
0x080001E4 2001 MOVS r0,#0x01
0x080001E6 F7FFFFF5 BL.W fun (0x080001D4)
0x080001EA BF00 NOP
0x080001EC E7FE B 0x080001EC
1.在A处设置断点;
2.全速运行至A,观察此时MSP=0x200002e4 未对齐;
3.在MDK中将SVC的挂起位置1,同时将0xE000ED14处的值由0x00000000改为0x00000200
(即将NVIC配置控制寄存器的STKALIGN置位)
4.在B处设置断点;
5.全速运行至B,观察此时MSP=0x200002b0 对齐了;
6.观察中断返回时的MSP=0x200002e4 调整回来了;
7.继续全速执行,观察buf中的字符为:1.234 正确;

这个实验说明了将NVIC配置控制寄存器的STKALIGN置位可以保护中断时栈仍是8字节对齐

六.总结
    综上所述,为了能够安全的使用严格遵守AAPCS规则的函数(比如sprintf)需要做到以下几点:
    1.保证MSP在初始的时候是8字节对齐的
    2.如果用到OS的话需要保证给每个任务分配的栈是保持8字节对齐的
    3.如果用的是基于CM3内核的处理器需将NVIC配置控制寄存器的STKALIGN置位
七、使用系统时的操作(uCOS-Ⅲ)
    OS_TCB   FloatTaskTCB;   //任务控制块;
    __align(8)   CPU_STK  FLOAT_TASK_STK[FLOAT_STK_SIZE]; //任务堆栈;
     其中 __align(8) 是保证开辟堆栈保持8字节对齐;

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325648941&siteId=291194637