/*<<<***************************************************************************
The MIT License (MIT)

Copyright(C) 2013 - 2015 Marc Mirti Mancinelli, Christophe Lardau and Philippe Pamart (xdcube.org)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
////////////////////////////////////////////////////////////////////////////////
#Title: xbODstackWalk
#Project: xdcBase
////////////////////////////////////////////////////////////////////////////////
#Author: Marc MIRTI MANCINELLI
#History:
<17/08/2013: creation.
12/02/2014: this notice inserted.
***************************************************************************>>>*/
// stackwalk.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "../../xbFileTools.h"
#include "../../xbData.h"

#ifdef xbOS_MSWIN

//http://win32.mvps.org/misc/stackwalk.html

//memory dump info: 
// - http://www.laboratoire-microsoft.org/articles/teched_2002/4/
// - http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx

//OS symbols packages 
// - http://www.microsoft.com/whdc/devtools/debugging/symbolpkg.mspx

//#pragma pack( push, before_imagehlp, 8 )
#include <imagehlp.h>
//#pragma pack( pop, before_imagehlp )

#define gle (GetLastError())
#define lenof(a) (sizeof(a) / sizeof((a)[0]))
#define MAXNAMELEN 1024 // max name length for found symbols
#define TTBUFLEN 65536 // for a temp buffer

#ifdef XDCBASE_X64
#	define xbswMACHINETYPE			IMAGE_FILE_MACHINE_AMD64

#	define xbswDWORD				DWORD64
#	define xbswSTACKFRAME			STACKFRAME64
#	define xbswIMAGEHLP_SYMBOL		IMAGEHLP_SYMBOL64
#	define xbswIMAGEHLP_MODULE		IMAGEHLP_MODULE64
#	define xbswIMAGEHLP_LINE		IMAGEHLP_LINE64
#	define xbswPREAD_PROCESS_MEMORY_ROUTINE PREAD_PROCESS_MEMORY_ROUTINE64
#	define xbswPFUNCTION_TABLE_ACCESS_ROUTINE PFUNCTION_TABLE_ACCESS_ROUTINE64
#	define xbswPGET_MODULE_BASE_ROUTINE PGET_MODULE_BASE_ROUTINE64
#	define xbswPTRANSLATE_ADDRESS_ROUTINE PTRANSLATE_ADDRESS_ROUTINE64
#	define xbswCB(cbname)			(cbname "64")
#	define xbswDWORDFMT	"I64d"
#	define xbswADDRFMT "016I64x"
#else
#	define xbswMACHINETYPE			IMAGE_FILE_MACHINE_I386

#	define xbswDWORD				DWORD
#	define xbswSTACKFRAME			STACKFRAME
#	define xbswIMAGEHLP_SYMBOL		IMAGEHLP_SYMBOL
#	define xbswIMAGEHLP_MODULE		IMAGEHLP_MODULE
#	define xbswIMAGEHLP_LINE		IMAGEHLP_LINE
#	define xbswPREAD_PROCESS_MEMORY_ROUTINE PREAD_PROCESS_MEMORY_ROUTINE
#	define xbswPFUNCTION_TABLE_ACCESS_ROUTINE PFUNCTION_TABLE_ACCESS_ROUTINE
#	define xbswPGET_MODULE_BASE_ROUTINE PGET_MODULE_BASE_ROUTINE
#	define xbswPTRANSLATE_ADDRESS_ROUTINE PTRANSLATE_ADDRESS_ROUTINE
#	define xbswCB(cbname) cbname
#	define xbswDWORDFMT	"ld"
#	define xbswADDRFMT "08lx"
#endif

#define IMGSYMLEN ( sizeof xbswIMAGEHLP_SYMBOL )

#pragma warning(push)
#pragma warning(disable: 4311) //'cast de type' : troncation de pointeur de 'BYTE *' à 'DWORD'

class xbStackWalk
{
public:
	xbStackWalk()
	{
		pSC = NULL;
		pSFTA = NULL;
		pSGLFA = NULL;
		pSGMB = NULL;
		pSGMI = NULL;
		pSGO = NULL;
		pSGSFA = NULL;
		pSI = NULL;
		pSLM = NULL;
		pSSO = NULL;
		pSW = NULL;
		pUDSN = NULL;
//		threadAbortFlag = 0;
//		hTapTapTap = NULL;
		hImagehlpDll = NULL;
	}
	~xbStackWalk()
	{
		eProtect(cleanup(););
	}
	
	xbString Dump( EXCEPTION_POINTERS *ep );
	xbString ShowStack( HANDLE hThread, CONTEXT& c, EXCEPTION_RECORD *pER );

protected:
	HINSTANCE hImagehlpDll;
	
	// SymCleanup()
	typedef BOOL (__stdcall *tSC)( IN HANDLE hProcess );
	tSC pSC;

	// SymFunctionTableAccess()
	typedef PVOID (__stdcall *tSFTA)( HANDLE hProcess, xbswDWORD AddrBase );
	tSFTA pSFTA;

