`

VC异常:Free Heap block xxxxxxxx modified at xxxxxxxx after it was freed(转)

 
阅读更多
Free Heap block xxxxxxxx modified at xxxxxxxx after it was freed

api任务socketwindowsdelete测试
方法一:如果你是C++程序员,如果你写过一个很复杂的程序,如果你经常碰到莫名其妙的崩溃问题。那么你就有可能遭遇了野指针。如果你比较细心,注意了Debug output输出窗口的话,那么你就有可能注意到这样一行提示:
HEAP:   Free   Heap   block   xxxxxxxx modified   at   xxxxxxxx after   it   was   freed
网络上关于这个问题提问的人不少,但是真正给的出答案的却少之又少。在网上搜索了几天,终于发现了这个问题的解决之道。下面我来介绍一下。

GFlags是windows debug tools 工具包下的一个工具,在Windows 2000的Resource Kit中也可以找得到。用来设置一些调试属性,总体上分为3个级别System, Kernel, 和 Image File。
我们设置好Path环境变量,将其指向Debug tools工具的目录下。
输入如下命令行:
gflags /p App.exe /full
或者 pageheap app.exe /full
该命令行会在注册表里设置一些调试参数,使内存在使用的时候加入了保护机制,所以一旦内存写越界,或者发生野指针的问题,都会导致一个中断。由此,你就可以确定问题到底出在哪里了。
方法二:
多线程与内存(heap)
关于多线程中的内存问题,必需足够地重视,否则,程序总是会出现莫名其妙的错误。如果幻想某些情况“应该”不会出现,或者是认为某些步骤是按照正常的顺序下来的,那就错误大大地,因为有一个简单的事实,就是你没有遵守事实,想和CPU对着干!
xx SDK的报错退出问题,从去年直到现在都没有得到有效的解决,近段时间,被频繁地爆露出来,是到了不得不直面的时候了,所以,前两周,去了xx N次,直到现在,还不能确定问题是否真地解决了
SDK的现象:
在自己的测试demo上没有问题,而在平台上就有问题
后来的测试证明,不是demo没有问题,而是原来的demo只是在跑自己测试的设备,其数量少,环境不真实,实际的情况无法得到有效的测试,所以,demo不会有任何问题
直接运行程序,就是出现调试的那种错误,在平台的vc debug模式下,出现的是触发了用户断点的错误。在SDK的vc debug模式下,爆露了错误的本质:
HEAP[pingtai.exe]: HEAP: Free Heap block 2be3000 modified at 2be3200 after it was freed
这种错误过去从没有遇到过,经查,表明是因为堆被破坏而产生错误。种种迹象表明,是因为某个内存被删除之后,其内容又被修改了,这样就是破坏了堆,就出现了这种错误,模拟程序如下:
void fun1()
{
char *p = new char[128];
delete[] p;
strcpy( p, "abcdefghijklmnopqrstuvwxyz" );
}
在我模拟的时候,其情况如下:
1. 如果我strcpy()的字节少于13个字节,则不会报任何错误。此情况并不绝对
2. 当fun1()返回时,并不出错,当程序退出时,报错
3. 在fun1()返回后,再调用其它任何函数,即会报错
为方便后面的叙述,把错误的描述如下:
HEAP[<exe>]: HEAP: Free Heap block <addr1> modified at <addr2> after it was freed
对模拟程序的报错进行分析,得出如下结论:
对内存<addr1>进行了删除,然后,又修改了内存<addr2>处的内容,其中,<addr2>是被包含在<addr1>中的,形式如下:
|------------------------------|
| <addr1> | <addr2>           |
|------------------------------|
在模拟程序中,<addr1> = p,<addr2> >= p 且 <addr2> < p + 128
在真实的内存中,<addr1>并不是等于p,而是会比p小,其不表示程序代码中的任何一个内存,而是在new时,用于保存p的内存而产生的内存指针,该指针由windows保存和维护(注:个人认为,通过计算,应该是可以得到p的地址),实际的模式如下:
|------------------------------|
| <addr1> | p                 |
|------------------------------|
<addr1>是windows占用的内存,而p是程序占用的内存
既然内存的操作已经明确,而且也知道了如何产生了这种错误,那么就要根据<addr2>来找是程序中哪个地方出现了这种情况,采用倒推 的方式,即首先根据<addr2>找到是哪个内存被删除后又重新进行了附值(或者是其中的某处被重新附值),然后再根据情况来使删除和附值不 要冲突
程序的顺序应该没有问题,因为都是删除后就不再进行附值操作,或者是其它任何使用的可能了,所以,从需要分配内存(主要是char*类型的内存分 配)变量开始找起。把所有new出来的char*指针地址及其长度打印出来,从上万行的打印信息中一个一个查找,却发现没有<addr2>
这就奇怪了,难道是程序的执行顺序出现了问题?不可能吧
把所有new出来的类地址及其内存范围进行打印,再一行一行地仔细找,汗......
竟然是CAccept类出现了这种情况!难道其删除之后,还有被使用的情况发生?根据以上内存的分析,肯定是有这种使用情况存在,否则....,找!
因为CAccept的类使用较少,找起来也比较容易,主要就在两个地方使用,一个是完成端口的死循环中,一个是任务检查的死循环中,肯定是这两个地方的使用有冲突。
完成端口的工作模式:
while( true )
{
if ( !getcompleteio() )
break;
if ( isaccept() )
{
CAccept *pacc = getaccept(); // 从列表中获取
// 使用pacc,其中有附值操作
... ...
// 删除pacc
SAFE_DELETE(pacc)
}
else
{
// 数据收发及socket端口的其它消息处理
}
}
任务检查的工作模式:
while ( true )
{
wait(5000); // 等待5秒钟,开始任务检查
lock();
while ( not_end_accept_list )
{
CAccept *pacc = nextaccept();
if ( pacc->tooLongTime )
SAFE_DELETE(pacc) // 删除 pacc
}
unlock();
// 连接检查
...
}
对accept的操作子函数(比如createaccept()、getaccept()、deleteaccept()等),都是被放入临界区中 的,这里仔细检查发现,完成端口中对CAccept的使用没有被放入临界区中,虽然其getaccept()中有临界区的操作,虽然任务检查中对 accept列表的操作也是在临界区中进行的,那么,问题肯定就是出现在了这里。为了验证,对任务检查处的accept删除和在完成端口中对accept 的使用进行信息打印,发现,当完成端口中还没有对其获取的CAccept使用完时,已经被任务检查的循环删除了!
这种情况在正常情况很难出现,为 了使它容易出现,对临界区的操作加了延时,即人为使每个操作都等待较长的时间(原来是没有任何等待的),比如100毫秒,或者50毫秒等,这样,可以使很 多的操作都被挂起,在设备的连接达到一定多的时候,这种挂起的操作会越集越多,采用这种方法,可以使一些问题较容易地复现出来
找到问题了,解决起 来应该是比较容易的了(有些可是棘手的,比如完成端口中else分支里的socket消息的处理),只需要在完成端口中把对CAccept的使用也放入相 应的临界区中即可,修改完毕,测试,再也没有CAccept的堆问题了,至此,该部分的问题已“完美”地被解决了,那个痛快啊---------,就别提 了... ...
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
还有两个问题,一个是关于上层对SDK的调用问题,一个是SDK内部的问题。
这两个问题,都和以上的问题的本质一样,就是删除了后还在使用!
1. 上层对SDK的调用
SDK 已经通知了上层设备断开,但是,上层在得到该消息之后,竟然还在调用相应的接口。这本身不成问题,因为上层调用SDK的时候,SDK会从相关的设备列表中 获取相应的连接,然后拿这个连接对设备进行操作,如果是该设备已经断开,那自然就会从相应的列表中删除掉,那么在上层调用的API中获取连接时,肯定是获 取失败,SDK在判断后,自然直接返回失败。没错!你说得太对了,就是这样。...... 停!你想,仔细地想一想,然后再往下看
================================
在 SDK中,不知道上层对SDK的调用和消息接收,是在一个线程中还是两个线程中,那么,SDK无从知道上层对SDK的消息处理和API调用的先后顺序,有 一种情况,应该是比较容易理解的,当SDK向上层进行了很多的消息通知,上层可能是会把这个消息放入消息队列中,然后一个一个地慢慢处理,其处理的某一个 消息,也许是10秒钟之前的消息(有点夸张),如果是这样,而且如果刚好这个消息就是连接断开的消息,那么在其处理该消息之前的3秒钟,用户要对该设备下 发命令,用户看到的结果就是,该设备在线,即下发命令失败,而且显示的错误消息是设备不在线!这样,程序也不会出错,因为7秒了,太久了,SDK应该是已 经把这个连接从连接列表中删除了。
但不幸的是,并不总是这种情况,而是有一个糟糕的情况可能会出现。当API函数中获取到相应的连接后,正在向设 备下发命令时,连接列表被任务检查中的连接检查循环删除了(比如有1分钟都没有收到任何数据),这时,如果下发命令的函数中是读取,而不进行任何的附值操 作,则出现的错误,应该是内存非法读的异常,否则,出现的错误,应该是和上面一样的堆错误。这种错误,也许解决起来比较容易,比如在使用时,也放入临界 区,这样当然可行,只是当有100个API时,你就必需得进行100次临界区的操作,这不算重要,更重要的是,由于你在最外层增加了临界区的操作,那么在 SDK的内部,必需要小心地保证临界区的操作不会产生冲突,否则,就会发生程序无反应的情况,需要使用任务管理器来结束的后果。如果其中有附值操作,这也 许是唯一的一种解决方法,但如果是没有附值操作,则可能会比较简单,比如,我只在那时出现了这个错误的API外层,增加了 try...catch()...,这样,如果执行的中间其实例被删除了,则该API直接返回失败。具体的解决方案,可以根据实际情况的不同,进行不同的 处理,这里,只是阐明我自己的想法而已
2. SDK内部的另外一个问题
和CAccept完全一样,只是其复杂度相当大,由于其几乎涉及到程序的所有地方,所以,解决起来相当棘手,而且,由于其和要上层通讯,则一不小心,就会使临界区冲突(除了上层的代码之外,SDK中的代码都被放入临界区):
[收到socket消息] --> [通知上层] --> [上层调用SDK API] --> [API调用SDK内部的函数]
这个过程中,因为SDK通知上层后,需要等到上层返回,才进行下一步处理,上层需要调用API的返回后自己才返回,而API调用内部函数时候,可能需要先进入临界区才能操作,如此便出现了临界区的冲突。
所以,在处理收到的socket消息时,被放入临界区是比较危险的做法,所以,这里如果要找到一个好的解决方案,比较棘手
由于时间问题,暂只浅谈之
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics