STM32H7无RTOS应用堆栈机制与检测

news2025/1/23 10:27:26

摘要:单片机堆栈溢出会引发不可预知的错误。本文探讨了基于STM32CubeIDE设置STM32H7xx堆栈在无RTOS时的使用与检测方法。

一、堆栈的设置

STM32CubeIDE对工程设置堆栈很简单,在CubeMX中设置最小size如下图

堆(Heap)为0x400,等于1024字节,栈(Stack)为0x800,等于2048字节。堆用于用于程序运行中动态申请内存分配和释放,例如用malloc()和calloc()函数申请内存,用完free()函数释放。栈由编译器自动分配,用于函数调用形参、函数局部变量等临时数据存放,栈的使用量和工程程序的复杂程度密切相关,如果连续调用子函数,或者使用大量局部变量就要留足栈空间。

生成工程后在链接脚本文件(*.ld)中就可以看到,在第7,8行

/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = ORIGIN(RAM_D1) + LENGTH(RAM_D1);    /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x400;      /* required amount of heap  */
_Min_Stack_Size = 0x800; /* required amount of stack */

/* Specify the memory areas */
MEMORY
{
  FLASH (rx)     : ORIGIN = 0x08000000, LENGTH = 12K  /* 512K=12+1+499 */
  FLASH2 (rx)    : ORIGIN = 0x08100000, LENGTH = 512K
  DTCMRAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 128K
  RAM_D1 (xrw)   : ORIGIN = 0x24000000, LENGTH = 512K
  RAM_D2 (xrw)   : ORIGIN = 0x30000000, LENGTH = 288K
  RAM_D3 (xrw)   : ORIGIN = 0x38000000, LENGTH = 64K
  ITCMRAM (xrw)  : ORIGIN = 0x00000000, LENGTH = 64K
  FLASH_VER (rx) : ORIGIN = 0x08003000, LENGTH = 1K
  FLASH_REM (rx) : ORIGIN = 0x08003400, LENGTH = 499K
}

STM32H7的堆栈属于SRAM,就是上面的RAM_D1域,起始地址0x24000000,长度512K

二、堆栈的分配

在启动文件(*.s)中定义了一些内存地址全局变量,在上面的链接脚本文件中也引用了,比如_estack、_Min_Heap_Size、_Min_Stack_Size,在工程的sysmem.c文件中使用了这些全局变量。文件中主要是定义了_sbrk()函数,这个函数是申请动态内存,比如malloc()执行后会被调用,返回内存地址指针,调整堆顶地址的功能。

/* Includes */
#include <errno.h>
#include <stdint.h>

/**
 * Pointer to the current high watermark of the heap usage
 */
//static
uint8_t *__sbrk_heap_end = NULL;

/**
 * @brief _sbrk() allocates memory to the newlib heap and is used by malloc
 *        and others from the C library
 *
 * @verbatim
 * ############################################################################
 * #  .data  #  .bss  #       newlib heap       #          MSP stack          #
 * #         #        #                         # Reserved by _Min_Stack_Size #
 * ############################################################################
 * ^-- RAM start      ^-- _end                             _estack, RAM end --^
 * @endverbatim
 *
 * This implementation starts allocating at the '_end' linker symbol
 * The '_Min_Stack_Size' linker symbol reserves a memory for the MSP stack
 * The implementation considers '_estack' linker symbol to be RAM end
 * NOTE: If the MSP stack, at any point during execution, grows larger than the
 * reserved size, please increase the '_Min_Stack_Size'.
 *
 * @param incr Memory size
 * @return Pointer to allocated memory
 */
__attribute__((weak)) void *_sbrk(ptrdiff_t incr)
{
  extern uint8_t _end; /* Symbol defined in the linker script */
  extern uint8_t _estack; /* Symbol defined in the linker script */
  extern uint32_t _Min_Stack_Size; /* Symbol defined in the linker script */
  const uint32_t stack_limit = (uint32_t)&_estack - (uint32_t)&_Min_Stack_Size;
  const uint8_t *max_heap = (uint8_t *)stack_limit;
  uint8_t *prev_heap_end;

  /* Initialize heap end at first call */
  if (NULL == __sbrk_heap_end)
  {
    __sbrk_heap_end = &_end;
  }

  /* Protect heap from growing into the reserved MSP stack */
  if (__sbrk_heap_end + incr > max_heap)
  {
    errno = ENOMEM;
    return (void *)-1;
  }

  prev_heap_end = __sbrk_heap_end;
  __sbrk_heap_end += incr;

  return (void *)prev_heap_end;
}

