研究嵌入式软件架构时遇到的初始化堆栈溢出问题

文章目录

  • 2025年4月10日新增
    • 分析PC寄存器指针值排查问题
    • map文件设计到的知识点
      • 1. **.bss 段(Block Started by Symbol)**
      • 2. **.data 段**
      • 3. **.text 段**
      • 4. **.heap 段**
      • 5. **.stack 段**
      • 6. **.rodata 段(只读数据段)**
      • 7. **.init 和 .fini 段**
      • 8. **符号信息(符号表)**
      • 总结
  • 如题
  • 如何解决?
  • 涉及到的知识点
  • 记录遇到的坑


2025年4月10日新增

首先程序框架已经解决所有问题,可以完美运行了!今天从git上download下来昨天的出错版本,重新分析下昨天的错误程序

在这里插入图片描述
堆栈设置的是1K空间
在这里插入图片描述
这个uart_init()函数里面的buff数组太大导致了堆栈溢出,昨天出了好几种堆栈溢出的情况,这是其一,那个systick初始化的问题我今天没复现,忘记昨天啥情况了,上面即使uart_inti()函数不调用,但是只要上面定义了还是会栈溢出,为什么呢?
在这里插入图片描述
左边error的RW多了0.25k空间,多出来的主要是ZI Data数据
在这里插入图片描述
发现error的map文件中,出现了stdio_streams.o,这个跟printf()函数有关
在这里插入图片描述
可以看出跟uart_init()函数没有关系
在这里插入图片描述
注释了这行代码就解决问题了
在这里插入图片描述
分析两个程序的内存布局差异及跑飞原因
一、内存布局差异对比
​​堆栈空间分配​​
​​异常程序​​:
STACK 段起始地址 0x20000500,大小 1024 bytes
HEAP 段起始地址 0x20000300,大小 512 bytes
​​正常程序​​:
STACK 段起始地址 0x200003f8,大小 1024 bytes
HEAP 段起始地址 0x200001f8,大小 512 bytes
​​差异​​:正常程序的堆栈和堆地址更紧凑,可能是通过调整启动文件中的 Stack_Size 和 Heap_Size 宏定义优化了内存分布。
​​全局变量区域(.bss/.data)​​
​​异常程序​​的 .bss 段存在多模块重叠分配(如 hal_uart1.o 和 stdio_streams.o),导致 active_config 等变量地址冲突(如 0x20000190 和 0x20000078)。
​​正常程序​​的 .bss 段地址分配连续且无重叠,例如 hal_uart1.o 的 rx_buffer 从 0x2000007c 开始,与前后模块无冲突。
二、跑飞原因推测
​​堆栈溢出​​
​​异常程序​​的堆栈起始地址 0x20000500 与 .bss 段末端 0x200002a0 间隔仅 0x260 bytes(约 608 字节),若存在深层次函数调用或中断嵌套,可能超过栈容量导致数据覆盖。
​​正常程序​​的堆栈起始地址 0x200003f8 与 .bss 末端 0x20000198 间隔 0x260 bytes,但通过优化内存分布避免了关键变量被覆盖。
​​内存访问越界​​
​​异常程序​​中 hal_uart1.o 的 tx_buffer 分配了 132 bytes,但其后的 active_config 地址 0x20000190 可能被越界写入(如环形缓冲区未正确管理),破坏相邻数据。
​​中断嵌套未考虑​​
若异常程序中存在高频中断(如 UART 接收中断),且未预留足够中断栈空间(STM32 默认使用主堆栈 MSP),嵌套中断可能耗尽 1024 bytes 的堆栈容量。

分析PC寄存器指针值排查问题

在这里插入图片描述
在这里插入图片描述
PC起始地址是0x8000240,是SystemInit
在这里插入图片描述

在这里插入图片描述
单步执行后发现PC指针跑飞
在这里插入图片描述
实际上,我这里是重定向出了问题

//int fputc(int ch, FILE *f)
//{
//	while (0 == (USART1->SR & 0X40));//	USART1->DR = (uint8_t)ch;//	return ch;
//}#if 1
#pragma import(__use_no_semihosting)// 使用#pragma指令来导入__use_no_semihosting,表示不使用半主机模式。
// 半主机模式通常用于调试,允许在目标设备上运行时与主机进行交互。
struct __FILE
{int handle;
};FILE __stdout;// 定义一个全局变量__stdout,类型为FILE,用于标准输出。
void _sys_exit(int x)
{x = x;
}int fputc(int ch, FILE *f)
{while (0 == (USART1->SR & 0X40));USART1->DR = (uint8_t)ch;return ch;
}
#endif

