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

    文章

    5343

    浏览量

    120373
  • C++语言
    +关注

    关注

    0

    文章

    147

    浏览量

    6992
  • 数组越界
    +关注

    关注

    0

    文章

    2

    浏览量

    5527
  • printf函数
    +关注

    关注

    0

    文章

    31

    浏览量

    5893

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

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

收藏 人收藏

    评论

    相关推荐

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

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

    指针数组和二维数组有没有区别

    指针数组和二维数组有没有区别?比如这样的两个代码。 int main(){ char *s1[] = { "hello", "world", "total" }; char s2[][6
    的头像 发表于 11-24 11:12 152次阅读

    C语言数组应用计算机导论A第6讲:数组

    C语言数组应用计算机导论A第6讲:数组
    发表于 11-20 15:33 0次下载

    MLCC断裂问题解决方案

    MLCC断裂问题解决方案
    的头像 发表于 10-16 09:43 479次阅读

    越界智能监测摄像机:安全防护的新利器

    随着社会的快速发展,安全问题日益凸显,尤其是在公共场所和重要设施周边,如何有效防范非法入侵和潜在威胁成为了亟待解决的难题。越界智能监测摄像机应运而生,以其先进的威廉希尔官方网站 和强大的功能,为安全防护提供了全新
    的头像 发表于 10-09 09:57 224次阅读
    <b class='flag-5'>越界</b>智能监测摄像机:安全防护的新利器

    TFPxxx常见问题解

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

    越界智能监测摄像机

    随着科技的不断发展,越界智能监测摄像机在安防领域得到了广泛应用。这种智能监测设备结合了图像识别威廉希尔官方网站 和人工智能算法,旨在实现对区域内物体的越界行为进行监测和警示。通过高清晰度的摄像头捕捉到场景中的物体
    的头像 发表于 09-05 11:06 254次阅读
    <b class='flag-5'>越界</b>智能监测摄像机

    labview字符串数组转化为数值数组

    在LabVIEW中,将字符串数组转换为数值数组是一项常见的任务,尤其是在处理数据采集、信号处理或用户输入时。 1. 理解LabVIEW的数据类型 在开始之前,了解LabVIEW中的数据类型是非
    的头像 发表于 09-04 17:47 2348次阅读

    嵌入式中零长度数组基本操作方法

    C语言零长度数组,听起来可能有点奇怪,因为它没有分配内存空间,无法存储数据。但实际上,零长度数组在Linux内核中随处可见。 零长度数组的定义 首先,我们要明白什么是零长度数组。简单来
    的头像 发表于 05-11 08:49 941次阅读
    嵌入式中零长度<b class='flag-5'>数组</b>基本操作方法

    深入探索KUKA KRL中的数组应用

    如果 CHAR 类型数组的所有数组元素都拥有相同的字符串,则不必单独初始化每个数组元素。忽略右侧的数组下标。(对于一维数组下标,不写下标。)
    的头像 发表于 04-18 10:37 1248次阅读
    深入探索KUKA KRL中的<b class='flag-5'>数组</b>应用

    鸿蒙TypeScript入门学习第11天【Array(数组)】

    数组对象是使用单独的变量名来存储一系列的值。 数组非常常用。
    的头像 发表于 04-09 14:38 1161次阅读
    鸿蒙TypeScript入门学习第11天【Array(<b class='flag-5'>数组</b>)】

    数组和链表在内存中的区别 数组和链表的优缺点

    数组和链表在内存中的区别 数组和链表的优缺点  数组和链表是常见的数据结构,用于组织和存储数据。它们在内存中的存储方式以及优缺点方面存在一些显著的差异。本文将详细探讨这些差异以及它们的优缺点。 1.
    的头像 发表于 02-21 11:30 1041次阅读

    数组和链表有何区别

    数组和链表的区别,这个问题,不仅面试中经常遇到,考研的同学也得掌握才行。
    的头像 发表于 02-19 15:33 512次阅读
    <b class='flag-5'>数组</b>和链表有何区别

    PHP中数组的使用方法!

    PHP中数组的使用方法! PHP是一种广泛使用的网络编程语言,它的数组功能非常强大且灵活。数组是一种数据结构,它允许我们在单个变量中存储多个值。 在本篇文章中,我将详细解释PHP数组
    的头像 发表于 01-12 15:11 551次阅读

    PCB压合问题解决方法

    PCB压合问题解决方法
    的头像 发表于 01-05 10:32 1071次阅读