/*++

Copyright (c) 1992  Microsoft Corporation

Module Name:

    heapmon.c

Abstract:

    This program monitors the heap usage of another process and updates
    its display every 10 seconds

Author:

    Steve Wood (stevewo) 01-Nov-1994

Revision History:

--*/

#include <ntos.h>
#include <nturtl.h>
#include <windows.h>
#include <dbghelp.h>
#include <stdio.h>
#include <stdlib.h>

void
Usage( void )
{
    fputs( "Usage: HEAPMON [-?] [-1] [-p id] [-t | -a | -f | -d | [-u | -b]] [-( | -)] [-e]\n"
           "where: -? displays this message.\n"
           "       -1 specifies to monitor the Win32 subsystem\n"
           "       -p specifies the process to monitor\n"
           "          Default is to monitor the Win32 subsystem\n"
           "       -t sort output by tag name\n"
           "       -a sort output by #allocations\n"
           "       -f sort output by #frees\n"
           "       -d sort output by #allocations - #frees\n"
           "       -u sort output by bytes used\n"
           "       -b same as -u\n"
           "       -( Changes #allocations and #frees above to be #bytes\n"
           "          allocated and freed\n"
           "       -) same as -(\n"
           "       -e enables display of total lines\n"
           "       -l enables highlighing of changed lines\n"
           "\n"
           "While HEAPMON is running you can type any of the following\n"
           "switch characters to change the output:\n"
           "       t - sort output by tag name\n"
           "       a - sort output by #allocations\n"
           "       f - sort output by #frees\n"
           "       d - sort output by #allocations - #frees\n"
           "       u or b - specifies the sort output by bytes used\n"
           "       ( or ) - toggles interpretation of #allocations and #frees above\n"
           "                to be #bytes allocated and freed.\n"
           "       e - toggles display of total lines.\n"
           "       l - toggles highlighing of changed lines\n"
           "       ? or h - displays help text\n"
           "       q - quit the program.\n"
           , stderr);
    ExitProcess( 1 );
}

#define TAG 0
#define ALLOC 1
#define FREE 2
#define DIFF 3
#define BYTES 4

BOOL fFirstTimeThrough;
BOOL fDisplayTotals;
BOOL fHighlight = TRUE;
BOOL fParen;
BOOL fInteractive;
BOOL fQuit;
BOOL fHelp;
ULONG DelayTimeMsec = 5000;
ULONG SortBy = TAG;
HANDLE InputHandle;
DWORD OriginalInputMode;
HANDLE OriginalOutputHandle;
HANDLE OutputHandle;
COORD ConsoleBufferSize;
CONSOLE_SCREEN_BUFFER_INFO OriginalConsoleInfo;
WORD NormalAttribute;
WORD HighlightAttribute;
ULONG NumberOfCols;
ULONG NumberOfRows;
ULONG NumberOfDetailLines;
ULONG FirstDetailLine;
ULONG NumberOfInputRecords;
INPUT_RECORD InputRecord;

typedef struct _HEAP_ENTRY {
    struct _HEAP_ENTRY *Next;
    BOOL  Changed;
    PVOID HeapBase;
    PCHAR HeapName;
    SIZE_T BytesAllocated;
    SIZE_T BytesCommitted;
} HEAP_ENTRY, *PHEAP_ENTRY;

typedef struct _TAG_COUNTS {
    SIZE_T Allocs;
    SIZE_T Frees;
    SIZE_T Used;
    SIZE_T Allocs_Frees;
    SIZE_T UsedPerAlloc;
} TAG_COUNTS, *PTAG_COUNTS;

typedef struct _TAG_TOTALS {
    BOOL Changed;
    TAG_COUNTS Counts;
    TAG_COUNTS Differences;
} TAG_TOTALS, *PTAG_TOTALS;

typedef struct _TAG_ENTRY {
    struct _TAG_ENTRY *Next;
    PCHAR HeapName;
    PCHAR TagName;
    PVOID HeapBase;
    USHORT TagIndex;
    BOOL Changed;
    TAG_COUNTS Counts;
    TAG_COUNTS PrevCounts;
    TAG_COUNTS Differences;
} TAG_ENTRY, *PTAG_ENTRY;


ULONG HeapListLength;
PHEAP_ENTRY HeapListHead;
ULONG TagListLength;
PTAG_ENTRY TagListHead, *TagArray;
TAG_TOTALS TagTotals;

VOID
ShowHelpPopup( VOID );

VOID
UpdateDisplay( VOID );

VOID
DumpTagDataBase( VOID );

BOOLEAN
UpdateTagDataBase(
    PRTL_DEBUG_INFORMATION DebugInfo
    );

BOOLEAN
UpdateHeapDataBase(
    PRTL_DEBUG_INFORMATION DebugInfo
    );

