前言
在单片机中,栈stack由编译器自动分配释放,用于存放函数调用,局部变量等数据。堆heap用于动态内存分配。堆栈可以在启动文件或者链接脚本中指定大小,但在实际开发中,尤其工程量较大的项目中难以确定堆栈使用量,容易造成堆栈溢出,造成程序崩溃或数据错误。
参考网上的检测方法需要手动告诉检测程序堆栈地址和大小,使用起来不方便,每次堆栈地址或大小改变时都需要修改检测程序。用调试的方式一步一步查看msp寄存器是否溢出需要花费大量时间,而且难以找到堆栈的最高水位。
本文通过C语言使用汇编变量获取堆栈地址和大小,更方便使用。
一、堆栈大小设置
以MDK开发环境为例,堆栈在startup_stm32h743xx.s中设置,Stack_Size和Heap_Size分别表示栈和堆的大小。
Stack_Size EQU 0x1000
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x1000
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
二、修改汇编文件
在.map文件中搜索heap和stack可以找到堆栈的起始地址和大小。
还可以搜出来其他几个与堆栈有关的变量,这几个变量其实就是在startup_stm32h743xx.s汇编文件中定义的。这些汇编变量的值就是堆栈的起始地址,只要程序可以获取这些值就可以自动获取到堆栈地址。
正常情况下堆栈是连续存放在一起的,因此只需要这三个变量就可以确定堆和栈的起始位置和大小,但对于有多段不连续的内存的单片机来说这三个值无法确定堆的大小,因此还需要修改.s启动文件,增加一个__stack_base变量,并导出变量由C语言调用,修改后的.s启动文件如下。
Stack_Size EQU 0x1000
AREA STACK, NOINIT, READWRITE, ALIGN=3
__stack_base
Stack_Mem SPACE Stack_Size
__initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x1000
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
...
EXPORT __stack_base
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
...
三、设置和检测堆栈函数
?在C语言中使用汇编变量首先需要声明变量,获取汇编变量的值需要使用取址符。
extern int __initial_sp;
extern int __stack_base;
extern int __heap_base;
extern int __heap_limit;
在单片机启动时在堆栈剩余空间内填充幻数,需要检测堆栈时再检测这些幻数即可得到堆栈使用情况。栈填充幻数和检测幻数的代码如下,其中调用__get_MSP()获取当前msp寄存器的值。下面代码以检测栈为例,检测堆的代码类似。
const int s_magic = 0x43218765;
const int h_magic = 0x56781234;
int stack_set_guard(void)
{
__disable_irq();
int* msp = (int *)__get_MSP();
int* base = &__stack_base;
if(msp < base) {
__enable_irq();
return -1;
}
for( ; base != msp; base++)
*base = s_magic;
__enable_irq();
return (uint32_t)msp - (uint32_t)&__stack_base;
}
int stack_detect_guard(void)
{
__disable_irq();
int* msp = (int *)__get_MSP();
int* base = &__stack_base;
if(msp < base || *base != s_magic) {
__enable_irq();
return -1;
}
for( ; base != msp; base++) {
if(*base != s_magic)
break;
}
__enable_irq();
return (uint32_t)base - (uint32_t)&__stack_base;
}
四、使用范例
首先在主函数开始时调用堆设置幻数函数,将堆中目前未使用部分进行标记,接着在合适的位置调用堆检测函数检测幻数,返回栈历史高水位时堆剩余大小,若返回值为-1则说明发生栈溢出。也可在单个函数前后分别调用设置和检测函数,检测单个函数(包括其中的函数调用)的堆使用量。完整示例可以下载。
int main(void)
{
int ret = stack_set_guard();
/* 初始化 */
...
/* 调用一些函数 */
...
/* 检测单个函数栈使用量 */
ret = stack_set_guard();
xxx_func();
ret = stack_detect_guard() - ret;
while (1)
{
/* 调用一些函数 */
...
ret = stack_detect_guard();
printf("stack_detect_guard %d\r\n", ret);
}
}
|