0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

数组越界的问题解析

鱼鹰谈单片机 来源:鱼鹰谈单片机 2023-04-17 09:15 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

数组越界问题大家在软件开发过程中应该都司空见惯了。如果你没见过,大概率是一个新手,工作经验不足,倒不是说你自己会生产这种 BUG,但有些同事却可能是 BUG 搬运工。

在鱼鹰五年的工作开发过程中,除了在北京刚毕业那会没遇到这种隐藏问题(碰到的都是自己生产的 BUG,不过自产自销,也还行),在深圳的这几家公司都遇到了数组越界的问题。

问题一

第一个问题是关于串口驱动导致的越界(最终结果是 hardfault),这个鱼鹰在以前的笔记中也反复强调了,因为这个问题差点导致自己熬了一个通宵,也是醉了(老代码的一个 bug)。

当然这个问题的解决和当时没有在线调试环境(当时的 PCB 板子通过串口烧录代码,没有调试接口,大坑)有很大关系,否则解决起来会快不少。

当然当时鱼鹰也没掌握这个方法《BUG 终结者,现场抓获!|颠覆认知》,否则出现问题时,这种小问题分分钟定位它。

所以当时解决这个问题,全靠玄学:运气。

否则这个问题不知道要蹂躏鱼鹰多少天。

问题二

这个问题在前东家遇到。当时的环境是 boot + app 形式。boot 代码也是跑了多年的老代码,从来没有出现过问题。

直到有一次版本升级,发现程序不能跳转到 app 正常运行(具体细节不记得了)。

当时有同事怀疑是我当时更新的 printf 打印函数有关系,因为当时的版本更新有这个改动。但鱼鹰对自己写的代码还是比较有自信的,并且我的 printf 改动和 app 跳转能有什么关系。

但怀疑到你头上了,同时鱼鹰也经常负责定位这类疑难杂症,刚好空闲,那就去瞧瞧看了,证明一下这不是你的问题。

因为问题 100% 复现,又掌握了那个现场抓获的技巧,很快就定位到是 boot 的一段代码申请的栈数组空间不足,导致被调用的函数使用这块空间时越界了。

类似下面这种:

func2(uint8_t*buff)
{
i=5;
buff[i]= 0;
}
fun1()
{
uint8_t buff[4];
func2(buff);
}

当然实际代码肯定不可能这么简单,i 的值是变化的,不可能一眼看出。

这个问题也是导致 hardfault(退出 func2 时,破坏了返回地址)。

看到没有,有时候二分法(二分查找有问题的代码提交)查找问题也不是那么可靠,因为问题可能根本不在提交的的代码中。

而下面的问题三也证明了这一点(当然不是说二分法没用,只是不能全靠它作为你的结果判断)。

问题三

这个问题是现东家遇到的问题。

自己开发的一个新模块,当合并到主分支时,发现开机必定 hardfault,这让我百思不得其解。自己新加入的代码,都没用到数组,怎么会hardfault。

我的第一反应就是,不是我的锅。

但问题出现在我合并的过程,也只能由我定位了。还好经验丰富,一天时间+加班几个小时,总算是定位到了。

这个问题定位有几个难点:

1、使用 C++

2、使用O2 优化,而使用 O0 的方式问题不复现了(最蛋疼)

3、使用了 map 库函数

因此在复现率很高的情况下,还是花了这么多时间。

但好在顺利解决了(这么高的复现率,定位root case只是时间问题,信心也是 100%)。

简单来说,是以前的一段代码在使用 sprintf 时(这里强烈建议用 snprintf),导致栈缓存空间越界,然后导致上一层函数的局部变量被篡改,而这个局部变量会导致 map 传入的参数有问题,最终导致了 hardfault 。

可以看到,虽然根因在一个函数中,但最终出现问题却可能在另一个函数中。

就像犯罪现场,作案现场只有一个(root case),但可能案发现场并不是作案现场。

因此解决 bug 过程其实就是警察破案,通过蛛丝马迹找到第一作案现场,如此才能正确破案。