参考正点原子这样修改解决了问题

map文件设计到的知识点

在分析嵌入式系统或单片机的 map 文件时,map 文件提供了详细的内存布局和各个段(section)在内存中的分配情况。map 文件的内容对调试、优化和了解程序结构非常有帮助。

下面是对 map 文件中常见的几个段(例如 .bss 段、.data 段以及其他段)的详细分析。

1. .bss 段(Block Started by Symbol)

  • 描述.bss 段是用于未初始化的全局变量和静态变量的内存区域。在程序启动时,这些变量会被清零或初始化为零。
  • 特点
    • .bss 段通常不占用实际的存储空间。在编译时,链接器并不为 .bss 段中的变量分配实际的存储空间,而是指示程序运行时为这些变量分配空间,并在程序加载时清零它们。
    • 它通常包括未显式初始化为特定值的静态变量和全局变量。
  • 例子
    int global_var;  // 默认值为 0
    static int static_var;  // 默认值为 0
    
  • map 文件中的信息
    • map 文件中,.bss 段会列出所有未初始化的全局和静态变量及其所占的内存空间大小。由于这些变量会在程序启动时被清零,因此 .bss 段通常不会占用磁盘上的存储空间。
    • 大小map 文件中 .bss 段的大小通常会较大,尤其是未初始化变量较多时。

2. .data 段

  • 描述.data 段是用于存储已初始化的全局变量和静态变量的内存区域。与 .bss 段不同,.data 段中的变量在编译时就已经被赋予了初始值。
  • 特点
    • .data 段包含所有显式初始化的全局和静态变量。
    • 在程序启动时,.data 段的内容会从程序的可执行文件(或固件)加载到内存中。
  • 例子
    int global_var = 10;  // 已初始化的全局变量
    static int static_var = 20;  // 已初始化的静态变量
    
  • map 文件中的信息
    • map 文件会列出 .data 段中所有已初始化变量及其在内存中的起始地址和大小。
    • 大小.data 段的大小取决于已初始化的变量总量。

3. .text 段

  • 描述.text 段是程序的代码段,它存放了编译后的机器代码。在这个段中没有变量,只有程序的指令。
  • 特点
    • 代码段通常是只读的,因为程序代码在执行时不应该被修改。
    • .text 段通常是程序中占用空间最多的部分,尤其是复杂程序和函数较多时。
  • map 文件中的信息
    • map 文件会显示 .text 段的起始地址、大小以及每个函数的实际内存位置。
    • 大小:代码段的大小会受到程序中函数和指令的复杂度影响。

4. .heap 段

  • 描述.heap 段用于动态分配内存(例如通过 malloc, calloc, new 等函数分配的内存),通常位于程序的栈(.stack)段之后。
  • 特点
    • 动态内存分配的内存区域。
    • 程序运行时的内存分配和释放会发生在此区域。
  • map 文件中的信息
    • .heap 段的内存大小取决于程序运行时实际分配的内存量。
    • map 文件会显示 .heap 段的起始地址、大小等信息。

5. .stack 段

  • 描述.stack 段用于存储函数调用时的局部变量、返回地址等信息。栈是一个动态分配的内存区域,随着函数的调用和返回不断增长和缩小。
  • 特点
    • 栈的大小是由编译器或链接器预设的,通常在 map 文件中可以看到栈的大小。
    • 栈是一个向下增长的内存区域,通常在内存地址空间较高的位置。
  • map 文件中的信息
    • map 文件会显示栈的起始位置和大小等信息。栈的大小通常在 linker script 或编译器设置中配置。

6. .rodata 段(只读数据段)

  • 描述.rodata 段用于存储程序中的常量数据(如字符串字面量、常量数组等),这些数据在程序运行期间不会被修改。
  • 特点
    • .rodata 是只读的,不允许修改其中的数据。
    • 通常存储字符串常量、全局常量等。
  • map 文件中的信息
    • map 文件会列出 .rodata 段的内存占用情况,包括字符串常量等的内存地址和大小。