	// SymGetLineFromAddr()
	typedef BOOL (__stdcall *tSGLFA)( IN HANDLE hProcess, IN xbswDWORD dwAddr,
		OUT PDWORD pdwDisplacement, OUT xbswIMAGEHLP_LINE *Line );
	tSGLFA pSGLFA;

	// SymGetModuleBase()
	typedef xbswDWORD (__stdcall *tSGMB)( IN HANDLE hProcess, IN xbswDWORD dwAddr );
	tSGMB pSGMB;

	// SymGetModuleInfo()
	typedef BOOL (__stdcall *tSGMI)( IN HANDLE hProcess, IN xbswDWORD dwAddr, OUT xbswIMAGEHLP_MODULE *ModuleInfo );
	tSGMI pSGMI;

	// SymGetOptions()
	typedef DWORD (__stdcall *tSGO)( VOID );
	tSGO pSGO;

	// SymGetSymFromAddr()
	typedef BOOL (__stdcall *tSGSFA)( IN HANDLE hProcess, IN xbswDWORD dwAddr,
		OUT xbswDWORD *pdwDisplacement, OUT xbswIMAGEHLP_SYMBOL *Symbol );
	tSGSFA pSGSFA;

	// SymInitialize()
	typedef BOOL (__stdcall *tSI)( IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess );
	tSI pSI;

	// SymLoadModule()
	typedef DWORD (__stdcall *tSLM)( IN HANDLE hProcess, IN HANDLE hFile,
		IN PSTR ImageName, IN PSTR ModuleName, IN xbswDWORD BaseOfDll, IN xbswDWORD SizeOfDll );
	tSLM pSLM;

	// SymSetOptions()
	typedef DWORD (__stdcall *tSSO)( IN DWORD SymOptions );
	tSSO pSSO;

	// StackWalk()
	typedef BOOL (__stdcall *tSW)( DWORD MachineType, HANDLE hProcess,
		HANDLE hThread, xbswSTACKFRAME *StackFrame, PVOID ContextRecord,
		xbswPREAD_PROCESS_MEMORY_ROUTINE ReadMemoryRoutine,
		xbswPFUNCTION_TABLE_ACCESS_ROUTINE FunctionTableAccessRoutine,
		xbswPGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine,
		xbswPTRANSLATE_ADDRESS_ROUTINE TranslateAddress );
	tSW pSW;

	// UnDecorateSymbolName()
	typedef DWORD (__stdcall WINAPI *tUDSN)( PCSTR DecoratedName, PSTR UnDecoratedName,
		DWORD UndecoratedLength, DWORD Flags );
	tUDSN pUDSN;

	struct ModuleEntry
	{
		xbString imageName;
		xbString moduleName;
		xbswDWORD baseAddress;
		DWORD size;
	};
	typedef xbArray< ModuleEntry > ModuleList;
//	typedef ModuleList::iterator ModuleListIter;

//	int threadAbortFlag;
//	HANDLE hTapTapTap;

protected:
	xbString enumAndLoadModuleSymbols( HANDLE hProcess, DWORD pid );
	bool fillModuleList( ModuleList& modules, DWORD pid, HANDLE hProcess );
	bool fillModuleListTH32( ModuleList& modules, DWORD pid );
	bool fillModuleListPSAPI( ModuleList& modules, DWORD pid, HANDLE hProcess );

