/*++

Copyright (c) 1999-1999  Microsoft Corporation

Module Name:

    vm.cxx

Abstract:

    This module contains an NTSD debugger extension for dumping various
    virtual memory statistics.

Author:

    Keith Moore (keithmo) 15-Jan-1999

Revision History:

--*/

#include "inetdbgp.h"


//
// Private constants.
//

#define SMALL_REGION        (64 * 1024)
#define MEDIUM_REGION       (1 * 1024 * 1024)

#define IS_SMALL(c)         ((c) <= SMALL_REGION)
#define IS_MEDIUM(c)        (((c) > SMALL_REGION) && ((c) <= MEDIUM_REGION))
#define IS_LARGE(c)         ((c) > MEDIUM_REGION)

#define PRINTF_FORMAT       "%-7s %*s %*s %*s %*s\n"

#define CCH_ULONG_COMMAS    sizeof("4,294,967,296")


//
// Private types.
//

typedef struct _INDIVIDUAL_STAT
{
    SIZE_T MinimumSize;
    SIZE_T MaximumSize;
    SIZE_T TotalSize;
    SIZE_T BlockCount;

} INDIVIDUAL_STAT, *PINDIVIDUAL_STAT;

typedef struct _VM_STATS
{
    INDIVIDUAL_STAT Summary;
    INDIVIDUAL_STAT Small;
    INDIVIDUAL_STAT Medium;
    INDIVIDUAL_STAT Large;

} VM_STATS, *PVM_STATS;

typedef struct PROTECT_MASK
{
    DWORD Bit;
    PSTR Name;

} PROTECT_MASK, *PPROTECT_MASK;


//
// Private globals.
//

PROTECT_MASK ProtectMasks[] =
    {
        {
            PAGE_NOACCESS,
            "NA"
        },

        {
            PAGE_NOCACHE,
            "NC"
        },

        {
            PAGE_GUARD,
            "G"
        },

        {
            PAGE_READONLY,
            "Rd"
        },

        {
            PAGE_READWRITE,
            "RdWr"
        },

        {
            PAGE_WRITECOPY,
            "WrCp"
        },

        {
            PAGE_EXECUTE,
            "Ex"
        },

        {
            PAGE_EXECUTE_READ,
            "ExRd"
        },

        {
            PAGE_EXECUTE_READWRITE,
            "ExRdWr"
        },

        {
            PAGE_EXECUTE_WRITECOPY,
            "ExWrCp"
        }
    };

#define NUM_PROTECT_MASKS (sizeof(ProtectMasks) / sizeof(ProtectMasks[0]))


//
// Private functions.
//

PSTR
ULongLongToString(
    IN ULONGLONG Value,
    OUT PSTR Buffer
    )
{

    PSTR p1;
    PSTR p2;
    CHAR ch;
    INT digit;
    INT count;
    BOOL needComma;
    INT length;

    //
    // Handling zero specially makes everything else a bit easier.
    //

    if( Value == 0 ) {
        Buffer[0] = '0';
        Buffer[1] = '\0';
        return Buffer;
    }

    //
    // Pull the least signifigant digits off the value and store them
    // into the buffer. Note that this will store the digits in the
    // reverse order.
    //

    p1 = p2 = Buffer;
    count = 3;
    needComma = FALSE;

    while( Value != 0 ) {

        if( needComma ) {
            *p1++ = ',';
            needComma = FALSE;
        }

        digit = (INT)( Value % 10 );
        Value = Value / 10;

        *p1++ = '0' + digit;

        count--;
        if( count == 0 ) {
            count = 3;
            needComma = TRUE;
        }

    }

    length = DIFF(p1 - Buffer);

    //
    // Reverse the digits in the buffer.
    //

    *p1-- = '\0';

    while( p1 > p2 ) {

        ch = *p1;
        *p1 = *p2;
        *p2 = ch;

        p2++;
        p1--;

    }

    return Buffer;

}   // ULongLongToString