7. .init 和 .fini 段

  • 描述
    • .init 段包含程序初始化代码,在程序启动时被调用,通常用于设置初始化操作,如硬件初始化等。
    • .fini 段包含程序结束时的清理代码,通常在程序退出前进行资源清理。
  • 特点
    • 这两个段通常由编译器或链接器自动处理。
  • map 文件中的信息
    • map 文件会显示 .init.fini 段的地址和大小。

8. 符号信息(符号表)

  • 描述map 文件中还包含符号表,列出了程序中的所有符号(如变量、函数等)及其在内存中的地址。
  • 特点
    • 符号表包含了所有变量和函数的名称、类型和内存地址等信息,通常用于调试。
  • map 文件中的信息
    • 每个符号(如全局变量、静态变量、函数等)都会有一个对应的内存地址和大小。

总结

map 文件中,除了 .bss.data.text 段外,还包含了栈、堆、只读数据段等信息。每个段都有不同的作用,且在编译和链接过程中被分配到不同的内存区域。通过查看 map 文件,你可以清晰地了解程序的内存分布、变量和函数的位置,进而优化内存使用和提高程序性能。

如题

//hal_systick_config_t cfg =
//{
//    .clk_source = SYSTICK_CLK_HCLK_DIV8,
//    .reload_value = 21000,
//    .sys_clk_frequency = 168,
//    .tick_callback = timer_ticks_count
//};static void systick_init(void)
{hal_systick_config_t cfg ={.clk_source = SYSTICK_CLK_HCLK_DIV8,.reload_value = 21000,.sys_clk_frequency = 168,.tick_callback = timer_ticks_count};hal_systick_init(&cfg);
}

这个代码中,只要cfg结构体变量放到函数外面一切正常,只要放在函数内部则进入debug模式后需要三次run之后程序才会执行,把栈空间调大依然会出现这个问题

如何解决?

在这里插入图片描述

涉及到的知识点

一、堆栈初始值计算公式
STM32 的堆栈起始地址(即 SP 初始值)由以下公式决定:

SP_初始值=SRAM起始地址+RW_Data大小+ZI_Data大小+Stack_Size
​​SRAM起始地址​​:STM32F407 的 SRAM 起始地址为 0x20000000
​​RW_Data(可读写数据)​​:已初始化的全局变量和静态变量
​​ZI_Data(零初始化数据)​​:未初始化的全局变量和静态变量(默认填充为0)
​​Stack_Size​​:启动文件中定义的栈大小(您设置为 0x400,即 1KB)
二、现象解释
在您的案例中,​​SP 初始值为 0x20000900​​ 的具体计算逻辑如下:

​​默认内存布局​​:
SRAM 起始地址:0x20000000
程序中的全局变量(RW+ZI)总大小:假设为 0x900 字节
栈大小(Stack_Size):0x400 字节(1KB)
则 SP 初始值 = 0x20000000 + 0x900 + 0x400 = 0x20000D00
但实际观察到的 SP 值为 0x20000900,这表明 ​​RW+ZI 实际仅占用 0x500 字节​​(0x20000900 - 0x20000000 - 0x400 = 0x500)。
​​堆栈生长方向​​:
STM32 的栈是 ​​向下生长​​ 的,SP 初始值指向栈顶(高地址),栈底为 SP - Stack_Size。因此:
栈顶地址:0x20000900
栈底地址:0x20000900 - 0x400 = 0x20000500
栈空间范围:0x20000500 ~ 0x20000900。

根据.map文件分析,可以得出以下关于栈溢出问题的结论:

