软件编程低级错误:内存越界ppt课件_第1页
软件编程低级错误:内存越界ppt课件_第2页
软件编程低级错误:内存越界ppt课件_第3页
软件编程低级错误:内存越界ppt课件_第4页
软件编程低级错误:内存越界ppt课件_第5页
已阅读5页,还剩38页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

公司常见软件编程低级错误:内存越界,C语言软件编程规范工作组,前言,这套材料作为编程规范的辅助材料,帮助大家理解编程规范背后的原理。C和C+语言是我司的主流编程语言,然而C/C+具有很多强大的语言特性,从而导致C/C+非常复杂,使得代码更容易出现BUG、难以阅读和维护。业界知名的编程规范都对C/C+容易出现问题的语言特性进行管理。例如MISRA(汽车工业软件可靠性联合会)制定的1998版的MISRAC规范指出,一些在C看来可以接受,却存在隐患的地方有127处之多。2004版的MISRAC规范将针对C语言的规则增加到了141条。对于程序员来说,能工作的代码并不等于“好”代码。“好”代码的指标很多,包括可读性、可维护性、可移植性和可靠性等。出现网上问题的代码,大多数是不良编程习惯引起的。不遵守编程规范的代码,往往也是最不可靠的代码。本胶片收集了常见的内存越界案例,给出了相应的纠正措施。对应的编程规范:防止内存操作越界,常见的内存越界问题和解决措施建议,数组分配未考虑最大情况,导致空间不够,【问题描述】透明消息下发处理导致网上单板复位【问题定位】当消息发送失败后的异常处理流程中,存在局部数据越界,导致别的任务在使用错误的堆栈信息时,发生数据访问异常,而单板复位。UINT8aucErrMsg128=0;/*用于返回错误信息字符串*/ulRet=VOS_SendMsg(pMsgBlock);if(VOS_OK!=ulRet)VOS_sprintf(CHAR*)aucErrMsg,rnDBG_SimExternalMsgAct:VOS_SendMsgfail!ulRet(%u),ulSenderCpuId(0 x%x),ulSenderPid(%u),ulReceiverCpuId(0 x%x),ulReceiverPid(%u),ulRet,pMsgBlock-ulSenderCpuId,pMsgBlock-ulSenderPid,pMsgBlock-ulReceiverCpuId,pMsgBlock-ulReceiverPid);%u输出的十进制无符号整数,范围04294967295,长度111位不定,随着程序的运行,数值会越来越大,从1位数变成2位数,3位数,11位数等,导致内存越界。同样%x的长度最大可以达到8位。aucErrMsg的长度最大应该为:120(字符长度)+3*11(3个无符号整数长度%u)+2*8(2个十六进制整数长度%x)169,不是128.【纠正措施】将数组aucErrMsg大小定义扩大为256。【举一反三】数组的大小要考虑最大情况,避免数组分配空间不够。,数组分配未考虑最大情况,导致空间不够(续一),【问题描述】实时性能统计,可选指标达到最大,单击保存设置,造成网管实时性能后台重启【问题定位】代码的数组长度为256字节,当选择满20个指标时,字符串长度为276个,造成栈空间越位。【纠正措施】C语言:把字符串缓冲区改正1024个字节长度;C+:使用相应的类库std:string等。【举一反三】多大的缓冲区才是安全的?对C来说,要考虑到各种应用场合,特别是考虑到函数参数的边界条件,按最大的可能分配空间。能够程序计算的,尽量自动计算。对C+而言,不要使用C语言风格的数组、指针运算和内存管理原语操作实现数组,使用vector或者string,请把这种复杂的体力劳动交给类库吧!,数组分配未考虑最大情况,导致空间不够(续二),【问题描述】数据库刷新间隔设为值1073741823时,系统监控后台coredump,监控前台抛异常。【问题定位】使用itoa()将整型数转换为字符串时:charszTempShold10;itoa(usProcFrecy,szTempShold,10);szTempShold是以0结尾的字符数组,只能存储9个字符,而usProcFrecy的最大值可达到10位,导致符数组szTempShold越界。【纠正措施】将数组设置成12位【举一反三】按最大的可能分配空间,一个int(32位)在21474836472147483648之间,使用itoa函数安全的分配空间是12位。,使用危险函数操作字符串,【问题描述】发送短消息的时候进程COREDUMP【问题定位】使用了一个1024个字节的字符数组进行sprintf格式化操作,当告警发送数量比较大,写入日志的字符串很长,内存越界【纠正措施】将sprintf替换成安全函数snprintf,指定缓冲区大小,确保内存不会越界【举一反三】C语言提供的字符串库函数sprintf/vsprintf/strcpy/strcat/gets等非常危险,很容易导致内存越界,应该使用安全的字符串库函数snprintf/strncpy/strncat/fgets,指定操作的内存大小。对C语言,应该使用相应的类库如std:string,std:stringstream,boost:lexical_cast操作字符串,而不要直接调用C语言的字符串函数。,使用危险函数操作字符串(续1),【问题描述】代码飞检时发现如下代码:charpszInfoBuf250;sprintf(pszInfoBuf,”*File:%sLine:%d*”,_FILE_,_LINE_);【问题定位】“_FILE_”在预编译时,被编译时的目录名和源文件名代替,但目录和文件名的长度可变,很可能超出250个字节,导致内存越界【纠正措施】将sprintf替换成安全函数snprintf,指定缓冲区大小,确保内存不会越界snprintf(pszInfoBuf,INFOBUF_SIZE-1,”*File:%sLine:%d*”,_FILE_,_LINE_);pszInfoBufINFOBUF_SIZE-1=0;,使用危险函数操作字符串(续2),【问题描述】代码飞检时发现如下代码:charpszInfoBuf32;strcpy(pszInfoBuf,pMsg);【问题定位】定义的pszInfoBuf共32字节,pMsg是从网络上接收的数据包,可能超出32个字节,导致内存越界【纠正措施】将strcpy替换成安全函数strncpy,指定缓冲区大小,确保内存不会越界strncpy(pszInfoBuf,pMsg,INFOBUF_SIZE-1);pszInfoBufINFOBUF_SIZE-1=0;,使用memcpy/memset没有判断长度,【问题描述】局有用户投诉访问不了WAP业务,现场维护人员重启模块后业务恢复正常。【问题定位】对core文件分析,确认如下代码导致内存越界:memcpy(vpstResBody-pcData,wbxml,wbxml_len);其中vpstResBody-pcData分配的内存数为3270,wbxml_len是wbxml的长度,wbxml取决与用户输入的URL。由于没有判断长度,当页面内容中几个相对URL链接的页面,这里发生了越界。【纠正措施】增加长度保护:长度少于或等于vpstResBody-pcData的长度;【举一反三】使用memcpy/memset时一定要确保长度不要越界,使用memcpy/memset没有判断长度(续),【问题描述】代码飞检发现如下代码:charpDeviceID20;charpDefaultDeviceID80;/异常情况使用缺省设备IDmemcpy(pDeviceID,pDefaultDeviceID,sizeof(pDefaultDeviceID);【问题定位】pDefaultDeviceID的长度是80,pDeviceID长度是20,从而导致内存越界。【纠正措施】修改pDeviceID长度,和pDefaultDeviceID长度一致。由于是字符串拷贝,改用strncpy。#defineDEVICEID_LEN80charpDeviceIDDEVICEID_LEN;charpDefaultDeviceIDDEVICEID_LEN;strncpy(pDeviceID,pDefaultDeviceID,DEVICEID_LEN);pDeviceIDDEVICEID_LEN-1=0;【举一反三】使用memcpy/memset时一定要确保长度不要越界;注意memcpy/strncpy的区别,字符串没有考虑最后的0,【问题描述】在现场测试补丁版本时,用命令RMVVSBR删除V5用户,导致主、备FCCU复位。【问题定位】使用memcpy拷贝内存,通过strlen获得原始内存的长度,但传入的原始内存虽然是字符,但结尾并没有0,而strlen继续向后寻找,直到找到一个0,结果拷贝了很大一块内存,导致内存越界【纠正措施】函数调用时,除了传入原始内存的指针外,增加一个参数表明内存的长度【举一反三】有些产品在设计上将字符串作为一块内存进行处理,在函数之间传递UINT8*的指针,在传递过程中,很可能没有拷贝字符串结尾的0,所以在这种设计下必须禁止调用C语言提供的字符串函数,包括strlen/snprintf。这种指针不能作为字符串使用。,字符串没有考虑最后的0(续1),【问题描述】版本升级,并使用配置台修改代理服务器配置数据,发送配置;来话可以进入排队机,但无法接入话务台,平台倒换重启等措施无效。【问题定位】从版本3.2开始,cfgcprs.dll的解压缩接口输出的数据长度不包括末尾位置的0,配置台代码将其拷贝进系统数据区时,也没有添加0。通常情况下,系统数据区后面的内存全是0,但在极例外的异常情况下为非0,此时内存读越界,导致配置台把配置数据转换成字符串后出现了乱码,解析异常,丢失了VDN数据。【纠正措施】在拷贝配置数据时强制添加0【举一反三】产品处理配置数据的规则不明确,版本3.1将配置数据作为字符串处理,版本3.2中的cfgcprs.dll将配置数据作为一块内存处理,但有的模块继续作为字符串处理。必须将处理规则明确写作在设计文档中。,字符串没有考虑最后的0(续2),【问题描述】代码飞检时发现如下代码:ulNewSizeVOS_strlen(“rn”)+VOS_strlen(pRevData)+VOS_strlen(pszVlanInfo)+VOS_strlen(pszQinQinfo);pszDisInfo=VOS_Malloc(MID_BVLAN,ulNewSize);.VOS_strcat(pszDisInfo,pszQinQinfo);【问题定位】以ulNewSize为长度申请内存,但忘记字符串结尾必须有一个0,调用strcat时拷贝了字符串本身和最后的0,导致内存越界【纠正措施】计算ulNewSize时,增加1个字节,字符串没有考虑最后的0(续3),【问题描述】代码飞检时发现如下代码pszInfo=(CHAR*)VOS_Malloc(MID_CFM,ulSize);.pszInfoulSize=0;【问题定位】以ulSize为长度申请内存,作者最后想起来需要在字符串最后添加0,但边界值考虑不当。【纠正措施】分配内存时,增加1个字节;或将赋值语句修改为pszInfoulSize-1=0;,字符串没有考虑最后的0(续4),【问题描述】代码飞检时发现如下代码:pErrMsgBuffer=VOS_Malloc(MODULE_ID_MAPPERDRV,dwBufSize+1);strncpy(pErrMsgBuffer,strErrMsg,dwBufSize);【问题定位】当源字符串长度超出dwBufSize时,strncpy拷贝dwBufSize长度的字符,但目标字符串的结尾没有被赋值为0,以后引用字符串pErrMsgBuffer时可能出错【纠正措施】强制将最后一个字节赋值为0strncpy(pErrMsgBuffer,strErrMsg,dwBufSize)pErrMsgBufferdwBufSize=0;【举一反三】strncpy等安全函数,当拷贝字符串到达指定的长度时,不会在目标字符串结尾添加0,必须手工添加0。可以使用Dopra提供的改进的安全函数VOS_strncpy,自动在目标字符串结尾添加0。注意Dopra提供的VOS_strNcpy的实现与标准C库一致,不会自动添加0。两者的大小写不同(小写的n和大写的N),字符串没有考虑最后的0(注意事项),字符串必须以0结尾,所有的字符串函数都是基于这个假定实现的intstrlen(constchar*str)intlength=0;while(*str+)+length;return(length);如果*str是指向一串char的指针,但最后一个char不是0,则会内存越界。总结:在调用字符串函数时,一定要搞清楚是char*指针还是字符串,字符串函数只能针对字符串使用。使用strncpy代替strcpy时,必须手工添加结尾的0,可以在申请内存时,将最后一个字节置为0;也可以调用strncpy后,紧接着赋0。如果调用Dopra提供的VOS_strncpy函数,库函数会自动赋0,但要注意此时拷贝的字节数比标准C库的strncpy少了一个字节字符串与数组一样,下标从0开始。申请内存或对最后一个字节赋值时,注意检查边界值,指针加减操作时,没有考虑指针类型,【问题描述】小区负荷话统结果总是零【问题定位】代码中有如下语句:pMsg=pMsg+sizeof(tmp_msg);/*pMsg是structMsgCB*指针*/本意是将指针增加sizeof(tmp_msg)个字节,但实际上增加了sizeof(tmp_msg)*sizeof(MsgCB)个字节【纠正措施】修改代码为:pMsg=(MsgCB*)(char*)pMsg+sizeof(tmp_msg);【举一反三】指针加减操作时,1并不是增加一个字节,而是增加一个指针类型的长度,如果将指针作为无结构的内存块进行操作,必须先将其转化为char*类型,魔鬼数字:人工计算字符串长度出错,【问题描述】在诊断模式下查询sar连接情况,主mpu死机并被复位【问题定位】if(Number8K,导致内存写越界。【纠正措施】程序员3次修改代码,最开始将最大打印条数修改为100/90条,没有通过内部评审和测试。最后认真计算了每条消息的长度,打印6个整数,加上空格,最多为70个字符,(8192-120-100)/70113.9条,取110条,修改代码为if(Number=110),内部测试通过。程序员认为前面改为100/90条也是正确的,测试不通过的原因是测试环境有问题,代码没有错【问题重新定位】代码飞检人员查出以上修改的代码,存在隐患。因为%d的长度不定,随着程序的运行,每个整数的数值会越来越大,从1位数变成2位数,3位数,几十位数。如果6个整数都很大,并且持续110次,则110条信息的总长度会超过8K。这种情况发生的概率很小,所以有时能够通过测试,有时不能。【正确的纠正措施】见下页,魔鬼数字:人工计算字符串长度出错(续1),【正确的纠正措施】不再使用人工计算的魔鬼数字,自动计算是否达到8K的缓冲区限制(暂不考虑输出到缓冲区结尾时截断数据产生的负面影响)。修改代码如下:iCount=snprintf(CHAR*)pMsg+iPosition),(MSG_BUFF_LEN1)iPosition,nr%d%d0 x%08x0 x%08x%d%d,Vpi,Vci,SarVccIndex,Conntype,SegQueueId,RsmQueueId);/*如果iCount返回值=0,说明达到了缓冲区的结尾;对结尾赋0,然后跳出循环*/if(iCount=0)pMsgMSG_BUFF_LEN-1=0;break;iPosition+=iCount;【举一反三】计算的工作应该交给计算机完成,程序员不要做这类简单工作,魔鬼数字:人工计算字符串长度出错(续2),【问题描述】如下一段代码导致内存越界:pDeviceName=(CHAR*)VOS_Malloc(MID_VOS_VFS|SID_CHAR,VOS_strlen(pTemp)+1+6);(VOID)VOS_strcpy(pDeviceName,slot);(VOID)VOS_ltoa(lBoardNo,pDeviceName+VOS_strlen(slot);(VOID)VOS_strcat(pDeviceName,#);(VOID)VOS_strcat(pDeviceName,pTemp);【问题定位】pDeviceName申请的长度是“strlen(pTemp)+1+6”,写代码的人考虑到了pTemp后面结尾的0符号。除此之外多申请了6个字节用于加上一个路径头。lBoardNo是主控板槽位号,不是17就是18,路径头就可能是“slot17#”或者“slot18#”,路径头有7个字符,而不是6个字符。【纠正措施】见下页,魔鬼数字:人工计算字符串长度出错(续3),【纠正措施】不再使用人工计算的魔鬼数字,通过表达式自动计算路径头长度。修改代码如下:#defineMAX_PATH_LEN32charszPathMAX_PATH_LEN;snprintf(szPath,sizeof(szPath)-1,slot%d#,lBoardNo);szPathsizeof(szPath)-1=0;pDeviceName=(CHAR*)VOS_Malloc(MID_VOS_VFS|SID_CHAR,VOS_strlen(pTemp)+1+strlen(szPath);(VOID)VOS_strcpy(pDeviceName,szPath);(VOID)VOS_strcat(pDeviceName,pTemp);【举一反三】计算的工作应该交给计算机完成,程序员不要做这类简单工作,魔鬼数字:人工计算结构长度出错,【问题描述】连续向加载进程提交多个加载请求,加载进程异常退出【问题定位】代码如下:pbData=newBYTEiDataSize+APP_FRAME_HEAD_SIZE;pAppFrame=(CAppFrame*)pbData;pAppFrame应用帧类CAppFrame实例,占用内存字节数:iDataSize+APP_FRAME_HEAD_SIZE。其中宏定义APP_FRAME_HEAD_SIZE为帧头的数据结构长度,等于36,是人工计算出的结构长度。数据结构在内存占用的大小跟字节对齐方式有关。检查编译开关,为8bytes对齐。在该方式下,由于要按8字节对齐,将比人工计算的36多消耗4个字节,达到40字节,导致内存越界。【纠正措施】见下页,魔鬼数字:人工计算结构长度出错(续1),【纠正措施】使用sizeof运算符,不再使用宏定义。代码修改如下:pbData=newBYTEiDataSize+sizeof(CAppFrame);pAppFrame=(CAppFrame*)pbData;【举一反三】大家都知道,在程序中直接写作的数字是魔鬼数字,违反了编程规范。但使用宏定义简单替换这些数字,在很多情况下并没有消除魔鬼数字。除了使用宏表明数字的含义外,还应该从若干个基础数字自动运算出衍生数字。任何情况下都不要手工计算数字。,未考虑结构类型,内存分配不够,【问题描述】OMU运行过程中出现内存越界错误【问题定位】代码如下:pstNewWaitMsg=BIN_MemAlloc(m_pstBinConnectStatusulIndex.ulAllWaitNum*2);if(VOS_NULL_PTR=pstNewWaitMsg)returnBIN_MSGPROC_ALLOC_MEM_FAIL;BIN_MemZero(pstNewWaitMsg,sizeof(BIN_MSG_DATA)*m_pstBinConnectStatusulIndex.ulAllWaitNum*2);VOS_MemCpy(pstNewWaitMsg,m_pstBinConnectStatusulIndex.pstWaitMsg,sizeof(BIN_MSG_DATA)*m_pstBinConnectStatusulIndex.ulAllWaitNum);在内存分配时没有考虑数据结构类型,导致进行初始化的内存大小远远超过了实际申请的大小。【纠正措施】扩大实际需要申请的内存大小,确保进行初始化的内存和实际申请的大小一致,问题不再出现。pstNewWaitMsg=BIN_MemAlloc(sizeof(BIN_MSG_DATA)*m_pstBinConnectStatusulIndex.ulAllWaitNum*2);,数组下标未检查导致越界(自动计算的数组下标未检查),【问题描述】版本倒换,主控板一起来就出现指令异常,单板复位【问题定位】对数组下标没有保护,导致栈空间破坏,系统复位。代码如下:CHARsLogInfo1024;UCHARszTmp10;/*只分配了10个字节*/char*pTemp=sLogInfo;.while(*pTemp!=-)szTmpi=*pTemp;/*没有找到-,继续进行,导致堆栈被写坏*/pTemp+;i+;szTmpi=0;【纠正措施】增加判断:i等于8时,跳出循环【举一反三】对于while或者for循环里操作数组下标要做下标的保护,“小心驶得万年船”。,数组下标未检查导致越界(边界错误),【问题描述】代码飞检发现如下代码:ULONGAAA_Translate(char*pStr,intnLen)for(inti=0;i=nLen;i+)charcTemp=pStri;【问题定位】指针长度是nLen,由于for循环=,循环多执行一次,导致内存读越界:【纠正措施】将ucTos没有被赋值。程序员之所以在这个函数中遗漏了对ucTos的初始化,是因为很多其它函数对pCurrentCR进行了初始化,初始化的地方很多;大多数情况下,调用此函数之前就已经初始化了pCurrentCR里面的所有成员变量,但存在一种特殊情况,pCurrentCR-ucTos没有被以前的任何函数初始化。【纠正措施】在其它函数中增加对ucTos的初始化【举一反三】不要在多个地方初始化同一个变量,可能导致以后的修改出错,字节对齐出错,【问题描述】产品下发命令,导致主控板复位【问题定位】由于一个头文件里面字节对齐的pack标志太多,没有把结构定义放在正确的地方,本该一字节对接的结构变成了四字节对齐。【纠正措施】将结构改为1字节对齐【举一反三】头文件的声明应该统一规划,一个文件里面不要有多种字节对齐方式。,没有校验外部输入参数,【问题描述】R2入局收到超长号码,使CR表内字段被踩,导致FCCU单板复位【问题定位】使用17个字节的数组存储被叫号码,当接收号码超出17个字节时,内存写越界。【纠正措施】写入数组时进行长度检查【举一反三】在外部接口处,校验所有的输入数据,没有校验外部输入参数(续1),【问题描述】在启动

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论