//*******************************************************************
//
// File: Asura_Platform_CallStack.cpp                  
//
// Created: 29 Mar 2006
//
// Description: 
//
//*******************************************************************

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// Stdlib platform-specific.
#include <windows.h>
#include <dbghelp.h>

// Engine independent.
#include "Core/Asura_CallStack.h"
#include "Core/Asura_String.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Debug_Timers.h"
#include "Debugging/Asura_Logfile.h"

#pragma comment( lib, "DbgHelp")

// P R O T O T Y P E S   A N D   I N L I N E   F U N C T I O N S /////////////

static const char* GetFunctionInfo( HANDLE xCurProcess, const void* pAddr );
#if defined( ASURA_TOOLS)
static const char* GetFunctionInfoXML( HANDLE xCurProcess, const void* pAddr );
#endif

// DATA

static bool s_bSymInitialised = false;

// C L A S S   I M P L E M E N T A T I O N S /////////////////////////////////

const u_int uMAX_LEVELS_TO_PROCESS = 32;

struct Asura_StackDumpPointers
{
	u_int m_uStackDepth;
	void* m_apStack[ uMAX_LEVELS_TO_PROCESS];
};

void* Asura_CallStack::Platform_CreateStackDump( u_int uLevelsToSkip )
{
    HANDLE xCurProcess = GetCurrentProcess();

	// DLH - 23 February 2007 09:51:59 - We have no idea if SymInitialize() has been called as there is no initialise routine and this is used for more than the Memory Manager
	if( false == s_bSymInitialised)
	{
		SymInitialize( xCurProcess, NULL, TRUE );
		s_bSymInitialised = true;
	}
	
    // Allocate room for a count then all the addresses.
//    const u_int uDATA_SIZE = sizeof(u_int) + uMAX_LEVELS_TO_PROCESS * sizeof(u_int);
	const u_int uDATA_SIZE = sizeof( Asura_StackDumpPointers);
    void* pvData = HeapAlloc( GetProcessHeap(), 0, uDATA_SIZE );
    if( !pvData ) { return NULL; }
    
    Asura_StackDumpPointers* pxData = static_cast< Asura_StackDumpPointers*>( pvData);

//    u_int &uAddrCount = pxData->m_uStackDepth;
//    u_int* puStackAddrs = puAddrCount + 1;
    void** ppStackAddrs = pxData->m_apStack;

	HANDLE xCurThread  = GetCurrentThread();

	STACKFRAME64 xStackFrame = { 0};
	memset( &xStackFrame, 0, sizeof(xStackFrame) );

	xStackFrame.AddrPC.Mode    = AddrModeFlat;
  	xStackFrame.AddrStack.Mode = AddrModeFlat;
	xStackFrame.AddrFrame.Mode = AddrModeFlat;

#if defined( _WIN64)
	CONTEXT xContext = {0};
	CONTEXT* pxContext = &xContext;

	RtlCaptureContext( &xContext);
	
	xStackFrame.AddrPC.Offset = xContext.Rip;
	xStackFrame.AddrStack.Offset = xContext.Rsp;
	xStackFrame.AddrFrame.Offset = xContext.Rsp;
#else
	CONTEXT* pxContext = NULL;
	{
		DWORD uAddrPCOffset = 0;
		DWORD uAddrStackOffset = 0;
		DWORD uAddrFrameOffset = 0;
		__asm
		{
			call geip;
		geip:
			pop uAddrPCOffset;
		};
		__asm mov uAddrStackOffset, esp
		__asm mov uAddrFrameOffset, ebp;
		xStackFrame.AddrPC.Offset = uAddrPCOffset;
		xStackFrame.AddrStack.Offset = uAddrStackOffset;
		xStackFrame.AddrFrame.Offset = uAddrFrameOffset;
    }
#endif

    // Skip an extra level to account for the way we retrieve the PC above.
    ++uLevelsToSkip;

	u_int uLevel = 0;
    u_int uAddressCount = 0;
    do 
    {
		#if defined( _WIN64)
		DWORD uMachineType = IMAGE_FILE_MACHINE_AMD64;
		#elif defined( _WIN32)
		DWORD uMachineType = IMAGE_FILE_MACHINE_I386;
		#endif
        if( !StackWalk64( uMachineType, xCurProcess, xCurThread, &xStackFrame, pxContext, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL) )
        {
            // Either an error or we're finished.
            break;
        }

        if( !xStackFrame.AddrFrame.Offset )
        {
            // I think this also means we're done.
            break;
        }

		if( uLevel++ < uLevelsToSkip )
        {
            continue;
        }

		void* pAddr = (void*)xStackFrame.AddrPC.Offset;
		*ppStackAddrs = pAddr;
		ppStackAddrs++;
		++uAddressCount;
    }
    while( uAddressCount < uMAX_LEVELS_TO_PROCESS );

    pxData->m_uStackDepth = uAddressCount;

    return pvData;
}