PCHAR
GetNameForHeapBase(
    PVOID HeapBase
    );

PVOID
CreateNameTable(
    IN ULONG NumberOfBuckets
    );

PCHAR
AddNameToNameTable(
    IN PVOID pNameTable,
    IN PCHAR Name
    );

PVOID NameTable;

BOOL
ProcessOptionCharacter(
    IN CHAR c
    );

VOID
ScreenUpdateLoop(
    PRTL_DEBUG_INFORMATION p
    );

int
__cdecl main(
    int argc,
    char *argv[]
    )
{
    DWORD_PTR ProcessId;
    PCHAR s, s1;
    NTSTATUS Status;
    PRTL_DEBUG_INFORMATION p;
    SMALL_RECT NewWindowRect;

    NameTable = CreateNameTable( 37 );

    ProcessId = 0xFFFFFFFF;
    fHelp = FALSE;
    fInteractive = TRUE;
    while (--argc) {
        s = *++argv;
        if (*s == '-' || *s == '/') {
            while (*++s) {
                if (!ProcessOptionCharacter( *s )) {
                    switch( toupper( *s ) ) {
                        case '1':
                            fQuit = TRUE;
                            fInteractive = FALSE;
                            break;

                        case 'P':
                            if (--argc) {
                                ProcessId = atoi( *++argv );
                                }
                            else {
                                fprintf( stderr, "HEAPMON: missing argument to -p switch\n" );
                                fHelp = TRUE;
                                }
                            break;

                        default:
                            fprintf( stderr, "HEAPMON: invalid switch - /%c\n", *s );
                            fHelp = TRUE;
                            break;
                        }
                    }
                }
            }
        else {
            fprintf( stderr, "HEAPMON: invalid argument - %s\n", s );
            fHelp = TRUE;
            }
        }

    if (fHelp) {
        Usage();
        }

    if (ProcessId == -1) {
        HANDLE Process;
        OBJECT_ATTRIBUTES ObjectAttributes;
        UNICODE_STRING UnicodeString;
        PROCESS_BASIC_INFORMATION BasicInfo;

        RtlInitUnicodeString( &UnicodeString, L"\\WindowsSS" );
        InitializeObjectAttributes( &ObjectAttributes,
                                    &UnicodeString,
                                    0,
                                    NULL,
                                    NULL
                                  );
        Status = NtOpenProcess( &Process,
                                PROCESS_ALL_ACCESS,
                                &ObjectAttributes,
                                NULL
                              );
        if (NT_SUCCESS(Status)) {
            Status = NtQueryInformationProcess( Process,
                                                ProcessBasicInformation,
                                                (PVOID)&BasicInfo,
                                                sizeof(BasicInfo),
                                                NULL
                                              );
            NtClose( Process );
            }

        if (!NT_SUCCESS(Status)) {
            fprintf( stderr, "HEAPMON: Unable to access Win32 server process - %08x", Status );
            if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {
                fprintf( stderr, "\nUse GFLAGS.EXE to ""Enable debugging of Win32 Subsystem"" and reboot.\n" );
                }
            ExitProcess( 1 );
            }

        ProcessId = BasicInfo.UniqueProcessId;
        }

    InputHandle = GetStdHandle( STD_INPUT_HANDLE );
    OriginalOutputHandle = GetStdHandle( STD_OUTPUT_HANDLE );
    if (fInteractive) {
        if (InputHandle == NULL ||
            OriginalOutputHandle == NULL ||
            !GetConsoleMode( InputHandle, &OriginalInputMode )
           ) {
            fInteractive = FALSE;
            }
        else {
            OutputHandle = CreateConsoleScreenBuffer( GENERIC_READ | GENERIC_WRITE,
                                                      FILE_SHARE_WRITE | FILE_SHARE_READ,
                                                      NULL,
                                                      CONSOLE_TEXTMODE_BUFFER,
                                                      NULL
                                                    );
            if (OutputHandle == NULL ||
                !GetConsoleScreenBufferInfo( OriginalOutputHandle, &OriginalConsoleInfo ) ||
                !SetConsoleScreenBufferSize( OutputHandle, OriginalConsoleInfo.dwSize ) ||
                !SetConsoleActiveScreenBuffer( OutputHandle ) ||
                !SetConsoleMode( InputHandle, 0 )
               ) {
                if (OutputHandle != NULL) {
                    CloseHandle( OutputHandle );
                    OutputHandle = NULL;
                    }

                fInteractive = FALSE;
                }
            else {
                NormalAttribute = 0x1F;
                HighlightAttribute = 0x71;
                NumberOfCols = OriginalConsoleInfo.dwSize.X;
                NumberOfRows = OriginalConsoleInfo.dwSize.Y;
                NumberOfDetailLines = NumberOfRows;
                }
            }
        }

    p = RtlCreateQueryDebugBuffer( 0, TRUE );
    if (p == NULL) {
        fprintf( stderr, "HEAPMON: Unable to create query buffer.\n" );
        ExitProcess( 1 );
        }

    if (GetPriorityClass( GetCurrentProcess() ) == NORMAL_PRIORITY_CLASS) {
        SetPriorityClass( GetCurrentProcess(), HIGH_PRIORITY_CLASS );
        }

    Status = RtlQueryProcessDebugInformation( (HANDLE)ProcessId,
                                              RTL_QUERY_PROCESS_MODULES |
                                              RTL_QUERY_PROCESS_HEAP_SUMMARY |
                                              RTL_QUERY_PROCESS_HEAP_TAGS,
                                              p
                                            );
    if (!NT_SUCCESS( Status )) {
        fprintf( stderr, "HEAPMON: Unable to query heap tags from Process %u (%x)\n", ProcessId, Status );
        fprintf( stderr, "         Be sure target process was launched with the\n" );
        fprintf( stderr, "         'Enable heap tagging' option enabled.\n" );
        fprintf( stderr, "         Use the GFLAGS.EXE application to do this.\n" );
        ExitProcess( 1 );
        }

    ScreenUpdateLoop( p );

    RtlDestroyQueryDebugBuffer( p );

    if (fInteractive) {
        SetConsoleActiveScreenBuffer( OriginalOutputHandle );
        SetConsoleMode( InputHandle, OriginalInputMode );
        CloseHandle( OutputHandle );
        }

    ExitProcess( 0 );
    return 0;
}