一、栈空间配置分析
​​栈大小设置​​
在startup_stm32f40_41xxx.o中明确显示:
STACK 0x20000500 0x00000400 (1KB)
这与用户设置的0x400(1024字节)完全一致。
​​内存布局验证​​
栈的地址范围:0x20000500 ~ 0x20000900
堆的地址范围:0x20000300 ~ 0x20000500(512字节)
全局变量(ZI + RW Data):总占用2304字节(RW_IRAM1区域)。
二、栈溢出风险判断
​​栈使用量估算​​
根据Image component sizes,ZI Data(零初始化数据)为2268字节,RW Data为36字节,总占用2304字节。
​​关键点​​:ZI Data包含全局变量和静态变量,而栈和堆的地址范围与ZI/RW区域相邻。如果函数调用链中的局部变量过多,可能导致栈指针(SP)超出0x20000900,覆盖其他内存区域。
​​调试现象关联​​
用户提到“变量放在函数内部时需要多次运行才成功”,这与栈溢出的典型现象(随机性崩溃、HardFault)高度吻合。局部变量存储在栈中,若超过0x400限制,会破坏中断向量表或关键数据,导致程序异常。
​​溢出检测方法​​
​​静态分析​​:检查函数调用层级和局部变量总大小。例如,若某函数定义了char buffer[1024],直接占满栈空间,必然溢出。
​​动态验证​​:在Keil调试器中观察SP寄存器值是否超出0x20000900,或通过Memory窗口查看栈内存是否被意外改写。

记录遇到的坑

1、函数指针一定不能跑飞,加上保护,防止程序跑飞
2、局部变量或者局部数据,尤其局部大数组一定要static化,或者直接全局化,防止堆栈溢出
3、利用三元运算符替代if逻辑,程序看起来简洁优雅
4、巧用#ifndef HAL_STATUS_T_DEFINED
#define HAL_STATUS_T_DEFINED
命令来解决重复定义

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

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

相关文章

软件架构评估两大法:ATAM 和 SAAM 的对比与实践

架构权衡分析方法(ATAM)和软件架构分析方法(SAAM)是软件架构评估领域中非常重要的两种方法,以下为你详细介绍: 一、架构权衡分析方法(ATAM) 1.背景与起源:ATAM 是由卡耐…

Python爬虫-爬取全球股市涨跌幅和涨跌额数据

前言 本文是该专栏的第52篇,后面会持续分享python爬虫干货知识,记得关注。 本文中,笔者将基于Python爬虫,实现批量采集全球股市行情(亚洲,美洲,欧非,其他等)的各股市“涨跌幅”以及“涨跌额”数据。 具体实现思路和详细逻辑,笔者将在正文结合完整代码进行详细介绍。…

电流互感器的两相星形接线的建模与仿真

微♥“电击小子程高兴的MATLAB小屋”获取巨额优惠 1.模型简介 本仿真模型基于MATLAB/Simulink(版本MATLAB 2016Rb)软件。建议采用matlab2016 Rb及以上版本打开。(若需要其他版本可联系代为转换) 2.仿真模型 3.仿真结果 3.1一次…

详解 kotlin 相对 Java 特有的关键字及使用

文章目录 1. val 和 var2. fun3. when4. is 和 !is5. lateinit6. by7. reified8. companion 本文首发地址:https://h89.cn/archives/366.html 最新更新地址:https://gitee.com/chenjim/chenjimblog Kotlin 在兼容Java的基础上,引入了许多特有…

国标GB28181视频平台EasyCVR如何搭建汽车修理厂远程视频网络监控方案

一、背景分析 近年我国汽车保有量持续攀升,与之相伴的汽车保养维修需求也逐渐提高。随着社会经济的发展,消费者对汽车维修服务质量的要求越来越高,这使得汽车维修店的安全防范与人员管理问题面临着巨大挑战。 多数汽车维修店分布分散&#…

linux RCU技术

RCU(Read-Copy-Update)是Linux内核中的一种同步机制,用于在多核处理器环境中实现无锁读取和延迟更新。Linux RCU(Read-Copy-Update)技术通过一种高效的同步机制来处理并发冲突,确保在多核环境中读者和写者对…

【笔记ing】AI大模型-02开发环境搭建

按实验需求合理选用实例规格,一般:模型开发阶段:使用最低算力2U8GB CPU。训练或推理阶段:切换至GPU规格,用完及时关闭算力环境,且切回最低算力规格。 每次实验结束手动关闭实例。使用ModelArts公有云资源。…

Python——numpy测试题目