VOID
InitVmStats(
    OUT PVM_STATS Stats
    )
{
    ZeroMemory( Stats, sizeof(*Stats) );
    Stats->Summary.MinimumSize = (SIZE_T)-1L;
    Stats->Small.MinimumSize = (SIZE_T)-1L;
    Stats->Medium.MinimumSize = (SIZE_T)-1L;
    Stats->Large.MinimumSize = (SIZE_T)-1L;

}   // InitVmStats

VOID
UpdateIndividualStat(
    IN OUT PINDIVIDUAL_STAT Stat,
    IN SIZE_T BlockSize
    )
{
    Stat->BlockCount++;
    Stat->TotalSize += BlockSize;

    if( BlockSize > Stat->MaximumSize ) {
        Stat->MaximumSize = BlockSize;
    }

    if( BlockSize < Stat->MinimumSize ) {
        Stat->MinimumSize = BlockSize;
    }

}   // UpdateIndividualStat

VOID
UpdateVmStats(
    IN OUT PVM_STATS Stats,
    IN SIZE_T BlockSize
    )
{
    UpdateIndividualStat( &Stats->Summary, BlockSize );

    if( IS_SMALL(BlockSize) ) {
        UpdateIndividualStat( &Stats->Small, BlockSize );
    }

    if( IS_MEDIUM(BlockSize) ) {
        UpdateIndividualStat( &Stats->Medium, BlockSize );
    }

    if( IS_LARGE(BlockSize) ) {
        UpdateIndividualStat( &Stats->Large, BlockSize );
    }

}   // UpdateVmStats

VOID
PrintVmStatsHeader(
    VOID
    )
{
    dprintf(
        PRINTF_FORMAT,
        "TYPE",
        CCH_ULONG_COMMAS,
        "MINIMUM",
        CCH_ULONG_COMMAS,
        "MAXIMUM",
        CCH_ULONG_COMMAS,
        "AVERAGE",
        CCH_ULONG_COMMAS,
        "BLK COUNT"
        );

    printf(
        PRINTF_FORMAT,
        "~~~~",
        CCH_ULONG_COMMAS,
        "~~~~~~~",
        CCH_ULONG_COMMAS,
        "~~~~~~~",
        CCH_ULONG_COMMAS,
        "~~~~~~~",
        CCH_ULONG_COMMAS,
        "~~~~~~~~~"
        );

}   // PrintVmStatsHeader

VOID
PrintIndividualStat(
    IN PSTR Name,
    IN PINDIVIDUAL_STAT Stat
    )
{
    SIZE_T average;
    SIZE_T minsize;
    CHAR minStr[CCH_ULONG_COMMAS];
    CHAR maxStr[CCH_ULONG_COMMAS];
    CHAR avgStr[CCH_ULONG_COMMAS];
    CHAR countStr[CCH_ULONG_COMMAS];

    if( Stat->BlockCount == 0 ) {
        average = 0;
        minsize = 0;
    } else {
        average = Stat->TotalSize / Stat->BlockCount;
        minsize = Stat->MinimumSize;
    }

    dprintf(
        PRINTF_FORMAT,
        Name,
        CCH_ULONG_COMMAS,
        ULongLongToString(
            (ULONGLONG)minsize,
            minStr
            ),
        CCH_ULONG_COMMAS,
        ULongLongToString(
            (ULONGLONG)Stat->MaximumSize,
            maxStr
            ),
        CCH_ULONG_COMMAS,
        ULongLongToString(
            (ULONGLONG)average,
            avgStr
            ),
        CCH_ULONG_COMMAS,
        ULongLongToString(
            (ULONGLONG)Stat->BlockCount,
            countStr
            )
        );

}   // PrintIndividualStat

