ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

7.20 OLED的OLED_Refresh函数栈溢出踩内存

7.20 OLED的OLED_Refresh函数栈溢出踩内存 void OLEDS_Refresh(void) { u8 i, n; //这里可能会导致栈溢出 换成静态变量static static uint8_t buf[129]; /* buf[0]控制码 0x40, buf[1..128]像素数据 */ for (i 0; i 8; i) { /* 设置页地址和列地址 */ OLEDS_WR_Byte(0xB0 i, OLEDS_CMD); /* 页地址 0~7 */ OLEDS_WR_Byte(0x00, OLEDS_CMD); /* 列地址低 4 位 */ OLEDS_WR_Byte(0x10, OLEDS_CMD); /* 列地址高 4 位 */ /* 组装整页数据 */ buf[0] 0x40; /* 控制码数据 */ for (n 0; n 128; n) { buf[n 1] OLEDS_GRAM[n][i]; } /* 一次 I2C 事务发完 129 字节 */ OLEDS_SendBulk(buf, 129); } }其中!!!!很重要里面的static uint8_t buf[129]; /* buf[0]控制码 0x40, buf[1..128]像素数据 */这个数组一开始没加静态变量 导致栈溢出,内存被踩,导致数据被写到别的地方,出现野数据,导致程序运行不了一、解释:1.什么是RAM?(1)RAM定义Random Access Memory随机存取存储器,是计算机和单片机用来临时存放正在运行的程序和数据的存储器。2特点掉电丢失一旦断电RAM中的所有内容立刻消失。所以他只能存临时数据不能做永久存储。读写数据速度快比Flash硬盘快的多CPU直接从RAM中读取指令读写变量。可随机访问任意地址都能直接读写不需要像磁带一样顺序找。3与ROM/Flash的区别在MSPM0G3507中Flash存放代码RAM存放全局变量堆栈临时计算数据。4RAM的重要性堆栈函数调用局部变量中断现场都占用RAM。中断嵌套或者大数组比如OLED_Refresh中的buf[129]和环形缓冲区中的buffer[128]。堆动态内存分配malloc用的空间嵌入式比较少用。优化若RAM不够变量会被挤占内存被踩程序就会飞。合理安排数组大小和全局/局部变量的适用很关键。二、内存布局MSPM0 RAM 从地到高大致是0x20000000 ┌──────────────┐│ .data / .bss │ ← 全局变量track_state, OLEDS_GRAM, servo_x...│ (静态区) │├──────────────┤│ 堆 (heap) │ ← malloc 用│ ↓ ││ ││ ↑ ││ 栈 (stack) │ ← 局部变量、函数调用返回地址0x2000xxxx └──────────────┘栈从RAM顶部向下长全局变量从底部向上放所以如果栈用太多就会踩进全局变量区三、在上面例子中加了static与不加static修饰数组对比1.不加static修饰时void OLEDS_Refresh(void) { uint8_t buf[129]; // ← 129 字节全压在栈上 ... }129字节全压在栈上在该项目里main() ~32 字节→ Global_KeyDispatch() ~16 字节→ OLEDS_ShowString() × 4次 ~64 字节 (累计 framebuffer 写入)→ OLEDS_Refresh() ~20 字节 (局部变量 i, n) buf[129] ~129 字节 ← 致命一击→ OLEDS_SendBulk() ~16 字节→ I2C_Start/SendByte/WaitAck ~32 字节─────────────────────────────────────────总计 ~309 字节当309自己的栈怼到track_state所在的位置时OLEDS_Refresh往buf[0..128]写数据实际写到了track_state 的内存地址上所以调试时候出现的状态012却出现值4就是这样来的因为可能buf某个位置的像素数据恰好是0x042.加了static修饰时候void OLEDS_Refresh(void) { static uint8_t buf[129]; // ← 搬到 .bss 静态区不占栈 ... }用static修饰成静态变量搬到bss静态区不占栈栈用量309 - 129 180字节离track_state还远踩不到它的内存总结static变量只初始化一次存在静态区和全局变量放在一起它的地址是固定的不会和栈争空间四、为什么刚好踩到track_state的内存track_state在Global.c里面定义。链接器按.o文件顺序排列BSS段巧合的是track_state 离栈最近栈溢出的第一个受害者就是它五、更多发生内存被踩的原因1.常见原因1数组越界写入数组时索引超出定义范围覆盖了紧邻内存中的其他变量。2栈溢出局部变量太多或递归太深导致栈空间用尽会覆盖全局变量或堆上的数据。3指针错误野指针、未初始化指针、释放后还使用的指针写入了非预期的地址4中断与主循环竞争一个变量在中断和主循环中同时被修改但没有用volatile修饰或缺乏原子操作保护导致数据不一致看起来像被踩。5编译器优化或内存对齐问题结构体填充位域操作不当可能导致相邻字段被修改。6DMA冲突当DMA还在传输CPU就去读写了同一片内存数据可能损坏。2.内存被踩典型表现1程序运行一段时间后突然进入 Hard Fault 或其他异常。2某个全局变量的值莫名其妙地改变导致控制错乱。3死机、重启、数据错乱但断点调试时又似乎正常。3.如何排查和解决1静态分析仔细检查数组大小循环边界memcpy的长度。2可以在关键变量防止特殊数字定期检查是否被修改uint32_t guard 0xDEADBEEF; int my_data; uint32_t guard2 0xCAFEBABE; // 定时检查 guard 和 guard2 是否完好3增加MPU\堆栈保护MSPM0有存储器保护单元可以设置区域为只读来捕获非法写入。4查看MAP文件了解变量在内存中的布局判断谁可能会踩到谁。5在可疑代码段加断电数据观察点调试器可以设置在某个内存地址被写入时自动停止。6确保终端和主循环共享变量加volatile必要时在写操作时关中断。7保证DMA缓冲区不在CPU同时读写的时候使用或者使用DMA_Cmd暂停双重缓冲。
返回列表