题目: 生成一个2行3列随机整数二维数组a使用Numpy方法对(1)中数组a进行整体求积使用Numpy方法对(1)中数组a进行求每列最大值索引定义一个NumPy一维数组 b,元素为 1 到 10 的整数获取(4&#x…

系分论文《论面向服务开发方法在设备租赁行业的应用》

系统分析师论文系列 【摘要】 2022年5月,我司承接某工程机械租赁企业"智能租赁运营管理平台"建设项目,我作为系统分析师主导系统架构设计。该项目需整合8大类2000余台设备资产,覆盖全国15个区域运营中心与300家代理商,实…

Unity UI中的Pixels Per Unit

Pixels Per Unit在图片导入到Unity的时候,将图片格式设置为Sprite的情况下会出现,其意思是精灵中的多少像素对应世界中的一个单位,默认是100 1. 对于在世界坐标中 在世界坐标中,一般对于Sprite的应用是Sprite Renderer组件 使…

Boost Graph Library (BGL) 介绍与使用示例

Boost Graph Library (BGL) 介绍与使用示例 Boost Graph Library (BGL) 是 Boost 库中用于图论计算的模块,提供了处理图数据结构的通用接口和多种图算法实现。 BGL 主要特性 提供多种图表示方式:邻接表、邻接矩阵等包含常用图算法:DFS、BF…

opencv(C++)操作图像像素

文章目录 添加噪点的案例图像像素值1、访问图像属性2、像素访问方法 at灰度图像彩色图像 3、OpenCV 的向量类型4、 图像传递方式 The cv::Mat_ 类1、作用及优点2、使用 cv::Mat_ 简化像素访问 用指针扫描图像背景算法案例原理1. 图像数据存储的基本结构2、行填充(Pa…

Python实现贪吃蛇一

贪吃蛇是一款经典的小游戏,最近尝试用Python实现它。先做一个基础版本实现以下目标: 1、做一个按钮,控制游戏开始 2、按Q键退出游戏 3、右上角显示一个记分牌 4、随机生成一个食物,蛇吃到食物后长度加一,得10分 5、蛇碰…

《AI大模型应知应会100篇》第13篇:大模型评测标准:如何判断一个模型的优劣

第13篇:大模型评测标准:如何判断一个模型的优劣 摘要 近年来,大语言模型(LLMs)在自然语言处理、代码生成、多模态任务等领域取得了显著进展。然而,随着模型数量和规模的增长,如何科学评估这些模…

工会考试重点内容有哪些:核心考点与备考指南

工会考试重点内容总结:核心考点与备考指南 工会考试主要考察考生对工会法律法规、职能职责、实务操作等内容的掌握程度,适用于企事业单位工会干部、社会化工会工作者等岗位的选拔。本文梳理工会考试的核心考点,帮助考生高效备考。 一、工会…

Verilog学习-1.模块的结构

module aoi(a,b,c,d,f);/*模块名为aoi,端口列表a、b、c、d、f*/ input a,b,c,d;/*模块的输入端口为a,b,c,d*/ output f;;/*模块的输出端口为f*/ wire a,b,c,d,f;/*定义信号的数据类型*/ assign f~((a&b)|(~(c&d)));/*逻辑功能描述*/ endmoduleveirlog hdl 程…

MySQL数据库备份与恢复详解

在数据库管理中,数据的备份与恢复是至关重要的一环。对于MySQL数据库,定期备份不仅能防止数据丢失,还能在发生故障时快速恢复数据库。本文将详细介绍MySQL数据库的备份与恢复方法,覆盖所有常用备份和恢复方式,帮助大家…

FFMPEG和opencv的编译

首先 sudo apt-get update -qq && sudo apt-get -y install autoconf automake build-essential cmake git-core libass-dev libfreetype6-dev libgnutls28-dev libmp3lame-dev libsdl2-dev libtool libva-dev libvdpau-dev libvorbis-de…

华为机试—最大最小路

题目 对于给定的无向无根树&#xff0c;第 i 个节点上有一个权值 wi​ 。我们定义一条简单路径是好的&#xff0c;当且仅当&#xff1a;路径上的点的点权最小值小于等于 a &#xff0c;路径上的点的点权最大值大于等于 b 。 保证给定的 a<b&#xff0c;你需要计算有多少条简…

spring cloud微服务开发中声明式服务调用详解及主流框架/解决方案对比

声明式服务调用详解 1. 核心概念 定义&#xff1a;通过配置或注解声明服务调用逻辑&#xff0c;而非手动编写客户端代码&#xff0c;提升开发效率与可维护性。核心特性&#xff1a; 解耦&#xff1a;调用逻辑与业务代码分离内置容错&#xff1a;熔断、超时、重试等动态发现&am…