Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Tracking BSTR Memory Allocations to Find Memory Leaks

5.00/5 (2 votes)
13 Feb 2014CPOL3 min read 23K   130  
If you are looking for memory leaks related to BSTR pointers, this tip might be very relevant for you.

Introduction

If you find yourself needing to track memory leaks related to memory that was allocated using SysAllocString / SysAllocStringLen and you don't have a leak detection tool that can work with your application without crashing / hanging, this tip can help you verify whether or not you found a leaked memory. The tip assumes you are familiar with windbg at least in basic level to debug a process. 

Background

I had to find a memory leak in a process that reached its maximum memory limit as the process is 32 bit and thus limited to 2 GB (can be stretched to 3 GB on 32 bit OS and to 4 GB on 64 bit OS).

I tried using any known tool for the evaluation but had no success to launch the process and monitor its allocations. Some tools just failed to launch while others crash or hang. It was clear that there is no other way of finding the leaks but to use windbg.

After finding the memory allocations that grow the most, I have noticed they are all BSTR strings and had to find the allocation points in order to understand where it was not freed.

BSTR Allocation Tracking

After setting gflags to store the stack traces for the memory allocations, I have run the process again and got the stack trace of the BSTR allocations that were marked as a potential leak.
I searched for the point where these strings were freed and noticed that although there is a call to SysFreeString, the memory is still marked as busy (using winbg command !heap -x <memory address>).
When printing the stack trace, I got a result that showed allocation point that seems like no longer relevant. After "Googling", I have found that SysAllocString may use cache for the allocated memory for BSTR and thus even when you "free" the memory, it is not really free and will probably be returned in the next call to SysAllocString assuming the size is adequate.
This caching behavior might be confusing for searching memory leaks as the stack trace being stored for the memory is the stack trace for the first time this memory was allocated which might not be relevant.

In order to get the correct stack trace of the memory allocation during debugging, you need to define an environment variable named OANOCACHE and set its value to 1.
Restart the debugger so that it will get the updated environment variable's value and run the program being traced. After every call to SysFreeString, you will now see the memory appears as free and printing the stack trace of the memory allocation will show the correct stack trace for each BSTR allocation.

If setting the environment variable doesn't affect the memory caching behavior, a reboot will be needed. 

Code that was Used to Examine the Described Behavior 

C++
#include "stdafx.h"

void AllocateFirstBstr()
{
	BSTR firstStr = SysAllocString(L"The first string to be free");
	SysFreeString(firstStr);
}

void AllocateSecondBSTR()
{
	BSTR secondStr = SysAllocString(L"Second string to be free");
	SysFreeString(secondStr);
}


int _tmain(int argc, _TCHAR* argv[])
{

	AllocateFirstBstr();

	AllocateSecondBSTR();

	return 0;
}

Running the Code under windbg with Caching Enabled (Default Behavior)

Inside method AllocateFirstBstr after the call to SysAllocString 

0:000> dv
       firstStr = 0x004ae2f4 "The first string to be free"
    
    0:000> !heap -p -a 0x004ae2f4 
    address 004ae2f4 found in
    _HEAP @ 460000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        004ae2d8 000b 0000  [00]   004ae2f0    00040 - (busy)
        7704df72 ntdll!RtlAllocateHeap+0x00000274
        74b7ea43 ole32!CRetailMalloc_Alloc+0x00000016
        74aa4557 OLEAUT32!APP_DATA::AllocCachedMem+0x00000060
        74aa460b OLEAUT32!SysAllocStringLen+0x0000003d
        74aa4677 OLEAUT32!SysAllocString+0x0000002c
        105141b DemoAppForBSTRAllocs!AllocateFirstBstr+0x0000002b
        1051473 DemoAppForBSTRAllocs!wmain+0x00000023
        1051a39 DemoAppForBSTRAllocs!__tmainCRTStartup+0x00000199
        1051c2d DemoAppForBSTRAllocs!wmainCRTStartup+0x0000000d
        7679336a kernel32!BaseThreadInitThunk+0x0000000e
        7700bf32 ntdll!__RtlUserThreadStart+0x00000070
        7700bf05 ntdll!_RtlUserThreadStart+0x0000001b

Inside method AllocateFirstBstr after the call to SysFreeString 

0:000> !heap -x 0x004ae2f4
    Entry     User      Heap      Segment       Size  PrevSize  Unused    Flags
    ---------------------------------------------------------------------------
    004ae2d8  004ae2f0  00460000  004a3190        58      -           18  LFH;busy  stack_trace

The memory is marked as busy although a call to SysFreeString was made.

Inside method AllocateSecondBSTR after the call to SysAllocString 

0:000> dv
      secondStr = 0x004ae2f4 "Second string to be free"
