热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【进阶】堆栈溢出,也就这么回事!

1、聊一聊虽然没有第13个月,但和大家有着无数个明年,就用一首《飞鸟和蝉》告别2020年的一切吧!2、正文部分1先说几句前些日子bug交流

1、聊一聊

     虽然没有第13个月,但和大家有着无数个明年,就用一首《飞鸟和蝉》告别2020年的一切吧!

2、正文部分

1

先说几句 

前些日子bug交流群里的小哥调试了一个堆栈溢出的bug,动不动数据就被篡改了,应该也是搞得焦头烂额,头皮发麻!当时bug菌看了下抛出了自己的一些调试经验,一般这样的问题80%是越界和堆栈溢出造成的,没想到还真是堆栈溢出。

所以对于一些问题的处理不仅仅是经验的积累,还需要多多交流!堆栈溢出问题bug菌和他算是“老朋友”了,所以非常想让相关文章跟大家见面,没想到这几天事情颇多,每天回家都没有太多的精力去更文,但是作为一名有态度的号主还是要坚持为大家带来点东西!

2

理一理堆栈溢出

1

堆栈名称 

认识堆栈溢出首先我们要知道什么是" 堆栈 " ? 

堆栈从名字上理解似乎是堆和栈的结合,而我们在数据结构中知道堆和栈是两种不同的数据结构,但这里的堆栈指的仅仅是栈,从英文名我们就可以知道 : 堆栈(stack)和堆(heap) , 至于把stack叫做堆栈是有一定的历史和翻译原因的,bug菌就不追溯了。

对于栈,在bug菌的往期文章中也有提及,其实就是一种先进后出的数据结构;而在CPU层面有着堆栈寄存器,push和pop堆栈操作指令等等都是用于操作栈区的。

在C语言环境中栈是为了保存现场的信息,当程序需要执行函数调用,任务切换等等都会把相应的数据push到栈中,一旦回到原来函数和任务又会pop弹出之前的数据继续往下执行。

但栈是有具体大小的,一旦入栈的数据过多,就会导致罪恶的"堆栈溢出"问题。

2

图解堆栈溢出 

来我们首先看一个函数:

void RecvData(void);
{int Cnt;int Buff[6];......do something...
}

这样的代码打死我也不敢相信会有什么大问题,然而一名经验老道、饱经bug洗礼的嵌入式程序员会自然而然的考虑是否有堆栈溢出的风险,如下图所示:

上图就不区分堆栈增长方向了,仅仅只是表述堆栈溢出现象,由于SP_end以外的内容未知,一般都由编译器分配决定,如果编译器把重要数据分配到此区域,一旦程序访问到Buff[3]往下的数据便会导致数据篡改,从而程序发生一些奇怪的行为,甚至奔溃。

那么很多朋友就会想,直接给这个任务或者系统分配一个1024或者4096个字节的堆栈,这总不会造成堆栈溢出了吧!我只想说:"你太秀了!"。

2

如何分配堆栈空间大小

1

堆栈内容 

盲目的分配过大的堆栈空间,无非就是对资源的浪费。如果你的项目能够让你这样任性,那你们产品成本估算就真是个形式。所以合理的分配堆栈大小是非常重要的,首先我们得看看堆栈中主要放些什么 ?

  • 局部变量的分配。

  • 函数调用嵌套的返回地址等等数据的push,这个需要根据具体的CPU进行函数调用约定来进行分析。

  • 函数的参数,因为有时候编译器为了增加执行效率会把相关参数放在寄存器中传递,但是毕竟这样的寄存器有限,过多的参数还是会通过堆栈来传递。

  • 当我们触发中断CPU一般会自动把相应的信息压入堆栈中,从而保存中断现场。

  • 对于RTOS进行任务切换、中断等过程中一般系统仅自动保存了部分寄存器等信息,而为了全面的保存好现场,还需要手动的压入一些其他的信息,比如stm32中的FPU相关寄存器信息等。

2

计算最大堆栈空间难题 

有了前面堆栈中放了些啥的分析,要确定堆栈的空间大小自然而然的就会想到把一个个加起来算堆栈最大暂用情况,算出该值以后预留一定的空间就再合适不过了。

现在对于比较强大的IDE,比如keil和IAR,都可以提供计算堆栈占用最大的情况,而对于我们采用函数指针这样的间接调用函数的方式或者是C嵌入式汇编等等,那IDE也无能为力。

更加可怕的是使用printf这种可变参数的函数,其堆栈的占用情况是根据参数的多少而动态变化的,其并不那么容易确定。

当然还有最让bug菌难以忘记的情况 : 递归 , 递归就是反复的函数调用,那么一系列的返回现场数据都会压入栈中,堆栈占用情况也是未知的,所以在嵌入式中使用递归一定要限制递归的深度,防止堆栈溢出。

3

确定堆栈大小的好办法 

