快看:谈谈嵌入式C语言踩内存问题

面包芯语   2023-06-19 14:29:47

踩内存,通过字面理解即可。本来是操作这一块内存,因为设计失误操作到了相邻内存,篡改了相邻内存的数据。


(资料图片)

踩内存,轻则导致功能异常,重则导致程序崩溃死机。

内存,粗略地分:

存储于相同存储区的变量才有互踩内存的可能。

一、静态存储区踩内存

下面,分享一个之前在实际项目中遇到的问题。

在Linux中,一个进程默认可以打开的文件数为1024个,fd的范围为0~1023。

项目中使用了串口,串口fd为static全局变量,某次这个fd突然变为一个超范围得值,显然被踩了。

出问题的代码如:

floatarr[5];intcount=8;for(size_ti=0;i

操作同属于静态存储区的arr数组出现了数组越界操作,踩了后面几个连续变量,fd也踩了。

实际中,纯靠log打印调试很难定位fd的相邻变量,需要花比较多的时间。

在Linux中,这个问题我们可以通过生成生成map文件来查看,在CMakeLists.txt中生成map文件的代码如:

set(CMAKE_EXE_LINKER_FLAGS "-Wl,-Map=output.map")  # 生成map文件set(CMAKE_C_FLAGS "-fdata-sections")               # 把static变量地址输出到map文件set(CMAKE_CXX_FLAGS "-fdata-sections")

二、动态存储区踩内存

动态堆内存踩内存典型例子:malloc与strcpy搭配使用不当导致缓冲区溢出。

#include#include#include#includeintmain(void){char*str="hello";intstr_len=strlen(str);///<此时str_len=5printf("str_len=%d\n",str_len);///<申请5字节的堆内存char*ptr=(char*)malloc(str_len);if(NULL==ptr){printf("mallocerror\n");exit(EXIT_FAILURE);}///<定义一个指针p_a指向ptr向后偏移5字节的地址,并在这个地址里写入整数20char*p_a=ptr+5;*p_a=20;printf("*p_a=%d\n",*p_a);///<拷贝字符串str到ptr指向的地址strcpy(ptr,str);///<打印结果:a指向的地方被踩了printf("ptr=%s\n",ptr);printf("*p_a=%d\n",*p_a);///<释放对应内存if(ptr){free(ptr);ptr=NULL;}return0;}

运行结果:

显然,经过strcpy操作之后,数据a的值被篡改了。

原因:忽略了strcpy操作会把字符串结束符一同拷贝到目的缓冲区。

如果相邻的空间里没有存放其它业务数据,那么踩了也不会出现问题,如果正好存放了重要数据,这时候可能会出现大bug,而且可能是偶现的,不好复现定位。

针对这种情况,我们可以借助一些工具来定位问题,比如:

valgrind的简单使用可阅读往期笔记:工具 | Valgrind仿真调试工具的使用

当然,我们也可以在我们的代码里进行一些尝试。针对这类问题,分享一个检测思路:

我们在申请内存时,在申请内存的前后增加两块标识区(红区),里面写入固定数据。申请、释放内存的时候去检测这两块标识区有没有被破坏(检测操作堆内存时是否踩到高压红区)。

为了能定位到后面的标识区,在增加一块len区用来存储实际申请的空间的长度。

此处,我们定义:

1、自定义申请内存函数

除了数据存储区之外,多申请12个字节。自定义申请内存的函数自然是要兼容malloc的使用方法。malloc原型:

void*malloc(size_t__size);

自定义申请内存的函数:

void*Malloc(size_t__size);

返回值自然要返回数据存储区的地址。具体实现:

#defineBEFORE_RED_AREA_LEN(4)///<前红区长度#defineAFTER_RED_AREA_LEN(4)///<后红区长度#defineLEN_AREA_LEN(4)///<长度区长度#defineBEFORE_RED_AREA_DATA(0x11223344u)///<前红区数据#defineAFTER_RED_AREA_DATA(0x55667788u)///<后红区数据void*Malloc(size_t__size){///<申请内存:4 + 4 +__size + 4void*ptr=malloc(BEFORE_RED_AREA_LEN+AFTER_RED_AREA_LEN+__size+LEN_AREA_LEN);if(NULL==ptr){printf("[%s]mallocerror\n",__FUNCTION__);returnNULL;}///<往前红区地址写入固定值*((unsignedint*)(ptr))=BEFORE_RED_AREA_DATA;///<往长度区地址写入长度*((unsignedint*)(ptr+BEFORE_RED_AREA_LEN))=__size;///<往后红区地址写入固定值*((unsignedint*)(ptr+BEFORE_RED_AREA_LEN+LEN_AREA_LEN+__size))=AFTER_RED_AREA_DATA;///<返回数据区地址void*data_area_ptr=(ptr+BEFORE_RED_AREA_LEN+LEN_AREA_LEN);returndata_area_ptr;}

2、自定义检测内存函数

申请完内存并往内存里写入数据后,检测本该写入到数据存储区的数据有没有写到红区。这种内存检测方法我们是用在开发调试阶段的,所以检测内存,我们可以使用断言,一旦触发断言,直接终止程序报错。

检测前后红区里的数据有没有被踩:

voidCheckMem(void*ptr,size_t__size){void*data_area_ptr=ptr;///<检测是否踩了前红区printf("[%s]before_red_area_data=0x%x\n",__FUNCTION__,*((unsignedint*)(data_area_ptr-LEN_AREA_LEN-BEFORE_RED_AREA_LEN)));assert(*((unsignedint*)(data_area_ptr-LEN_AREA_LEN-BEFORE_RED_AREA_LEN))==BEFORE_RED_AREA_DATA);///<检测是否踩了长度区printf("[%s]len_area_data=0x%x\n",__FUNCTION__,*((unsignedint*)(data_area_ptr-LEN_AREA_LEN)));assert(*((unsignedint*)(data_area_ptr-LEN_AREA_LEN))==__size);///<检测是否踩了后红区printf("[%s]after_red_area_data=0x%x\n",__FUNCTION__,*((unsignedint*)(data_area_ptr+__size)));assert(*((unsignedint*)(data_area_ptr+__size))==AFTER_RED_AREA_DATA);}

3、自定义释放内存函数

要释放所有前面申请内存。释放前同样要进行检测:

voidFree(void*ptr){void*all_area_ptr=ptr-LEN_AREA_LEN-BEFORE_RED_AREA_LEN;///<检测是否踩了前红区printf("[%s]before_red_area_data=0x%x\n",__FUNCTION__,*((unsignedint*)(all_area_ptr)));assert(*((unsignedint*)(all_area_ptr))==BEFORE_RED_AREA_DATA);///<读取长度区内容size_t__size=*((unsignedint*)(all_area_ptr+BEFORE_RED_AREA_LEN));///<检测是否踩了后红区printf("[%s]before_red_area_data=0x%x\n",__FUNCTION__,*((unsignedint*)(all_area_ptr+BEFORE_RED_AREA_LEN+LEN_AREA_LEN+__size)));assert(*((unsignedint*)(all_area_ptr+BEFORE_RED_AREA_LEN+LEN_AREA_LEN+__size))==AFTER_RED_AREA_DATA);///<释放所有区域内存free(all_area_ptr);}

我们使用这种方法检测上面的 malloc与strcpy搭配使用不当导致缓冲区溢出的例子:

测试代码:

//公众号:嵌入式大杂烩#include#include#include#include#include#defineBEFORE_RED_AREA_LEN(4)///<前红区长度#defineAFTER_RED_AREA_LEN(4)///<后红区长度#defineLEN_AREA_LEN(4)///<长度区长度#defineBEFORE_RED_AREA_DATA(0x11223344u)///<前红区数据#defineAFTER_RED_AREA_DATA(0x55667788u)///<后红区数据void*Malloc(size_t__size){///<申请内存:4 + 4 +__size + 4void*ptr=malloc(BEFORE_RED_AREA_LEN+AFTER_RED_AREA_LEN+__size+LEN_AREA_LEN);if(NULL==ptr){printf("[%s]mallocerror\n",__FUNCTION__);returnNULL;}///<往前红区地址写入固定值*((unsignedint*)(ptr))=BEFORE_RED_AREA_DATA;///<往长度区地址写入长度*((unsignedint*)(ptr+BEFORE_RED_AREA_LEN))=__size;///<往后红区地址写入固定值*((unsignedint*)(ptr+BEFORE_RED_AREA_LEN+LEN_AREA_LEN+__size))=AFTER_RED_AREA_DATA;///<返回数据区地址void*data_area_ptr=(ptr+BEFORE_RED_AREA_LEN+LEN_AREA_LEN);returndata_area_ptr;}voidCheckMem(void*ptr,size_t__size){void*data_area_ptr=ptr;///<检测是否踩了前红区printf("[%s]before_red_area_data=0x%x\n",__FUNCTION__,*((unsignedint*)(data_area_ptr-LEN_AREA_LEN-BEFORE_RED_AREA_LEN)));assert(*((unsignedint*)(data_area_ptr-LEN_AREA_LEN-BEFORE_RED_AREA_LEN))==BEFORE_RED_AREA_DATA);///<检测是否踩了长度区printf("[%s]len_area_data=0x%x\n",__FUNCTION__,*((unsignedint*)(data_area_ptr-LEN_AREA_LEN)));assert(*((unsignedint*)(data_area_ptr-LEN_AREA_LEN))==__size);///<检测是否踩了后红区printf("[%s]after_red_area_data=0x%x\n",__FUNCTION__,*((unsignedint*)(data_area_ptr+__size)));assert(*((unsignedint*)(data_area_ptr+__size))==AFTER_RED_AREA_DATA);}voidFree(void*ptr){void*all_area_ptr=ptr-LEN_AREA_LEN-BEFORE_RED_AREA_LEN;///<检测是否踩了前红区printf("[%s]before_red_area_data=0x%x\n",__FUNCTION__,*((unsignedint*)(all_area_ptr)));assert(*((unsignedint*)(all_area_ptr))==BEFORE_RED_AREA_DATA);///<读取长度区内容size_t__size=*((unsignedint*)(all_area_ptr+BEFORE_RED_AREA_LEN));///<检测是否踩了后红区printf("[%s]before_red_area_data=0x%x\n",__FUNCTION__,*((unsignedint*)(all_area_ptr+BEFORE_RED_AREA_LEN+LEN_AREA_LEN+__size)));assert(*((unsignedint*)(all_area_ptr+BEFORE_RED_AREA_LEN+LEN_AREA_LEN+__size))==AFTER_RED_AREA_DATA);///<释放所有区域内存free(all_area_ptr);}intmain(void){char*str="hello";intstr_len=strlen(str);///<此时str_len=5printf("str_len=%d\n",str_len);///<申请5字节的堆内存char*ptr=(char*)Malloc(str_len);///<自定义的Mallocif(NULL==ptr){printf("mallocerror\n");exit(EXIT_FAILURE);}///<定义一个指针p_a指向ptr向后偏移5字节的地址,并在这个地址里写入整数20char*p_a=ptr+5;*p_a=20;printf("*p_a=%d\n",*p_a);///<拷贝字符串str到ptr指向的地址strcpy(ptr,str);///<操作完堆内存之后,要检测写入操作有没有踩到红区CheckMem(ptr,str_len);///<打印结果:a指向的地方被踩了printf("ptr=%s\n",ptr);printf("*p_a=%d\n",*p_a);///<释放对应内存if(ptr){Free(ptr);ptr=NULL;}return0;}

没有踩内存的情况:

相关资料:

热文榜单