	bool init();
	void cleanup();

	

/*exemple
	int main( void )
	{
		HANDLE hThread;
		DWORD idThread;
		HINSTANCE hImagehlpDll = NULL;

		// we load imagehlp.dll dynamically because the NT4-version does not
		// offer all the functions that are in the NT5 lib
		hImagehlpDll = xbLoadLibrary( "imagehlp.dll" );
		if ( hImagehlpDll == NULL )
		{
			printf( "LoadLibrary( \"imagehlp.dll\" ): gle = %lu\n", gle );
			return 1;
		}

		pSC = (tSC) GetProcAddress( hImagehlpDll, "SymCleanup" );
		pSFTA = (tSFTA) GetProcAddress( hImagehlpDll, "SymFunctionTableAccess" );
		pSGLFA = (tSGLFA) GetProcAddress( hImagehlpDll, "SymGetLineFromAddr" );
		pSGMB = (tSGMB) GetProcAddress( hImagehlpDll, "SymGetModuleBase" );
		pSGMI = (tSGMI) GetProcAddress( hImagehlpDll, "SymGetModuleInfo" );
		pSGO = (tSGO) GetProcAddress( hImagehlpDll, "SymGetOptions" );
		pSGSFA = (tSGSFA) GetProcAddress( hImagehlpDll, "SymGetSymFromAddr" );
		pSI = (tSI) GetProcAddress( hImagehlpDll, "SymInitialize" );
		pSSO = (tSSO) GetProcAddress( hImagehlpDll, "SymSetOptions" );
		pSW = (tSW) GetProcAddress( hImagehlpDll, "StackWalk" );
		pUDSN = (tUDSN) GetProcAddress( hImagehlpDll, "UnDecorateSymbolName" );
		pSLM = (tSLM) GetProcAddress( hImagehlpDll, "SymLoadModule" );

		if ( pSC == NULL || pSFTA == NULL || pSGMB == NULL || pSGMI == NULL ||
			pSGO == NULL || pSGSFA == NULL || pSI == NULL || pSSO == NULL ||
			pSW == NULL || pUDSN == NULL || pSLM == NULL )
		{
			puts( "GetProcAddress(): some required function not found." );
			FreeLibrary( hImagehlpDll );
			return 1;
		}

		hTapTapTap = CreateEvent( NULL, false, false, NULL );

		hThread = CreateThread( NULL, 5*524288, TargetThread, NULL, 0, &idThread );
		// let the thread complete the exception-handler part
		// and yes, I know I should check the return code
		WaitForSingleObject( hTapTapTap, INFINITE );
		CloseHandle( hTapTapTap );

		// time to stop the victim
		if ( (DWORD) (-1L) == SuspendThread( hThread ) )
		{
			printf( "SuspendThread(): gle = %lu\n", gle );
			goto cleanup;
		}

		CONTEXT c;
		memset( &c, '\0', sizeof c );
		c.ContextFlags = CONTEXT_FULL;

		// init CONTEXT record so we know where to start the stackwalk
		if ( ! GetThreadContext( hThread, &c ) )
		{
			printf( "GetThreadContext(): gle = %lu\n", gle );
			goto cleanup;
		}

		ShowStack( hThread, c );

	cleanup:
		++ threadAbortFlag;
		Sleep( 100 ); // to let the thread commit suicide
		FreeLibrary( hImagehlpDll );
		return 0;
	}*/

/*	void ThreadFunc2()
	{
		// first, we cause an exception and dump our own stack
		__try
		{
			char *p = NULL;
			*p = 'A'; // BANG!
		}
		__except ( Filter( GetExceptionInformation() ) )
		{
			printf( "Handled exception.\n" );
		}

		// phase 1 done, tell main thread we can be autopsied from the outside
		SetEvent( hTapTapTap );

		SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_IDLE );
		for ( ; ! threadAbortFlag; )
		{
			Sleep( 10 );
		}
		SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_NORMAL );
	}



	void ThreadFunc1()
	{
		ThreadFunc2();
	}



	DWORD __stdcall TargetThread( void *arg )
	{
		(void) arg;
		ThreadFunc1();
		return 0;
	}

*/







	// miscellaneous toolhelp32 declarations; we cannot #include the header
	// because not all systems may have it
	#define MAX_MODULE_NAME32 255
	#define TH32CS_SNAPMODULE   0x00000008
	#pragma pack( push, 8 )
	typedef struct tagMODULEENTRY32
	{
		DWORD   dwSize;
		DWORD   th32ModuleID;       // This module
		DWORD   th32ProcessID;      // owning process
		DWORD   GlblcntUsage;       // Global usage count on the module
		DWORD   ProccntUsage;       // Module usage count in th32ProcessID's context
		BYTE  * modBaseAddr;        // Base address of module in th32ProcessID's context
		DWORD   modBaseSize;        // Size in bytes of module starting at modBaseAddr
		HMODULE hModule;            // The hModule of this module in th32ProcessID's context
		char    szModule[MAX_MODULE_NAME32 + 1];
		char    szExePath[MAX_PATH];
	} MODULEENTRY32;
	typedef MODULEENTRY32 *  PMODULEENTRY32;
	typedef MODULEENTRY32 *  LPMODULEENTRY32;
	#pragma pack( pop )



	// miscellaneous psapi declarations; we cannot #include the header
	// because not all systems may have it
	typedef struct _MODULEINFO {
		LPVOID lpBaseOfDll;
		DWORD SizeOfImage;
		LPVOID EntryPoint;
	} MODULEINFO, *LPMODULEINFO;



};


