| Function | test1!Ctest1Dlg::OnBnClickedButton1+114 |
| Allocation type | BSTR allocation(s) |
| Allocation Count | 5 allocation(s) |
| Allocation Size | 47.86 KBytes |
| Leak Probability | 43% |
首先,对于COM内存泄漏的问题,UMDH不是一个好的方案。原因有二:
a) UMDH的主要作用是处理系统堆,也就是通过HeapAlloc()得到的
内存的泄漏问题。COM显然是有自己的堆管理的,因为它支持Marshall和线程
内存Apartment模型,这个功能系统堆和CRT堆都没有提供。
b) 从实践上讲,UMDH的检查方式是通过比较两次快照来得到堆内存使用情况的,
如果没有确认一个大概的造成泄漏的代码区间,那么UMDH的检查无异于大海
捞针,工作效率不高。至少在我的实验里,UMDH没有抓到100次的泄漏。
第二,能够管用的工具:微软的DebugDiag,不过我自己没有成功抓到我的代码
里的leak。如果Sagasw方便的话,可否大概贴出一个步骤?
第三,API级别的方案是有的,不过很麻烦。对于特定于COM的内存调试,可以通
过注册一个实现了IMallocSpy的调试对象来做到。
http://msdn.microsoft.com/en-us/library/ms688508(VS.85).aspx
通过这个Object,我们可以在每一次实际分配内存的时候人为地添加一个cookie,主
要是通过IMallocSpy::PreAlloc()将需要分配内存的大小改大一点,比如8byte,然后在
IMallocSpy::PostAlloc()里可以操作那块多出来的内存,作为一个cookie来保存内存地
址之类的信息,以供最后比较。
关于IMallocSpy,有一个不错的例子:http://comcorba.tripod.com/comleaks.htm。我
自己也写了一个例子,不过时间所限,做得非常简陋,这个例子比我的强很多,推荐参
考。
但这个方案一样存在大海捞针的风险,因为IMallocSpy是作用于整个COM runtime,
所以中间可能会记录大量正常的分配/释放操作,不利于快速缩小怀疑范围。
我知道Windows支持对单个进程打开User Mode栈回溯,所以有一种思路是配合栈回
溯在每一次分配时打印出栈信息。可惜我最近也才刚刚进入这个领域,手头没有一个
现成的例子,如果有高手的话不吝赐教。
2010/2/24 Zhiming G <gao...@gmail.com>
--
如果用Global System Hook,我们公司倒是有内部的工具,不过主要是用来做Fuzzying测
试,而且未免过于重量级。我个人认为IMallocSpy的做法不错,首先它不限于Debug build,其
次cookie的格式可以自己定义,所以它适合做成一个函数库。如果公司产品大量地使用COM而且
产品需要长期运行(比如服务器),那么花些精力写这么一个函数库用来做压力测试,从长远看是
有收益的。
我不喜欢Just Let Leak Happen的主要理由是这个办法只有当Leak已经很大的时候才会容易被人
察觉,而泄漏太多的情况下有三个问题:
a) 如果单次泄漏内存不大,那么当总泄漏量很大的时候往往表示这个软件需要长时间运行,测试
重现可能需要太长的时间。
b) Let it happen的办法不会帮助我们定位。而调试时最耗时间的就是定位,代码大了尤其如此。
c) Let it happen最可怕的是它可能在客户那边才出现。如果泄漏的严重程度已经到了客户气得要
上门骂人,那么这个时候开发方面的压力会非常大。站在团队角度考虑,我宁可花时间写测试
工具也不愿让客户发现这个问题。相信我,我曾经有一个同事一个类似的问题折腾得很惨,幸
亏是handle泄漏,相对容易发现。
2010/2/24 sagasw <sag...@gmail.com>:
在retail版本,它什么都不做,直接把对应的调用pass到系统库即可。在debug/release版本,它都负责做记录/检查/核对的工作。碰到情况复杂的,还可在每次调用时通过dbghelp库抓取当前的call-stack,如果是第一次碰到就保存下来,然后把调用和call-stak的key关联起来。这样重复释放/leak/甚至冲内存都可以定位。
上面你们提到的这种问题,解决三四个问题的时间就可以写出这么一个系统了,能够节省以后海量的时间。
不过仅限于你在调试有源码的系统,对那些做支持的同学表示安慰。
2010/2/25 Fuzhou Chen <cppo...@gmail.com>:
对于CRT的debug,比如MFC的DEBUG_NEW,我能想到的东西就是用自己的
堆管理代替。不过具体到AllocSysString()会有两个问题:
a) AllocSysString()参数包含初始化,所以不能简单地利用宏来修改长度值获得
用于cookie的额外内存。
b) AllocSysString()用的应该是COM的堆。如果我们用我们自己的堆管理系统
代替它,那么在跨Apartment传递字符串的时候就可能造成崩溃。
如果限制绝对不许用IMallocSpy,那么我倒是有一个蠢办法。
我可以不用cookie,而是做WriteLog(),打印出每一次分配释放的__FILE__,
__LINE__以及字符串首地址。这样执行完一遍后再写个脚本,根据字符串首
地址对所有的分配和释放操作进行配对。如果存在泄漏,则必定有些Alloc操
作找不到对应的Free调用。这样的脚本用Perl来写并不困难。
如果我们试图为一个已经存在的项目添加支持,则需要定义SysAllocString和
SysFreeString宏,并要求所有的cxx文件在包含文件列表的最后包含我们的
宏定义(具体见下面的strdbg.h)。这样后面的SysAllocString()和
SysFreeString()才能被替换为我们的debug版本,并且输出log。
我说我的办法蠢的主要原因是它不能替换所有的函数调用。因为别的函数
库,比如MFC,也是会包含OLE的头文件并使用Sys*函数的。如果这些库是以
目标文件的方式连接进来,则这时候这个方案就不具备跟踪能力,除非我们能
够保证我们用的库永远是模板——显然这是不现实的。
另外,如果我们真的想用自己的宏替换第三方模板文件里的Sys*String()函
数,我们也可能必须得用一些奇技淫巧才能成功做到,因为头文件之间的包含
关系本来就很难理顺。这个我没办法展开,因为我确实从来没有用过MFC,所
以不敢夸口,而且过于花巧的方法可能会给以后的阅读理解造成困难。所以
我的建议是最好避开这个问题。
// ==== strdbg.h ====
#ifndef _STRDBG_H_
#define _STRDBG_H_
extern BSTR
_DEBUG_SysAllocString(const OLESTR* oleStr, TCHAR* szFile, DWORD dwLine);
extern VOID
_DEBUG_SysFreeString(const BSTR* bstr, TCHAR* szFile, DWORD dwLine);
#define SysAllocString(str) __DEBUG_SysAllocString(str, __FILE__, __LINE__)
#define SysFreeString(str) __DEBUG_SysFreeString(str, __FILE__, __LINE__)
#endif /* End of strdbg.h */
// ==== strdbg.cxx ====
// XXX Warning: NEVER include strdbg.h
#include <atlbase.h>
BSTR
_DEBUG_SysAllocString(const OLESTR* oleStr, TCHAR* szFile, DWORD dwLine)
{
BSTR* newStr = ::SysAllocString(oleStr);
WriteSomeLog(L"[A]%u, %s, %d", oleStr, szFile, dwLine);
return newStr;
}
VOID
_DEBUG_SysFreeString(const BSTR* bstr, TCHAR* szFile, DWORD dwLine);
{
WriteSomeLog(L"[F]%u, %s, %d", oleStr, szFile, dwLine);
::SysFreeString(bstr);
}
// ==================
// ==== YourCode.cxx ====
#include "stdafx.h"
#include "atlbase.h"
#include "XXXX.h"
#include "strdbg.h"
static void DoMyStuff()
{
// Now we will actually call _DEBUG_SysAllocString(), with
__FILE__ and __LINE__
BSTR* bstrMyStr = ::SysAllocString(L"Leak test");
}
2010/2/24 sagasw <sag...@gmail.com>:
首先就是同一内存分配的出入口,比如实现自己的TLMalloc, TLFree,
TLRealloc类。然后呢,看你有没有合适的利用C++特性来替换new/delete的机会,以Python实现为例,它就能够找到一个足够高的同一父类Object来做这件事情。在自己能够控制的范围内尽可能的统一入口。
当有了自己的入口以后,TLMalloc可以添加‘额外参数来表示来自什么模块,什么特性,对对齐有什么要求,根据不同的用法,可以把性质不同的分配放在不同的区域内。比如所以尺寸小于128字节的放入一个块里,然后128~1024间的在一起,这种内存管理算法的代码很多,可以google,Linux采用的DL
malloc就十分的经典(在用于windows下时候,极端情况会有问题,需要关闭segment合并)。
接下来的就是在中间层里添加管理,记录和检查代码了。
再回来尝试解决你的问题:
#1你如果需要的仅仅是一个普通的String类,那么CString不是好选择,更不是唯一的选择。比如STL或者boost,都提供你替换默认的allocator。用自己的allocator来负责记录/分配。
#2如果你因为某种原因,不能使用外部的/开源的库,或者遗留代码带多的话,自己动手也未尝不可。参考MFC代码,
typedef ATL::CStringT< TCHAR, StrTraitMFC_DLL< TCHAR > > CString;
其中StrTraitMFC_DLL就是最终负责分配内存的实现,见前文的ATL.ChTraitsCRT.AllocSysString。实现你自己的TL_CString来替换掉它。这里顺便说句额外的,项目开始的时候定义一下
typedef signed __int8 TL_SBYTE;
typedef signed __int32 TL_INT;
typedef CString TL_String;
都是好习惯,不论是为了开发方便,或者跨平台等等。额外的封装就是剥离。
#3不论什么办法,开发人员自己注意永远是最重要的。参考我说的John的那本调试的书,他举了一个他自己的例子,有一个公司碰到menory
问题,全项目全部停下来查了很多久都没有搞定,产品严重延期,花钱请他查(他单干前,在一个很出名的内存检查工具公司,名字我记不起来了),他要求说派两个新的不能再新的新人给他帮忙,公司说,我们有很资深的程序员可以帮你,他说不要,他们对系统太了解,成见太深了。一开始,他让这个两个程序员帮他一起把所有的未初始化的指针都初始化,就是int
*p=null然后,,,,问题就解决了。一句话,要当心。我不知道有哪些强力的工具,不知道有没有工具(非静态分析)可以查到下面的问题,求工具达人指点。
{
LPVOID *p = VirtualAlloc(...., 4096,.....);
}
大致就这样吧。
顺便给这个我很稀饭的大牛做个广告:
http://www.wintellect.com/
The company was founded in spring 2000 by three developers — Jeff
Prosise, Jeffrey Richter, and John Robbins — and businessman Lewis
Frazer.
他们的Instructors名单里更是大牛云集,哈哈。
2010/2/25 sagasw <sag...@gmail.com>:
说得难听一点,大家都是出来混碗饭吃的,遇到问题就撂挑子是很
潇洒,可家里的老婆孩子还等着要这份工资呢。。
所以结论就如同黑格尔的话:凡是现实的都是合理的。适当掌握
一些调试的技巧是必须的。
2010/2/25 机械唯物主义 : linjunhalida <linjun...@gmail.com>:
2010/2/25 Fuzhou Chen <cppo...@gmail.com>:
其实我倒是很好奇那个VirtualAlloc(...., 4096, ....)的问题。我工作里的主力
用LocalAlloc和COM,一般不允许直接用VirtualAlloc(),莫非和4K字节对齐
有关,或者是因为忘记VirtualFree()导致内存泄漏?
另外,你说的那个John Robbins大牛参加过的很有名的静态软件检查工具
公司,莫非指的是Coverity(http://www.coverity.com)?
我这边快一点了,先睡。
2010/2/25 yichen wang <wangyic...@gmail.com>:
至于说LPVOID *p = VirtualAlloc(....,
4096,.....);是因为我在看Microsoft.Windows.Internals
5e,里面对于进程创建过程的描述让我觉得几乎没有办法在virtual memory page
层面上分辨什么是leak,什么是不是。在其他工具或者CRT有机会介入前系统已经在虚拟内存页表里添加大量的信息了,而且这张表本身是不断变动的,虚地址到物理地址的变化经常会变,如何指正某一个是leak呢?
说到这里,实际上在VirtualAlloc继续向下走,会进入到很多Rtl开头的函数,名字上看是辅助作用的,也许微软会在某个层次上开放一个接口来专供调试用。我没有怎么留意内存调试工具,所以问问你们。
2010/2/25 Fuzhou Chen <cppo...@gmail.com>:
所以我也就是胡猜,这个时候用我那个蠢办法,配合打印虚拟地址,也许还可靠一点?
2010/2/25 yichen wang <wangyic...@gmail.com>:
2010/2/26 Fuzhou Chen <cppo...@gmail.com>:
> 我的看法是如果搞用户态应用,那么就不该试图直接定位物理内存。Rtl系列函数已经是DDK
> 提供的API,一般情况下写个服务时我是不敢随便碰的。
>
> 所以我也就是胡猜,这个时候用我那个蠢办法,配合打印虚拟地址,也许还可靠一点?
>
> 2010/2/25 yichen wang <wangyic...@gmail.com>:
>> 也不是输赢,我怕你误会我的态度^_^。
>>
>> 至于说LPVOID *p = VirtualAlloc(....,
>> 4096,.....);是因为我在看Microsoft.Windows.Internals
>> 5e,里面对于进程创建过程的描述让我觉得几乎没有办法在virtual memory page
>> 层面上分辨什么是leak,什么是不是。在其他工具或者CRT有机会介入前系统已经在虚拟内存页表里添加大量的信息了,而且这张表本身是不断变动的,虚地址到物理地址的变化经常会变,如何指正某一个是leak呢?
>>
>> 说到这里,实际上在VirtualAlloc继续向下走,会进入到很多Rtl开头的函数,名字上看是辅助作用的,也许微软会在某个层次上开放一个接口来专供调试用。我没有怎么留意内存调试工具,所以问问你们。
>>
>> 2010/2/25 Fuzhou Chen <cppo...@gmail.com>:
>>> 呵呵不用这么郑重吧。都是有感而发而已。论坛上都是坐而论道,求的又不是
>>> 输赢。
>>>
>>> 其实我倒是很好奇那个VirtualAlloc(...., 4096, ....)的问题。我工作里的主力
>>> 用LocalAlloc和COM,一般不允许直接用VirtualAlloc(),莫非和4K字节对齐
>>> 有关,或者是因为忘记VirtualFree()导致内存泄漏?
>>>
>>> 另外,你说的那个John Robbins大牛参加过的很有名的静态软件检查工具
>>> 公司,莫非指的是Coverity(http://www.coverity.com)?
>>>
>>>
>>> 我这边快一点了,先睡。
>>>
>>> 2010/2/25 yichen wang <wangyic...@gmail.com>:
>>>> 兄弟,到这里,我诚恳的说,你说的是对的,都对,包括另外一个邮件里的。
>>>>
>>>> 2010/2/25 Fuzhou Chen <cppo...@gmail.com>:
>>>>> 对于新代码而言,这条路确实更靠谱;不过这世上老代码多了去了,
>>>>> 事到临头时总有必须调试的时候。何况万恶的商业总能给你整出
>>>>> 无数的理由让你不能看代码、不能现场调试......总归什么都不能。
>>>>>>> The company was founded in spring 2000 by three developers -- Jeff
>>>>>>> Prosise, Jeffrey Richter, and John Robbins -- and businessman Lewis
2010/2/27 John Lan <lan....@gmail.com>: