代码测试之内存越界

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

   内存越界是我们软件开发中经常遇到的一个问题。不经意间的复制常常导致很严重的后果。经常使用memset、memmove、strcpy、strncpy、strcat、sprintf的朋友肯定对此印象深刻,下面就是我个人在开发中实际遇到的一个开发问题,颇具典型。

#define MAX_SET_STR_LENGTH  50
#define MAX_GET_STR_LENGTH 100
int* process(char* pMem, int size)
{
	char localMemory[MAX_SET_STR_LENGTH] = {0};
	int* pData = NULL;
	/*  code process */
	memset(localMemory, 1, MAX_GET_STR_LENGTH);
	memmove(pMem, localMemory, MAX_GET_STR_LENGTH);
	return pData;
}

这段代码看上去没有什么问题。我们本意是对localMemory进行赋值,然后拷贝到pMem指向的内存中去。其实问题就出在这一句memset的大小。根据localMemory初始化定义语句,我们可以看出localMemory其实最初的申明大小只有MAX_SET_STR_LENGTH,但是我们赋值的时候,却设置成了MAX_GET_STR_LENGTH。之所以会犯这样的错误,主要是因为MAX_GET_STR_LENGTH和MAX_SET_STR_LENGTH极其相似。这段代码编译后,产生的后果是非常严重的,不断冲垮了堆栈信息,还把返回的int*设置成了非法值。

    那么有没有什么好的办法来处理这样一个问题?我们可以换一个方向来看。首先我们查看,在软件中存在的数据类型主要有哪些?无非就是全局数据、堆数据、栈临时数据。搞清楚了需要控制的数据之后,我们应该怎么对这些数据进行监控呢,一个简单有效的办法就是把memset这些函数替换成我们自己的函数,在这些函数中我们严格对指针的复制、拷贝进行判断和监督。

    (1)事实上,一般来说malloc的数据是不需要我们监督的,因为内存分配的时候,通常库函数会比我们要求的size多分配几个字节,这样在free的时候就可以判断内存的开头和结尾处有没有指针溢出。朋友们可以试一下下面这段代码。

void heap_memory_leak()
{
	char* pMem = (char*)malloc(100);
	pMem[-1] = 100;
	pMem[100] = 100;
	free(pMem);
}
 pMem[-1] = 100是堆左溢出, pMem[100]是堆右溢出。

    

    (2)堆全局数据和栈临时数据进行处理时,我们利用memset初始化记录全局指针或者是堆栈临时指针

    a) 首先对memset处理,添加下面一句宏语句

    #define memset(param, value, size)      MEMORY_SET_PROCESS(__FUNCTION__, __LINE__, param, value, size)


    b) 定义内存节点结构

typedef struct _MEMORY_NODE
{
	char functionName[64];
	int line;
	void* pAddress;
	int size;
	struct _MEMORY_NODE* next;

}MEMORY_NODE;

其中functionName记录了函数名称,line记录文件行数, pAddress记录了指针地址, size指向了pAddress指向的内存大小,next指向下一个结构节点。


     c)记录内存节点属性

    在MEMORY_SET_PROCESS处理过程中,不仅需要调用memset函数,还需要对当前内存节点进行记录和保存。可以通过使用单链表节点的方法进行记录。但是如果发现pAddress指向的内存是malloc时候分配过的,此时就不需要记录了,因为堆内存指针溢出的问题lib库已经帮我们解决了。


    d)改造原有内存指针操作函数

    比如对memmove等函数进行改造,不失去一般性,我们就以memmove作为范例。

    添加宏语句 #define memmove(dst, src, size)        MEMMOVE_PROCESS(dst, src, size)

void MEMMOVE_PROCESS(void* dst, const void* src, int size)
{
	MEMORY_NODE* pMemNode = check_node_exist(dst);
	if(NULL == pMemNode) return;

	assert(dst >= (pMemNode->pAddress));
	assert(((char*)dst + size) <= ((char*)pMemNode->pAddress + pMemNode->size));
        memmove(dst, src, size);
	return;
}

  e)下面就是内存节点的删除工作。

    我们知道函数是需要反复使用堆栈的。不同时间相同的堆栈地址对应的是完全不同的指针内容,这就要求我们在函数返回的时候对内存地址进行清理,把内存节点从对应的链表删除。

    我们知道在函数运行后,ebp和esp之间的内存就是通常意义上临时变量的生存空间,所以下面的一段宏就可以记录函数的内存空间。

#ifdef MEMORY_LEAK_TEST
#define FUNCTION_LOCAL_SPACE_RECORD()\
{\
	int* functionBpRecord = 0;\
	int*  functionSpRecord = 0;\
}
#else
#define FUNCTION_LOCAL_SPACE_RECORD()
#endif

#ifdef MEMORY_LEAK_TEST
#define FUNCTION_LEAVE_PROCESS()\
{\
__asm { mov functionBpRecord, bp\
    mov functionSpRecord, sp}\
	FREE_MEMORY_NODE(functionBpRecord, functionSpRecord)\
}
#else
#define FUNCTION_LEAVE_PROCESS()
#endif

这两段宏代码,需要插在函数的起始位置和结束的位置,这样在函数结束的时候就可以根据ebp和esp删除堆栈空间中的所有内存,方便了堆栈的重复使用。如果是全局内存,因为函数的变化不会导致地址的变化,所以没有必要进行全局内存节点的处理。

内存溢出检查流程总结:

    (1)对memset进行重新设计,记录除了malloc指针外的一切内存;

    (2)对memmove, strcpy, strncpy,strcat,sprintf等全部函数进行重新设计,因为我们需要对他们的指针运行范围进行判断;

    (3)在函数的开头和结尾位置添加宏处理。函数运行返回前进行节点清除。


C语言中动态内存分配错误的排查:全网最有效最简单的解决方案 排查和解决C语言中的动态内存分配错误需要综合运用代码审查、调试工具和日志输出等方法。通过代码审查,我们可以检查内存分配和释放的逻辑是否正确;使用调试工具如Valgrind和AddressSanitizer可以快速定位内存泄漏、野指针和重复释放等错误;添加日志输出可以帮助我们跟踪内存的使用情况。在解决问题时,要确保及时释放内存、避免重复释放和野指针,并使用边界检查函数防止内存越界访问。希望本文提供的方法能帮助大家更高效地排查和解决C语言中的动态内存分配错误,提高程序的稳定性和可靠性。 阅读详情

相关推荐

内存越界定位】mprotect

mprotect()函数可以修改调用进程内存页的保护属性,如果调用进程尝试以违反保护属性的方式访问该内存,则内核会发出一个SIGSEGV信号给该进程。 函数介绍 头文件:#include <sys/mman.h> 函数定义: int mprotect(void *addr, size_t len, int prot); 入参: addr:内存地址要求是一个内存页的首地址,简而言之为页大小(一般是 4KB == 4096字节)整数倍。 len:被修改保护属性区域的长度,页大小整数倍。修改

叮咚的博客 1233

memset函数清空越界问题

memset 的函数说明如下: NAME memset - fill memory with a constant byte SYNOPSIS #include <string.h> void *memset(void *s, int c, size_t n); DESCRIPTION The memset() functi...

时有限 2023

memset函数越界分析

一、memset()函数原型     将buffer所指向的某一块内存中的前count个字节的内容全部设置为c指定的ASCII值 extern void *memset(void *buffer, int c, int count) { const unsigned char uc = c; unsigned char *su; for(

Leonban的博客 3435

memset内存越界导致函数堆栈崩溃的例子

以下代码在vs2005下测试struct st{ int n; DWORD dwID[12]; st() {  memset(dwID, 0, sizeof(st)); //内存越界了,dwID应该为this就不会越界  n = 0; }} void test(){ st tt; printf("run"); return; //内存越界导致堆栈发生错误} 

zdleek的专栏 5785

常见的C语言内存错误及对策

一、指针没有指向一块合法的内存 定义了指针变量,但是没有为指针分配内存,即指针没有指向一块合法的内存。浅显的例子就不举了,这里举几个比较隐蔽的例子。 1、结构体成员指针未初始化 struct student {    char *name;    int score; }stu,*pstu; intmain() {    strcpy(stu.name,"Jimy");

bytxl的专栏 1070

php 数组 没有越界,C++_浅析C语言编程中的数组越界问题,因为C语言不检查数组越界,而 - phpStudy...

浅析C语言编程中的数组越界问题因为C语言不检查数组越界,而数组又是我们经常用的数据结构之一,所以程序中经常会遇到数组越界的情况,并且后果轻者读写数据不对,重者程序crash。下面我们来分析一下数组越界的情况:1) 堆中的数组越界因为堆是我们自己分配的,如果越界,那么会把堆中其他空间的数据给写掉,或读取了其他空间的数据,这样就会导致其他变量的数据变得不对,如果是一个指针的话,那么有可能会引起cras...

weixin_36371053的博客 315

linux c 代码测试内存越界内存泄露

内存越界是我们软件开发中经常遇到的一个问题。不经意间的复制常常导致很严重的后果。经常使用memset、memmove、strcpy、strncpy、strcat、sprintf的朋友肯定对此印象深刻,下面就是我个人在开发中实际遇到的一个开发问题,颇具典型。 [cpp] view plain copy #define MAX_SET_STR_LEN

benpaobagzb的博客 2806

一段用于监测C++内存越界代码 , VC++,GCC编译测试通过

一段用于监测C++内存越界代码 , VC++,GCC编译测试通过。

ADF1230的专栏 3448

记录一次linux内核内存越界导致系统重启的排查工作。

根据内核发生oops的log及每次发生crash的位置都不一样,断定是内存越界导致内存被破坏,进而导致重启。因此,开启内核slub debug功能进行定位,经过一晚上的压力测试,得到如下log: 通过反汇编及代码分析,发现是gether_connect及gether_disconnect俩个函数重复释放同一块内存导致。 修复后跑压力测试,发现另外一个问题,分析如下 ...

cf125313的专栏 1246

使用valgrind检查内存越界

下载 http://valgrind.org/downloads/current.html 解压 编译 ./configure make sudo make install 测试 #include &lt;stdlib.h&gt; #include &lt;stdio.h&gt; #include &lt;string.h&gt; int main(int argc, char...

柳鲲鹏 1326

【OpenCL】kernel函数 内存越界导致错误

使用 Y=0, U=128, V=128 测试下来处理后的nv12 图片 Y分量正常,UV 分量值异常,UV值排布如下:处理后的 nv12 图像如下,并不是预期的 纯黑图像。

lijian2017的博客 372

c语言二维数组for循环赋值_C语言数组越界导致无限循环

昨天晚上,我正在给之前获奖的同学发奖品,正在进行中的时候,看到黄兄推送了一篇文章,里面讲到数组越界会导致无限循环。关于抽奖看这篇文章《Linux进程管理数据结构》,以后也会不定期抽奖,今年多赚点钱,过年的时候,希望给大家抽一个大奖,喜欢的读者使劲给转发点赞,关注,未来是你们的,大奖也是你们的,感谢支持,不喜欢的请轻拍。过年抽奖我还是很期待的,每年公司的年会抽奖的时候,我总是能小中一把,有一年我还中...

weixin_31022203的博客 3214
上一篇: 代码测试之内存泄露
下一篇: 编写通用Makefile
ymangu
博客等级 码龄16年 202粉丝 249原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值