文章目录
- 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
 命令来解决重复定义