bool xbStackWalk::init()
{
	if (hImagehlpDll)
		return true;

	// we load imagehlp.dll dynamically because the NT4-version does not
	// offer all the functions that are in the NT5 lib
	hImagehlpDll = xbLoadLibrary( "imagehlp.dll" );
	if ( hImagehlpDll == NULL )
	{
		xbRelOutput(xbFormat("STACKWALK: LoadLibrary( \"imagehlp.dll\" ): gle = %lu\n", gle));
		return false;
	}

	pSC = (tSC) GetProcAddress( hImagehlpDll, "SymCleanup" );
	pSFTA = (tSFTA) GetProcAddress( hImagehlpDll, xbswCB("SymFunctionTableAccess") );
	pSGLFA = (tSGLFA) GetProcAddress( hImagehlpDll, xbswCB("SymGetLineFromAddr") );
	pSGMB = (tSGMB) GetProcAddress( hImagehlpDll, xbswCB("SymGetModuleBase") );
	pSGMI = (tSGMI) GetProcAddress( hImagehlpDll, xbswCB("SymGetModuleInfo") );
	pSGO = (tSGO) GetProcAddress( hImagehlpDll, "SymGetOptions" );
	pSGSFA = (tSGSFA) GetProcAddress( hImagehlpDll, xbswCB("SymGetSymFromAddr") );
	pSI = (tSI) GetProcAddress( hImagehlpDll, "SymInitialize" );
	pSSO = (tSSO) GetProcAddress( hImagehlpDll, "SymSetOptions" );
	pSW = (tSW) GetProcAddress( hImagehlpDll, xbswCB("StackWalk") );
	pUDSN = (tUDSN) GetProcAddress( hImagehlpDll, "UnDecorateSymbolName" );
	pSLM = (tSLM) GetProcAddress( hImagehlpDll, xbswCB("SymLoadModule") );

	if ( pSC == NULL || pSFTA == NULL || pSGMB == NULL || pSGMI == NULL ||
		pSGO == NULL || pSGSFA == NULL || pSI == NULL || pSSO == NULL ||
		pSW == NULL || pUDSN == NULL || pSLM == NULL )
	{
		xbRelOutput("STACKWALK: some required function not found");
		cleanup();
		return false;
	}
	return true;
}

void xbStackWalk::cleanup()
{
	if (!hImagehlpDll)
		return;
	xbFreeLibrary( hImagehlpDll );
	hImagehlpDll=NULL;
}

// if you use C++ exception handling: install a translator function
// with set_se_translator(). In the context of that function (but *not*
// afterwards), you can either do your stack dump, or save the CONTEXT
// record as a local copy. Note that you must do the stack sump at the
// earliest opportunity, to avoid the interesting stackframes being gone
// by the time you do the dump.
xbString xbStackWalk::Dump( EXCEPTION_POINTERS *ep )
{
	HANDLE hThread=NULL;

	xbString ret;
	DuplicateHandle( GetCurrentProcess(), GetCurrentThread(),
		GetCurrentProcess(), &hThread, 0, false, DUPLICATE_SAME_ACCESS );
//	CONTEXT ctx(*(ep->ContextRecord));
	ret = ShowStack( hThread, *(ep->ContextRecord), ep->ExceptionRecord );
	CloseHandle( hThread );

	return ret;
}