VOID
ScreenUpdateLoop(
    PRTL_DEBUG_INFORMATION p
    )
{
    NTSTATUS Status;
    COORD cp;
    COORD newcp;
    COORD originalCp;
    LONG ScrollDelta;
    ULONG i, MaxLines;
    UCHAR LastKey = 0;

    fFirstTimeThrough = TRUE;
    while (TRUE) {
        if (!UpdateTagDataBase( p )) {
            fprintf( stderr, "HEAPMON: Unable to compute tag data base\n" );
            break;
            }
        fFirstTimeThrough = FALSE;

        if (fInteractive) {
            UpdateDisplay();
            while (WaitForSingleObject( InputHandle, DelayTimeMsec ) == STATUS_WAIT_0) {
                //
                // Check for input record
                //
                if (ReadConsoleInput( InputHandle, &InputRecord, 1, &NumberOfInputRecords ) &&
                    InputRecord.EventType == KEY_EVENT &&
                    InputRecord.Event.KeyEvent.bKeyDown
                   ) {
                    LastKey = InputRecord.Event.KeyEvent.uChar.AsciiChar;
                    if (!ProcessOptionCharacter( LastKey )
                       ) {
                        if (LastKey < ' ') {
                            ScrollDelta = 0;
                            if (LastKey == 'C'-'A'+1) {
                                fQuit = TRUE;
                                }
                            else
                            switch (InputRecord.Event.KeyEvent.wVirtualKeyCode) {
                                case VK_ESCAPE:
                                    fQuit = TRUE;
                                    break;

                                case VK_PRIOR:
                                    ScrollDelta = -(LONG)(InputRecord.Event.KeyEvent.wRepeatCount * NumberOfDetailLines);
                                    break;

                                case VK_NEXT:
                                    ScrollDelta = InputRecord.Event.KeyEvent.wRepeatCount * NumberOfDetailLines;
                                    break;

                                case VK_UP:
                                    ScrollDelta = -InputRecord.Event.KeyEvent.wRepeatCount;
                                    break;

                                case VK_DOWN:
                                    ScrollDelta = InputRecord.Event.KeyEvent.wRepeatCount;
                                    break;

                                case VK_HOME:
                                    FirstDetailLine = 0;
                                    break;

                                case VK_END:
                                    FirstDetailLine = TagListLength - NumberOfDetailLines;
                                    break;
                                }

                            if (ScrollDelta != 0) {
                                if (ScrollDelta < 0) {
                                    if (FirstDetailLine <= (ULONG)-ScrollDelta) {
                                        FirstDetailLine = 0;
                                        ScrollDelta = 0;
                                        }
                                    }
                                FirstDetailLine += ScrollDelta;
                                if (FirstDetailLine >= (TagListLength - NumberOfDetailLines)) {
                                    FirstDetailLine = TagListLength - NumberOfDetailLines;
                                    }
                                }

                            if (FirstDetailLine > TagListLength) {
                                FirstDetailLine = TagListLength;
                                }
                            }
                        else {
                            switch (toupper( LastKey )) {
                                case 'Q':
                                    //
                                    //  Go to the bottom of the current screen when
                                    //  we quit.
                                    //
                                    fQuit = TRUE;
                                    break;
                                }
                            }
                        }
                    else {
                        FirstDetailLine = 0;
                        }

                    break;
                    }
                }

            if (fQuit) {
                break;
                }

            if (fHelp) {
                fHelp = FALSE;
                ShowHelpPopup();
                }
            }
        else {
            DumpTagDataBase();

            if (fQuit) {
                break;
                }

            Sleep( DelayTimeMsec );
            }

        Status = RtlQueryProcessDebugInformation( p->TargetProcessId,
                                                  p->Flags,
                                                  p
                                                );
        if (!NT_SUCCESS( Status )) {
            fprintf( stderr, "HEAPMON: Unable to update heap tags from Process %p (%x)\n", p->TargetProcessId, Status );
            break;
            }
        }

    return;
}