0:000> !heap -p -a 0x004ae2f4 
    address 004ae2f4 found in
    _HEAP @ 460000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        004ae2d8 000b 0000  [00]   004ae2f0    00040 - (busy)
        7704df72 ntdll!RtlAllocateHeap+0x00000274
        74b7ea43 ole32!CRetailMalloc_Alloc+0x00000016
        74aa4557 OLEAUT32!APP_DATA::AllocCachedMem+0x00000060
        74aa460b OLEAUT32!SysAllocStringLen+0x0000003d
        74aa4677 OLEAUT32!SysAllocString+0x0000002c
        105141b DemoAppForBSTRAllocs!AllocateFirstBstr+0x0000002b
        1051473 DemoAppForBSTRAllocs!wmain+0x00000023
        1051a39 DemoAppForBSTRAllocs!__tmainCRTStartup+0x00000199
        1051c2d DemoAppForBSTRAllocs!wmainCRTStartup+0x0000000d
        7679336a kernel32!BaseThreadInitThunk+0x0000000e
        7700bf32 ntdll!__RtlUserThreadStart+0x00000070
        7700bf05 ntdll!_RtlUserThreadStart+0x0000001b

In the above stack trace, it can be seen that the memory allocation shows the first time the memory was allocated and not the actual allocation point which is in method AllocateSecondBSTR.

Inside method AllocateSecondBSTR after the call to SysFreeString 

0:000> !heap -x 0x004ae2f4 
Entry     User      Heap      Segment       Size  PrevSize  Unused    Flags
----------------------------------------------------------------------------
004ae2d8  004ae2f0  00460000  004a3190        58      -           18  LFH;busy  stack_trace

Running the Code under windbg Without Caching

Inside method AllocateFirstBstr after the call to SysAllocString 

0:000> dv
       firstStr = 0x004fe33c "The first string to be free"
0:000> !heap -p -a 0x004fe33c 
    address 004fe33c found in
    _HEAP @ 4b0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        004fe320 000b 0000  [00]   004fe338    00040 - (busy)
        7704df72 ntdll!RtlAllocateHeap+0x00000274
        74b7ea43 ole32!CRetailMalloc_Alloc+0x00000016
        74aa4557 OLEAUT32!APP_DATA::AllocCachedMem+0x00000060
        74aa460b OLEAUT32!SysAllocStringLen+0x0000003d
        74aa4677 OLEAUT32!SysAllocString+0x0000002c
        126141b DemoAppForBSTRAllocs!AllocateFirstBstr+0x0000002b
        1261473 DemoAppForBSTRAllocs!wmain+0x00000023
        1261a39 DemoAppForBSTRAllocs!__tmainCRTStartup+0x00000199
        1261c2d DemoAppForBSTRAllocs!wmainCRTStartup+0x0000000d
        7679336a kernel32!BaseThreadInitThunk+0x0000000e
        7700bf32 ntdll!__RtlUserThreadStart+0x00000070
        7700bf05 ntdll!_RtlUserThreadStart+0x0000001b

Inside method AllocateFirstBstr after the call to SysFreeString 

        0:000> !heap -x 0x004fe33c 
Entry     User      Heap      Segment       Size  PrevSize  Unused    Flags
-----------------------------------------------------------------------------
004fe320  004fe328  004b0000  004f3190        58      -            0  LFH;free

When caching is disabled, the memory is marked as free after calling SysFreeString.

Inside method AllocateSecondBSTR after the call to SysAllocString 

0:000> dv
      secondStr = 0x004fe33c "Second string to be free"
    0:000> !heap -p -a 0x004fe33c 
    address 004fe33c found in
    _HEAP @ 4b0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        004fe320 000b 0000  [00]   004fe338    00040 - (busy)
        7704df72 ntdll!RtlAllocateHeap+0x00000274
        74b7ea43 ole32!CRetailMalloc_Alloc+0x00000016
        74aa4557 OLEAUT32!APP_DATA::AllocCachedMem+0x00000060
        74aa460b OLEAUT32!SysAllocStringLen+0x0000003d
        74aa4677 OLEAUT32!SysAllocString+0x0000002c
        1261b0b DemoAppForBSTRAllocs!AllocateSecondBSTR+0x0000002b
        1261478 DemoAppForBSTRAllocs!wmain+0x00000028
        1261a39 DemoAppForBSTRAllocs!__tmainCRTStartup+0x00000199
        1261c2d DemoAppForBSTRAllocs!wmainCRTStartup+0x0000000d
        7679336a kernel32!BaseThreadInitThunk+0x0000000e
        7700bf32 ntdll!__RtlUserThreadStart+0x00000070
        7700bf05 ntdll!_RtlUserThreadStart+0x0000001b

Now, the correct stack trace is being shown so that you can find where exactly the memory was allocated.

Inside method AllocateSecondBSTR after the call to SysFreeString 

0:000> !heap -x 0x004fe33c 
Entry     User      Heap      Segment       Size  PrevSize  Unused    Flags
-----------------------------------------------------------------------------
004fe320  004fe328  004b0000  004f3190        58      -            0  LFH;free

Note: Don't forget to remove the environment variable after finding the leak as it affects the behavior for all other programs as well.

You'll need Skype CreditFree via Skype

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)