xbString xbStackWalk::ShowStack( HANDLE hThread, CONTEXT& c, EXCEPTION_RECORD *pER )
{
	xbString msgDump;
	if (pER)
	{
		for(;;)
		{
			if (pER->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && pER->NumberParameters == 2)
			{
				if (pER->ExceptionInformation[0])
					msgDump += xbFormat("Application has tried to write data at invalid address %" xbswADDRFMT ".\n",pER->ExceptionInformation[1]);
				else
					msgDump += xbFormat("Application has tried to read data at invalid address %" xbswADDRFMT ".\n",pER->ExceptionInformation[1]);
				break;
			}
			if (pER->NumberParameters <= 0 || pER->NumberParameters > EXCEPTION_MAXIMUM_PARAMETERS)
				break;

			msgDump += "Exception parameters:\n";
			for(DWORD i = 0; i < pER->NumberParameters;i++)
			{
				msgDump += xbFormat("    parameter %2d = %\n" xbswADDRFMT,i,pER->ExceptionInformation[i]);
			}

			break;
		}
	}
	if(!hThread)
		return msgDump;
	if (!init())
		return msgDump;

	// normally, call ImageNtHeader() and use machine info from PE header
	DWORD imageType = xbswMACHINETYPE;
	HANDLE hProcess = GetCurrentProcess(); // hProcess normally comes from outside
	int frameNum; // counts walked frames
	xbswDWORD offsetFromSymbol; // tells us how far from the symbol we were
	DWORD symOptions; // symbol handler settings
	xbswIMAGEHLP_SYMBOL *pSym = (xbswIMAGEHLP_SYMBOL *) malloc( IMGSYMLEN + MAXNAMELEN );
	char undName[MAXNAMELEN]; // undecorated name
	char undFullName[MAXNAMELEN]; // undecorated name with all shenanigans
	xbswIMAGEHLP_MODULE Module;
	xbswIMAGEHLP_LINE Line;

	// NOTE: normally, the exe directory and the current directory should be taken
	// from the target process. The current dir would be gotten through injection
	// of a remote thread; the exe fir through either ToolHelp32 or PSAPI.

	// build symbol search path from:

	xbString symSearchPath;
	//BOSsys dir
	if (::xbDirExists(xbXDCSYSPATH "\\debugInfo"))
			symSearchPath += xbXDCSYSPATH "\\debugInfo;";

	// current directory
	{
		xbString curDir(::xbGetCurrentDir(false));
		if (curDir.IsValid())
			symSearchPath += curDir+";";
	}
	// dir with executable
	{
		xbString curDir(::xbGetFileDir(xbGetInstanceHandle(),false));
		if (curDir.IsValid())
			symSearchPath += curDir+";";
	}

	{
		xbString curDir;
		 
		// environment variable _NT_SYMBOL_PATH
		if ( GetEnvironmentVariable( "_NT_SYMBOL_PATH", xbStrExp(&curDir,TTBUFLEN), TTBUFLEN ) )
			symSearchPath += curDir+";";
		// environment variable _NT_ALTERNATE_SYMBOL_PATH
		if ( GetEnvironmentVariable( "_NT_ALTERNATE_SYMBOL_PATH", xbStrExp(&curDir,TTBUFLEN), TTBUFLEN ) )
			symSearchPath += curDir+";";
		// environment variable SYSTEMROOT
		if ( GetEnvironmentVariable( "SYSTEMROOT", xbStrExp(&curDir,TTBUFLEN), TTBUFLEN ) )
			symSearchPath += curDir+";";
	}

	if ( symSearchPath.Length > 0 ) // if we added anything, we have a trailing semicolon
		symSearchPath.Length = symSearchPath.Length - 1;

	xbRdbgOutput("STACKWALK: symbols path: "+symSearchPath);

	// why oh why does SymInitialize() want a writeable string?
	xbData dtaSearchPath;
	strncpy( (LPSTR)dtaSearchPath.GetWritePtr(TTBUFLEN-1), symSearchPath, TTBUFLEN-1 );

	// init symbol handler stuff (SymInitialize())
	if ( ! pSI( hProcess, (LPSTR)(LPCVOID)dtaSearchPath, TRUE ) )
	{
		xbRelOutput( xbFormat("STACKWALK: SymInitialize(): gle = %lu\n", gle ));
		free( pSym );
		return msgDump;
	}

	// SymGetOptions()
	symOptions = pSGO();
	symOptions |= SYMOPT_LOAD_LINES;
//	symOptions |= SYMOPT_LOAD_ANYTHING;
	symOptions &= ~SYMOPT_UNDNAME;
	pSSO( symOptions ); // SymSetOptions()

	// Enumerate modules and tell imagehlp.dll about them.
	// On NT, this is not necessary, but it won't hurt.
	msgDump += enumAndLoadModuleSymbols( hProcess, GetCurrentProcessId() );

	// init STACKFRAME for first call
	// Notes: AddrModeFlat is just an assumption. I hate VDM debugging.
	// Notes: will have to be #ifdef-ed for Alphas; MIPSes are dead anyway,
	// and good riddance.

	xbswSTACKFRAME s; // in/out stackframe
	memset( &s, '\0', sizeof s );

#ifdef XDCBASE_X64
	s.AddrPC.Offset = c.Rip;
	s.AddrFrame.Offset = c.Rbp;
	s.AddrStack.Offset = c.Rsp;
#else
	s.AddrPC.Offset = c.Eip;
	s.AddrFrame.Offset = c.Ebp;
	s.AddrStack.Offset = c.Esp;
#endif
	s.AddrPC.Mode = AddrModeFlat;
	s.AddrFrame.Mode = AddrModeFlat;
	s.AddrStack.Mode = AddrModeFlat;

	memset( pSym, '\0', IMGSYMLEN + MAXNAMELEN );
	pSym->SizeOfStruct = IMGSYMLEN;
	pSym->MaxNameLength = MAXNAMELEN;

	memset( &Line, '\0', sizeof Line );
	Line.SizeOfStruct = sizeof Line;

	memset( &Module, '\0', sizeof Module );
	Module.SizeOfStruct = sizeof Module;

	offsetFromSymbol = 0;

	msgDump += "\nRegisters:";
#ifdef XDCBASE_X64
	msgDump += xbFormat("\nRAX = %016I64x RBX = %016I64x RCX = %016I64x",c.Rax,c.Rbx,c.Rcx);
	msgDump += xbFormat("\nRDX = %016I64x RSI = %016I64x RDI = %016I64x",c.Rdx,c.Rsi,c.Rdi);
	msgDump += xbFormat("\nR8  = %016I64x R9  = %016I64x R10 = %016I64x",c.R8,c.R9,c.R10);
	msgDump += xbFormat("\nR11 = %016I64x R12 = %016I64x R13 = %016I64x",c.R11,c.R12,c.R13);
	msgDump += xbFormat("\nR14 = %016I64x R15 = %016I64x",c.R14,c.R15);
	msgDump += xbFormat("\nRSP = %016I64x RBP = %016I64x RIP = %016I64x",c.Rsp,c.Rbp,c.Rip);
#else
	msgDump += xbFormat("\nEAX = %08lx EBX = %08lx ECX = %08lx",c.Eax,c.Ebx,c.Ecx);
	msgDump += xbFormat("\nEDX = %08lx ESI = %08lx EDI = %08lx",c.Edx,c.Esi,c.Edi);
	msgDump += xbFormat("\nESP = %08lx EBP = %08lx EIP = %08lx",c.Esp,c.Ebp,c.Eip);
#endif

#ifdef XDCBASE_X64
	//							 123 xx  1234567812345678  1234567812345678  1234567812345678  1234567812345678  xxxxxx
	msgDump += "\n\nCall stack:\n--# FV  EIP-----          RetAddr-          FramePtr          StackPtr          Symbol\n" ;
#else
	//							 123 xx  12345678  12345678  12345678  12345678  xxxxxx
	msgDump += "\n\nCall stack:\n--# FV  EIP-----  RetAddr-  FramePtr  StackPtr  Symbol\n" ;
#endif
	for ( frameNum = 0; ; ++ frameNum )
	{
		// get next stack frame (StackWalk(), SymFunctionTableAccess(), SymGetModuleBase())
		// if this returns ERROR_INVALID_ADDRESS (487) or ERROR_NOACCESS (998), you can
		// assume that either you are done, or that the stack is so hosed that the next
		// deeper frame could not be found.
		if ( ! pSW( imageType, hProcess, hThread, &s, &c, NULL,
			pSFTA, pSGMB, NULL ) )
			break;

		if (msgDump.Length > 128000)
		{
			msgDump += "\n[...]";
			break;
		}
		// display its contents
		msgDump += xbFormat( "\n%3d %c%c  %" xbswADDRFMT "  %" xbswADDRFMT "  %" xbswADDRFMT "  %" xbswADDRFMT " ",
			frameNum, s.Far? 'F': '.', s.Virtual? 'V': '.',
			s.AddrPC.Offset, s.AddrReturn.Offset,
			s.AddrFrame.Offset, s.AddrStack.Offset );

		if ( s.AddrPC.Offset == 0 )
		{
			msgDump += "(no-symbols PC == 0)\n";
		}
		else
		{ // we seem to have a valid PC
			// show procedure info (SymGetSymFromAddr())
			if ( ! pSGSFA( hProcess, s.AddrPC.Offset, &offsetFromSymbol, pSym ) )
			{
				if ( gle != 487 )
					msgDump += xbFormat("(symbol not found:%lu)\n", gle );
			}
			else
			{
				// UnDecorateSymbolName()
				pUDSN( pSym->Name, undName, MAXNAMELEN, UNDNAME_NAME_ONLY );
				pUDSN( pSym->Name, undFullName, MAXNAMELEN, UNDNAME_COMPLETE );
				msgDump += undName;
				if ( offsetFromSymbol != 0 )
					msgDump += xbFormat( " %+" xbswDWORDFMT " bytes", offsetFromSymbol );
				msgDump += '\n';
				msgDump += xbFormat( "    Decl: %s\n", undFullName );
				//msgDump += xbFormat( ", Sig:  %s\n", pSym->Name );
			}

			// show line number info, NT5.0-method (SymGetLineFromAddr())
			if ( pSGLFA != NULL )
			{ // yes, we have SymGetLineFromAddr()
				ulong offs;
				if ( ! pSGLFA( hProcess, s.AddrPC.Offset, &offs, &Line ) )
				{
					if ( gle != 487 )
						msgDump += xbFormat( "(line not found:%lu)\n", gle );
				}
				else
				{
					msgDump += xbFormat( "    Line: %s(%lu) %+ld bytes\n",
						Line.FileName, Line.LineNumber, offs );
				}
			} // yes, we have SymGetLineFromAddr()

			// show module info (SymGetModuleInfo())
			if ( ! pSGMI( hProcess, s.AddrPC.Offset, &Module ) )
			{
				msgDump += xbFormat( "(module not found:%lu)\n", gle );
			}
			else
			{ // got module info OK
				char ty[80];
				switch ( Module.SymType )
				{
				case SymNone:
					strcpy( ty, "no-symbols" );
					break;
				case SymCoff:
					strcpy( ty, "COFF" );
					break;
				case SymCv:
					strcpy( ty, "CV" );
					break;
				case SymPdb:
					strcpy( ty, "PDB" );
					break;
				case SymExport:
					strcpy( ty, "exports" );
					break;
				case SymDeferred:
					strcpy( ty, "deferred" );
					break;
				case SymSym:
					strcpy( ty, "SYM" );
					break;
				default:
					_snprintf( ty, sizeof ty, "symtype=%ld", (long) Module.SymType );
					break;
				}

				msgDump += xbFormat( "    Mod:  %s[%s], base: %" xbswADDRFMT "h",
					Module.ModuleName, Module.ImageName, Module.BaseOfImage );
				msgDump += xbFormat( ", Sym:  type: %s, file: %s\n",
					ty, Module.LoadedImageName );
			} // got module info OK
		} // we seem to have a valid PC

		// no return address means no deeper stackframe
		if ( s.AddrReturn.Offset == 0 )
		{
			// avoid misunderstandings in the printf() following the loop
			SetLastError( 0 );
			break;
		}

	} // for ( frameNum )

	if ( gle != 0 )
		msgDump += xbFormat( "\n(StackWalk error: %lu)\n", gle );

	// de-init symbol handler etc. (SymCleanup())
	pSC( hProcess );
	free( pSym );

	return msgDump;
}