BOOL
WriteConsoleLine(
    HANDLE OutputHandle,
    WORD LineNumber,
    LPSTR Text,
    BOOL Highlight
    )
{
    COORD WriteCoord;
    DWORD NumberWritten;
    DWORD TextLength;

    WriteCoord.X = 0;
    WriteCoord.Y = LineNumber;
    if (!FillConsoleOutputCharacter( OutputHandle,
                                     ' ',
                                     NumberOfCols,
                                     WriteCoord,
                                     &NumberWritten
                                   )
       ) {
        return FALSE;
        }

    if (!FillConsoleOutputAttribute( OutputHandle,
                                     (WORD)((Highlight && fHighlight) ? HighlightAttribute : NormalAttribute),
                                     NumberOfCols,
                                     WriteCoord,
                                     &NumberWritten
                                   )
       ) {
        return FALSE;
        }


    if (Text == NULL || (TextLength = strlen( Text )) == 0) {
        return TRUE;
        }
    else {
        return WriteConsoleOutputCharacter( OutputHandle,
                                            Text,
                                            TextLength,
                                            WriteCoord,
                                            &NumberWritten
                                          );
        }
}


VOID
ShowHelpPopup( VOID )
{
    HANDLE PopupHandle;
    WORD n;

    PopupHandle = CreateConsoleScreenBuffer( GENERIC_READ | GENERIC_WRITE,
                                             FILE_SHARE_WRITE | FILE_SHARE_READ,
                                             NULL,
                                             CONSOLE_TEXTMODE_BUFFER,
                                             NULL
                                           );
    if (PopupHandle == NULL) {
        return;
        }

    SetConsoleActiveScreenBuffer( PopupHandle );
    n = 0;

    WriteConsoleLine( PopupHandle, n++, NULL, FALSE );
    WriteConsoleLine( PopupHandle, n++, "                HeapMon Help", FALSE );
    WriteConsoleLine( PopupHandle, n++, NULL, FALSE );
    WriteConsoleLine( PopupHandle, n++, " columns:", FALSE );
    WriteConsoleLine( PopupHandle, n++, "   Heap Name is the name or hex address of the heap", FALSE );
    WriteConsoleLine( PopupHandle, n++, "   Tag Name is a string given to the heap allocation", FALSE );
    WriteConsoleLine( PopupHandle, n++, "       For untagged allocations, the tag name is a function of the size", FALSE );
    WriteConsoleLine( PopupHandle, n++, "       Objects=  32 - objects of size 32 bytes", FALSE );
    WriteConsoleLine( PopupHandle, n++, "       Objects>1024 - objects larger than 1024 bytes are lumped under this tag", FALSE );
    WriteConsoleLine( PopupHandle, n++, "       VirtualAlloc - objects larger than 1MB bytes are lumped under this tag", FALSE );
    WriteConsoleLine( PopupHandle, n++, NULL, FALSE );
    WriteConsoleLine( PopupHandle, n++, "   Allocations is count of all alloctions", FALSE );
    WriteConsoleLine( PopupHandle, n++, "   (   ) is difference in Allocations column from last update", FALSE );
    WriteConsoleLine( PopupHandle, n++, NULL, FALSE );
    WriteConsoleLine( PopupHandle, n++, "   Frees is count of all frees", FALSE );
    WriteConsoleLine( PopupHandle, n++, "   (   ) difference in Frees column from last update", FALSE );
    WriteConsoleLine( PopupHandle, n++, NULL, FALSE );
    WriteConsoleLine( PopupHandle, n++, "   Diff is (Allocations - Frees)", FALSE );
    WriteConsoleLine( PopupHandle, n++, NULL, FALSE );
    WriteConsoleLine( PopupHandle, n++, "   Bytes Used is the total bytes consumed in heap", FALSE );
    WriteConsoleLine( PopupHandle, n++, "   (   ) difference in Bytes column from last update", FALSE );
    WriteConsoleLine( PopupHandle, n++, NULL, FALSE );
    WriteConsoleLine( PopupHandle, n++, " switches:                                                                     ", FALSE );
    WriteConsoleLine( PopupHandle, n++, "   ? or h - gives this help", FALSE );
    WriteConsoleLine( PopupHandle, n++, "   q - quits", FALSE );
    WriteConsoleLine( PopupHandle, n++, "   e - toggles totals lines on and off", FALSE );
    WriteConsoleLine( PopupHandle, n++, "   l - toggles highlighting of changed lines on and off", FALSE );
    WriteConsoleLine( PopupHandle, n++, NULL, FALSE );
    WriteConsoleLine( PopupHandle, n++, " sorting switches:", FALSE );
    WriteConsoleLine( PopupHandle, n++, "   t - tag    a - allocations", FALSE );
    WriteConsoleLine( PopupHandle, n++, "   f - frees  d - difference", FALSE );
    WriteConsoleLine( PopupHandle, n++, "   b - bytes  (u is the same as b)", FALSE );
    WriteConsoleLine( PopupHandle, n++, NULL, FALSE );
    WriteConsoleLine( PopupHandle, n++, "   ) - toggles sort between primary value and value in (  )", FALSE );
    WriteConsoleLine( PopupHandle, n++, NULL, FALSE );
    WriteConsoleLine( PopupHandle, n++, " command line switches", FALSE );
    WriteConsoleLine( PopupHandle, n++, "   -eltafdbu) - as listed above", FALSE );
    WriteConsoleLine( PopupHandle, n++, NULL, FALSE );
    WriteConsoleLine( PopupHandle, n++, NULL, FALSE );

    while (TRUE) {
        if (WaitForSingleObject( InputHandle, DelayTimeMsec ) == STATUS_WAIT_0 &&
            ReadConsoleInput( InputHandle, &InputRecord, 1, &NumberOfInputRecords ) &&
            InputRecord.EventType == KEY_EVENT &&
            InputRecord.Event.KeyEvent.bKeyDown &&
            InputRecord.Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE
           ) {
            break;
        }
    }

    SetConsoleActiveScreenBuffer( OutputHandle );
    CloseHandle( PopupHandle );
    return;
}