void Asura_CallStack::Platform_GetHumanReadableStackDump( const void* pvRawStackDump, char* szOutputBfr, u_int uOutputBfrSize )
{
    Asura_Assert( szOutputBfr && uOutputBfrSize, "" );

//    const u_int* puAddrCount  = static_cast<const u_int*>( pvRawStackDump );
    const Asura_StackDumpPointers* pxData = static_cast< const Asura_StackDumpPointers*>( pvRawStackDump);

    const void* const * ppStackAddrs = pxData->m_apStack;

    const u_int uNumStackAddresses = pxData->m_uStackDepth;
    if( !uNumStackAddresses )
    {
        *szOutputBfr++ = '\0';
        return;
    }

    HANDLE xCurProcess = GetCurrentProcess();

	// DLH - 23 February 2007 09:51:59 - We have no idea if SymInitialize() has been called as there is no initialise routine and this is used for more than the Memory Manager
	if( false == s_bSymInitialised)
	{
		ASURA_CPU_PROFILE_SCOPED_TIMER( "Asura_CallStack::Platform_GetHumanReadableStackDump SymInitialize");
	
		SymInitialize( xCurProcess, NULL, TRUE );
		s_bSymInitialised = true;
	}
	
    // NB: We assume that SymInitialize() has already been called. Currently
    // that's done in Asura_MemoryManagement_System::Platform_Initialise(),
    // since that's the only place this class is used.
	SymSetOptions( (SymGetOptions() | SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS) & ~SYMOPT_UNDNAME );

    u_int uRemainingOutputBfrChars = uOutputBfrSize;
    for( u_int u = 0; u < uNumStackAddresses; ++u )
    {
        const void* pNextAddr = *ppStackAddrs;
        ppStackAddrs++;
        const char* szFnInfo = GetFunctionInfo( xCurProcess, pNextAddr );

        u_int uCharsOutput = Asura_PrintfToBfr( szOutputBfr,
                                                "0x%08p: %s\n",
                                                uRemainingOutputBfrChars, true,
                                                pNextAddr, (szFnInfo ? szFnInfo : "<unknown>") );

        Asura_Assert( uCharsOutput < uRemainingOutputBfrChars, "" ); // Should be a strict inequality since we're forcing a nul terminator and that's not included.
        szOutputBfr += uCharsOutput;
        uRemainingOutputBfrChars -= uCharsOutput;
    }
}