既然正面计算堆栈占用最糟糕的情形如此麻烦,那我们从侧面出击,那就是我们常用的检测堆栈使用峰值法,实时的采集和输出堆栈的使用信息,我们根据堆栈的最大值*1.5倍的样子,基本上就可以把堆栈大小确定下来。

像目前的RTOS(如ucos、freertos等)都提供了对应的堆栈信息输出API,比如ucos中的OSTaskStkChk函数 : 

typedef struct os_stk_data 
{INT32U  OSFree;/* Number of free entries on the stack*/INT32U  OSUsed;/* Number of entries used on the stack                          */
} OS_STK_DATA;
......
INT8U OSTaskStkChk (INT8U prio,OS_STK_DATA *p_stk_data);

通过调用该函数获得已经使用的和没有使用的堆栈大小,便可以获得堆栈的使用情况,如:

堆栈占用率 = (OSUsed/(OSUsed + OSFree)) * 100%

从而可以将该参数输出作为我们评估每个任务分配的堆栈是否合适,当然你需要让程序运行足够长的时间和尽量多的情况,从而获得最差的情况,再考虑预留>20%的空间,最终重新调整每个堆栈大小到合适状态。

3、结束语

    好了,今天堆栈溢出就分享到这里,这也是2020年bug菌分享的最后一个bug ! 记得给bug菌点个赞! 

推荐好文  点击蓝色字体即可跳转

☞  jlink调试器中你不知道的"那些事"?(ARM篇)

☞【MCU】可怕,别人把我MCU固件给反汇编了!(逆向)

☞ 【硬壳】C程序里面嵌点"机器码"玩一玩"(小知识揭露大道理)

☞ 【原创bug】如何看出应聘公司研发实力?



推荐阅读
  • 阿里Treebased Deep Match(TDM) 学习笔记及技术发展回顾
    本文介绍了阿里Treebased Deep Match(TDM)的学习笔记,同时回顾了工业界技术发展的几代演进。从基于统计的启发式规则方法到基于内积模型的向量检索方法,再到引入复杂深度学习模型的下一代匹配技术。文章详细解释了基于统计的启发式规则方法和基于内积模型的向量检索方法的原理和应用,并介绍了TDM的背景和优势。最后,文章提到了向量距离和基于向量聚类的索引结构对于加速匹配效率的作用。本文对于理解TDM的学习过程和了解匹配技术的发展具有重要意义。 ... [详细]
  • IhaveconfiguredanactionforaremotenotificationwhenitarrivestomyiOsapp.Iwanttwodiff ... [详细]
  • C语言注释工具及快捷键,删除C语言注释工具的实现思路
    本文介绍了C语言中注释的两种方式以及注释的作用,提供了删除C语言注释的工具实现思路,并分享了C语言中注释的快捷键操作方法。 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • OO第一单元自白:简单多项式导函数的设计与bug分析
    本文介绍了作者在学习OO的第一次作业中所遇到的问题及其解决方案。作者通过建立Multinomial和Monomial两个类来实现多项式和单项式,并通过append方法将单项式组合为多项式,并在此过程中合并同类项。作者还介绍了单项式和多项式的求导方法,并解释了如何利用正则表达式提取各个单项式并进行求导。同时,作者还对自己在输入合法性判断上的不足进行了bug分析,指出了自己在处理指数情况时出现的问题,并总结了被hack的原因。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文介绍了在Mac上安装Xamarin并使用Windows上的VS开发iOS app的方法,包括所需的安装环境和软件,以及使用Xamarin.iOS进行开发的步骤。通过这种方法,即使没有Mac或者安装苹果系统,程序员们也能轻松开发iOS app。 ... [详细]
  • 如何在跨函数中使用内存?
    本文介绍了在跨函数中使用内存的方法,包括使用指针变量、动态分配内存和静态分配内存的区别。通过示例代码说明了如何正确地在不同函数中使用内存,并提醒程序员在使用动态分配内存时要手动释放内存,以防止内存泄漏。 ... [详细]
  • 恶意软件分析的最佳编程语言及其应用
    本文介绍了学习恶意软件分析和逆向工程领域时最适合的编程语言,并重点讨论了Python的优点。Python是一种解释型、多用途的语言,具有可读性高、可快速开发、易于学习的特点。作者分享了在本地恶意软件分析中使用Python的经验,包括快速复制恶意软件组件以更好地理解其工作。此外,作者还提到了Python的跨平台优势,使得在不同操作系统上运行代码变得更加方便。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • 深入解析Linux下的I/O多路转接epoll技术
    本文深入解析了Linux下的I/O多路转接epoll技术,介绍了select和poll函数的问题,以及epoll函数的设计和优点。同时讲解了epoll函数的使用方法,包括epoll_create和epoll_ctl两个系统调用。 ... [详细]
author-avatar
man_2_瞳皆哥仔
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有