VOID
UpdateDisplay( VOID )
{
    ULONG HeapLines, DetailLines, SummaryLines;
    WORD DisplayLine;
    PHEAP_ENTRY pHeap;
    PTAG_ENTRY p, *pp;
    CHAR Buffer[ 512 ];

    HeapLines = HeapListLength;
    if (fDisplayTotals) {
        SummaryLines = 2;
        RtlZeroMemory( &TagTotals, sizeof( TagTotals ) );
        }
    else {
        SummaryLines = 0;
        }
    DetailLines = NumberOfRows - HeapLines - SummaryLines - 3;
    NumberOfDetailLines = DetailLines;
    if (DetailLines > (TagListLength - FirstDetailLine)) {
        DetailLines = TagListLength - FirstDetailLine;
        }

    DisplayLine = 0;
    WriteConsoleLine( OutputHandle,
                      DisplayLine++,
                      "Heap Name                Address  Allocated      Committed        Free",
                      FALSE
                    );

    pHeap = HeapListHead;
    while (pHeap != NULL && HeapLines--) {
        sprintf( Buffer,
                 "%-20.20s     %p %8u        %8u      %8u",
                 pHeap->HeapName,
                 pHeap->HeapBase,
                 pHeap->BytesAllocated,
                 pHeap->BytesCommitted,
                 pHeap->BytesCommitted - pHeap->BytesAllocated
               );
        WriteConsoleLine( OutputHandle,
                          DisplayLine++,
                          Buffer,
                          pHeap->Changed
                        );

        pHeap->Changed = FALSE;

        pHeap = pHeap->Next;
        }

    pp = &TagArray[ FirstDetailLine ];
    WriteConsoleLine( OutputHandle,
                      DisplayLine++,
                      "Heap Name            Tag Name             Allocations           Frees     Diff      Bytes Used",
                      FALSE
                    );
    while (DetailLines--) {
        p = *pp++;
        sprintf( Buffer,
                 "%-20.20s %-14.14s %8u (%6u) %8u (%6u) %6u %8u (%6u)",
                 p->HeapName,
                 p->TagName,
                 p->Counts.Allocs,
                 p->Differences.Allocs,
                 p->Counts.Frees,
                 p->Differences.Frees,
                 p->Counts.Allocs_Frees,
                 p->Counts.Used,
                 p->Differences.Used
               );
        if (fDisplayTotals) {
            TagTotals.Counts.Allocs += p->Counts.Allocs;
            TagTotals.Differences.Allocs += p->Differences.Allocs;
            TagTotals.Counts.Frees += p->Counts.Frees;
            TagTotals.Differences.Frees += p->Differences.Frees;
            TagTotals.Counts.Allocs_Frees += p->Counts.Allocs_Frees;
            TagTotals.Differences.Allocs_Frees += p->Counts.Used;
            TagTotals.Differences.Used += p->Differences.Used;
            TagTotals.Changed |= p->Changed;
            }

        WriteConsoleLine( OutputHandle,
                          DisplayLine++,
                          Buffer,
                          p->Changed
                        );

        p->Changed = FALSE;
        }

    while (SummaryLines--) {
        if (SummaryLines) {
            WriteConsoleLine( OutputHandle,
                              DisplayLine++,
                              NULL,
                              FALSE
                            );
            }
        else {
            sprintf( Buffer,
                     "%-20.20s %-14.14s %8u (%6u) %8u (%6u) %6u %8u (%6u)",
                     "Totals",
                     "",
                     TagTotals.Counts.Allocs,
                     TagTotals.Differences.Allocs,
                     TagTotals.Counts.Frees,
                     TagTotals.Differences.Frees,
                     TagTotals.Counts.Allocs_Frees,
                     TagTotals.Differences.Allocs_Frees,
                     TagTotals.Differences.Used
                   );
            WriteConsoleLine( OutputHandle,
                              DisplayLine++,
                              Buffer,
                              TagTotals.Changed
                            );
            }
        }

    while (DisplayLine < NumberOfRows) {
        WriteConsoleLine( OutputHandle,
                          DisplayLine++,
                          NULL,
                          FALSE
                        );
        }

    return;
}