#if defined( ASURA_TOOLS)
void Asura_CallStack::Platform_GetXMLStackDump( const void* pvRawStackDump, char* szOutputBfr, u_int uOutputBfrSize )
{
    Asura_Assert( szOutputBfr && uOutputBfrSize, "" );
    
    char* szOutputBfrStart = szOutputBfr;
    char* szOutputBfrEnd = szOutputBfr + (uOutputBfrSize - 1);
    
    static const char *pszXMLFooter = "</stack>";
    const char* szOutputBfrStartFooter = szOutputBfrEnd - strlen( pszXMLFooter);
    
//    const u_int* puAddrCount  = static_cast<const u_int*>( pvRawStackDump );
//    const u_int* puStackAddrs = puAddrCount + 1;
    const Asura_StackDumpPointers* pxData = static_cast< const Asura_StackDumpPointers*>( pvRawStackDump);

    const void* const * ppStackAddrs = pxData->m_apStack;


//    const u_int uNumStackAddresses = *puAddrCount;
    const u_int uNumStackAddresses = pxData->m_uStackDepth;
    if( !uNumStackAddresses )
    {
        *szOutputBfr++ = '\0';
        return;
    }

	// DLH - 23 February 2007 09:51:59 - We have no idea if SymInitialize() has been called as there is no initialise routine and this is used for more than the Memory Manager
	if( false == s_bSymInitialised)
	{
		SymInitialize( GetCurrentProcess(), NULL, TRUE );
		s_bSymInitialised = true;
	}
	
    // NB: We assume that SymInitialize() has already been called. Currently
    // that's done in Asura_MemoryManagement_System::Platform_Initialise(),
    // since that's the only place this class is used.
	SymSetOptions( (SymGetOptions() | SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS) & ~SYMOPT_UNDNAME );

    HANDLE xCurProcess = GetCurrentProcess();

    u_int uRemainingOutputBfrChars = uOutputBfrSize;

	{    
		u_int uCharsOutput = Asura_PrintfToBfr( szOutputBfr, "<stack>", uRemainingOutputBfrChars, true);

		Asura_Assert( uCharsOutput < uRemainingOutputBfrChars, "" ); // Should be a strict inequality since we're forcing a nul terminator and that's not included.
        
        char* pszNewOutputBfr = szOutputBfr + uCharsOutput;
        if( szOutputBfrEnd <= pszNewOutputBfr)
        {
			szOutputBfrStart[0] = '\0';
			return;
        }
        szOutputBfr = pszNewOutputBfr;
        uRemainingOutputBfrChars -= uCharsOutput;
    }
    
    for( u_int u = 0; u < uNumStackAddresses; ++u )
    {
        const void* pNextAddr = *ppStackAddrs++;
        const char* szFnInfo = GetFunctionInfoXML( xCurProcess, pNextAddr);

        u_int uCharsOutput = Asura_PrintfToBfr( szOutputBfr,
                                                "<level><address value=\"0x%08p\"/>%s</level>",
                                                uRemainingOutputBfrChars, true,
                                                pNextAddr, (szFnInfo ? szFnInfo : "") );

        Asura_Assert( uCharsOutput < uRemainingOutputBfrChars, "" ); // Should be a strict inequality since we're forcing a nul terminator and that's not included.
        char* pszNewOutputBfr = szOutputBfr + uCharsOutput;
        if( szOutputBfrStartFooter <= pszNewOutputBfr)
        {
			szOutputBfr[0] = '\0';
			break;
        }
        szOutputBfr = pszNewOutputBfr;
        uRemainingOutputBfrChars -= uCharsOutput;
    }
    
	{    
		u_int uCharsOutput = Asura_PrintfToBfr( szOutputBfr, pszXMLFooter, uRemainingOutputBfrChars, true);
        char* pszNewOutputBfr = szOutputBfr + uCharsOutput;
        if( szOutputBfrEnd <= pszNewOutputBfr)
        {
			szOutputBfrStart[0] = '\0';
			return;
        }
        szOutputBfr = pszNewOutputBfr;

		Asura_Assert( uCharsOutput < uRemainingOutputBfrChars, "" ); // Should be a strict inequality since we're forcing a nul terminator and that's not included.
    }
    
}
#endif

void Asura_CallStack::Platform_DeallocStackDump( void* pvStackDump )
{
    HeapFree( GetProcessHeap(), 0, pvStackDump );
}

// F U N C T I O N   I M P L E M E N T A T I O N S ///////////////////////////

static const char* GetFunctionInfo( HANDLE xCurProcess, const void* pAddr)
{
	DWORD64 uAddr = DWORD64( pAddr);
    const u_int uSYMBOL_BFR_SIZE = 1024;
    char acSymbolBfr[uSYMBOL_BFR_SIZE];
    
	IMAGEHLP_SYMBOL64* pxSymbolInfo = reinterpret_cast<IMAGEHLP_SYMBOL64*>( &acSymbolBfr[0] );
    memset( pxSymbolInfo, 0, uSYMBOL_BFR_SIZE );
    pxSymbolInfo->SizeOfStruct = sizeof( IMAGEHLP_SYMBOL64);
    pxSymbolInfo->MaxNameLength = uSYMBOL_BFR_SIZE - pxSymbolInfo->SizeOfStruct;

	DWORD uDisplacementLine = 0;
	IMAGEHLP_LINE64 xLineInfo;
    memset( &xLineInfo, 0, sizeof( xLineInfo) );
	xLineInfo.SizeOfStruct = sizeof( xLineInfo);
	BOOL bOK = SymGetLineFromAddr64( xCurProcess, uAddr, &uDisplacementLine, &xLineInfo );
    if( !bOK ) 
    { 
		return NULL; 
	}
	
	DWORD64 uDisplacementSymbol = 0;
    bOK = SymGetSymFromAddr64( xCurProcess, uAddr, &uDisplacementSymbol, pxSymbolInfo );
    if( !bOK ) 
    { 
		return NULL; 
	}

    const u_int uUNDEC_BFR_SIZE = 1024;
    char szUndecBfr[uUNDEC_BFR_SIZE];
    u_int uUndecorateFlags = UNDNAME_COMPLETE
                           | UNDNAME_NO_THISTYPE
                           | UNDNAME_NO_SPECIAL_SYMS
                           | UNDNAME_NO_MEMBER_TYPE
                           | UNDNAME_NO_MS_KEYWORDS
                           | UNDNAME_NO_ACCESS_SPECIFIERS;
	DWORD uLen = UnDecorateSymbolName( pxSymbolInfo->Name, szUndecBfr, uUNDEC_BFR_SIZE, uUndecorateFlags );
    if( !uLen ) { return NULL; }

    const u_int uINTERNAL_BFR_SIZE = 1024;
    static char szInternalBfr[uINTERNAL_BFR_SIZE];
    Asura_PrintfToBfr( szInternalBfr,
                       "%s (%s @ %i)",
                       uINTERNAL_BFR_SIZE, true,
                       szUndecBfr, xLineInfo.FileName, xLineInfo.LineNumber );

    return szInternalBfr;
}

