/*++

Copyright (c) 1995  Microsoft Corporation
All rights reserved.

Module Name:

    debug.cxx

Abstract:

    Generic debug extensions.

Author:

    Albert Ting (AlbertT)  19-Feb-1995

Revision History:

--*/

#include "precomp.hxx"
#pragma hdrstop

HANDLE hCurrentProcess;
WINDBG_EXTENSION_APIS ExtensionApis;

PWINDBG_OUTPUT_ROUTINE Print;
PWINDBG_GET_EXPRESSION EvalExpression;
PWINDBG_GET_SYMBOL GetSymbolRtn;
PWINDBG_CHECK_CONTROL_C CheckControlCRtn;

USHORT SavedMajorVersion;
USHORT SavedMinorVersion;

BOOL bWindbg = FALSE;

VOID
WinDbgExtensionDllInit(
    PWINDBG_EXTENSION_APIS lpExtensionApis,
    USHORT MajorVersion,
    USHORT MinorVersion
    )
{
    ::ExtensionApis = *lpExtensionApis;

    SavedMajorVersion = MajorVersion;
    SavedMinorVersion = MinorVersion;

    bWindbg = TRUE;

    return;
}

/*++

Routine Name:

    ExtensionApiVersion

Routine Description:

    Windbg calls this function to match between the
    version of windbg and the extension. If the versions
    doesn't match, windbg will not load the extension.

Arguments:

    None.

Return Value:

    None.

--*/
extern "C"
LPEXT_API_VERSION
ExtensionApiVersion(
    VOID
    )
{
    static EXT_API_VERSION ApiVersion = { 3, 5, EXT_API_VERSION_NUMBER, 0 };
    return &ApiVersion;
}

/*++

Routine Name:

    CheckVersion

Routine Description:

    This function is called before every command. It gives
    the extension a chance to compare between the versions
    of the target and the extension.

Arguments:

    None

Return Value:

    None.

--*/
extern "C"
VOID
CheckVersion(
    VOID
    )
{
}

VOID
TDebugExt::
vDumpPDL(
    PDLINK pDLink
    )
{
    Print( "%x  %x", pDLink->FLink, pDLink->BLink );

    if( pDLink->FLink == pDLink->BLink ){
        Print( " <empty>\n" );
    } else {
        Print( "\n" );
    }
}

VOID
TDebugExt::
vDumpStr(
    LPCWSTR pszString
    )
{
    WCHAR szString[MAX_PATH];

    if( (LPCWSTR)pszString == NULL ){

        Print( "(NULL)\n" );
        return;
    }

    szString[0] = 0;

    //
    // First try reading to the end of 1k (pages are 4k on x86, but
    // most strings are < 1k ).
    //
    UINT cbShort = (UINT)(0x400 - ( (ULONG_PTR)pszString & 0x3ff ));
    BOOL bFound = FALSE;

    if( cbShort < sizeof( szString )){

        UINT i;

        move2( szString, pszString, cbShort );

        //
        // Look for a NULL.
        //
        for( i=0; i< cbShort/sizeof( pszString[0] ); ++i )
        {
            if( !szString[i] ){
                bFound = TRUE;
            }
        }

    }

    if( !bFound ){

        move( szString, pszString );
    }

    if( szString[0] == 0 ){
        Print( "\"\"\n" );
    } else {
        Print( "%ws\n", szString );
    }
}

VOID
TDebugExt::
vDumpStrA(
    LPCSTR pszString
    )
{
    CHAR szString[MAX_PATH];

    if( (LPCSTR)pszString == NULL ){

        Print( "(NULL)\n" );
        return;
    }

    szString[0] = 0;

    //
    // First try reading to the end of 1k (pages are 4k on x86, but
    // most strings are < 1k ).
    //
    UINT cbShort = 0x400 - (UINT)( (ULONG_PTR)pszString & 0x3ff );
    BOOL bFound = FALSE;

    if( cbShort < sizeof( szString )){

        UINT i;

        move2( szString, pszString, cbShort );

        //
        // Look for a NULL.
        //
        for( i=0; i< cbShort/sizeof( pszString[0] ); ++i )
        {
            if( !szString[i] ){
                bFound = TRUE;
            }
        }

    }

    if( !bFound ){

        move( szString, pszString );
    }

    if( szString[0] == 0 ){
        Print( "\"\"\n" );
    } else {
        Print( "%hs\n", szString );
    }
}