VOID
PrintVmStats(
    IN PSTR Name,
    IN PVM_STATS Stats
    )
{
    dprintf( "%s:\n", Name );

    PrintIndividualStat( "Small", &Stats->Small );
    PrintIndividualStat( "Medium", &Stats->Medium );
    PrintIndividualStat( "Large", &Stats->Large );
    PrintIndividualStat( "Summary", &Stats->Summary );

    dprintf( "\n" );

}   // PrintVmStats

PSTR
VmProtectToString(
    IN DWORD Protect,
    OUT PSTR Buffer
    )
{
    INT i;
    PPROTECT_MASK mask;

    Buffer[0] = '\0';

    for( i = 0, mask = &ProtectMasks[0] ;
        (i < NUM_PROTECT_MASKS) && (Protect != 0) ;
        i++, mask++ ) {
        if( mask->Bit & Protect ) {
            Protect &= ~mask->Bit;
            if( Buffer[0] != '\0' ) {
                strcat( Buffer, "|" );
            }
            strcat( Buffer, mask->Name );
        }
    }

    if( Protect != 0 ) {
        if( Buffer[0] != '\0' ) {
            strcat( Buffer, "|" );
        }
        sprintf( Buffer + strlen(Buffer), "%08lx", Protect );
    }

    return Buffer;

}   // VmProtectToString

PSTR
VmStateToString(
    IN DWORD State,
    OUT PSTR Buffer
    )
{
    PSTR result;
    CHAR invalidStr[sizeof("12345678")];

    switch( State )
    {
    case MEM_COMMIT:
        result = "Commit";
        break;

    case MEM_RESERVE:
        result = "Reserve";
        break;

    case MEM_FREE:
        result = "Free";
        break;

    default:
        sprintf( invalidStr, "%08lx", State );
        result = invalidStr;
        break;
    }

    strcpy( Buffer, result );
    return Buffer;

}   // VmStateToString

PSTR
VmTypeToString(
    IN DWORD Type,
    OUT PSTR Buffer
    )
{
    PSTR result;
    CHAR invalidStr[sizeof("12345678")];

    switch( Type )
    {
    case MEM_PRIVATE:
        result = "Private";
        break;

    case MEM_MAPPED:
        result = "Mapped";
        break;

    case MEM_IMAGE:
        result = "Image";
        break;

    case 0:
        result = "";
        break;

    default:
        sprintf( invalidStr, "%08lx", Type );
        result = invalidStr;
        break;
    }

    strcpy( Buffer, result );
    return Buffer;

}   // VmTypeToString


/************************************************************
 * Dump Virtual Memory Info
 ************************************************************/


DECLARE_API( vmstat )

/*++

Routine Description:

    This function is called as an NTSD extension to format and dump
    virtual memory statistics.

Arguments:

    hCurrentProcess - Supplies a handle to the current process (at the
        time the extension was called).

    hCurrentThread - Supplies a handle to the current thread (at the
        time the extension was called).

    CurrentPc - Supplies the current pc at the time the extension is
        called.

    lpExtensionApis - Supplies the address of the functions callable
        by this extension.

    lpArgumentString - Supplies the asciiz string that describes the
        ansi string to be dumped.

Return Value:

    None.

--*/

