监控栈有没有溢出,监控栈顶地址有没有超出边界是一种很好的方式,那么程序中如何获取栈顶地址呢,看下图:
uint32_t getStackTop()
{
uint32_t stackPointer;
__asm volatile (
"mov %0, sp\n"
: "=r"(stackPointer)
);
return stackPointer;
}
这段代码用到了复杂不太常用的汇编语句,看下GPT对这段代码的解释:
这段代码是一个内联汇编(inline assembly),用于在 C 或 C++ 代码中直接嵌入汇编指令。让我来解释一下:
1. `__asm` 是用于指示编译器后续代码是内联汇编的关键字。
2. `volatile` 关键字告诉编译器不要优化这段汇编代码,以确保汇编指令按照原样执行。
3. `"mov %0, sp\n"` 是具体的汇编指令,它的作用是将栈指针(SP 寄存器)的值移动到一个占位符 `%0` 所代表的寄存器中。
4. `: "=r"(stackPointer)` 是输出约束(output constraint),它告诉编译器将汇编指令中的结果存储到 C 变量 `stackPointer` 中。
因此,这段代码的作用是将栈指针的值存储到 C 变量 `stackPointer` 中,使得 C 代码可以使用这个值进行进一步的操作。
有了栈顶地址再加上栈底地址(__initial_sp)和栈的大小就可以判断出当前栈有没有溢出了。
但是,上述函数由于用到了C嵌汇编,所以会受到编译器的限制,有的编译器会不支持;所以,还有一种方式就是直接用汇编写,新建一个汇编文件或者直接在start.s文件中编写如下汇编函数:
Get_MSP PROC
PUSH {R0,R1,LR}
EXPORT Get_MSP
IMPORT get_msp_c
MOV R0,SP
BL get_msp_c
POP {R0,R1,PC}
ENDP
其中导入的函数 get_msp_c 是一个C函数:
unsigned int _msp=0;
void get_msp_c(unsigned int msp)
{
_msp=msp;
}
C函数很简单,就是将传入的msp参数赋值给全局变量_msp;
整体采用的原理就是:CM0/CM3/CM4内核在调用函数的时候会用R0-R4来传递参数,所以代码先把SP的值赋给R0,再调用get_msp_c函数,此时函数的传参msp就成了SP的值了。
值得注意的是:如果是嵌入了操作系统的,那么如果是在某个任务中调用此函数的话那么获取到的是线程栈的栈位置(PSP),此时就要结合任务栈的栈底地址和栈大小来判断任务栈有没有溢出了;当然对于很多嵌入式操作系统来说都会提供一个获取任务最高水位线的函数,如对于freertos是:uxTaskGetStackHighWaterMark()。