VOID
TDebugExt::
vDumpTime(
    const SYSTEMTIME& st
    )
{
    Print( "%d/%d/%d %d %d:%d:%d.%d\n",
           st.wMonth,
           st.wDay,
           st.wYear,
           st.wDayOfWeek,
           st.wHour,
           st.wMinute,
           st.wSecond,
           st.wMilliseconds );
}


VOID
TDebugExt::
vDumpFlags(
    ULONG_PTR dwFlags,
    PDEBUG_FLAGS pDebugFlags
    )
{
    ULONG_PTR dwFound = 0;

    Print( "%x [ ", dwFlags );

    for( ; pDebugFlags->dwFlag; ++pDebugFlags ){

        if( dwFlags & pDebugFlags->dwFlag ){
            Print( "%s ", pDebugFlags->pszFlag );
            dwFound |= pDebugFlags->dwFlag;
        }
    }

    Print( "]" );

    //
    // Check if there are extra bits set that we don't understand.
    //
    if( dwFound != dwFlags ){
        Print( "<ExtraBits: %x>", dwFlags & ~dwFound );
    }
    Print( "\n" );
}

VOID
TDebugExt::
vDumpValue(
    ULONG_PTR dwValue,
    PDEBUG_VALUES pDebugValues
    )
{
    Print( "%x ", dwValue );

    for( ; pDebugValues->dwValue; ++pDebugValues ){

        if( dwValue == pDebugValues->dwValue ){
            Print( "%s ", pDebugValues->pszValue );
        }
    }
    Print( "\n" );
}

VOID
TDebugExt::
vDumpTrace(
    ULONG_PTR dwAddress
    )
{
#ifdef TRACE_ENABLED

    INT i;
    CHAR szSymbol[64];
    ULONG_PTR dwDisplacement;
    ULONG_PTR adwTrace[ ( OFFSETOF( TBackTraceDB::TTrace, apvBackTrace ) +
                         sizeof( PVOID ) * VBackTrace::kMaxDepth )
                     / sizeof( ULONG_PTR )];

    if( !dwAddress ){
        return;
    }

    move( adwTrace, dwAddress );

    TBackTraceDB::TTrace *pTrace = (TBackTraceDB::TTrace*)adwTrace;

    printf( "Trace %x Hash = %x Count = %x\n",
            dwAddress,
            pTrace->_ulHash,
            pTrace->_lCount );

    for( i=0; i < VBackTrace::kMaxDepth; i++ ){

        if( !pTrace->apvBackTrace[i] ){
            break;
        }
        GetSymbolRtn( (PVOID)pTrace->apvBackTrace[i],
                      szSymbol,
                      &dwDisplacement );
        Print( "%08x %s+%x\n",
               pTrace->apvBackTrace[i], szSymbol,
               dwDisplacement );
    }

    if( i > 0 ){
        Print( "\n" );
    }
#endif
}

ULONG_PTR
TDebugExt::
dwEval(
    LPSTR& lpArgumentString,
    BOOL   bParam
    )
{
    ULONG_PTR dwReturn;
    LPSTR pSpace = NULL;

    while( *lpArgumentString == ' ' ){
        lpArgumentString++;
    }

    //
    // If it's a parameter, scan to next space and delimit.
    //
    if( bParam ){

        for( pSpace = lpArgumentString; *pSpace && *pSpace != ' '; ++pSpace )
            ;

        if( *pSpace == ' ' ){
            *pSpace = 0;
        } else {
            pSpace = NULL;
        }
    }

    dwReturn = (ULONG_PTR)EvalExpression( lpArgumentString );

    while( *lpArgumentString != ' ' && *lpArgumentString ){
        lpArgumentString++;
    }

    if( pSpace ){
        *pSpace = ' ';
    }

    return dwReturn;
}

/********************************************************************

    Generic extensions

********************************************************************/