VOID
DumpTagDataBase( VOID )
{
    ULONG i;
    PTAG_ENTRY p;

    for (i=0; i<TagListLength; i++) {
        p = TagArray[ i ];
        if (p->Changed && (p->Counts.Used != 0)) {
            printf( "%-14.14s%-20.20s %8u (%6u) %8u (%6u) %6u %8u (%6u)\n",
                    p->HeapName,
                    p->TagName,
                    p->Counts.Allocs,
                    p->Differences.Allocs,
                    p->Counts.Frees,
                    p->Differences.Frees,
                    p->Counts.Allocs_Frees,
                    p->Counts.Used,
                    p->Differences.Used
                  );
            p->Changed = FALSE;
            }
        }

    return;
}

__inline int DiffSizeT(SIZE_T s1, SIZE_T s2)
{
    if (s1 == s2)
        return 0;

    if (s1 > s2)
        return -1;
    else
        return 1;
}

int
__cdecl
CompareTagFunction(
    const void *e1,
    const void *e2
    )
{
    int Result;
    PTAG_ENTRY p1, p2;
    SIZE_T s1, s2;

    p1 = *(PTAG_ENTRY *)e1;
    p2 = *(PTAG_ENTRY *)e2;

    switch (SortBy) {
        case TAG:
            Result = _stricmp( p1->HeapName, p2->HeapName );
            if (!Result) {
                Result = _stricmp( p1->TagName, p2->TagName );
                }
            return Result;

        case ALLOC:
            if (fParen) {
                return DiffSizeT(p2->Differences.Allocs, p1->Differences.Allocs);
                }
            else {
                return DiffSizeT(p2->Counts.Allocs, p1->Counts.Allocs);
                }

        case FREE:
            if (fParen) {
                return DiffSizeT(p2->Differences.Frees, p1->Differences.Frees);
                }
            else {
                return DiffSizeT(p2->Counts.Frees, p1->Counts.Frees);
                }

        case BYTES:
            if (fParen) {
                return DiffSizeT(p2->Differences.Used, p1->Differences.Used);
                }
            else {
                return DiffSizeT(p2->Counts.Used, p1->Counts.Used);
                }

        case DIFF:
            return DiffSizeT(p2->Counts.Allocs_Frees, p1->Counts.Allocs_Frees);
    }

    return(0);
}