而这种代码在工程里面有好几处.....并且在合入我的代码之前,运行良好。所以,数组越界也不一定会 hardfault,就看你破坏的是啥了。

为什么?

大家很奇怪,为毛数据越界大部分情况下会 hardfault,有时却不会产生问题。只有思考到更深层的原因,你才能在 BUG 环绕中有所成长。

这个时候,就看你的基础扎实不扎实了。

这里来个简单示意函数(优化O0)

void func2()
{
inti= 0;
intbuff[4];
 
 buff[4] = 0;
}
voidfunc1()
{
intj=0;//假设该局部变量使用r4
func2();
}

栈空间如下(因为只有 4 个字,编译器可能 buff[4] 直接使用寄存器了,但为了简单说明,这里假设 buff 都使用了栈):

552128a6-dcbc-11ed-bfe3-dac502259ad0.png

从上图我们可以知道,进入 func2 函数时,先 push,离开时 pop。

局部变量 i 使用 r4 寄存器,但是栈空间 r4 保存的是 func1 使用的j的值。

因此,当我们数组越界时(一般越界是往高地址,因为数组索引一般是自加),很容易破坏上一个函数的栈空间,在这里破坏的是 j 的值。如果 j 很重要,那么很可能会导致 hardfault 或者其它问题(能引起 hardfault 反而是好事)。

并且这里面还有重要的返回地址 lr,如果这个值被越界破坏,那么大概率都是hardfault,因为你企图跳转到一个不存在的地址执行。

数组越界是一个很危险的 BUG,能观察到现象还好,万一是默默破坏而不能很快被察觉,成为一个隐藏 BUG,那才是最危险的。

那为啥问题三增加别的代码会触发这个 BUG ,修改优化等级又会消失呢?

这和编译器有关系,有可能你的代码导致有问题的代码使用了不同的内存布局,从而越界篡改的位置变成了重要的内存,因此出现了现象,而优化等级对栈内存布局更是有很大影响。

另外本篇笔记介绍的局部缓存数组的越界,实际上还有全局数组的越界,那种问题相对简单许多,看 map 文件即可。

因此,操作数组时,一定要时时刻刻检测数组的索引的大小,以防越界。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 寄存器
    +关注

    关注

    31

    文章

    5588

    浏览量

    129035
  • C++语言
    +关注

    关注

    0

    文章

    147

    浏览量

    7583
  • 数组越界
    +关注

    关注

    0

    文章

    2

    浏览量

    5592
  • printf函数
    +关注

    关注

    0

    文章

    31

    浏览量

    6225

原文标题:数组越界是一颗隐形炸弹