VOID
TDebugExt::
vLookCalls(
    HANDLE hProcess,
    HANDLE hThread,
    ULONG_PTR dwStartAddr,
    ULONG_PTR dwLength,
    ULONG_PTR dwFlags
    )
{
#if i386
    struct OPCODES {
        BYTE op;
        UINT uLen;
    };

    OPCODES Opcodes[] = {
        0xe8, 5,            // rel16
        0xff, 6,            // r/m16
        0x0, 0
    };

    dwStartAddr = DWordAlign( dwStartAddr );
    dwLength = DWordAlign( dwLength );

    if( !dwStartAddr ){

        //
        // No address specified; use esp.
        //
        dwStartAddr = DWordAlign( (ULONG_PTR)EvalExpression( "ebp" ));
    }

    if( !dwLength ){

        DWORD Status;
        THREAD_BASIC_INFORMATION ThreadInformation;

        Status = NtQueryInformationThread( hThread,
                                           ThreadBasicInformation,
                                           &ThreadInformation,
                                           sizeof( ThreadInformation ),
                                           NULL
                                           );
        if( NT_SUCCESS( Status )){

            TEB Teb;
            ULONG_PTR dwBase;

            move( Teb, ThreadInformation.TebBaseAddress );

            dwBase = DWordAlign( (ULONG_PTR)Teb.NtTib.StackBase );

            if( dwBase > dwStartAddr ){
                dwLength = dwBase - dwStartAddr;
            }

        } else {

            Print( "Unable to get Teb %d\n", Status );
            return;
        }
    }

    Print( "Start %x End %x (Length %x)\n",
           dwStartAddr, dwStartAddr + dwLength, dwLength );

    if( !( dwFlags & (kLCFlagAll|kLCVerbose )))
    {
        Print( "FramePtr Arguments                   "
               "Next call (move one line up)\n" );
    }

    ULONG_PTR dwAddr;

    for( dwAddr = dwStartAddr;
         dwAddr < dwStartAddr + dwLength;
         ++dwAddr ){

        if( CheckControlCRtn()){
            Print( "Aborted.\n" );
            return;
        }

        //
        // Get a value on the stack and see if it looks like an ebp on
        // the stack.  It should be close to the current address, but
        // be greater.
        //
        ULONG_PTR dwNextFrame = 0;
        move( dwNextFrame, dwAddr );

        BOOL bLooksLikeEbp = dwNextFrame > dwAddr &&
                             dwNextFrame - dwAddr < kMaxCallFrame;
        //
        // If we are dumping all, or it looks like an ebp, dump it.
        //
        if(( dwFlags & kLCFlagAll ) || bLooksLikeEbp ){

            //
            // Check if next address looks like a valid call request.
            //
            ULONG_PTR dwRetAddr = 0;

            //
            // Get return address.
            //
            move( dwRetAddr, dwAddr + sizeof( DWORD ));

            //
            // Get 16 bytes before return address.
            //
            BYTE abyBuffer[16];
            ZeroMemory( abyBuffer, sizeof( abyBuffer ));
            move( abyBuffer, dwRetAddr - sizeof( abyBuffer ));

            //
            // Check if previous bytes look like a call instruction.
            //
            UINT i;
            for( i = 0; Opcodes[i].op; ++i ){

                if( abyBuffer[sizeof( abyBuffer )-Opcodes[i].uLen] ==
                    Opcodes[i].op ){

                    CHAR szSymbol[64];
                    ULONG_PTR dwDisplacement;
                    LPCSTR pszNull = "";
                    LPCSTR pszStar = "*** ";
                    LPCSTR pszPrefix = pszNull;

                    if(( dwFlags & kLCFlagAll ) && bLooksLikeEbp ){
                        pszPrefix = pszStar;
                    }

                    GetSymbolRtn( (PVOID)dwRetAddr,
                                  szSymbol,
                                  &dwDisplacement );

                    //
                    // Found what could be a match: dump it out.
                    //

                    DWORD dwArg[4];
                    move( dwArg, dwAddr + 2*sizeof( DWORD ));

                    if( dwFlags & kLCVerbose ){

                        Print( "%s%x %s+0x%x\n",
                               pszPrefix,
                               dwRetAddr,
                               szSymbol,
                               dwDisplacement );

                        Print( "%s%08x: %08x %08x  %08x %08x %08x %08x\n",
                               pszPrefix,
                               dwAddr,
                               dwNextFrame, dwRetAddr,
                               dwArg[0], dwArg[1], dwArg[2], dwArg[3] );

                        DWORD adwNextFrame[2];
                        ZeroMemory( adwNextFrame, sizeof( adwNextFrame ));
                        move( adwNextFrame, dwNextFrame );

                        Print( "%s%08x: %08x %08x\n\n",
                               pszPrefix,
                               dwNextFrame, adwNextFrame[0], adwNextFrame[1] );
                    } else {

                        Print( "%08x %08x %08x %08x  %08x-%s+0x%x\n",
                               dwAddr,
                               dwArg[0], dwArg[1], dwArg[2],
                               dwRetAddr, szSymbol, dwDisplacement );
                    }
                }
            }
        }
    }
#else
    Print( "Only supported on x86\n" );
#endif
}