{

    NTSTATUS status;
    ULONG_PTR address;
    MEMORY_BASIC_INFORMATION memInfo;
    VM_STATS freeStats;
    VM_STATS reserveStats;
    VM_STATS commitStats;
    VM_STATS privateStats;
    VM_STATS mappedStats;
    VM_STATS imageStats;

    INIT_API();

    //
    // Setup.
    //

    InitVmStats( &freeStats );
    InitVmStats( &reserveStats );
    InitVmStats( &commitStats );
    InitVmStats( &privateStats );
    InitVmStats( &mappedStats );
    InitVmStats( &imageStats );

    address = 0;

    //
    // Scan the virtual address space.
    //

    for( ; ; ) {
        status = NtQueryVirtualMemory(
                     hCurrentProcess,
                     (PVOID)address,
                     MemoryBasicInformation,
                     &memInfo,
                     sizeof(memInfo),
                     NULL
                     );

        if( !NT_SUCCESS(status) ) {
            break;
        }

        //
        // Interpret the memory state.
        //

        switch( memInfo.State ) {
        case MEM_FREE:
            UpdateVmStats( &freeStats, memInfo.RegionSize );
            break;

        case MEM_RESERVE:
            UpdateVmStats( &reserveStats, memInfo.RegionSize );
            break;

        case MEM_COMMIT:
            UpdateVmStats( &commitStats, memInfo.RegionSize );
            break;
        }

        //
        // Interpret the memory type.
        //

        switch( memInfo.Type ) {
        case MEM_PRIVATE:
            UpdateVmStats( &privateStats, memInfo.RegionSize );
            break;

        case MEM_MAPPED:
            UpdateVmStats( &mappedStats, memInfo.RegionSize );
            break;

        case MEM_IMAGE:
            UpdateVmStats( &imageStats, memInfo.RegionSize );
            break;
        }

        //
        // Advance to the next block.
        //

        address += memInfo.RegionSize;
    }

    //
    // Dump it.
    //

    PrintVmStatsHeader();
    PrintVmStats( "Free", &freeStats );
    PrintVmStats( "Reserve", &reserveStats );
    PrintVmStats( "Commit", &commitStats );
    PrintVmStats( "Private", &privateStats );
    PrintVmStats( "Mapped", &mappedStats );
    PrintVmStats( "Image", &imageStats );

}   // DECLARE_API( vmstat )


DECLARE_API( vmmap )

/*++

Routine Description:

    This function is called as an NTSD extension to format and dump
    the debugee's virtual memory address space.

Arguments:

    hCurrentProcess - Supplies a handle to the current process (at the
        time the extension was called).

    hCurrentThread - Supplies a handle to the current thread (at the
        time the extension was called).

    CurrentPc - Supplies the current pc at the time the extension is
        called.

    lpExtensionApis - Supplies the address of the functions callable
        by this extension.

    lpArgumentString - Supplies the asciiz string that describes the
        ansi string to be dumped.

Return Value:

    None.

--*/

{

    NTSTATUS status;
    ULONG_PTR address;
    MEMORY_BASIC_INFORMATION memInfo;
    CHAR protectStr[32];
    CHAR aprotectStr[32];
    CHAR stateStr[16];
    CHAR typeStr[16];

    INIT_API();

    //
    // Setup.
    //

    address = 0;

    dprintf(
        "%-*s %-*s %-*s  %-13s %-13s %-8s %-8s\n",
        sizeof(PVOID) * 2,
        "Start",
        sizeof(PVOID) * 2,
        "Stop",
        sizeof(PVOID) * 2,
        "Length",
        "AllocProtect",
        "Protect",
        "State",
        "Type"
        );

    //
    // Scan the virtual address space.
    //

    for( ; ; ) {
        status = NtQueryVirtualMemory(
                     hCurrentProcess,
                     (PVOID)address,
                     MemoryBasicInformation,
                     &memInfo,
                     sizeof(memInfo),
                     NULL
                     );

        if( !NT_SUCCESS(status) ) {
            break;
        }

        //
        // Dump the current entry.
        //

        dprintf(
            "%p-%p %p  %-13s %-13s %-8s %-8s\n",
            memInfo.BaseAddress,
            (ULONG_PTR)memInfo.BaseAddress + memInfo.RegionSize - 1,
            memInfo.RegionSize,
            VmProtectToString( memInfo.AllocationProtect, aprotectStr ),
            VmProtectToString( memInfo.Protect, protectStr ),
            VmStateToString( memInfo.State, stateStr ),
            VmTypeToString( memInfo.Type, typeStr )
            );

        //
        // Advance to the next block.
        //

        address += memInfo.RegionSize;
    }

}   // DECLARE_API( vmmap )

