/*++

Copyright (c) 1996  Microsoft Corporation
All rights reserved.

Module Name:

    trace.cxx

Abstract:

    Holds logging routines.

Author:

    Albert Ting (AlbertT)  24-May-1996

Revision History:

--*/

#include "spllibp.hxx"
#pragma hdrstop

#if DBG

#include "trace.hxx"

/*
**  Turn off memory tracing. Turning this on keeps all the back traces in
** memory.
#if i386
#define BACKTRACE_ENABLED
#endif
*/

CRITICAL_SECTION gcsBackTrace;

#ifdef TRACE_ENABLED

TBackTraceDB* gpBackTraceDB;

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

    BackTrace DB

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

TBackTraceDB::
TBackTraceDB(
    VOID
    ) : _pTraceHead( NULL )

/*++

Routine Description:

    Initialize the trace database.

    Generally you will have just one database that holds all the
    traces.

Arguments:

Return Value:

--*/

{
    _pMemBlock = new TMemBlock( kBlockSize, TMemBlock::kFlagGlobalNew );
}

TBackTraceDB::
~TBackTraceDB(
    VOID
    )

/*++

Routine Description:

    Destroy the back trace database.

Arguments:

Return Value:

--*/

{
    delete _pMemBlock;
}

BOOL
TBackTraceDB::
bValid(
    VOID
    )
{
    return _pMemBlock && _pMemBlock->bValid();
}



HANDLE
TBackTraceDB::
hStore(
    IN ULONG ulHash,
    IN PVOID pvBackTrace
    )

/*++

Routine Description:

    Store a backtrace into the database.

Arguments:

    ulHash - Hash for this backtrace.

    pvBackTrace - Actual backtrace; must be NULL terminated.

Return Value:

    HANDLE - backtrace handle.

--*/

{
    TTrace *ptRet;
    TTrace **ppTrace;

    //
    // First see if we can find a backtrace.  If we can't, then
    // pTrace will hold the slot where it should be.
    //
    ptRet = ptFind( ulHash, pvBackTrace, &ppTrace );

    if( !ptRet ){

        //
        // Didn't find one; add it.
        //
        ptRet = TTrace::pNew( this, ulHash, pvBackTrace, ppTrace );
    }

    return ptRet;
}

PLONG
TBackTraceDB::
plGetCount(
    HANDLE hData
    )

/*++

Routine Description:

    Get data from a HANDLE retrieved from hStore.  There is one ULONG
    per stack backtrace.

Arguments:

    hData - Returned from hStore.

Return Value:

    PLONG.

--*/

{
    TTrace *ptTrace = static_cast<TTrace*>( hData );
    return &ptTrace->lCount();
}

TBackTraceDB::TTrace*
TBackTraceDB::
ptFind(
    IN     ULONG ulHash,
    IN     PVOID pvBackTrace,
       OUT TTrace ***pppTrace OPTIONAL
    )

/*++

Routine Description:

    Find a backtrace in the database.  If one does not exist,
    then return NULL and a pointer to where it would exist
    in the database.

Arguments:

    ulHash - Hash of the backtrace.

    pvBackTrace - Backtrace to find.

    pppTrace - If not found, this holds the address of where it should
        be stored in the database.  Adding the trace here is sufficient
        to add it.

Return Value:

    TTrace* the actual trace, NULL if not found.

--*/

{
    //
    // Traverse the binary tree until we find the end or the
    // right one.
    //
    TTrace **ppTrace = &_pTraceHead;

    while( *ppTrace ){

        //
        // Check if this one matches ours.
        //
        COMPARE Compare = (*ppTrace)->eCompareHash( ulHash );

        if( Compare == kEqual ){

            //
            // Now do slow compare in case the hash is a collision.
            //
            Compare = (*ppTrace)->eCompareBackTrace( pvBackTrace );

            if( Compare == kEqual ){

                //
                // Break out of while loop and quit.
                //
                break;
            }
        }

        ppTrace = ( Compare == kLess ) ?
            &(*ppTrace)->_pLeft :
            &(*ppTrace)->_pRight;
    }

    if( pppTrace ){
        *pppTrace = ppTrace;
    }
    return *ppTrace;
}


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

    TBackTraceDB::TTrace

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

COMPARE
TBackTraceDB::
TTrace::
eCompareHash(
    ULONG ulHash
    ) const

/*++

Routine Description:

    Quickly compare two trace hashes.

Arguments:

    ulHash - Input hash.

Return Value:

--*/

{
    if( _ulHash < ulHash ){
        return kLess;
    }

    if( _ulHash > ulHash ){
        return kGreater;
    }

    return kEqual;
}

COMPARE
TBackTraceDB::
TTrace::
eCompareBackTrace(
    PVOID pvBackTrace
    ) const

/*++

Routine Description:

    Compare backtrace to one stored in this.

Arguments:

    pvBackTrace - Must be NULL terminated.

Return Value:

    COMAARE: kLess, kEqual, kGreater.

--*/

{
    PVOID *pSrc;
    PVOID *pDest;

    for( pSrc = (PVOID*)this, pDest = (PVOID*)&pvBackTrace;
        *pSrc && *pDest;
        pSrc++, pDest++ ) {

        if ( *pSrc != *pDest ){
            return (ULONG_PTR)*pSrc < (ULONG_PTR)*pDest ?
                kLess :
                kGreater;
        }
    }
    return kEqual;
}

TBackTraceDB::TTrace*
TBackTraceDB::
TTrace::
pNew(
    IN     TBackTraceDB *pBackTraceDB,
    IN     ULONG ulHash,
    IN     PVOID pvBackTrace,
       OUT TTrace ** ppTrace
    )

/*++

Routine Description:

    Constructs a new TTrace and puts it in pBackTraceDB.

    Assumes the trace does _not_ exist already, and ppTrace points
    to the place where it should be stored to ensure the database
    is kept consistent.

Arguments:

    pBackTraceDB - Storage for the new trace.

    ulHash - Hash for the trace.

    pvBackTrace - The actual backtrace.

    ppTrace - Where the trace should be stored in the database.

Return Value:

    TTrace* - New trace, NULL if failed.

--*/

{
    COUNT cCalls;
    PVOID *ppvCalls;

    //
    // Calculate size of backtrace.  Start with cCalls = 1 so that
    // we include 1 extra for the NULL terminator.
    //
    for( ppvCalls = (PVOID*)pvBackTrace, cCalls = 1;
        *ppvCalls;
        ++ppvCalls, ++cCalls )

        ;

    ++cCalls;

    COUNTB cbSize = OFFSETOF( TTrace, apvBackTrace ) +
                    cCalls * sizeof( PVOID );

    TTrace* pTrace = (TTrace*)pBackTraceDB->_pMemBlock->pvAlloc( cbSize );

    if( pTrace ){

        pTrace->_pLeft = NULL;
        pTrace->_pRight = NULL;
        pTrace->_ulHash = ulHash;
        pTrace->_lCount = -1;

        CopyMemory( pTrace->apvBackTrace,
                    (PVOID*)pvBackTrace,
                    cCalls * sizeof( PVOID ));

        //
        // Add it in the right spot into the database.
        //
        *ppTrace = pTrace;
    }

    return pTrace;
}


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

    Back tracing: abstract base class.

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

BOOL VBackTrace::gbInitialized = FALSE;

#endif // TRACE_ENABLED

VBackTrace::
VBackTrace(
    ULONG_PTR fOptions1,
    ULONG_PTR fOptions2
    ) : _fOptions1( fOptions1 ), _fOptions2( fOptions2 )
{
}

VBackTrace::
~VBackTrace(
    VOID
    )
{
}

BOOL
VBackTrace::
bInit(
    VOID
    )
{

#ifdef TRACE_ENABLED

    InitializeCriticalSection(&gcsBackTrace);
    gpBackTraceDB = new TBackTraceDB();
    gbInitialized = TRUE;

    return gpBackTraceDB != NULL;

#else

    return TRUE;

#endif

}

VOID
VBackTrace::
vDone(
    VOID
    )
{

#ifdef TRACE_ENABLED

    if( gbInitialized )
    {
        DeleteCriticalSection(&gcsBackTrace);
    }

#endif

}

PLONG
VBackTrace::
plGetCount(
    HANDLE hData
    )
{

#ifdef TRACE_ENABLED

    return gpBackTraceDB->plGetCount( hData );

#else

    return NULL;

#endif

}

#ifndef TRACE_ENABLED


HANDLE
VBackTrace::
hCapture(
    ULONG_PTR Info1,
    ULONG_PTR Info2,
    ULONG_PTR Info3,
    PULONG pHash
    )

/*++

Routine Description:

    In the case that tracing is disabled, this function is coded
    to return NULL.

Arguments:

Return Value:

    NULL

--*/

{
    return NULL;
}

#endif // ndef TRACE_ENABLED


#ifdef TRACE_ENABLED

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

    Back tracing to memory.

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

TBackTraceMem::
TBackTraceMem(
    ULONG_PTR fOptions1,
    ULONG_PTR fOptions2
    ) : VBackTrace( fOptions1, fOptions2 ), _uNextFree( 0 )
{
    _pLines = new TLine[kMaxCall];
    if( _pLines ){
        ZeroMemory( _pLines, sizeof( TLine[kMaxCall] ));
    }
}

TBackTraceMem::
~TBackTraceMem(
    VOID
    )
{
    UINT i;
    TLine* pLine;

    if( _pLines ){
        for( i=0, pLine = _pLines; i< kMaxCall; i++, pLine++ ){

            if( _fOptions1 & kString ){
                DbgFreeMem( (PVOID)pLine->_Info1 );
            }

            if( _fOptions2 & kString ){
                DbgFreeMem( (PVOID)pLine->_Info2 );
            }
        }
        delete [] _pLines;
    }
}

VOID
TBackTraceMem::
vCaptureLine(
    IN OUT TLine* pLine,
    IN     ULONG_PTR Info1,
    IN     ULONG_PTR Info2,
    IN     ULONG_PTR Info3,
       OUT PVOID apvBackTrace[kMaxDepth+1], OPTIONAL
       OUT PULONG pulHash OPTIONAL
    )

/*++

Routine Description:

    Captures information into a TLine structure; freeing previous
    contents if necessary.

Arguments:

    pLine - Fully initialized pLine structure.  On output, everything
        _except_ _hTrace is filled in.

    ** Both apvBackTrace && pulHash must both be valid if either is valid **

    apvBackTrace - Buffer to receive backtrace.

    pulHash - Buffer to receive ulHash.

Return Value:

--*/

{
    //
    // Free memory if necessary.
    //
    if( _fOptions1 & kString ) {
        DbgFreeMem( (PVOID)pLine->_Info1 );
    }

    if( _fOptions2 & kString ) {
        DbgFreeMem( (PVOID)pLine->_Info2 );
    }

    pLine->_TickCount = GetTickCount();
    pLine->_Info1 = Info1;
    pLine->_Info2 = Info2;
    pLine->_Info3 = Info3;

    pLine->_ThreadId = GetCurrentThreadId();
    pLine->_hTrace = NULL;

#ifdef BACKTRACE_ENABLED

    if( apvBackTrace && pulHash ){

        ULONG ulHash;

        //
        // Capture a backtrace at this spot for debugging.
        //
        UINT uDepth = RtlCaptureStackBackTrace( 2,
                                                kMaxDepth,
                                                apvBackTrace,
                                                pulHash );

        //
        // NULL terminate.
        //
        apvBackTrace[uDepth] = NULL;
    }
#else
    apvBackTrace[0] = NULL;
    *pulHash = 0;
#endif

}

HANDLE
TBackTraceMem::
hCapture(
    ULONG_PTR Info1,
    ULONG_PTR Info2,
    ULONG_PTR Info3,
    PULONG pHash
    )
{
    UINT uDepth;
    TLine* pLine;
    ULONG ulHash;
    PVOID apvBackTrace[kMaxDepth+1];

    if( !_pLines ){
        return NULL;
    }

    EnterCriticalSection( &gcsBackTrace );

    pLine = &_pLines[_uNextFree];

    vCaptureLine( pLine, Info1, Info2, Info3, apvBackTrace, &ulHash );
    pLine->_hTrace = gpBackTraceDB->hStore( ulHash, apvBackTrace );

    _uNextFree++;

    if( _uNextFree == kMaxCall )
        _uNextFree = 0;

    LeaveCriticalSection( &gcsBackTrace );

    if( pHash )
    {
        *pHash = ulHash;
    }

    return (PVOID)pLine->_hTrace;
}

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

    Backtracing to File.

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

COUNT TBackTraceFile::gcInstances;

TBackTraceFile::
TBackTraceFile(
    ULONG_PTR fOptions1,
    ULONG_PTR fOptions2
    ) : VBackTrace( fOptions1, fOptions2 )
{
    TCHAR szFile[kMaxPath];

    EnterCriticalSection( &gcsBackTrace );

    wsprintf( szFile,
              TEXT( "spl_%d.%d.log" ),
              GetCurrentProcessId(),
              gcInstances );

    ++gcInstances;

    LeaveCriticalSection( &gcsBackTrace );

    _hFile = CreateFile( szFile,
                         GENERIC_WRITE,
                         FILE_SHARE_READ,
                         NULL,
                         OPEN_ALWAYS,
                         FILE_ATTRIBUTE_COMPRESSED,
                         NULL );

    if( _hFile == INVALID_HANDLE_VALUE ){

        OutputDebugStringA( "SPLLIB: Unable to open file " );
        OutputDebugString( szFile );
        OutputDebugStringA( "\n" );
        return;
    }
}

TBackTraceFile::
~TBackTraceFile(
    VOID
    )
{
    if( _hFile != INVALID_HANDLE_VALUE ){
        CloseHandle( _hFile );
    }
}

HANDLE
TBackTraceFile::
hCapture(
    ULONG_PTR Info1,
    ULONG_PTR Info2,
    ULONG_PTR Info3,
    PULONG pHash
    )
{
    TLine Line;
    PVOID apvBackTrace[kMaxDepth+1];
    DWORD cbWritten;

    CHAR szLine[kMaxLineStr];
    szLine[0] = 0;

#ifdef BACKTRACE_ENABLED

    ULONG ulHash;

    //
    // Capture a backtrace at this spot for debugging.
    //
    UINT uDepth = RtlCaptureStackBackTrace( 2,
                                            kMaxDepth,
                                            apvBackTrace,
                                            &ulHash );
#endif

    EnterCriticalSection( &gcsBackTrace );

    //
    // Print out strings as appropriate.
    //

    if( _fOptions1 & kString )
    {
        WriteFile( _hFile,
                   (LPCVOID)Info1,
                   lstrlenA( (LPCSTR)Info1 ),
                   &cbWritten,
                   NULL );
    }

    if( _fOptions2 & kString )
    {
        WriteFile( _hFile,
                   (LPCVOID)Info2,
                   lstrlenA( (LPCSTR)Info2 ),
                   &cbWritten,
                   NULL );
    }

    //
    // Print out the hex info.
    //

    wsprintfA( szLine,
               "\n\t%08x: %08x %08x %08x threadid=%x tc=%x < %x >: ",
               this,
               Info1,
               Info2,
               Info3,
               GetCurrentThreadId(),
               GetTickCount(),
               Info1 + Info2 );

    if( _hFile )
    {
        WriteFile( _hFile, szLine, lstrlenA( szLine ), &cbWritten, NULL );
    }

#ifdef BACKTRACE_ENABLED

    //
    // Print out the backtrace.
    //

    UINT i;
    UINT uLineEnd = 1;
    szLine[0] = '\t';

    for( i=0; i < uDepth; ++i )
    {
        uLineEnd += wsprintfA( szLine + uLineEnd, "%08x ", apvBackTrace[i] );
    }

    if( _hFile && i )
    {
        szLine[uLineEnd++] = '\n';
        WriteFile( _hFile, szLine, uLineEnd, &cbWritten, NULL );
    }

#endif

    //
    // Add extra blank line.
    //
    szLine[0] = '\n';
    WriteFile( _hFile, szLine, 1, &cbWritten, NULL );

    LeaveCriticalSection( &gcsBackTrace );

    //
    // Free memory if necessary.
    //
    if( _fOptions1 & kString )
    {
        DbgFreeMem( (PVOID)Info1 );
    }

    if( _fOptions2 & kString )
    {
        DbgFreeMem( (PVOID)Info2 );
    }

#ifdef BACKTRACE_ENABLED

    if( pHash )
    {
        *pHash = ulHash;
    }

#endif

    return NULL;
}

#endif // TRACE_ENABLED

#endif // #ifdef DBG