VOID
TDebugExt::
vFindPointer(
    HANDLE hProcess,
    ULONG_PTR dwStartAddr,
    ULONG_PTR dwEndAddr,
    ULONG_PTR dwStartPtr,
    ULONG_PTR dwEndPtr
    )
{
    BYTE abyBuf[kFPGranularity];

    //
    // Read each granularity chunk then scan the buffer.
    // (Start by rounding down.)
    //
    ULONG_PTR dwCur;
    for( dwCur = dwStartAddr & ~( kFPGranularity - 1 );
         dwCur < dwEndAddr;
         dwCur += kFPGranularity ){

        ZeroMemory( abyBuf, sizeof( abyBuf ));

        move( abyBuf, dwCur );

        ULONG_PTR i;
        for( i=0; i< kFPGranularity; i += sizeof( DWORD ) ){

            ULONG_PTR dwVal = *((PDWORD)(abyBuf+i));
            if( dwVal >= dwStartPtr &&
                dwVal <= dwEndPtr &&
                dwCur + i >= dwStartAddr &&
                dwCur + i <= dwEndAddr ){

                Print( "%08x : %08x\n", dwCur + i, dwVal );
            }
        }

        if( CheckControlCRtn()){
            Print( "Aborted at %08x.\n", dwCur+i );
            return;
        }
    }
}

VOID
TDebugExt::
vCreateRemoteThread(
    HANDLE hProcess,
    ULONG_PTR dwAddr,
    ULONG_PTR dwParm
    )
{
    DWORD dwThreadId = 0;

    if( !CreateRemoteThread( hProcess,
                             NULL,
                             4*4096,
                             (LPTHREAD_START_ROUTINE)dwAddr,
                             (LPVOID)dwParm,
                             0,
                             &dwThreadId )){

        Print( "<Error: Unable to ct %x( %x ) %d.>\n",
               dwAddr, dwParm, GetLastError( ));
        return;
    }

    Print( "<ct %x( %x ) threadid=<%d>>\n", dwAddr, dwParm, dwThreadId );
}


/********************************************************************

    Extension entrypoints.

********************************************************************/

DEBUG_EXT_HEAD( help )
{
    DEBUG_EXT_SETUP_VARS();

    Print( "Spllib Extensions\n" );
    Print( "---------------------------------------------------------\n" );
    Print( "d       dump spooler structure based on signature\n" );
    Print( "ds      dump pIniSpooler\n" );
    Print( "dlcs    dump localspl's critical section (debug builds only)\n" );
    Print( "lastlog dump localspl's debug tracing (uses ddt flags)\n" );
    Print( "ddev    dump Unicode devmode\n" );
    Print( "ddeva   dump Ansi devmode\n" );
    Print( "dmem    dump spllib heap blocks\n" );
    Print( "---------------------------------------------------------\n" );
    Print( "dcs     dump spllib critical section\n" );
    Print( "ddt     dump spllib debug trace buffer\n" );
    Print( "        ** Recent lines printed first! **\n" );
    Print( "        -c Count (number of recent lines to print)\n" );
    Print( "        -l Level (DBG_*) to print\n" );
    Print( "        -b Dump backtrace (x86 only)\n" );
    Print( "        -d Print debug message (default if -x not specified)\n" );
    Print( "        -x Print hex information\n" );
    Print( "        -r Dump raw buffer: specify pointer to lines\n" );
    Print( "        -t tid: Dump specific thread $1\n" );
    Print( "        -s Skip $1 lines that would otherwise print.\n" );
    Print( "        -m Search for $1 in memory block (gpbtAlloc/gpbtFree only)\n" );
    Print( "dbt     dump raw backtrace\n" );
    Print( "dtbt    dump text backtrace\n" );
    Print( "ddp     dump debug pointers\n" );
    Print( "---------------------------------------------------------\n" );
    Print( "fl      free library $1 (hLibrary)\n" );
    Print( "ct      create thread at $1 with arg $2\n" );
    Print( "fp      find pointer from $1 to $2 range, ptr $3 to $4 range\n" );
    Print( "lc      look for calls at $1 for $2 bytes (x86 only)\n" );
    Print( "        -a Check all for return addresses\n" );
    Print( "        -v Verbose\n" );
    Print( "sleep   Sleep for $1 ms\n" );
    Print( "dbti    Dump KM backtrace index\n");
}