上面代码进行部分修改,原代码具体可以查看自己的文件。其中返回值prev_heap_end是本次申请内存的起始地址指针,而__sbrk_heap_end则是当前堆顶地址的指针。

下图是RAM_D1域的各区域分布情况

上图及sysmem.c文件中涉及的(全局)变量如下

  • _sdata:SRAM_D1空间起始地址,即0x24000000
  • _end:存放堆底地址的全局变量(由编译器给出,在ld文件),也是整个堆栈区的最小地址,_sdata~_end范围是全局变量和静态变量
  • __sbrk_heap_end:动态指向堆顶的指针,初始值等于_end的地址
  • _estack:存放栈顶地址的全局变量(在ld文件),大于_end与工程设置的堆与栈最小值之和,也是RAM_D1空间的上限,即0x24080000
  • _Min_Stack_Size:存放设置的栈最小空间大小(0x800)的全局变量
  • _Min_Heap_Size:存放设置的堆最小空间大小(0x400)的全局变量
  • stack_limit:计算的结果就是栈底地址
  • max_heap:stack_limit这个地址常量的指针,即栈底指针,也是理论上的堆顶指针上限
  • prev_heap_end:改变前的堆顶指针

_sdata到_end之间是静态存储区,由编译器存放全局变量、静态局部变量等永久使用的数据。_end到_estack之间是堆栈区域,其中栈区由_estack到减去_Min_Stack_Size长度的地址,其他剩余的属于堆区。因此,工程设置的栈最小长度是确定值,而堆最小长度_Min_Heap_Size一般就是参考值。堆从堆底(_end)开始向上生长,栈从栈顶(_estack)开始向下生长。

三、堆栈的检测

为了实现对堆栈使用情况的监测,修改了sysmen.c文件

#include "stm32h7xx_hal.h"

/* Includes */
#include <errno.h>
#include <stdint.h>


extern uint8_t _estack;           /* 链接脚本定义的栈顶地址 */
extern uint8_t _end;              /* 链接脚本定义的堆底地址 */
extern uint8_t _sdata;            /* 链接脚本定义的静态数据区起始地址 */
extern uint32_t _Min_Stack_Size;  /* 链接脚本定义的栈长度 */
extern uint32_t _Min_Heap_Size;   /* 链接脚本定义的堆长度 */
extern uint8_t *__sbrk_heap_end;  /* 堆指针,当前堆顶地址 */
static uint8_t *__sbrk_heap_max;  /* 堆指针,历史最高地址 */


/**
 * @brief _sbrk() allocates memory to the newlib heap and is used by malloc
 *        and others from the C library
 *
 * @verbatim
 * ############################################################################
 * #  .data  #  .bss  #       newlib heap       #          MSP stack          #
 * #         #        #                         # Reserved by _Min_Stack_Size #
 * ############################################################################
 * ^-- RAM start      ^-- _end                             _estack, RAM end --^
 * @endverbatim
 *
 * This implementation starts allocating at the '_end' linker symbol
 * The '_Min_Stack_Size' linker symbol reserves a memory for the MSP stack
 * The implementation considers '_estack' linker symbol to be RAM end
 * NOTE: If the MSP stack, at any point during execution, grows larger than the
 * reserved size, please increase the '_Min_Stack_Size'.
 *
 * @param incr Memory size
 * @return Pointer to allocated memory
 */
void *_sbrk(ptrdiff_t incr)
{
  const uint32_t stack_limit = (uint32_t)&_estack - (uint32_t)&_Min_Stack_Size;
  const uint8_t *max_heap = (uint8_t *)stack_limit;
  uint8_t *prev_heap_end;

  /* Initialize heap end at first call */
  if (NULL == __sbrk_heap_end)
  {
    __sbrk_heap_end = &_end;
    __sbrk_heap_max = &_end;
  }

  /* Protect heap from growing into the reserved MSP stack */
  if (__sbrk_heap_end + incr > max_heap)
  {
    errno = ENOMEM;
    return (void *)-1;
  }

  prev_heap_end = __sbrk_heap_end;
  __sbrk_heap_end += incr;

  if ((uint32_t)__sbrk_heap_end > (uint32_t)__sbrk_heap_max)
  {
    __sbrk_heap_max = __sbrk_heap_end;
  }

  return (void *)prev_heap_end;
}