xbString xbStackWalk::enumAndLoadModuleSymbols( HANDLE hProcess, DWORD pid )
{
	xbString modOk,modFailed;
	ModuleList modules;
	xbswIMAGEHLP_MODULE Module;
	memset( &Module, '\0', sizeof Module );
	Module.SizeOfStruct = sizeof Module;
	char ty[80];

	// fill in module list
	fillModuleList( modules, pid, hProcess );

	for ( ModuleEntry *it = modules.Begin(); it < modules.End(); ++ it )
	{
		// unfortunately, SymLoadModule() wants writeable strings
		if ( pSLM( hProcess, 0, (LPSTR)(LPCSTR)it->imageName, (LPSTR)(LPCSTR)it->moduleName, 
			(*it).baseAddress, (*it).size ) == 0 && gle != 0)
		{
			modFailed += xbFormat( ", %s(%lu)", (LPCSTR)(*it).moduleName, gle );
			continue;
		}

		// show module info (SymGetModuleInfo())
		if ( ! pSGMI( hProcess, (*it).baseAddress, &Module ) )
		{
			modFailed += xbFormat( ", %s(?%lu)", (LPCSTR)(*it).moduleName, gle );
			continue;
		}
		bool bNoSymbols=true;
		switch ( Module.SymType )
		{
		case SymNone:
			strcpy( ty, "no-symbols" );
			break;
		case SymCoff:
			strcpy( ty, "COFF" );
			bNoSymbols=false;
			break;
		case SymCv:
			strcpy( ty, "CV" );
			bNoSymbols=false;
			break;
		case SymPdb:
			strcpy( ty, "PDB" );
			bNoSymbols=false;
			break;
		case SymExport:
			strcpy( ty, "exports" );
			break;
		case SymDeferred:
			strcpy( ty, "deferred" );
			break;
		case SymSym:
			strcpy( ty, "SYM" );
			bNoSymbols=false;
			break;
		default:
			_snprintf( ty, sizeof ty, "symtype=%ld", (long) Module.SymType );
			break;
		}
		if ( bNoSymbols )
		{
			modFailed += xbFormat( ", %s(%s)", (LPCSTR)(*it).moduleName,ty );
			continue;
		}

/*		if ( stricmp(Module.ModuleName,it->moduleName)!=0 )
		{
			modFailed += xbFormat( ", \"%s\"(!=%s/%s)",(LPCSTR)(*it).moduleName, (LPCSTR)Module.ModuleName,ty );
			continue;
		}
*/
		modOk += xbFormat( ", %s(%s)", (LPCSTR)(*it).moduleName,ty );
	}

	
	xbString ret;
	if (modOk.IsValid())
		ret += "\nModules loaded with symbols: "+modOk.Mid(1)+"\n";
	if (modFailed.IsValid())
		ret += "\nModules loaded without symbols: "+modFailed.Mid(1)+"\n";
	return ret;
}