BOOLEAN
UpdateTagDataBase(
    PRTL_DEBUG_INFORMATION DebugInfo
    )
{
    PTAG_ENTRY p, p1, *pp;
    PLIST_ENTRY Next, Head;
    ULONG HeapNumber;
    PRTL_PROCESS_HEAPS Heaps;
    PRTL_HEAP_INFORMATION HeapInfo;
    PRTL_HEAP_TAG HeapTagEntry;
    PVOID HeapNameBase;
    PCHAR HeapName;
    ULONG TagIndex;
    UCHAR Buffer[ MAX_PATH ];
    BOOL CalcDifferences;

    if (!UpdateHeapDataBase( DebugInfo )) {
        return FALSE;
        }

    HeapNameBase = INVALID_HANDLE_VALUE;

    pp = &TagListHead;
    Heaps = DebugInfo->Heaps;
    HeapInfo = &Heaps->Heaps[ 0 ];
    for (HeapNumber = 0; HeapNumber < Heaps->NumberOfHeaps; HeapNumber++) {
        if (HeapInfo->Tags != NULL && HeapInfo->NumberOfTags > 0) {
            HeapTagEntry = HeapInfo->Tags;
            for (TagIndex=0; TagIndex<HeapInfo->NumberOfTags; TagIndex++) {
                p = *pp;
                if (p == NULL ||
                    p->HeapBase != HeapInfo->BaseAddress ||
                    p->TagIndex != HeapTagEntry->TagIndex
                   ) {
                    if (HeapTagEntry->NumberOfAllocations != 0 ||
                        HeapTagEntry->NumberOfFrees != 0 ||
                        HeapTagEntry->BytesAllocated != 0
                       ) {
                        *pp = RtlAllocateHeap( RtlProcessHeap(),
                                               HEAP_ZERO_MEMORY,
                                               sizeof( *p )
                                             );
                        if (*pp == NULL) {
                            return FALSE;
                            }

                        (*pp)->Next = p;
                        p = *pp;
                        if (p->Next == NULL) {
                            pp = &p->Next;
                            }

                        p->HeapBase = HeapInfo->BaseAddress;
                        if (p->HeapBase != HeapNameBase) {
                            HeapName = GetNameForHeapBase( HeapNameBase = p->HeapBase );
                            }
                        p->HeapName = HeapName;

                        p->TagIndex = HeapTagEntry->TagIndex;
                        sprintf( Buffer, "%ws", HeapTagEntry->TagName );
                        p->TagName = AddNameToNameTable( NameTable, Buffer );
                        p->Changed = !fFirstTimeThrough;
                        TagListLength += 1;
                        CalcDifferences = FALSE;
                        }
                    else {
                        p = NULL;
                        }
                    }
                else {
                    pp = &p->Next;
                    p->PrevCounts = p->Counts;
                    CalcDifferences = TRUE;
                    p->Changed = FALSE;
                    }

                if (p != NULL) {
                    p->Counts.Allocs = HeapTagEntry->NumberOfAllocations;
                    p->Counts.Frees = HeapTagEntry->NumberOfFrees;
                    p->Counts.Used = HeapTagEntry->BytesAllocated;
                    p->Counts.Allocs_Frees = p->Counts.Allocs - p->Counts.Frees;
                    if (CalcDifferences) {
                        p->Differences.Allocs = p->Counts.Allocs - p->PrevCounts.Allocs;
                        p->Differences.Frees = p->Counts.Frees - p->PrevCounts.Frees;
                        p->Differences.Used = p->Counts.Used - p->PrevCounts.Used;
                        p->Differences.Allocs_Frees = p->Counts.Allocs_Frees - p->PrevCounts.Allocs_Frees;
                        if (p->Differences.Allocs != 0 ||
                            p->Differences.Frees != 0 ||
                            p->Differences.Used != 0 ||
                            p->Differences.Allocs_Frees != 0
                           ) {
                            p->Changed = TRUE;
                            }
                        }
                    }

                HeapTagEntry += 1;
                }
            }

        HeapInfo += 1;
        }


    if (TagArray != NULL) {
        RtlFreeHeap( RtlProcessHeap(), 0, TagArray );
        }
    TagArray = RtlAllocateHeap( RtlProcessHeap(), 0, TagListLength * sizeof( *TagArray ) );
    if (TagArray == NULL) {
        return FALSE;
        }

    p = TagListHead;
    pp = TagArray;
    while (p != NULL) {
        *pp++ = p;
        p = p->Next;
        }

    qsort( (void *)TagArray,
           (size_t)TagListLength,
           (size_t)sizeof( *TagArray ),
           CompareTagFunction
         );

    return TRUE;
}