/* 栈开始地址(最高地址) */
void *BSP_MEM_StackTop(void)
{
  return (void *)&_estack;
}

/* 栈结束地址(最低地址),堆结束地址(最高地址) */
void *BSP_MEM_StackHeap(void)
{
  const uint32_t stack_limit = (uint32_t)&_estack - (uint32_t)&_Min_Stack_Size;
  const uint8_t *max_heap = (uint8_t *)stack_limit;

  return (void *)max_heap;
}

/* 堆开始地址(最低地址),静态数据结束地址 */
void *BSP_MEM_HeapBottom(void)
{
  return (void *)&_end;
}

/* 堆当前地址 */
void *BSP_MEM_HeapPos(void)
{
  return (void *)__sbrk_heap_end;
}

/* 堆历史最高地址 */
void *BSP_MEM_HeapMax(void)
{
  return (void *)__sbrk_heap_max;
}

/* 栈当前地址 */
void *BSP_MEM_StackPos(void)
{
  return (void *)__get_MSP();
}

/* 静态数据开始地址 */
void *BSP_MEM_StaticData(void)
{
  return (void *)&_sdata;
}

/* 栈设置最小长度 */
void *BSP_MEM_StackSize(void)
{
  return (void *)&_Min_Stack_Size;
}

/* 堆设置最小长度 */
void *BSP_MEM_HeapSize(void)
{
  return (void *)&_Min_Heap_Size;
}

增加了几个函数,用于读取堆或者栈的地址。

对于堆最大使用空间的计算比较简单。每次调用_sbrk()函数后,比较保留最大地址的指针__sbrk_heap_max就可以,调用函数BSP_MEM_HeapMax()就行。

对于栈最大使用空间的计算比较困难,原因是无法捕捉到进入子函数后的情况。一个不可靠的方法是用定时中断(尽可能小,但太频繁中断会影响程序运行。记得监测后删掉代码),用函数__get_MSP()读取栈地址后再判断,基本上不是很靠谱了。

#ifdef DEBUG
  /* 获取栈指针极限值 */
  uint32_t stack_msp = __get_MSP();
  if (stack_msp < stack_limit)
  {
    stack_limit = stack_msp;
  }
#endif

主程序定义全局变量

/* 堆栈 */
__attribute__((unused)) uint32_t stack_limit = 0;

打印堆栈信息如下

#ifdef DEBUG
  /* 堆栈测试 */
  const uint32_t stack_top = (uint32_t)BSP_MEM_StackTop();
  const uint32_t stack_heap = (uint32_t)BSP_MEM_StackHeap();
  const uint32_t heap_bottom = (uint32_t)BSP_MEM_HeapBottom();
  const uint32_t stack_size_min = (uint32_t)BSP_MEM_StackSize();
  const uint32_t heap_size_min = (uint32_t)BSP_MEM_HeapSize();
  const uint32_t stack_size = stack_top - stack_heap;
  const uint32_t heap_size = stack_heap - heap_bottom;
  uint32_t heap_usage = 0;
  uint32_t stack_usage = 0;
  uint32_t heap_usage_max = 0;
  uint32_t stack_usage_max = 0;

  stack_limit = stack_top;

  LOG_DBG("# stack top:   0x%lX\r\n", stack_top);
  LOG_DBG("# stack pos:   0x%lX\r\n", (uint32_t)BSP_MEM_StackPos());
  LOG_DBG("# stack/heap:  0x%lX\r\n", stack_heap);
  LOG_DBG("# heap pos:    0x%lX\r\n", (uint32_t)BSP_MEM_HeapPos());
  LOG_DBG("# heap end:    0x%lX\r\n", heap_bottom);
  LOG_DBG("# static data: 0x%lX\r\n", (uint32_t)BSP_MEM_StaticData());
  LOG_DBG("# stack size:  0x%lX(min 0x%lX)\r\n", stack_size, stack_size_min);
  LOG_DBG("# heap size:   0x%lX(min 0x%lX)\r\n", heap_size, heap_size_min);
#endif

动态监测如下