文章出处:【微信号:emOsprey,微信公众号:鱼鹰谈单片机】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    数组的初体验

    程序中也需要容器,只不过该容器有点特殊,它在程序中是一块连续的,大小固定并且里面的数据类型一致的内存空间,它还有个好听的名字叫数组。可以将数组理解为大小固定,所放物品为同类的一个购物袋,在该购 物
    发表于 11-25 08:06

    二维数组介绍

    大家不要认为二维数组在内存中就是按行、列这样二维存储的,实际上,不管二维、三维数组… 都是编译器的语法糖。 存储上和一维数组没有本质区别,举个例子: int array[3][3
    发表于 11-25 07:42

    上位机报错2033问题处理方案

    、解决方案三个维度,系统性地分析该问题的处理流程。 一、错误机理深度解析 报错2033的本质是内存地址访问冲突,通常发生在以下场景: 1. 指针越界操作:当程序试图通过指针访问未被分配的内存区域时(如数组
    的头像 发表于 11-13 17:40 504次阅读

    精彩回顾 | 《器件选型EMC问题解析与交流》直播圆满结束!

    赛盛技术于11月12日18:30举办了《器件选型EMC问题解析与交流》专题直播。感谢每一位观众的热情参与与支持,让我们的直播活动得以圆满落幕。在此,小编将带大家一起回顾本次直播中的精彩亮点。让我们
    的头像 发表于 11-13 15:13 262次阅读
    精彩回顾 | 《器件选型EMC问<b class='flag-5'>题解析</b>与交流》直播圆满结束!

    精彩回顾 | 《电源EMC问题解析与交流》直播圆满结束!

    赛盛技术于10月29日18:30举办了《电源EMC问题解析与交流》专题直播。感谢每一位观众的热情参与与支持,让我们的直播活动得以圆满落幕。在此,小编将带大家一起回顾本次直播中的精彩亮点。让我们一同
    的头像 发表于 10-30 17:24 892次阅读
    精彩回顾 | 《电源EMC问<b class='flag-5'>题解析</b>与交流》直播圆满结束!

    野外地物光谱仪的常见问题解析

    光谱仪的使用和功能提出了很多疑问。本文将深入解析用户在使用野外地物光谱仪时常见的5大问题,帮助用户更好地理解该技术,增强品牌信任度。 1. 什么是野外地物光谱仪? 野外地物光谱仪是一种用于收集地面物体反射光谱数据的设备,这些
    的头像 发表于 10-30 10:42 101次阅读

    rt_malloc_align函数内存越界问题怎么解决?

    字节用来保存真实申请的内存地址(0x2000001),而0x2000000并不是申请到的内存,会造成内存越界操作。
    发表于 09-22 08:30

    mqtt dns解析失败是为什么?

    解析域名的ip地址就能正常连上,而直接解析域名就不行,为什么呢
    发表于 09-16 06:38

    变频器与传动使用的常见问题解

    变频器与传动系统作为工业自动化领域的核心设备,其稳定运行直接影响生产效率和设备寿命。以下是针对实际应用中高频问题的系统性解答,结合技术原理与现场经验,为从业者提供实用参考。 一、电机过热问题解析
    的头像 发表于 06-10 07:35 430次阅读
    变频器与传动使用的常见问<b class='flag-5'>题解</b>答

    精彩回顾 | 《电磁兼容仿真技术与电源EMC问题解析》直播圆满结束!

    赛盛技术于4月22日19:00举办了《电磁兼容仿真技术与电源EMC问题解析》专题直播。感谢每一位观众的热情参与与支持,让我们的直播活动得以圆满落幕。在此,小编将带大家一起回顾本次直播中的精彩亮点
    的头像 发表于 04-23 11:24 711次阅读
    精彩回顾 | 《电磁兼容仿真技术与电源EMC问<b class='flag-5'>题解析</b>》直播圆满结束!

    stm32 DMA串口接收到数组数组元素顺序错乱怎么解决?

    配置DMA循环模式,使用HAL_UART_Receive_DMA(&huart1,buffer,4)函数将串口数据循环发送到4个元素的buffer数组内,上位机20ms发送一次
    发表于 03-12 08:02

    【电磁兼容技术案例分享】USB屏蔽电缆接头搭接导致的BCI问题解决案例

    【电磁兼容技术案例分享】USB屏蔽电缆接头搭接导致的BCI问题解决案例
    的头像 发表于 02-11 09:41 925次阅读
    【电磁兼容技术案例分享】USB屏蔽电缆接头搭接导致的BCI问<b class='flag-5'>题解</b>决案例

    EE-175: 仿真器与EZ-KIT Lite评估系统问题解决指南

    电子发烧友网站提供《EE-175: 仿真器与EZ-KIT Lite评估系统问题解决指南.pdf》资料免费下载
    发表于 01-08 14:41 0次下载
    EE-175: 仿真器与EZ-KIT Lite评估系统问<b class='flag-5'>题解</b>决指南

    数组的下标为什么可以是负数

    最近有同学发来这样一段代码,并提出一个问题,数组的下标为什么可以是负数?     #include int main(){ const char *s = "helloworld"; const
    的头像 发表于 12-20 11:18 851次阅读

    C2000常见问题解

    电子发烧友网站提供《C2000常见问题解答.pdf》资料免费下载
    发表于 12-06 16:04 0次下载
    C2000常见问<b class='flag-5'>题解</b>答