bool xbStackWalk::fillModuleList( ModuleList& modules, DWORD pid, HANDLE hProcess )
{
#ifndef XDCBASE_X64
	// try toolhelp32 first
	if ( fillModuleListTH32( modules, pid ) )
		return true;
#endif
	// nope? try psapi, then
	return fillModuleListPSAPI( modules, pid, hProcess );
}

bool xbStackWalk::fillModuleListTH32( ModuleList& modules, DWORD pid )
{
	// CreateToolhelp32Snapshot()
	typedef HANDLE (__stdcall *tCT32S)( DWORD dwFlags, DWORD th32ProcessID );
	// Module32First()
	typedef BOOL (__stdcall *tM32F)( HANDLE hSnapshot, LPMODULEENTRY32 lpme );
	// Module32Next()
	typedef BOOL (__stdcall *tM32N)( HANDLE hSnapshot, LPMODULEENTRY32 lpme );

	// I think the DLL is called tlhelp32.dll on Win9X, so we try both
	const char *dllname[] = { "kernel32.dll", "tlhelp32.dll" };
	HINSTANCE hToolhelp;
	tCT32S pCT32S;
	tM32F pM32F;
	tM32N pM32N;

	HANDLE hSnap;
	MODULEENTRY32 me = { sizeof me };
	bool keepGoing;
	ModuleEntry e;
	int i;

	for ( i = 0; i < lenof( dllname ); ++ i )
	{
		hToolhelp = xbLoadLibrary( dllname[i] );
		if ( hToolhelp == 0 )
			continue;
		pCT32S = (tCT32S) GetProcAddress( hToolhelp, "CreateToolhelp32Snapshot" );
		pM32F = (tM32F) GetProcAddress( hToolhelp, "Module32First" );
		pM32N = (tM32N) GetProcAddress( hToolhelp, "Module32Next" );
		if ( pCT32S != 0 && pM32F != 0 && pM32N != 0 )
			break; // found the functions!
		xbFreeLibrary( hToolhelp );
		hToolhelp = 0;
	}

	if ( hToolhelp == 0 ) // nothing found?
		return false;

	hSnap = pCT32S( TH32CS_SNAPMODULE, pid );
	if ( hSnap == (HANDLE) -1 )
		return false;

	keepGoing = !!pM32F( hSnap, &me );
	while ( keepGoing )
	{
		// here, we have a filled-in MODULEENTRY32
		//printf( "%08lXh %6lu %-15.15s %s\n", me.modBaseAddr, me.modBaseSize, me.szModule, me.szExePath );
		e.imageName = me.szExePath;
		e.moduleName = me.szModule;
		e.baseAddress = (xbswDWORD) me.modBaseAddr;
		e.size = me.modBaseSize;
		modules.Add( e );
		keepGoing = !!pM32N( hSnap, &me );
	}

	CloseHandle( hSnap );

	xbFreeLibrary( hToolhelp );

	return modules.IsValid();
}