BOOLEAN
UpdateHeapDataBase(
    PRTL_DEBUG_INFORMATION DebugInfo
    )
{
    PHEAP_ENTRY p, *pp;
    PRTL_PROCESS_HEAPS Heaps;
    PRTL_HEAP_INFORMATION HeapInfo;
    PRTL_HEAP_TAG HeapTagEntry;
    ULONG i;
    UCHAR Buffer[ MAX_PATH ];
    PCHAR s;

    pp = &HeapListHead;
    Heaps = DebugInfo->Heaps;
    HeapInfo = Heaps->Heaps;
    for (i=0; i<Heaps->NumberOfHeaps; i++) {
        p = *pp;
        if (p == NULL ||
            p->HeapBase != HeapInfo->BaseAddress
           ) {
            *pp = RtlAllocateHeap( RtlProcessHeap(), HEAP_ZERO_MEMORY, sizeof( *p ) );
            if (*pp == NULL) {
                return FALSE;
                }
            (*pp)->Next = p;
            p = *pp;
            if (p->Next == NULL) {
                pp = &p->Next;
                }

            p->HeapBase = HeapInfo->BaseAddress;
            HeapTagEntry = HeapInfo->Tags + HeapInfo->NumberOfPseudoTags;
            if (HeapInfo->NumberOfTags > HeapInfo->NumberOfPseudoTags &&
                HeapTagEntry->TagName[ 0 ] != UNICODE_NULL
               ) {
                sprintf( Buffer, "%ws", HeapTagEntry->TagName );
                }
            else {
                sprintf( Buffer, "%p", p->HeapBase );
                }
            p->HeapName = AddNameToNameTable( NameTable, Buffer );
            p->BytesAllocated = HeapInfo->BytesAllocated;
            p->BytesCommitted = HeapInfo->BytesCommitted;
            p->Changed = !fFirstTimeThrough;
            HeapListLength += 1;
            }
        else {
            p->Changed = FALSE;
            if (HeapInfo->BytesAllocated != p->BytesAllocated) {
                p->Changed = TRUE;
                p->BytesAllocated = HeapInfo->BytesAllocated;
                }

            if (HeapInfo->BytesCommitted != p->BytesCommitted) {
                p->Changed = TRUE;
                p->BytesCommitted = HeapInfo->BytesCommitted;
                }

            pp = &p->Next;
            }

        HeapInfo += 1;
        }

    return TRUE;
}

PCHAR
GetNameForHeapBase(
    PVOID HeapBase
    )
{
    PHEAP_ENTRY p;

    p = HeapListHead;
    while (p != NULL) {
        if (p->HeapBase == HeapBase) {
            return p->HeapName;
            }
        else {
            p = p->Next;
            }
        }
    return NULL;
}

typedef struct _NAME_TABLE_ENTRY {
    struct _NAME_TABLE_ENTRY *HashLink;
    UCHAR Name[ 1 ];
} NAME_TABLE_ENTRY, *PNAME_TABLE_ENTRY;

typedef struct _NAME_TABLE {
    ULONG NumberOfBuckets;
    PNAME_TABLE_ENTRY Buckets[1];
} NAME_TABLE, *PNAME_TABLE;


PVOID
CreateNameTable(
    IN ULONG NumberOfBuckets
    )
{
    PNAME_TABLE p;
    ULONG Size;

    Size = FIELD_OFFSET( NAME_TABLE, Buckets ) +
           (sizeof( PNAME_TABLE_ENTRY ) * NumberOfBuckets);

    p = (PNAME_TABLE)RtlAllocateHeap( RtlProcessHeap(), HEAP_ZERO_MEMORY, Size );
    if (p != NULL) {
        p->NumberOfBuckets = NumberOfBuckets;
        }

    return p;
}


PCHAR
AddNameToNameTable(
    IN PVOID pNameTable,
    IN PCHAR Name
    )
{
    PNAME_TABLE NameTable = pNameTable;
    PNAME_TABLE_ENTRY p, *pp;
    ULONG Value;
    ULONG n, Hash;
    UCHAR c;
    PCHAR s;
    PNAME_TABLE_ENTRY *pa, a;

    s = Name;
    Hash = 0;
    while (c = *s++) {
        c = (UCHAR)toupper( c );
        Hash = Hash + (c << 1) + (c >> 1) + c;
        }
    n = (ULONG)((PCHAR)s - (PCHAR)Name);

    pp = &NameTable->Buckets[ Hash % NameTable->NumberOfBuckets ];
    while (p = *pp) {
        if (!_stricmp( p->Name, Name )) {
            break;
            }
        else {
            pp = &p->HashLink;
            }
        }

    if (p == NULL) {
        p = RtlAllocateHeap( RtlProcessHeap(), 0, sizeof( *p ) + n );
        if (p == NULL) {
            return NULL;
            }
        p->HashLink = NULL;
        strcpy( p->Name, Name );
        *pp = p;
        }

    return p->Name;
}


BOOL
ProcessOptionCharacter(
    IN CHAR c
    )
{
    switch (toupper( c )) {
        case 'T':
            SortBy = TAG;
            return TRUE;

        case 'A':
            SortBy = ALLOC;
            return TRUE;

        case 'U':
        case 'B':
            SortBy = BYTES;
            return TRUE;

        case 'F':
            SortBy = FREE;
            return TRUE;

        case 'D':
            SortBy = DIFF;
            return TRUE;

        case '(':
        case ')':
            fParen = !fParen;
            return TRUE;

        case 'E':
            fDisplayTotals = !fDisplayTotals;
            return TRUE;

        case 'L':
            fHighlight = !fHighlight;
            break;

        case 'H':
        case '?':
            fHelp = TRUE;
            return TRUE;
        }

    return FALSE;
}