#ifdef DEBUG
      /* 堆栈检测 */
      heap_usage = ((uint32_t)BSP_MEM_HeapMax() - heap_bottom) * 100 / heap_size;
      stack_usage = (stack_top - stack_limit) * 100 / stack_size;
      if (heap_usage > heap_usage_max)
      {
        heap_usage_max = heap_usage;
        LOG_DBG("# heap usage: %ld%%\r\n", heap_usage_max);
      }
      if (stack_usage > stack_usage_max)
      {
        stack_usage_max = stack_usage;
        LOG_DBG("# stack usage: %ld%%\r\n", stack_usage_max);
      }
#endif

使用效果

APP init...
  stack top:   0x24080000
  stack pos:   0x2407FF90
  stack/heap:  0x2407F800
  heap pos:    0x24006908
  heap end:    0x240064E0
  static data: 0x24000000
  stack size:  0x800(min 0x800)
  heap size:   0x79320(min 0x400)

APP loop...
heap usage: 44%
stack usage: 21%
stack usage: 30%

根据监测结果调整堆栈空间大小,保持足够的余量即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1978118.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

低空经济-低空智联网技术体系白皮书

目录 低空定义 低空政策 低空市场规模 低空应用场景 通信需求 监管需求 低空智联网技术体系 低空定义 低空经济是指在3000米以下&#xff0c;以低空空域为依托&#xff0c;以各种有人和无人驾驶航空器的低空飞行活动为牵引&#xff0c;辐射带动相关领域融合发展的综合性…

【MySQL是怎样运行的 | 第三篇】MySQL的MVCC机制

文章目录 3.MySQL的MVCC机制3.1前言3.2undo log日志3.3三个隐藏字段3.4undo log版本链3.5当前读VS快照读3.6ReadView3.7举例3.7.1RC&#xff08;读已提交&#xff09;3.7.2RR&#xff08;可重复读&#xff09; 3.8扩展&#xff1a;RR能解决幻读问题吗&#xff1f; 4.白云 3.MyS…

【Python系列】深入理解 Python 中的 `nonlocal` 关键字

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

进程状态(二)----- linux 中具体的进程状态(上)

目录 前言1. R 状态2. S 状态3. D 状态 前言 继上一篇文章 进程状态&#xff08;一&#xff09;---- 运行&#xff0c;阻塞&#xff0c;挂起 介绍了操作系统都有的三个进程状态&#xff0c;而这篇文章则是将进程状态具象化&#xff0c;谈论具体到 linux 系统中的进程状态都有哪…

Geoserver源码解读七 插件(二)扩展图层预览界面

系列文章目录 Geoserver源码解读一 环境搭建 Geoserver源码解读二 主入口 Geoserver源码解读三 GeoServerBasePage Geoserver源码解读四 REST服务 Geoserver源码解读五 Catalog Geoserver源码解读六 插件&#xff08;怎么在开发模式下使用&#xff09; 目录 系列文章目录…

vector中 resize()和reserve()

1.resize()改变容器大小 resize除了预留内存以外&#xff0c;还会调用容器元素的构造函数&#xff0c;不仅分配了N个对象的内存&#xff0c;还会构造N个对象。从这个层面上来说&#xff0c;resize()在时间效率上是比reserve()低的。 2.reserve()容器大小管理 用于预留内存。 …

【Linux】shell命令与Linux权限的概念

目录 一、shell命令二、Linux权限的概念2.1 Linux权限的概念2.1.1 用户2.1.2 指令2.1.2.1 su指令2.1.2.2 sudo指令 2.2 Linux权限管理2.2.1 文件访问者的分类&#xff08;人&#xff09;2.2.2 文件类型和访问权限&#xff08;事物属性&#xff09;2.2.2.1 文件类型2.2.2.2 基本…

C++ 中迭代器的first和second

c 里面的map容器的迭代器里面 有个first 和 second&#xff0c;分别指向键值和数值 it.first就是在迭代器中获取map键值&#xff0c;it.second同理 #include<iostream> #include<map> using namespace std;int main(void){map<string, string> m;//新建一个m…

lower_bound函数和upper_bound函数

lower_bound 和 upper_bound 函数都是 C 标准库算法&#xff0c;用于在已排序的范围内查找元素。它们返回的是迭代器&#xff0c;指向满足特定条件的元素位置。 lower_bound(begin, end, val) 功能&#xff1a;返回指向第一个不小于 val 的元素的迭代器。含义&#xff1a;如果…

如何快速下载拼多多图片信息,效率高

