windbggflags

How to benefit from heap tagging by DLL?


How do I use and benefit from the GFlags setting Enable heap tagging by DLL?

I know how to activate the setting for a process, but I did not find useful information in the output of !heap -t in WinDbg. I was expecting some output like this:

0:000> !heap -t
Index   Address   Allocated by 
1:      005c0000  MyDll.dll
2:      006b0000  AnotherDll.dll

so that I can identify which heap was created by which DLL and then e.g. identify the source of a memory leak.

Is this a misunderstanding of the term "heap tagging by DLL" or do I need some more commands to get to the desired result?

My research so far:


Solution

  • again a very late answer

    to benefit from HeapTagging you need to create a tag first in your code.
    as far as i know (that is upto xp-sp3) there were no Documented APIS to Create a tag

    (I havent mucked with heap since then so i am not aware of latest apis in os > vista Rewrites were done to heap manager so probably many of the ^^^features^^^ that i post below might have been corrected or bettered or bugs removed )

    in xp-sp3 you can use undocumented RtlCreateTagHeap to create a new tag to either Process Heap or Private Heap

    and after you create tha tag you need to set the global flag 8000 | 800

    htg - Enable heap tagging
    htd - Enable heap tagging by DLL
    

    and theoratically all allocs and frees must get tagged .

    but practically only allocations > 512 kB gets tagged in xp-sp3 with these basic steps

    it either is a bug or a feature that limits tagging to allocations and frees > 512 kB
    HeapAlloc goes through ZwAllocateVirtualMemory in case of Allocations > 512 kB in 32 bit process refer HeapCreate / HeapAlloc Documentation in msdn

    and as a debuging aid you can patch ntdll.dll on the fly to enable tagging for all Allocations and frees .

    below is a sample code that demonstrates the tagging and how to view it all in windbg

    compile using cl /Zi /analyze /W4 <src> /link /RELEASE

    use windbg to execute the app and watch tagging with !heap * -t command

    #include <windows.h>
    #include <stdio.h>
    
    //heaptags are kinda broken or they are intentionally 
    //given only to allocations > 512 kb // allocation > 512 kb
    //go through VirtualAlloc Route for Heap created with maxsize 
    //set to 0 uncomment ALLOCSIZE 0xfdfd2 and recompile to watch 
    // tagging increase by 100% with ALLOCSIZE  0xfdfd1 only 50 allocs 
    // and frees that are > 512 kB will be tagged these magic numbers 
    // are related to comment in HeapCreate Documentation that state 
    // slightly less than 512 kB will be allocated for 32 bit process 
    // tagging can be dramatically increased by patching ntdll when 
    // stopped on system breakpoint patch 7c94b8a4 (xpsp3 ntdll.dll) 
    // use the below command in windbg for finding the offset of pattern
    // command must be in single line no line breaks
    // .foreach /pS 4 /ps 4 ( place  { !grep -i -e call -c 
    // "# call*RtlpUpdateTagEntry 7c900000 l?20000" } ) { ub place }
    // the instruction we are searching to patch is 
    //7c94b8a1 81e3ff0fffff    and     ebx,0FFFF0FFFh 
    // patch 0f to 00 at system breakpoint with eb 7c94b8a1+3 00 
    
    #define BUFFERSIZE 100
    #define ALLOCSIZE  0xfdfd1
    //#define ALLOCSIZE  0xfdfd2
    
    typedef int ( __stdcall *g_RtlCreateTagHeap) ( 
        HANDLE hHeap ,
        void * unknown, 
        wchar_t * BaseString, 
        wchar_t * TagString 
        );
    
    void HeapTagwithHeapAllocPrivate()
    {
        PCHAR pch[BUFFERSIZE] = {};
        HANDLE hHeap    = 0;
        ULONG tag1      = 0;
        ULONG tag2      = 0;
        ULONG tag3      = 0;
        ULONG tag4      = 0;
        ULONG tag5      = 0;
        g_RtlCreateTagHeap RtlCreateTagHeap = 0;
        HMODULE hMod = LoadLibrary("ntdll.dll");
        if(hMod)
        {
            RtlCreateTagHeap = (g_RtlCreateTagHeap) 
                GetProcAddress( hMod,"RtlCreateTagHeap");
        }
        if (hHeap == 0)
        {
            hHeap = HeapCreate(0,0,0);
            if (RtlCreateTagHeap != NULL)
            {
                tag1 = RtlCreateTagHeap (hHeap,0,L"HeapTag!",L"MyTag1");
                tag2 = RtlCreateTagHeap (hHeap,0,L"HeapTag!",L"MyTag2"); 
                tag3 = RtlCreateTagHeap (hHeap,0,L"HeapTag!",L"MyTag3");
                tag4 = RtlCreateTagHeap (hHeap,0,L"HeapTag!",L"MyTag4");
            }
        }
        HANDLE DefHeap = GetProcessHeap();
        if ( (RtlCreateTagHeap != NULL)  && (DefHeap != NULL ))
        {
            tag5 = RtlCreateTagHeap (DefHeap,0,L"HeapTag!",L"MyTag5");
            for ( int i = 0; i < BUFFERSIZE ; i++ )
            {
                pch[i]= (PCHAR) HeapAlloc( DefHeap,HEAP_ZERO_MEMORY| tag5, 1 );
                HeapFree(DefHeap,NULL,pch[i]);
            }
    
        }
        if(hHeap)
        {
            for ( int i = 0; i < BUFFERSIZE ; i++ )
            {
                pch[i]= (PCHAR) HeapAlloc( hHeap,HEAP_ZERO_MEMORY| tag1, 1 );
                //lets leak all allocs patch ntdll to see the tagging details
                //HeapFree(hHeap,NULL,pch[i]);
            }
            for ( int i = 0; i < BUFFERSIZE ; i++ )
            {
                pch[i]= (PCHAR) HeapAlloc( hHeap,HEAP_ZERO_MEMORY| tag2, 100 );
                // lets leak 40% allocs patch ntdll to see the tagging details
                if(i >= 40)
                    HeapFree(hHeap,NULL,pch[i]);
            }
            // slightly less than 512 kb no tagging
            for ( int i = 0; i < BUFFERSIZE / 2 ; i++ ) 
            {
                pch[i]= (PCHAR) HeapAlloc( 
                    hHeap,HEAP_ZERO_MEMORY| tag3, ALLOCSIZE / 2 );
            }
            // > 512 kb  default tagging 
            for ( int i = BUFFERSIZE / 2; i < BUFFERSIZE ; i++ ) 
            {
                pch[i]= (PCHAR) HeapAlloc( 
                    hHeap,HEAP_ZERO_MEMORY | tag4 ,ALLOCSIZE );
            }
            for (int i =0 ; i < BUFFERSIZE ; i++)
            {
                HeapFree(hHeap,NULL,pch[i]);
            }
        }
    }
    void _cdecl main()
    {
        HeapTagwithHeapAllocPrivate();
    }
    

    the compiled exe to be run with windbg as below

    DEFAULT execution and inspection
    **only 50 tags will be visible all of them are > 512 kB Allocations

    cdb -c "g;!heap * -t;q" newheaptag.exe | grep Tag**

    heaptag:\>cdb -c "g;!heap * -t;q" newheaptag.exe | grep Tag
     Tag  Name                   Allocs    Frees   Diff  Allocated
     Tag  Name                   Allocs    Frees   Diff  Allocated
     Tag  Name                   Allocs    Frees   Diff  Allocated
    0004: HeapTag!MyTag4             50       50      0        0
    

    patching ntdll on system breakpoint should make all tags visible

    eb = write byte patch and run the exe on exit inspect heaps with tags cdb -c "eb 7c94b8a1+3 00;g;!heap * -t;q" newheaptag.exe | grep Tag

    heaptag:\>cdb -c "eb 7c94b8a1+3 00;g;!heap * -t;q" newheaptag.exe | grep Tag
     Tag  Name                   Allocs    Frees   Diff  Allocated
    0012: HeapTag!MyTag5            100      100      0        0  <-our tag in process heap
     Tag  Name                   Allocs    Frees   Diff  Allocated
     Tag  Name                   Allocs    Frees   Diff  Allocated
    0001: HeapTag!MyTag1            100        0    100     3200  <--- leak all
    0002: HeapTag!MyTag2            100       60     40     5120  <--- leak 40 %
    0003: HeapTag!MyTag3             50       50      0        0  <--- clean < 512 kB
    0004: HeapTag!MyTag4             50       50      0        0  <----clean > 512 kB