DEBUG_EXT_HEAD( dtbt )
{
    DEBUG_EXT_SETUP_VARS();

    for( ; *lpArgumentString; )
    {
        ULONG_PTR p;
        CHAR szSymbol[64];

        p = TDebugExt::dwEval( lpArgumentString, FALSE );

        if( !p )
        {
            break;
        }

        ULONG_PTR dwDisplacement;

        GetSymbolRtn( (PVOID)p,
                      szSymbol,
                      &dwDisplacement );

        Print( "%08x %s+%x\n", p, szSymbol, dwDisplacement );
    }
}

DEBUG_EXT_HEAD( fl )
{
    DEBUG_EXT_SETUP_VARS();

    //
    // Relies on the fact that kernel32 won't be relocated.
    //
    TDebugExt::vCreateRemoteThread( hCurrentProcess,
                                    (ULONG_PTR)&FreeLibrary,
                                    TDebugExt::dwEval( lpArgumentString, FALSE ));
}

DEBUG_EXT_HEAD( fp )
{
    DEBUG_EXT_SETUP_VARS();

    ULONG_PTR dwStartAddr = TDebugExt::dwEval( lpArgumentString, TRUE );
    ULONG_PTR dwEndAddr = TDebugExt::dwEval( lpArgumentString, TRUE );

    ULONG_PTR dwStartPtr = TDebugExt::dwEval( lpArgumentString, TRUE );
    ULONG_PTR dwEndPtr = TDebugExt::dwEval( lpArgumentString, TRUE );

    TDebugExt::vFindPointer( hCurrentProcess,
                             dwStartAddr,
                             dwEndAddr,
                             dwStartPtr,
                             dwEndPtr );
}


DEBUG_EXT_HEAD( ct )
{
    DEBUG_EXT_SETUP_VARS();

    ULONG_PTR dwStartAddr = TDebugExt::dwEval( lpArgumentString, TRUE );
    ULONG_PTR dwParm = TDebugExt::dwEval( lpArgumentString, FALSE );

    TDebugExt::vCreateRemoteThread( hCurrentProcess,
                                    dwStartAddr,
                                    dwParm );
}

DEBUG_EXT_HEAD( sleep )
{
    DEBUG_EXT_SETUP_VARS();

    const UINT_PTR kSleepInterval = 500;

    ULONG_PTR SleepMS = atoi(lpArgumentString);

    UINT_PTR i = SleepMS / kSleepInterval;

    Sleep((DWORD)(SleepMS % kSleepInterval));

    for (i = SleepMS / kSleepInterval; i; --i)
    {
        if (CheckControlCRtn())
        {
            break;
        }
        Sleep(kSleepInterval);
    }
}

DEBUG_EXT_HEAD( lc )
{
    DEBUG_EXT_SETUP_VARS();

    ULONG_PTR dwFlags = 0;

    for( ; *lpArgumentString; ++lpArgumentString ){

        while( *lpArgumentString == ' ' ){
            ++lpArgumentString;
        }

        if (*lpArgumentString != '-') {
            break;
        }

        ++lpArgumentString;

        switch( *lpArgumentString ){
        case 'A':
        case 'a':

            dwFlags |= TDebugExt::kLCFlagAll;
            break;

        case 'V':
        case 'v':

            dwFlags |= TDebugExt::kLCVerbose;
            break;

        default:
            Print( "Unknown option %c.\n", lpArgumentString[0] );
            return;
        }
    }

    ULONG_PTR dwStartAddr = TDebugExt::dwEval( lpArgumentString, TRUE );
    ULONG_PTR dwLength = TDebugExt::dwEval( lpArgumentString, FALSE );

    TDebugExt::vLookCalls( hCurrentProcess,
                           hCurrentThread,
                           dwStartAddr,
                           dwLength,
                           dwFlags );
}