图片是电商吸引顾客的关键因素&#xff0c;高质量的商品图片能提升产品吸引力&#xff0c;增强用户购买欲望。良好的视觉展示有助于建立品牌形象&#xff0c;提高转化率。同时&#xff0c;图片也是商品信息的主要传递媒介&#xff0c;对消费者决策过程至关重要。 使用图快下载器…

Linux内核有什么之块设备驱动有什么第七回 —— 邂逅的三个文件系统之二:实际文件系统(4)

接前一篇文章&#xff1a;Linux内核有什么之块设备驱动有什么第六回 —— 邂逅的三个文件系统之二&#xff1a;实际文件系统&#xff08;3&#xff09; 本文内容参考&#xff1a; 《Linux设备驱动开发详解 —— 基于最新的Linux4.0内核》 宋宝华&#xff0c;机械工业出版社 3…

奥运会大规模使用中国AI大模型!

B站&#xff1a;啥都会一点的研究生公众号&#xff1a;啥都会一点的研究生 AI圈最近又发生了啥新鲜事&#xff1f; 巴黎奥运会大规模使用中国 AI 大模型 巴黎奥运会成为一场科技与体育的盛宴&#xff0c;其中包括了大量中国科技的应用。AI 技术将在多个方面发挥作用&#xf…

《计算机网络》(第8版)第7章 网络安全 复习笔记

第 7 章 网络安全 一、网络安全问题概述 1 计算机网络面临的安全性威胁 计算机网络上的通信面临两大类威胁&#xff0c;即被动攻击和主动攻击。 &#xff08;1&#xff09;被动攻击 这是指攻击者从网络上窃听他人的通信内容&#xff0c;通常把这类攻击称为截获。 &#xff08…

2.外部中断(EXTI)

理论 NVIC&#xff1a;嵌套向量中断控制器&#xff08;解释教程&#xff09; 外部通用中断线(EXTI0~EXTI15)&#xff1a;每个GPIO设置成中断模式&#xff0c;与中断控制器连接的线 外部中断触发方式 上升沿触发、下降沿触发、双边沿触发 外部中断触发函数 在stm32f1xx_it.c文件…

【AI作图:奥运会游泳冠军】

画一个&#xff1a;水花&#xff0c;上半身&#xff0c;游泳冠军&#xff0c;泳池背景&#xff0c;面部明亮&#xff0c;眼神光&#xff0c;亚洲运动员&#xff0c;超高品质&#xff0c;真人&#xff0c;完美容颜&#xff0c;健美&#xff0c;健身&#xff0c;身材娇好&#xf…

Jackson常用注解详解

Hi &#x1f44b;, Im shy 有人见尘埃&#xff0c;有人见星辰 Jackson常用注解详解 文章目录 Jackson常用注解详解0. 引入依赖1. JsonProperty2. JsonIgnore3. JsonFormat4. JsonInclude5. JsonCreator6. JsonValue7. JsonIgnoreProperties结论 Jackson是Java生态系统中广泛…

例题:使用一条命令将xxx目录下除了xx子目录之外的文件全部删除(find、管道、grep、exec)

文章目录 例题&#xff1a;删除xx子目录以外的所有目录和文件需求方式一方式二 例题&#xff1a;删除xx子目录以外的所有目录和文件 需求 使用一条命令将/opt目录下除了rh子目录之外的文件全部删除 [rootlocalhost opt]# cp /var/log/vm* /opt/ [rootlocalhost opt]# mkdir …

(一)循环依赖,你真的懂了吗?万字解析循环依赖底层原理 - 什么是Bean循环依赖?Spring是如何解决的?二级缓存可以解决吗?遇到相关报错如何处理?

theme: vuepress 一、相关知识点简介 Spring Boot是基于Spring框架的一个快速开发平台&#xff0c;旨在简化Spring应用的创建和部署。通过提供一系列开箱即用的默认配置和自动化工具&#xff0c;Spring Boot使开发者能够专注于业务逻辑&#xff0c;而无需处理复杂的配置和依赖…

【Python机器学习】支持向量机——在复杂数据上应用核函数

上图中&#xff0c;数据中存在某种可以识别的模式&#xff0c;其中一个问题就是&#xff1a;我们能否想线性情况一样&#xff0c;利用强大的工具来捕捉数据中的这种模式&#xff1f; 利用核函数将数据映射到高维空间 在上图中&#xff0c;数据点处于一个圆中&#xff0c;人类…

《零散知识点 · 自定义 HandleMapping》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…