bool xbStackWalk::fillModuleListPSAPI( ModuleList& modules, DWORD pid, HANDLE hProcess )
{
	// EnumProcessModules()
	typedef BOOL (__stdcall *tEPM)( HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded );
	// GetModuleFileNameEx()
	typedef DWORD (__stdcall *tGMFNE)( HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize );
	// GetModuleBaseName() -- redundant, as GMFNE() has the same prototype, but who cares?
	typedef DWORD (__stdcall *tGMBN)( HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize );
	// GetModuleInformation()
	typedef BOOL (__stdcall *tGMI)( HANDLE hProcess, HMODULE hModule, LPMODULEINFO pmi, DWORD nSize );

	HINSTANCE hPsapi;
	tEPM pEPM;
	tGMFNE pGMFNE;
	tGMBN pGMBN;
	tGMI pGMI;

	DWORD i;
	ModuleEntry e;
	DWORD cbNeeded;
	MODULEINFO mi;
	HMODULE *hMods = 0;
	char *tt = 0;

	hPsapi = xbLoadLibrary( "psapi.dll" );
	if ( hPsapi == 0 )
		return false;

	modules.Empty();

	pEPM = (tEPM) GetProcAddress( hPsapi, "EnumProcessModules" );
	pGMFNE = (tGMFNE) GetProcAddress( hPsapi, "GetModuleFileNameExA" );
	pGMBN = (tGMFNE) GetProcAddress( hPsapi, "GetModuleBaseNameA" );
	pGMI = (tGMI) GetProcAddress( hPsapi, "GetModuleInformation" );
	if ( pEPM == 0 || pGMFNE == 0 || pGMBN == 0 || pGMI == 0 )
	{
		// yuck. Some API is missing.
		xbFreeLibrary( hPsapi );
		return false;
	}

	hMods = new HMODULE[TTBUFLEN / sizeof HMODULE];
	tt = new char[TTBUFLEN];
	// not that this is a sample. Which means I can get away with
	// not checking for errors, but you cannot. :)

	if ( ! pEPM( hProcess, hMods, TTBUFLEN, &cbNeeded ) )
	{
		printf( "EPM failed, gle = %lu\n", gle );
		goto cleanup;
	}

	if ( cbNeeded > TTBUFLEN )
	{
//		printf( "More than %lu module handles. Huh?\n", lenof( hMods ) );
		goto cleanup;
	}

	for ( i = 0; i < cbNeeded / sizeof hMods[0]; ++ i )
	{
		// for each module, get:
		// base address, size
		pGMI( hProcess, hMods[i], &mi, sizeof mi );
		e.baseAddress = (xbswDWORD) mi.lpBaseOfDll;
		e.size = mi.SizeOfImage;
		// image file name
		tt[0] = '\0';
		pGMFNE( hProcess, hMods[i], tt, TTBUFLEN );
		e.imageName = tt;
		// module name
		tt[0] = '\0';
		pGMBN( hProcess, hMods[i], tt, TTBUFLEN );
		e.moduleName = tt;
//		printf( "%08lXh %6lu %-15.15s %s\n", e.baseAddress,
//			e.size, e.moduleName.c_str(), e.imageName.c_str() );

		modules.Add( e );
	}

cleanup:
	if ( hPsapi )
		xbFreeLibrary( hPsapi );
	delete [] tt;
	delete [] hMods;

	return modules.IsValid();
}

xbString xbStackDump(EXCEPTION_POINTERS *ep)
{
	try
	{
		xbStackWalk sw;

		return sw.Dump(ep);
	}
	catch(...)
	{
	}
	return xbString("stackdump failed!");
}

#pragma warning(pop)
#endif
