[译]内存(泄漏)和异常跟踪(CRT和COM泄漏)
By robot-v1.0
本文链接 https://www.kyfws.com/applications/memory-leak-and-exception-trace-crt-and-com-leaks-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 24 分钟阅读 - 11723 个词 阅读量 0[译]内存(泄漏)和异常跟踪(CRT和COM泄漏)
原文地址:https://www.codeproject.com/Articles/3134/Memory-Leak-and-Exception-Trace-CRT-and-COM-Leaks
原文作者:Jochen Kalmbach [MVP VC++]
译文由本站 robot-v1.0 翻译
前言
With this utility you can simply find memory leaks (CRT and COM) in your program (with almost no time-cost during runtime). Each leak is written to a file with the callstack of the allocation.
使用此实用程序,您可以简单地在程序中查找内存泄漏(CRT和COM)(运行时几乎没有时间成本).每个泄漏都将与分配的调用堆栈一起写入文件.
- 下载项目(带有源代码)-26.1 Kb(Download project (with source) - 26.1 Kb)
- 下载MFC项目(包括源代码)-48 Kb(Download MFC project (with source) - 48 Kb)
- 下载MemLeakAnalyse-Tool(含源)-46.2 Kb(Download MemLeakAnalyse-Tool (with source) - 46.2 Kb)
介绍(Introduction)
使用此实用程序,您可以简单地在程序中查找内存泄漏(CRT和COM-Leaks!).每个泄漏都会与分配的调用堆栈(包括源代码行)一起显示.因此,在使用STL时,您可以轻松找到泄漏.如果您的应用程序崩溃,它还将在调用堆栈中写入一个文件(它也可以处理堆栈溢出!).它几乎没有运行时开销(运行时成本).最好的是:它是免费的((With this utility you can simply find memory leaks in your program (CRT and COM-Leaks!). Each leak is displayed with the callstack (including the source line) of the allocation. So, you can easily find leaks, while using the STL. It will also write a file with the callstack if your application crashes (it can also handle stack-overflows!). It almost has no runtime-overhead (runtime-cost). And the best: it is free () GNU较小通用公共许可证(GNU Lesser General Public License) ).().)
查找内存泄漏(Finding memory leaks)
在您现有的VC代码中很容易实现这一点:(It is easy to implement this in your existing VC code:)
- 加(Add)*Stackwalker.cpp(Stackwalker.cpp)*和(and)*Stackwalker.h(Stackwalker.h)*到您的项目.(to your project.)
- 包括(Include)*Stackwalker.h(Stackwalker.h)*在你的(in your)
main
源文件.(source file.) - 呼叫(Call)
InitAllocCheck()
在您开始之后(right after the beginning of your)main
.(.) - 呼叫(Call)
DeInitAllocCheck()
就在你的结尾(just before the end of your)main
(此处将报告所有泄漏).((here all the leaks will be reported).) 所有泄漏将在文件中列出(All the leaks will be listed in the file)*YouAppName.exe.mem.log(YouAppName.exe.mem.log)*在应用程序目录中(仅在调试版本中;在发行版本中已禁用).默认情况下,这还将激活异常处理(发布和调试版本).(in the application directory (only in debug builds; it is deactivated for release builds). This will also activate exception-handling by default (release and debug builds).)
仅使用异常处理(Only use exception-handling)
如果只想使用异常处理,则需要执行以下操作:(If you only want to use exception handing, you need to do the following:)
- 加(Add)*Stackwalker.cpp(Stackwalker.cpp)*和(and)*Stackwalker.h(Stackwalker.h)*到您的项目.(to your project.)
- 包括(Include)*Stackwalker.h(Stackwalker.h)*在你的(in your)
main
源文件.(source file.) - 呼叫(Call)
OnlyInstallUnhandeldExceptionFilter()
在您开始之后(right after the beginning of your)main
.(.) 如果发生异常,它将在应用程序目录中写入一个带有调用栈的文件,名称为(If an exception occurs, it will write a file with the callstack in the application directory with the name)YouAppName.exe.exp.log(YouAppName.exe.exp.log).(.)
例(Example)
下面是一个简单的示例:(A simple example is given below:)
#include <span class="code-keyword"><windows.h></span>
#include <span class="code-string">"Stackwalker.h"</span>
void main()
{
// Uncomment the following if you only
// need the UnhandledException-Filter
// (to log unhandled exceptions)
// then you can remove the "(De)InitAllocCheck" lines
//OnlyInstallUnhandeldExceptionFilter();
InitAllocCheck();
// This shows how the mem-leak function works
char *pTest1 = new char[100];
// This shows a COM-Leak
CoTaskMemAlloc(120);
// This shows the exception handling
// and log-file writing for an exception:
// If you want to try it, please comment it out...
//char *p = NULL;
//*p = 'A'; // BANG!
DeInitAllocCheck();
}
如果执行此示例,您将获得一个文件(If you execute this example, you will get a file)*Application-Name.exe.memory.log(Appication-Name.exe.mem.log)*具有以下内容:(with the following content:)
##### Memory Report ########################################
11/07/02 09:43:56
##### Leaks: ###############################################
RequestID: 42, Removed: 0, Size: 100
1: 11/07/02 09:43:56
1: f:\vs70builds\9466\vc\crtbld\crt\src\dbgheap.c(359)
+30 bytes (_heap_alloc_dbg)
1: f:\vs70builds\9466\vc\crtbld\crt\src\dbgheap.c(260)
+21 bytes (_nh_malloc_dbg)
1: f:\vs70builds\9466\vc\crtbld\crt\src\dbgheap.c(139) +21 bytes (malloc)
1: f:\vs70builds\9466\vc\crtbld\crt\src\newop.cpp(12) +9 bytes (operator new)
1: d:\privat\memory_and_exception_trace\
memory_and_exception_trace\main.cpp(9) +7 bytes (main)
1: f:\vs70builds\9466\vc\crtbld\crt\src\crt0.c(259)
+25 bytes (mainCRTStartup)
**** Number of leaks: 1
##### COM-Leaks: ###############################################
(shortened)
**** Number of leaks: 1
说明(Explanation)
现在,我将解释"内存报告文件":(Now, I will explain the Memory-Report-File:)
RequestID: 42, Removed: 0, Size: 100
这条线是(This line is the beginning of)**一(one)**泄漏.如果您有多个泄漏,则每个泄漏都会以一个(leak. If you have more than one leak, then each leak will start with a) RequestID
.(.)
RequestID
对于CRT:这是(For CRT: This is the)RequestID
传递给(which is passed to the)AllocHook
.此ID明确标识分配. CRT只会为每个分配增加此数字.您也可以将此号码与(. This ID clearly identifies an allocation. The CRT just increments this number for each allocation. You can also use this number with the)_CrtSetBreakAlloc
功能.(function.)
对于COM:这是分配的内存的地址.(For COM: This is the address of the allocated memory.)
-
Removed
在内存泄漏转储中,该值必须始终为0((In a memory leak dump this must always be 0 ()false
).().) -
Size
这是分配的内存块的大小.(This is the size of the allocated memory block.)
1: f:\vs70builds\9466\vc\crtbld\crt\src\dbgheap.c(359)
+30 bytes (_heap_alloc_dbg)
这是实际的堆栈条目.堆栈从顶部的最后一个功能开始显示,经过每个被调用方,直到到达堆栈的末尾.(This is an actual stack entry. The stack is shown from the last function on the top going through each callee until the end of the stack is reached.)
-
1:
对于每个完整的调用堆栈,此数字都会增加.您可以忽略此.(This number is incremented for each complete callstack. You can ignore this.) -
f:\vs70builds\9466\vc\crtbld\crt\src\dbgheap.c
实际的文件名.(The actual filename.) -
(359)
文件内的行号.(The line number inside the file.) -
+30 bytes
这是与该行的偏移量(以字节为单位)(如果一行产生多个汇编程序指令).(This is the offset from this line in bytes (if a line produces more than one assembler instruction).) -
(_heap_alloc_dbg)
函数的名称.(The name of the function.)
通过调用InitAllocCheck获得更多选项(More options by calling InitAllocCheck)
InitAllocCheck
具有三个参数:(has three parameters:)
参数名称(Parameter name) | 描述(Description) |
---|---|
-
eAllocCheckOutput
-
eOutput
|这是个(This is an)enum
用于输出格式.以下是可能的:(for output-format. The following is possible:) -
ACOutput_Simple
(default)如上所示,这将输出调用栈.(This outputs the callstack as seen above.) -
ACOutput_Advanced
这具有调用栈的更详细的输出.有关更多信息,请参见(This has a more detailed output of the callstack. For more info see) here . -
ACOutput_XML
这会将泄漏信息输出到XML文件中,以便您可以轻松地从其他应用程序中读取它,或者使用某些XSLT将其转换为所需的更具可读性的格式.有关更多信息,请参见(This outputs the leaks in an XML file so that you can read it easily from other applications or use some XSLT to transform it to a more readable format you want. For more info see) here .
| |
BOOL
bSetUnhandledExeptionFilter
(default:TRUE
) |如果已设置,(If this is set, an)UnhandledExceptionFilter
将会被安装.如果发生(未处理的)异常,它将把调用栈写入日志文件并终止.有关更多信息,请参见(will be installed. If an (unhandled) exception occurs it will write the callstack in a log file and terminate. For more info see) here .
| |
ULONG
ulShowStackAtAlloc
(default:0
) |Notice:仅适用于CRT-(: This works only for CRT-)alloc
s.
您可以在此处指定级别(Here you can specify the level of) malloc
s/ free
的日志记录.默认情况下,运行时不会在日志文件中记录任何内容.如果您需要知道执行程序时会发生什么,可以指定一个值.然后(s logging. By default nothing will be logged in the log file at runtime. If you need to know what happens while executing the program you can specify a value. Then the) malloc
/ free
操作将被记录到文件中(带或不带调用栈).(action will be logged to the file (either with or without callstack).)
有效值为:(Valid values are:)
0
=在运行时不写任何输出-(= Do not write any output during runtime-)alloc
-call(默认).(-call (default).)1
=只写(= Write only the)alloc
动作((action ()malloc
,realloc
,free
).2
=写(= Write)alloc
动作和调用堆栈仅用于(action and callstack only for)malloc
/realloc
.3
=写(= Write)alloc
操作和所有操作的调用堆栈.(action and callstack for all actions.) |
日志输出与更多信息(Log-output with more info)
您还可以获得有关每个堆栈条目的更多信息的输出.为此,您必须致电(You can also get an output with more info about each stack entry. For this you have to call) InitAllocCheck
将第一个参数设置为(with the first parameter set to) ACOutput_Advanced
.如果执行以下示例,您将获得一个文件(. If you execute the following sample you will get a file)*Application-Name.exe.memory.log(Appication-Name.exe.mem.log)*更多信息:(with more info:)
#include <span class="code-keyword"><windows.h></span>
#include <span class="code-string">"Stackwalker.h"</span>
void main()
{
InitAllocCheck(ACOutput_Advanced);
// This shows how the mem-leak function works
char *pTest1 = new char[100];
DeInitAllocCheck();
}
这是(缩短的)输出:(And here is the (shortened) output:)
##### Memory Report ########################################
11/04/02 09:04:04
##### Leaks: ###############################################
RequestID: 45, Removed: 0, Size: 100
1: 11/04/02 09:04:04
// ...
1: 5 main +49 bytes
1: Decl: main
1: Line: d:\privat\memory_and_exception_trace\main.cpp(27) +7 bytes
1: Mod: Memory_and_Exception_Trace, base: 00400000h
1: 6 mainCRTStartup +363 bytes
1: Decl: mainCRTStartup
1: Line: f:\vs70builds\9466\vc\crtbld\crt\src\crt0.c(259) +25 bytes
1: Mod: Memory_and_Exception_Trace, base: 00400000h
1: 7 _BaseProcessStart@4 +35 bytes
1: Decl: _BaseProcessStart@4
1: Mod: kernel32, base: 77e40000h
**** Number of leaks: 1
// ...
说明(Explanation)
在这里,我将解释内存报告文件:(Here, I will explain the Memory-Report-File:)
RequestID: 45, Removed: 0, Size: 100
该行与上面相同:(This line is the same as above:)
1: 5 main +49 bytes
-
1:
对于每个完整的调用堆栈,此数字都会增加.您可以忽略此.(This number is incremented for each complete callstack. You can ignore this.) -
5
这是调用栈的深度.对于每个堆栈条目,此数字都会递增.从顶部的最后一个函数(数字0)开始显示堆栈,该函数遍历每个被调用方,直到到达堆栈的末尾.(This is the depth of the callstack. This number is incremented for each stack entry. The stack is shown from the last function on the top (number 0) going through each callee until the end of the stack is reached.) -
main +49 bytes
从此函数开始的字节数,此调用堆栈的指令存储在该字节中.(The number of bytes from the beginning of this function, where the instruction for this callstack is stored.)
1: Decl: main
1: Line: d:\privat\memory_and_exception_trace\main.cpp(27) +7 bytes
1: Mod: Memory_and_Exception_Trace, base: 00400000h
-
Decl: main
这是函数的声明.(This is the declaration of the function.) -
Line: ....xyz.cpp(27) +7 bytes
这显示了调用堆栈的实际行(在方括号中)(此处:第27行).此外,它以字节为单位给出此行的偏移量(如果一行产生多个汇编程序指令).(This shows the actual line (in brackets) of the callstack (here: line 27). In addition, it gives the offset from this line in bytes (if a line produces more than one assembler instruction).) -
Mod: Memory_and_Exception_Trace
模块的名称(EXE,DLL,OCX,a.s.o.).(The name of the module (EXE, DLL, OCX, a.s.o.).) -
base: 00400000h
该模块的基地址.(The base address of this module.)
XML输出(XML output)
如果将第一个参数设置为(If you set the first parameter to) ACOutput_XML
将生成一个XML文件.它具有以下内容:(an XML file will be produced. It has the following contents:)
<MEMREPORT date="11/08/02" time="10:43:47">
<LEAK requestID="47" size="100">
<!--<span class="code-comment"> shortened --></span>
<STACKENTRY decl="main" decl_offset="+100"
srcfile="d:\...\main.cpp" line="16"
line_offset="+7" module="Memory_and_Exception_Trace" base="00400000"/>
<STACKENTRY decl="mainCRTStartup" decl_offset="+363"
srcfile="f:\...\crt0.c" line="259"
line_offset="+25" module="Memory_and_Exception_Trace" base="00400000"/>
</LEAK>
</MEMREPORT>
如果您看看(It is pretty self explaining if you take a look at the*) “advanced log output”*)“高级日志输出”( .(.)
内存泄漏分析工具(Mem-leak-analyse tool)
如果您使用的是XML输出格式,则可以使用我的MemLeakTool以排序顺序显示泄漏(按调用堆栈排序).只需选择" xml-leak"-文件,然后按"读取"即可.调用堆栈将显示在TreeView中.如果选择节点,则源代码将显示在右侧(如果可以找到).(If you are using the XML-output format then you can use my MemLeakTool to display the leaks in a sorted order (sorted by callstack). Just select the “xml-leak”-File and press “Read”. The callstack will be displayed in a TreeView. If you select a node, the source code will be shown in the right part (if it could be found).)
信息(Information):此程序需要.NET Framework 1.0!(: This program requires .NET Framework 1.0!)
关于泄漏的一句话(A word on leaks)
您应该知道,某些泄漏可能是其他泄漏的结果.例如,以下代码引发了两次泄漏,但是如果您删除了泄漏的"发起者",则另一个泄漏也将消失.例如:(You should be aware, that some leaks might be the result of other leaks. For example the following code throws two leaks, but if you remove the “originator” of the leaks, the other leak will also disappear. For example:)
#include <span class="code-keyword"><windows.h></span>
#include <span class="code-keyword"><stdlib.h></span>
#include <span class="code-string">"stackwalker.h"</span>
class MyTest
{
public:
MyTest(const char *szName)
{
// The following is the second resulting leak
m_pszName = strdup(szName);
}
~MyTest()
{
if (m_pszName != NULL)
free(m_pszName);
m_pszName = NULL;
}
protected:
char *m_pszName;
};
void main()
{
InitAllocCheck();
// This is the "main" leak
MyTest *pTest = new MyTest("This is an example");
DeInitAllocCheck();
}
工作原理(CRT)(How it works (CRT))
内存泄漏记录器的基础是一个哈希表,其中包含有关所有已分配内存(包括调用堆栈)的信息.基本上(The basis of the memory leak logger is a Hashtable with information about all the allocated memory (including callstack). Basically) _CrtSetAllocHook
被称为挂钩所有内存分配/释放.因此,仅记录C/C ++分配.在每个分配中,都将捕获一部分调用栈和指令指针并将其存储在Hashtable中,以及有关分配的一些其他信息.(is called to hook all the memory allocations / frees. Therefore only C/C++ allocations are logged. On every allocation a portion of the callstack and the Instruction-Pointer is caught and stored in the Hashtable, with some other information about the allocation.)
如果应用程序调用(If the application calls) DeinitAllocCheck
然后将对Hashtable进行迭代,并且所有条目的(保存的)调用堆栈将在文件中列出.为此,我们提供了指向我们的指针(then the Hashtable will be iterated and the (saved) callstack of all the entries will be listed in the file. For this we provide a pointer to our) ProcessMemoryRoutine
功能(function to the) StackWalk
功能.(function.)
详细(In detail)
哈希表(Hashtable)
哈希表默认包含1024个条目.如果您要进行许多分配并希望减少冲突,则可以更改此值.只需更改(The Hashtable contains by default 1024 entries. You can change this value if you are doing many allocations and want to reduce the collisions. Just change the) ALLOC_HASH_ENTRIES
定义.(define.)
作为哈希键,(As hash-key, the) lRequestID
对于每个分配使用.此ID传递给(for each allocation is used. This ID is passed to the) AllocHook
功能(至少对于(function (at least for) alloc
s).如果未传递(例如,用于释放),则传递(有效)地址.通过此地址,也可以获取(s). If it is not passed (for example, for freeing), then an (valid) address is passed. By having this address it is also possible to get the) lRequestID
,通过查看(, by looking into the) _CrtMemBlockHeader
分配的块.(of the allocated block.)
对于哈希,使用了非常简单,快速的哈希函数:(For hashing, a very simple and fast hash-function is used:)
static inline ULONG AllocHashFunction(long lRequestID) {
return lRequestID % ALLOC_HASH_ENTRIES;
} // AllocHashFunction
将分配插入哈希表(Insert an allocation into the Hashtable)
如果应将新分配插入到哈希表中,则首先通过调用为实际线程创建线程上下文(If a new allocation should be inserted into the Hashtable, first a thread context for the actual thread is made by calling) GetThreadContext
.此函数需要一个"真实"线程句柄,而不是由(. This function requires a “real” thread handle and not a pseudo handle which is returned by) GetCurrentThred
.因此,为此,我必须通过调用来创建"真实"句柄(. So, for this I have to create a “real” handle by calling) DuplicateHandle
.(.)
其实我只需要(Actually, I only need the current) Ebp
和(and) Eip
寄存器.这也可以通过仅使用内联汇编器读取寄存器来完成.现在有了寄存器,我读取了指定地址的存储器.对于(registers. This could also be done by just reading the registers with the inline assembler. Now having the registers, I read the memory at the specified address. For) Eip
,我只需要读取4个字节.我不知道为什么(, I only need to read 4 bytes. I do not know why) StackWalk
需要阅读(needs to read the) Eip
值,但如果无法从中读取值(values, but if the values could not be read from) StackWalk
它无法建立调用栈.真正重要的部分是调用堆栈,它存储在指向(it fails to build the callstack. The real important part is the callstack which is stored in the memory pointing from) Ebp
(要么((or) Esp
).().)
目前,我只是尝试通过调用(At the moment, I just try to read 0x500 bytes by calling the) ReadProcessMemory
功能.我没有阅读完整的堆栈,因为它可能会为太多分配使用过多的内存.因此,我将最大尺寸减小为0x500.如果您需要更深的调用堆栈,则可以更改(function. I don’t read the complete stack, because it might use too much memory for many allocations. So, I reduced the maximum size to 0x500. If you need a deeper callstack, you can change the) MAX_ESP_LEN_BUF
定义.(define.)
如果调用堆栈的深度不是0x500字节,则(If the callstack is not 0x500 bytes deep, then the) ReadProcessMemory
将失败(will fail with) ERROR_PARTIAL_COPY
.如果发生这种情况,我需要询问有多少种可能,阅读没有任何错误.为此,我需要通过调用来查询此值(. If this happens, I need to ask how many are possible, to read without any error. For this I need to query this value by calling) VirtualQuery
.然后,我尝试读取尽可能多的字节.(. Then, I try to read as many bytes as possible.)
有了调用栈,我可以简单地将条目插入到哈希表中.如果给定的哈希条目已被占用,我将创建一个链接列表,并将此条目附加到末尾.(Having the callstack I can simply insert the entry into the Hashtable. If the given hash-entry is already occupied, I make a linked list and append this entry to the end.)
建立泄漏清单(Building the leak-list)
如果你打电话(If you call) DeInitAllocCheck
我只是简单地遍历Hashtable并输出所有未释放的条目.为此,我打电话(I simply walk through the Hashtable and output every entry which was not freed. For this I call) StackWalk
带有指向我自己的内存读取功能的指针((with a pointer to my own memory-reading-function () ReadProcMemoryFromHash
).这个函数是从内部调用的(). This function is called from the internals of) StackWalk
.如果被调用,它将查找给定的哈希表(. If it is called it looks up the Hashtable for the given) lRequestID
并返回存储在哈希表中的内存.的(and returns the memory which was stored in the Hashtable. The) lRequestID
通过了(is passed in the) hProcess
的参数(parameter of the) StackWalk
功能(如(function (as stated in the documentation of) StackWalk
).().)
忽略分配(Ignoring allocations)
的分配/免费(Allocations/frees for) _CRT_BLOCK
被忽略(有关更多信息,请参见(are ignored (for more info, see) 这里(here) ).这是因为CRT为"特殊目的"动态分配了一些内存.该工具还会检查(). This is because CRT dynamically allocates some memory for “special purposes”. The tool also checks the) _CRTDBG_ALLOC_MEM_DF
的标志(flag of the) _crtDbgFlag
变量.如果关闭,则所有分配都将被忽略.有关更多详细信息,请参见(variable. If it is off, then all the allocations are ignored. For more details see) _CrtSetDbgFlag
.(.)
运作方式(COM)(How it works (COM))
要跟踪COM内存泄漏,您必须提供一个(To track COM-memory-leaks, you have to provide an) IMallocSpy
接口.该接口必须在(interface. This interface must be registered with) CoRegisterMallocSpy
.之后,(自己)(. After that the (own)) IMallocSpy
每个内存(重新)分配/空闲都会调用实例.因此,您可以跟踪所有内存操作.(instance is called for every memory (re)allocation/free. So, you can track all memory actions.)
调用堆栈的存储方式与CRT-相同(The storage of the callstack is done in the same way as for CRT-) alloc
s(在哈希表中).因此,有关更多信息,请阅读(s (in a Hashtable). So, for more info please read the) CRT截面(CRT-section) .(.)
关于COM泄漏(A word on COM-leaks)
实际上,没有什么可说的,但是…(Actually, there is nothing to say, but…)
如果您使用的是MSXML 3或4实现,则必须知道该解析器使用"智能"伪垃圾收集器的事实.这意味着他们分配了内存,并且在使用后将不会释放它!因此,您可能会看到一些仅是"缓存内存"的泄漏.如果您第一次打电话(If you are using MSXML 3 or 4 implementation, you have to be aware of the fact that this parser uses a “smart” pseudo-garbage collector. This means that they allocate memory and will not free it after it is used! So, you may see some leaks which are only “cached memory”. If you first call) CoUninitialize
,然后将释放所有缓存的内存,并报告"实际" COM泄漏.(, then all the cached memory will be freed and the “real” COM-leaks will be reported.)
有关更多信息,请参见:(For more info see:) 了解MSXML垃圾收集机制(Understanding the MSXML Garbage Collection Mechanism) .(.)
MFC用法(MFC usage)
MFC的问题在于(The problem with MFC is that the derived) CWinApp
C类在C运行时实例化,因为它是一个全局变量.实施Leak-Finder的最简单解决方案是声明以下内容(class is instantiated by the C-runtime, because it is a global variable. The easiest solution to implement the Leak-Finder is to declare the following) static struct
在你里面(inside your)MainApp.cpp(MainApp.cpp).(.)
您还必须添加(You also have to add)*stackwalk.cpp(stackwalk.cpp)*和(and)*stackwalk.h(stackwalk.h)*到您的项目.您还需要添加(to your project. You also need to add the) #include <span class="code-keyword"><stdafx.h></span>
在顶部(on top of the)*stackwalk.cpp(stackwalk.cpp)*文件(如果使用预编译头).本文顶部还提供了MFC应用程序的示例:(file (if you use precompiled headers). A sample of an MFC application is also available from the top of this article:)
static struct _test
{
_test()
{
InitAllocCheck();
}
~_test()
{
DeInitAllocCheck();
}
} _myLeakFinder;
暂时禁用日志记录(仅CRT)(Temporarily disable logging (only CRT))
如果您不想记录应用程序的特殊分配(无论出于何种原因; MFC经常这样做),则可以通过禁用CRT标志将其停用(When you don’t want to log a special allocation of your application (for whatever reason; MFC does this often), then you can simply deactivate it by disabling the CRT flag) _CRTDBG_ALLOC_MEM_DF
与(with the) _CrtSetDbgFlag
功能.以下是如何执行此操作的示例:(function. Here is an example of how you can do this:)
#include <span class="code-string">"Stackwalker.h"</span>
#include <span class="code-keyword"><crtdbg.h></span>
bool EnableMemoryTracking(bool bTrack)
{
int nOldState = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
if (bTrack)
_CrtSetDbgFlag(nOldState | _CRTDBG_ALLOC_MEM_DF);
else
_CrtSetDbgFlag(nOldState & ~_CRTDBG_ALLOC_MEM_DF);
return nOldState & _CRTDBG_ALLOC_MEM_DF;
}
void main()
{
InitAllocCheck();
// The following will be logged
char *pTest1 = new char[100];
EnableMemoryTracking(false); // disable logging
// The following will NOT be logged
char *pTest2 = new char[200];
EnableMemoryTracking(true); // enable logging
// The following will be logged
char *pTest3 = new char[300];
DeInitAllocCheck();
}
未处理的异常(Unhandled exceptions)
有三种方法可以使用此工具处理未处理的异常.(There are three ways to use this tool for unhandled exceptions.)
使用简单(Simple using)
如果你只是打电话(If you just call) InitAllocCheck
没有参数或第二个参数设置为(with no parameters or with the second parameter set to) TRUE
,则将安装未处理的异常过滤器.如果发生未处理的异常,将写入带有调用堆栈的日志文件,将显示一个带有异常消息的对话框,并且应用程序将终止于(, then an unhandled exception filter will be installed. If an unhandled exception occurs, a log file with the callstack will be written, a dialog box with the exception message will be displayed, and the application will be terminated with) FatalAppExit
.(.)
第二简单使用(Second simple using)
如果你不想(If you don’t want the) AllocCheck
-overhead((小的)开销仅在调试版本中存在),您可以简单地调用(-overhead (the (small) overhead is only present in debug builds), you can simply call) OnlyInstallUnhandeldExceptionFilter
.这将安装(. This will install the) UnhandledExceptionFilter
如果发生(未处理的)异常,它将写入日志文件.日志文件将以以下名称存储在应用程序目录中:(which writes a log file if an (unhandled) exception occurs. The log file will be stored in the application directory with the name)YouAppName.exe.exp.log(YouAppName.exe.exp.log):(:)
int main()
{
OnlyInstallUnhandeldExceptionFilter();
// do your main code here...
}
高级使用(Advanced using)
您可以编写自己的异常过滤器,然后调用(You can write your own exception filter and just call) StackwalkFilter
产生调用栈.然后,您可以做任何您想做的事.这是一个小例子:(to produce the callstack. Then you can do whatever you want. Here is a small example:)
static LONG __stdcall MyUnhandlerExceptionFilter(EXCEPTION_POINTERS* pExPtrs)
{
LONG lRet;
lRet = StackwalkFilter(pExPtrs,
EXCEPTION_EXECUTE_HANDLER, _T("\\exception.log"));
TCHAR lString[500];
_stprintf(lString,
_T("*** Unhandled Exception!\n")
_T(" ExpCode: 0x%8.8X\n")
_T(" ExpFlags: %d\n")
_T(" ExpAddress: 0x%8.8X\n")
_T(" Please report!"),
pExPtrs->ExceptionRecord->ExceptionCode,
pExPtrs->ExceptionRecord->ExceptionFlags,
pExPtrs->ExceptionRecord->ExceptionAddress);
FatalAppExit(-1,lString);
return lRet;
}
int main()
{
InitAllocCheck(ACOutput_Advanced, FALSE);
SetUnhandledExceptionFilter(MyUnhandlerExceptionFilter);
// do some stuff...
DeInitAlloocCheck();
}
常见错误(Common mistakes)
使用此工具时最常见的错误之一是您静态实例化了您的类(One of the most common mistakes while using this tool is that you statically instantiate classes in your) main
功能.问题是该类的析构函数被称为(function. The problem is that the destructor of the class is called)后(after)致电(the call to) DeInitAllocCheck
.如果在此类中分配了一些内存,则该内存将显示为泄漏.例如:(. If some memory was allocated inside this class, this memory will appear as a leak. For example:)
#include <span class="code-keyword"><windows.h></span>
#include <span class="code-string">"Stackwalker.h"</span>
#include <span class="code-keyword"><string></span>
void main()
{
InitAllocCheck();
std::string szTemp;
szTemp = "This is a really long string";
DeInitAllocCheck();
}
有两种解决方案.您可以在致电到(There are two solutions for this. You can start a block after the call to) InitAllocCheck
并在致电之前结束(and end it before the call to) DeInitAllocCheck
.这样,您可以确定在生成泄漏文件之前已调用析构函数.例如:(. With this you can be sure that the destructors are called before the leak file is produced. For example:)
#include <span class="code-keyword"><windows.h></span>
#include <span class="code-string">"Stackwalker.h"</span>
#include <span class="code-keyword"><string></span>
void main()
{
InitAllocCheck();
{
std::string szTemp;
szTemp = "This is a really long string";
}
DeInitAllocCheck();
}
第二种解决方案是使用与MFC应用程序相同的技术(请参见上文).(The second solution is to use the same technique that is used for MFC applications (see above).)
Visual Studio 7和Win2K/NT(Visual Studio 7 and Win2K / NT)
我发现用VS7构建并在Win2K或NT上运行的可执行文件有问题.问题是由于旧版本(I found a problem with the executables built with VS7 and run on Win2K or NT. The problem is due to an old version of)dbghelp.dll(dbghelp.dll).从VS7生成的PDB文件采用较新的格式(DIA).看来VS安装没有更新(. The PDB files generated from VS7 are in a newer format (DIA). It appears that the VS installations do not update)dbghelp.dll(dbghelp.dll)在Win2K上.因此,原始版本(5.0.)仍在系统上并将被使用.但是,使用此版本无法读取新的PDB格式.因此,无法显示任何调用堆栈.(on Win2K. So the original version (5.0.) is still on the system and will be used. But with this version it is not possible to read the new PDB format. So, no callstack can be displayed.)
要使其正常工作,您必须执行以下操作(To get it to work you have to do the following)
下载最新版本(Download the latest) Windows调试工具(Debugging Tools for Windows) (包括((which includes)dbghelp.dll(dbghelp.dll)).您必须安装它才能获取文件.但是你只需要(). You have to install it to get the files. But you only need the)dbghelp.dll(dbghelp.dll)!现在我们还有另一个问题.安装程序不会替换原始的(! Now we have another problem. The installer does not replace the original)dbghelp.dll(dbghelp.dll).所以我们需要复制(. So we need to copy the)*dbghelp.dll(dbghelp.dll)*在我们的EXE目录中.现在,要确保正确的版本已加载,您必须放置一个名称为(in our EXE dir. Now to make sure the right version is loaded you have to put a file with the name)*appname.local(appname.local)*在您的EXE目录中(请替换(in your EXE dir (please replace)*应用名称(appname)*EXE名称(不带扩展名).现在,它也可以在WinNT/2K上运行.(with the EXE name (without extension)). Now it should also work on WinNT/2K.)
已知的问题(Known issues)
- 仅当(The memory leak works correctly only if the)
lRequestID
不换行(32位值).如果值回绕,则不可能明确分配给定值(does not wrap (32-bit value). If the value wraps around, then it is not possible to clearly assign a given)lRequestID
分配给先前的分配,因为此ID有可能被使用两次(甚至更多).但这仅在VC7中发生,因为VC6在C运行时中存在一个错误,该错误会调用(to a previous allocation, because it is possible that this ID was used twice (or even more). But this happens only with VC7, because VC6 has a bug in the C-runtime which will call)_DbgBreak
如果(if the)lRequestID
包装(如果没有(wraps (if no)_CrtBreakAlloc
用来).(is used).) - 如果使用VC7上的"检测64位可移植性问题(/Wp64)“选项进行编译,它将生成警告.(If you compile with “Detect 64-bit portability issues (/Wp64)” option on VC7, it will generate a warning.)
- 如果在托管C ++中使用此工具,它将无法正确显示托管代码的调用堆栈.(If you use this tool in managed C++ it will not correctly display the callstack for managed code.)
- 由于某些原因,COM-(For some reason, the COM-)
alloc
-callstack无法显示真正调用(-callstack cannot display the stack-entry which really calls the)CoTaskMemAlloc
功能.仅显示上部堆栈条目.(function. Only the upper stack entry can be shown.)
参考文献(References)
- 多亏了他,大多数Stackwalker功能都来自Felix Kasza!(Most of the Stackwalker-functions are from Felix Kasza, thanks to him!)
- Bugslayer,1999年2月:非常好的文章和来源(Bugslayer, Feb. 1999: Very good article and source)
- Bugslayer,1997年10月:歼灭您附近的应用程序中的错误(Bugslayer, Oct. 1997: Annihilating Bugs in an Application Near You)
- 编写调试挂钩函数(Writing Debug Hook Functions)
- MS Book:调试应用程序(MS Book: Debugging applications)
- 如何使用Dh.exe解决用户模式内存泄漏(How to Use Dh.exe to Troubleshoot User-Mode Memory Leaks)
- Windows调试工具(最新的dbghelp.dll)(Debugging Tools for Windows (latest dbghelp.dll))
- dbghelp.dll可重新分发给NT(dbghelp.dll redistributable for NT)
- IMallocSpy接口(IMallocSpy-Interface)
- 了解MSXML垃圾收集机制(Understanding the MSXML Garbage Collection Mechanism)
- Umdhtools.exe:如何使用Umdh.exe查找内存泄漏(Umdhtools.exe: How to Use Umdh.exe to Find Memory Leaks) .(.)
历史(History)
-
4(4)日(th)2002年11月(Nov, 2002)
- 初始修订.(Initial revision.)
-
5(5)日(th)2002年11月(Nov, 2002)
- 更新为与MFC一起使用,添加了"临时禁用日志记录”," MFC使用"和"参考".(Updated to work with MFC, “Temporarily disable logging”, “MFC usage” and “References” added.)
-
6(6)日(th)2002年11月(Nov, 2002)
- 添加了对泄漏文件的"说明",“泄漏问题"和"工作原理:详细”.(“Explanation” for leak-file, “A word on leaks” and “How it works: In detail” added.)
-
7(7)日(th)2002年11月(Nov, 2002)
- 实现简单的泄漏输出.(Simple leak output implemented.)
- 解释简单的泄漏输出.(Explaining the simple leak output.)
- 添加了"常见错误".(“Common mistakes” added.)
-
8(8)日(th)2002年11月(Nov, 2002)
- 的解释(Explanation of the)
InitAllocCheck
参数已添加.(parameters added.) - XML输出已添加,“即将到来"和"历史记录"已添加.(XML-output added, “Soon to come” and “History” added.)
- 的解释(Explanation of the)
-
13(13)日(th)2002年11月(Nov, 2002)
- 更新了源代码以在VC6下再次编译(SymDIA未定义,稍后将进行更正).(Updated the source to compile again under VC6 (SymDIA undefined, will be corrected later).)
-
21(21)圣(st)2002年11月(Nov, 2002)
- 更新了源代码,以再次使用UNICODE构建进行编译.(Updated the source to compile again with UNICODE-builds.)
#include <span class="code-string">"stdafx.h"</span>
代替(instead of)#include <span class="code-keyword"><stdafx.h></span>
在MFC演示中.(in MFC-demo.)
-
6(6)日(th)2002年12月(Dec, 2002)
- 已更新,可与UNICODE一起正常使用.(Updated to work correctly with UNICODE.)
- 添加了MemLeakAnalyse工具;删除"即将到来”.(Added MemLeakAnalyse tool; “Soon to come” removed.)
-
19(19)日(th)2002年12月(Dec, 2002)
- 现在只使用(Now using only the)dbghelp.dll(dbghelp.dll). NT用户必须安装(. NT users have to install the)*dbghelp.dll(dbghelp.dll)*可重新分发给NT(请参阅上面的参考).(redistributable for NT (see references above).)
- 在NT/W2K上的VS7中添加了有关如何使用它的注释…(Added comments on how to use it from VS7 on NT/W2K…)
-
8(8)日(th)2003年1月(Jan, 2003)
- 重大更新:添加了对COM泄漏的支持.(Major update: Added support for COM-leaks.)
- MemLeakAnalyse工具的小更新;删除了一些小错误.(Small updates to MemLeakAnalyse tool; removed a few small bugs.)
-
9(9)日(th)2003年1月(Jan, 2003)
- 去除了内部的手柄泄漏(Removed the handle-leaks inside the)
IMallocSpy
接口.(interface.)
- 去除了内部的手柄泄漏(Removed the handle-leaks inside the)
-
23(23)rd(rd)2003年8月(Aug, 2003)
- 更新了一些链接.(Updated a few links.)
-
28(28)日(th)2003年8月(Aug, 2003)
- 更新到更高版本(Updated to the newer version of)dbghelp.dll(dbghelp.dll)((()
StackWalk64
).().) - 添加(Added)
OnlyInstallUnhandeldExceptionFilter
功能;许可证说明:LGPL.(function; License clarification: LGPL.)
- 更新到更高版本(Updated to the newer version of)dbghelp.dll(dbghelp.dll)((()
-
3(3)rd(rd)2003年9月(Sep, 2003)
- 更新了源代码以捕获"堆栈溢出".(Updated the source to catch “stack-overflows”.)
- 删除了一些"内存泄漏查找器内部的内存泄漏";哈希表大小现在是素数.(Removed some “memory leak inside the memory leak finder”; Hashtable-size is now a prime number.)
-
12(12)日(th)2003年9月(Sep, 2003)
- 修复了一个错误(Fixed a bug in)
PreRealloc
;感谢Christoph Weber.(; thanks to Christoph Weber.) - 许可证已更改为" zlib/libpng许可证".(License changed to “zlib/libpng license”.)
- AnalyseTool:添加了命令行支持(您可以在命令行中指定XML文件).(AnalyseTool: added command line support (you can specify the XML file in the command line).)
- AnalyseTool:支持用于查找源文件的其他搜索路径.(AnalyseTool: support for additional search paths for looking up the source-file.)
- 修复了一个错误(Fixed a bug in)
-
19(19)日(th)2005年10月(Oct, 2005)
- 已变更(Changed)
GetThreadContext
我自己的功能,因为XP-SP2中的更改.(to my own function, because of changes in XP-SP2.) - 将许可证更改为(Changed the license to) LGPL(LGPL) .(.)
- 已变更(Changed)
-
21(21)圣(st)2005年11月(Nov, 2005)
- 解决最新的错误(Working around a bug in the newest)dbghelp.dll(dbghelp.dll)(6.5.3.7/8).((6.5.3.7/8).)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
VC7.0 C++ VC6 Win2K WinXP .NET1.0 MFC COM Visual-Studio Dev 新闻 翻译