#if defined( ASURA_TOOLS)
static const char* GetFunctionInfoXML( HANDLE xCurProcess, const void* pAddr )
{
    const u_int uSYMBOL_BFR_SIZE = 1024;
    char acSymbolBfr[uSYMBOL_BFR_SIZE];
	IMAGEHLP_SYMBOL64* pxSymbolInfo = reinterpret_cast<IMAGEHLP_SYMBOL64*>( &acSymbolBfr[0] );
    memset( pxSymbolInfo, 0, uSYMBOL_BFR_SIZE );
    pxSymbolInfo->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
    pxSymbolInfo->MaxNameLength = uSYMBOL_BFR_SIZE - pxSymbolInfo->SizeOfStruct;

	DWORD64 uAddr64 = DWORD64( pAddr);
	DWORD uDisplacementLine = 0;
	IMAGEHLP_LINE64 xLineInfo;
    memset( &xLineInfo, 0, sizeof( xLineInfo) );
	xLineInfo.SizeOfStruct = sizeof( xLineInfo);
	BOOL bOK = SymGetLineFromAddr64( xCurProcess, uAddr64, &uDisplacementLine, &xLineInfo );
    if( !bOK ) { return NULL; }
	
	DWORD64 uDisplacementSym = 0;
    bOK = SymGetSymFromAddr64( xCurProcess, uAddr64, &uDisplacementSym, pxSymbolInfo );
    if( !bOK ) { return NULL; }

    const u_int uUNDEC_BFR_SIZE = 1024;
    char szUndecBfr[uUNDEC_BFR_SIZE];
    u_int uUndecorateFlags = UNDNAME_COMPLETE
                           | UNDNAME_NO_THISTYPE
                           | UNDNAME_NO_SPECIAL_SYMS
                           | UNDNAME_NO_MEMBER_TYPE
                           | UNDNAME_NO_MS_KEYWORDS
                           | UNDNAME_NO_ACCESS_SPECIFIERS;
	DWORD uLen = UnDecorateSymbolName( pxSymbolInfo->Name, szUndecBfr, uUNDEC_BFR_SIZE, uUndecorateFlags );
    if( !uLen ) { return NULL; }

    const u_int uINTERNAL_BFR_SIZE = 1024;
    static char szInternalBfr[uINTERNAL_BFR_SIZE];
    static char szFilenameXMLBfr[uINTERNAL_BFR_SIZE];
    static char szSymbolsXMLBfr[uINTERNAL_BFR_SIZE];
    
	Asura_SimpleLogfileConvertToXML( xLineInfo.FileName, szFilenameXMLBfr, uINTERNAL_BFR_SIZE);
	Asura_SimpleLogfileConvertToXML( szUndecBfr, szSymbolsXMLBfr, uINTERNAL_BFR_SIZE);
    
    Asura_PrintfToBfr( szInternalBfr,
                       "<source filename=\"%s\" fileline=\"%d\"/><symbols>%s</symbols>",
                       uINTERNAL_BFR_SIZE, true,
                       szFilenameXMLBfr, xLineInfo.LineNumber, szSymbolsXMLBfr);

    return szInternalBfr;
}
#endif

//*******************************************************************
//
// (c) 2006 Rebellion (Derby)
//
// Warning: This computer program is protected by copyright law and 
// international treaties. Unauthorized reproduction or distribution
// of this program, or any portion of it, may result in severe civil and
// criminal penalties, and will be prosecuted to the maximum extent 
// possible under the law.
//
//*******************************************************************

