Debug Memory Leaks on Windows Platform
Introduction
A memory leak is a time-consuming bug that rarely gets created and messes up with Memory & Performance of the applications. The detection of memory leaks is often tedious. Things get worst if the code is not written by you, or if the code base is quite huge.
Though there are very good tools available in the market that will help you in memory leak detection, most of these tools are not free. I found Windbg & DebugDiag provided by Microsoft as a freeware powerful tool to solve memory leak bugs on the Windows platform. At least, we get an idea about the code location which might be suspected to cause memory leaks.
In this article, we'll discuss very basic techniques about using Windbg, which is a powerful user/kernel space debugger from Microsoft, which can be downloaded and installed from Here, WinDbg can be used to debug Managed & UnManaged memory leaks
Sample Memory Leak Program:
https://github.com/ShivaGadapa/Low_Level_Design/blob/master/CppMemoryLeak.cpp
Pre-requisites :
Most import things to consider before starting your analysis using Windbg:
Trust me, the below-mentioned things will save lot of your time while tracing the memory leaks, so pay close attention to the details
- Know the "platform type" of your application, whether it's a 32-bit or 64-bit application?
- Create the dump file based on the application "platform type", else you are just wasting time with the acquired dump file
- Users can create Mini-Dump file from Windows Task Manager, To create 32-Bit dump file: "C:\windows\syswow64\Taskmgr.exe" To create 64-Bit dump file: Default windows Task manager
- Get your application Source path & Debug binaries(.PDB) and get it included with WindDbg, using ".sympath+" command under WinDbg prompt, There are cases if your application is built on .Net Framework/Uses Microsoft Libraries, you might need to download Microsoft symbols as well
- Try loading the valid Dependent/External DLL's using ".load" command
You can find all WinDbg Commands at:
http://windbg.info/doc/1-common-cmds.html#20_memory_heap
Step-By-Step :
Step-1:
Now, let's start with running the WinDbg tool, upon installation of Windows-10 SDK, you'll find WinDbg tool in the location the following location: "C:\Program Files (x86)\Windows Kits\10\Debuggers"
Now, again under the Debuggers directory, we have 32-bit WinDbg tool under "\x86" directory & 64-Bit WinDbg tool under "\x64", Run the 32-bit debugger if you're application and the dump file are 32-bit, just to make sure everything is under the same page
Step-2:
a) Open the applicable WinDbg tool, Goto "File" Menu -> Select "Open Crash Dump" -> select the dump file from your local computer, we'll see similar output
b) The application wants you to run the command "!analyze -v", under the WinDbg command prompt, which displays the detailed content about any kind of Exception/Error which you dump file holds
APPLICATION_VERIFIER_FLAGS: 0
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 0000000000000000
ExceptionCode: 80000003 (Break instruction exception)
ExceptionFlags: 00000000
NumberParameters: 0
FAULTING_THREAD: 00004f88
PROCESS_NAME: apple.exe
ERROR_CODE: (NTSTATUS) 0x80000003 - {EXCEPTION} Breakpoint A breakpoint has been reached.
EXCEPTION_CODE_STR: 80000003
STACK_TEXT:
0000001f`772ff1e0 00007fff`16af477c : 00007fff`136cca26 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlpxLookupFunctionTable+0xf2
0000001f`772ff280 00007fff`16af3b99 : 00007fff`16c5a558 0000001f`772ff390 00000000`00000006 00000000`00000000 : ntdll!RtlpLookupFunctionEntryForStackWalks+0x15c
0000001f`772ff2f0 00007fff`16af375a : 0000020b`ec309230 00000000`00000002 0000001f`772ff9d0 00000000`00000000 : ntdll!RtlpWalkFrameChain+0x3e9
0000001f`772ff950 00007fff`16af36d2 : 00000000`00000020 00000000`00000000 00000000`00000001 00000000`00000050 : ntdll!RtlWalkFrameChain+0x2a
0000001f`772ff980 00007fff`16bf6851 : 0000020b`94163850 0000001f`772ffbb9 0000020b`ec350000 00000000`00000011 : ntdll!RtlCaptureStackBackTrace+0x42
0000001f`772ff9b0 00007fff`16be2aa3 : 00000000`00000050 00000000`00000000 0000020b`ec350000 0000001f`772ffbb9 : ntdll!RtlpStackTraceDatabaseLogPrefix+0x51
0000001f`772ffb00 00007fff`16afda74 : 0000020b`94163850 0000020b`ec350000 0000020b`94163850 00007fff`00000000 : ntdll!RtlpCallInterceptRoutine+0x3f
0000001f`772ffb30 00007fff`136cca26 : 00000000`00000001 00000000`00000020 0000020b`ec3585f0 00000000`00000000 : ntdll!RtlpAllocateHeapInternal+0x9e4
0000001f`772ffc20 00007ff6`f3b4101b : 00000000`00215d6f 00000000`00000000 00000000`40000024 00000000`00000000 : ucrtbase!_malloc_base+0x36
0000001f`772ffc50 00007ff6`f3b41279 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : apple!main+0x1b
0000001f`772ffc80 00007fff`16237974 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : apple!__scrt_common_main_seh+0x11d
0000001f`772ffcc0 00007fff`16b5a271 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : kernel32!BaseThreadInitThunk+0x14
0000001f`772ffcf0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
STACK_COMMAND: ~0s; .ecxr ; kb
FAULTING_SOURCE_LINE: c:\users\apple\apple.cpp
FAULTING_SOURCE_FILE: c:\users\apple\apple.cpp
FAULTING_SOURCE_LINE_NUMBER: 30
FAULTING_SOURCE_CODE:
13:
14:
15:
16:
> 17:
18:
19:
20:
21:
22:
SYMBOL_NAME: apple!main+1b
MODULE_NAME: apple
IMAGE_NAME: apple.exe
FAILURE_BUCKET_ID: BREAKPOINT_80000003_apple.exe!main
OS_VERSION: 10.0.17763.1
BUILDLAB_STR: rs5_release
OSPLATFORM_TYPE: x64
OSNAME: Windows 10
FAILURE_ID_HASH: {41426f55-f2e5-465c-c1b9-93557afa13e4}
Followup: MachineOwner
c) Now, if you have all the symbols(.DLL's or .PDB's or Application Source path) related to your application are loaded correctly, then you can find the information related to the faulty module, the file name and also the line number which is causing the memory leak in the above output
d) If you can't find any information related to your project or the output doesn't make any sense, just include the symbol path using the command: ".sympath+ C:\Temp\MyProject\SourceFolder" and if you're application dependent on other DLL's then load the respective module using the command ".load C:\Temp\3rdPartyLibs\Abc.dll", you can load multiple symbol path and valid DLL's, to get the accurate results from the WinDbg commands
Step-3:
Incomplete Call Stack Output:
Run command "k", to view the call stack, Sometimes even after including all the symbols, source information the debugger won't be able to display the valid output, The below output(incomplete call stack) tells us that there are still some deferred symbols apple!main+0x1b --[c:\users\sg185301\source\repos\apple\apple\apple.cpp @ 30], but we actually know that problem residing inside multiple function calls which are stacked up
# Child-SP RetAddr Call Site 00 0000001f`772ff1e0 00007fff`16af477c ntdll!RtlpxLookupFunctionTable+0xf2 01 0000001f`772ff280 00007fff`16af3b99 ntdll!RtlpLookupFunctionEntryForStackWalks+0x15c 02 0000001f`772ff2f0 00007fff`16af375a ntdll!RtlpWalkFrameChain+0x3e9 03 0000001f`772ff950 00007fff`16af36d2 ntdll!RtlWalkFrameChain+0x2a 04 0000001f`772ff980 00007fff`16bf6851 ntdll!RtlCaptureStackBackTrace+0x42 05 0000001f`772ff9b0 00007fff`16be2aa3 ntdll!RtlpStackTraceDatabaseLogPrefix+0x51 06 0000001f`772ffb00 00007fff`16afda74 ntdll!RtlpCallInterceptRoutine+0x3f 07 0000001f`772ffb30 00007fff`136cca26 ntdll!RtlpAllocateHeapInternal+0x9e4 08 0000001f`772ffc20 00007ff6`f3b4101b ucrtbase!_malloc_base+0x36 0b 0000001f`772ffc50 00007ff6`f3b41279 apple!main+0x1b [c:\users\Temp\apple\apple\apple.cpp @ 30] 0c (Inline Function) --------`-------- apple!invoke_main+0x22 [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78] 0d 0000001f`772ffc80 00007fff`16237974 apple!__scrt_common_main_seh+0x11d [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 283] 0e 0000001f`772ffcc0 00007fff`16b5a271 kernel32!BaseThreadInitThunk+0x14 0f 0000001f`772ffcf0 00000000`00000000 ntdll!RtlUserThreadStart+0x21
You can verify if things are loaded correctly using the following commands:
Run ".sympath" (To Verify Debug symbols & Source Path info)
************* Path validation summary ************** Response Time (ms) Location Deferred srv* OK C:\Temp\apple
Run ".lm", (To Verify Deferred Modules & Symbols info)
0:000> lm start end module name 00007ff6`f3b40000 00007ff6`f3b47000 apple C (private pdb symbols) C:\ProgramData\dbg\sym\apple.pdb\E413EC11B66148EB843C815F8205CFC58\apple.pdb 00007fff`105e0000 00007fff`105f7000 VCRUNTIME140 (deferred) 00007fff`136c0000 00007fff`137ba000 ucrtbase (pdb symbols) C:\ProgramData\dbg\sym\ucrtbase.pdb\012A78335BB70F0E966EBE286E5364741\ucrtbase.pdb 00007fff`139c0000 00007fff`13c55000 KERNELBASE (pdb symbols) C:\ProgramData\dbg\sym\kernelbase.pdb\E7C1891B988087BD7F07596A6F866BC71\kernelbase.pdb 00007fff`16220000 00007fff`162d3000 kernel32 (pdb symbols) C:\ProgramData\dbg\sym\kernel32.pdb\2662E0289AC6F2BAEE6190EB49F51E071\kernel32.pdb 00007fff`16af0000 00007fff`16cdd000 ntdll (pdb symbols) C:\ProgramData\dbg\sym\ntdll.pdb\FF98FE65248A4F35130AD2BCEDC0BE371\ntdll.pdb
Run ".ld *", to load the unloaded symbols or Deferred Symbols/Links
Step-4:
After verification, now run the final command to view the detailed call stack of your application
Run "k" command, Bingo... Now the results are quite interesting, Now I can see at line-16 in Func2(), the problem exists
0:000> k # Child-SP RetAddr Call Site 00 0000001f`772ff1e0 00007fff`16af477c ntdll!RtlpxLookupFunctionTable+0xf2 01 0000001f`772ff280 00007fff`16af3b99 ntdll!RtlpLookupFunctionEntryForStackWalks+0x15c 02 0000001f`772ff2f0 00007fff`16af375a ntdll!RtlpWalkFrameChain+0x3e9 03 0000001f`772ff950 00007fff`16af36d2 ntdll!RtlWalkFrameChain+0x2a 04 0000001f`772ff980 00007fff`16bf6851 ntdll!RtlCaptureStackBackTrace+0x42 05 0000001f`772ff9b0 00007fff`16be2aa3 ntdll!RtlpStackTraceDatabaseLogPrefix+0x51 06 0000001f`772ffb00 00007fff`16afda74 ntdll!RtlpCallInterceptRoutine+0x3f 07 0000001f`772ffb30 00007fff`136cca26 ntdll!RtlpAllocateHeapInternal+0x9e4 08 0000001f`772ffc20 00007ff6`f3b4101b ucrtbase!_malloc_base+0x36 09 (Inline Function) --------` apple!Func2+0x15 [c:\Temp\apple\apple.cpp @ 16] 0a (Inline Function) --------` apple!Func1+0x15 [c:\Temp\apple\apple.cpp @ 23] 0b 0000001f`772ffc50 00007ff6` apple!main+0x1b [c:\Temp\apple\apple.cpp @ 30] 0c (Inline Function) --------`-------- apple!invoke_main+0x22 [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78] 0d 0000001f`772ffc80 00007fff`16237974 apple!__scrt_common_main_seh+0x11d [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 283] 0e 0000001f`772ffcc0 00007fff`16b5a271 kernel32!BaseThreadInitThunk+0x14 0f 0000001f`772ffcf0 00000000`00000000 ntdll!RtlUserThreadStart+0x21
Very good Article Shiva