///////////////////////////////////////////////////////////////////////////////
//
// LocalsFromAddr.cpp 
// 
// Author: Oleg Starodumov
// 
//


///////////////////////////////////////////////////////////////////////////////
// 
// Description: 
// 
// This example looks up a function by address, then enumerates local variables 
// and parameters of the function and displays some simple information about them. 
// 
// This example shows how to: 
// 
//   * Define _NO_CVCONST_H to be able to use various non-default declarations 
//     from DbgHelp.h (e.g. SymTagEnum enumeration) 
//   * Initialize DbgHelp 
//   * Load symbols for a module or from a .PDB file 
//   * Check what kind of symbols is loaded 
//   * Look up a function by address (supplied by the user)
//   * Enumerate local variables and parameters of a function 
//   * Display simple information about local variables and parameters 
//   * Deinitialize DbgHelp 
//
// Actions: 
// 
//   * Enable debug option 
//   * Initialize DbgHelp 
//   * If symbols should be loaded from a .PDB file, determine its size 
//   * Load symbols 
//   * Obtain and display information about loaded symbols 
//   * Look up a function by address 
//   * Enumerate local variables and parameters of the function 
//   * Display simple information about every local variable or parameter 
//   * Deinitialize DbgHelp 
//
// Command line parameters: 
// 
//   * Path to the module you want to load symbols for, 
//     or to a .PDB file to load the symbols from 
//   * Address of the function 
//


///////////////////////////////////////////////////////////////////////////////
// Include files 
//

#include <windows.h>
#include <tchar.h>

// Now we have to define _NO_CVCONST_H to be able to access 
// various declarations from DbgHelp.h, which are not available by default 
#define _NO_CVCONST_H
#include <dbghelp.h>

#include <stdio.h>
#include <limits.h>


///////////////////////////////////////////////////////////////////////////////
// Important types missing in DbgHelp.h 
// 

#ifdef _NO_CVCONST_H

// CV_HREG_e, originally from CVCONST.H in DIA SDK 
typedef enum CV_HREG_e {

	// Only a limited number of registers included here 

    CV_REG_EAX      =  17,
    CV_REG_ECX      =  18,
    CV_REG_EDX      =  19,
    CV_REG_EBX      =  20,
    CV_REG_ESP      =  21,
    CV_REG_EBP      =  22,
    CV_REG_ESI      =  23,
    CV_REG_EDI      =  24,

} CV_HREG_e;

#endif // _NO_CVCONST_H


///////////////////////////////////////////////////////////////////////////////
// Directives 
//

#pragma comment( lib, "dbghelp.lib" )


///////////////////////////////////////////////////////////////////////////////
// Declarations 
//

bool GetFileParams( const TCHAR* pFileName, DWORD64& BaseAddr, DWORD& FileSize );
bool GetFileSize( const TCHAR* pFileName, DWORD& FileSize );

BOOL CALLBACK MyEnumSymbolsCallback( SYMBOL_INFO* pSymInfo, ULONG SymbolSize, PVOID UserContext );
void ShowSymbolDetails( SYMBOL_INFO& SymInfo ); 

const TCHAR* TagStr( ULONG Tag ); 
LPCTSTR RegisterStr( ULONG RegCode ); 


///////////////////////////////////////////////////////////////////////////////
// CSymbolInfoPackage class declaration 
// 
// Wrapper for SYMBOL_INFO_PACKAGE structure 
//

struct CSymbolInfoPackage : public SYMBOL_INFO_PACKAGE 
{
	CSymbolInfoPackage() 
	{
		si.SizeOfStruct = sizeof(SYMBOL_INFO); 
		si.MaxNameLen   = sizeof(name); 
	}
};


///////////////////////////////////////////////////////////////////////////////
// main 
//

int _tmain( int argc, const TCHAR* argv[] ) 
{
	BOOL bRet = FALSE; 


	// Check command line parameters 

	if( argc < 3 ) 
	{
		_tprintf( _T("Usage: %s <FilePathAndName> <FunctionAddress> \n"), argv[0] ); 
		return 0; 
	}

	DWORD64 SymAddr = 0; 

	if( _stscanf( argv[2], _T("%I64x"), &SymAddr ) != 1 ) 
	{
		_tprintf( _T("Usage: %s <FilePathAndName> <FunctionAddress> \n"), argv[0] ); 
		return 0; 
	}


	// Set options 

	DWORD Options = SymGetOptions(); 

		// SYMOPT_DEBUG option asks DbgHelp to print additional troubleshooting 
		// messages to debug output - use the debugger's Debug Output window 
		// to view the messages 

	Options |= SYMOPT_DEBUG; 

	::SymSetOptions( Options ); 


	// Initialize DbgHelp and load symbols for all modules of the current process 

	bRet = ::SymInitialize ( 
	            GetCurrentProcess(),  // Process handle of the current process 
	            NULL,                 // No user-defined search path -> use default 
	            FALSE                 // Do not load symbols for modules in the current process 
	          ); 

	if( !bRet ) 
	{
		_tprintf(_T("Error: SymInitialize() failed. Error code: %u \n"), ::GetLastError());
		return 0; 
	}


	do 
	{
		// Determine the base address and the file size 

		const TCHAR* pFileName = argv[1]; 

		DWORD64   BaseAddr  = 0; 
		DWORD     FileSize  = 0; 

		if( !GetFileParams( pFileName, BaseAddr, FileSize ) ) 
		{
			_tprintf( _T("Error: Cannot obtain file parameters (internal error).\n") ); 
			break; 
		}


		// Load symbols for the module 

		_tprintf( _T("Loading symbols for: %s ... \n"), pFileName ); 

		DWORD64 ModBase = ::SymLoadModule64 ( 
								GetCurrentProcess(), // Process handle of the current process 
								NULL,                // Handle to the module's image file (not needed)
								pFileName,           // Path/name of the file 
								NULL,                // User-defined short name of the module (it can be NULL) 
								BaseAddr,            // Base address of the module (cannot be NULL if .PDB file is used, otherwise it can be NULL) 
								FileSize             // Size of the file (cannot be NULL if .PDB file is used, otherwise it can be NULL) 
							); 

		if( ModBase == 0 ) 
		{
			_tprintf(_T("Error: SymLoadModule64() failed. Error code: %u \n"), ::GetLastError());
			break; 
		}

		_tprintf( _T("Load address: %I64x \n"), ModBase ); 


		// Look up symbol by address 

		_tprintf( _T("Looking for symbol at address %I64x ... \n"), SymAddr ); 

		CSymbolInfoPackage sip; // it contains SYMBOL_INFO structure plus additional 
		                        // space for the name of the symbol 

		DWORD64 Displacement = 0; 

		bRet = ::SymFromAddr( 
								GetCurrentProcess(), // Process handle of the current process 
								SymAddr,             // Symbol address 
								&Displacement,       // Address of the variable that will receive the displacement 
								&sip.si              // Address of the SYMBOL_INFO structure (inside "sip" object) 
							);

		if( !bRet ) 
		{
			_tprintf( _T("Error: SymFromAddr() failed. Error code: %u \n"), ::GetLastError() ); 
			break; 
		}


		// Symbol found. Is it a function ? 

		if( sip.si.Tag != SymTagFunction ) 
		{
			// No, it is not a function 
			_tprintf( _T("No function found at the given address.\n") ); 
			break; 
		}


		// Enumerate local variables of the function 

			// Set the context to the user-specified address 

		IMAGEHLP_STACK_FRAME sf; 

		sf.InstructionOffset = SymAddr; 

		bRet = ::SymSetContext( 
			            GetCurrentProcess(), // Process handle of the current process 
			            &sf,                 // The context 
			            0                    // Not used 
			          ); 

		if( !bRet ) 
		{
			_tprintf( _T("Error: SymSetContext() failed. Error code: %u \n"), ::GetLastError() ); 
			break; 
		}

			// Enumerate local variables 

		int NumLocals = 0; // the number of local variables found 

		bRet = ::SymEnumSymbols( 
		              GetCurrentProcess(), // Process handle of the current process 
		              0,                   // 0 -> SymEnumSymbols will use the context set with SymSetContext 
		              0,                   // Mask must also be 0 to use the context 
		              MyEnumSymbolsCallback,  // The callback function 
		              &NumLocals              // User-defined context 
		            ); 

		if( !bRet ) 
		{
			_tprintf( _T("Error: SymEnumSymbols() failed. Error code: %u \n"), ::GetLastError() ); 
			break; 
		}

		if( NumLocals == 0 ) 
		{
			_tprintf( _T("The function does not have parameters and local variables.\n") ); 
		}

	}
	while( 0 ); 


	// Deinitialize DbgHelp 

	bRet = ::SymCleanup( GetCurrentProcess() ); 

	if( !bRet ) 
	{
		_tprintf(_T("Error: SymCleanup() failed. Error code: %u \n"), ::GetLastError());
		return 0; 
	}


	// Complete 

	return 0; 
}


///////////////////////////////////////////////////////////////////////////////
// Functions 
//

bool GetFileParams( const TCHAR* pFileName, DWORD64& BaseAddr, DWORD& FileSize ) 
{
	// Check parameters 

	if( pFileName == 0 ) 
	{
		return false; 
	}


	// Determine the extension of the file 

	TCHAR szFileExt[_MAX_EXT] = {0}; 

	_tsplitpath( pFileName, NULL, NULL, NULL, szFileExt ); 

	
	// Is it .PDB file ? 

	if( _tcsicmp( szFileExt, _T(".PDB") ) == 0 ) 
	{
		// Yes, it is a .PDB file 

		// Determine its size, and use a dummy base address 

		BaseAddr = 0x10000000; // it can be any non-zero value, but if we load symbols 
		                       // from more than one file, memory regions specified 
		                       // for different files should not overlap 
		                       // (region is "base address + file size") 
		
		if( !GetFileSize( pFileName, FileSize ) ) 
		{
			return false; 
		}

	}
	else 
	{
		// It is not a .PDB file 

		// Base address and file size can be 0 

		BaseAddr = 0; 
		FileSize = 0; 
	}


	// Complete 

	return true; 

}

bool GetFileSize( const TCHAR* pFileName, DWORD& FileSize )
{
	// Check parameters 

	if( pFileName == 0 ) 
	{
		return false; 
	}


	// Open the file 

	HANDLE hFile = ::CreateFile( pFileName, GENERIC_READ, FILE_SHARE_READ, 
	                             NULL, OPEN_EXISTING, 0, NULL ); 

	if( hFile == INVALID_HANDLE_VALUE ) 
	{
		_tprintf( _T("CreateFile() failed. Error: %u \n"), ::GetLastError() ); 
		return false; 
	}


	// Obtain the size of the file 

	FileSize = ::GetFileSize( hFile, NULL ); 

	if( FileSize == INVALID_FILE_SIZE ) 
	{
		_tprintf( _T("GetFileSize() failed. Error: %u \n"), ::GetLastError() ); 
		// and continue ... 
	}


	// Close the file 

	if( !::CloseHandle( hFile ) ) 
	{
		_tprintf( _T("CloseHandle() failed. Error: %u \n"), ::GetLastError() ); 
		// and continue ... 
	}


	// Complete 

	return ( FileSize != INVALID_FILE_SIZE ); 

}

BOOL CALLBACK MyEnumSymbolsCallback( SYMBOL_INFO* pSymInfo, ULONG SymbolSize, PVOID UserContext ) 
{
	if( pSymInfo != 0 ) 
	{
		// Increase the counter of found local variables and parameters 

		if( UserContext != 0 ) 
			*(int*)UserContext += 1; 


		// Display information about the symbol 

		ShowSymbolDetails( *pSymInfo ); 
	}

	return TRUE; // Continue enumeration 
}

void ShowSymbolDetails( SYMBOL_INFO& SymInfo ) 
{
	// Is it parameter or a local variable ? 

	if( SymInfo.Flags & SYMFLAG_PARAMETER ) 
	{
		// Parameter 
		_tprintf( _T("Parameter: %s  "), SymInfo.Name ); 
	}
	else if( SymInfo.Flags & SYMFLAG_LOCAL ) 
	{
		// Local variable 
		_tprintf( _T("Local:     %s  "), SymInfo.Name ); 
	}
	else 
	{
		// Something else ? 
		_tprintf( _T("Unknown:   %s  "), SymInfo.Name ); 
	}


	// Register 

	_tprintf( _T("%s"), RegisterStr(SymInfo.Register) ); 


	// Offset 

	long Offset = (long)SymInfo.Address; 

	if( Offset >= 0 ) 
		_tprintf( _T("+%x  "), Offset ); 
	else 
		_tprintf( _T("-%x  "), (UINT_MAX - Offset + 1) ); 


	// Size 

	_tprintf( _T("Size: %u  "), SymInfo.Size ); 

	_tprintf( _T("\n") ); 

}

const TCHAR* TagStr( ULONG Tag ) 
{
	switch( Tag ) 
	{
    case SymTagNull:
			return _T("Null"); 
    case SymTagExe:
			return _T("Exe"); 
    case SymTagCompiland:
			return _T("Compiland"); 
    case SymTagCompilandDetails:
			return _T("CompilandDetails"); 
    case SymTagCompilandEnv:
			return _T("CompilandEnv"); 
    case SymTagFunction:
			return _T("Function"); 
    case SymTagBlock:
			return _T("Block"); 
    case SymTagData:
			return _T("Data"); 
    case SymTagAnnotation:
			return _T("Annotation"); 
    case SymTagLabel:
			return _T("Label"); 
    case SymTagPublicSymbol:
			return _T("PublicSymbol"); 
    case SymTagUDT:
			return _T("UDT"); 
    case SymTagEnum:
			return _T("Enum"); 
    case SymTagFunctionType:
			return _T("FunctionType"); 
    case SymTagPointerType:
			return _T("PointerType"); 
    case SymTagArrayType:
			return _T("ArrayType"); 
    case SymTagBaseType:
			return _T("BaseType"); 
    case SymTagTypedef:
			return _T("Typedef"); 
    case SymTagBaseClass:
			return _T("BaseClass"); 
    case SymTagFriend:
			return _T("Friend"); 
    case SymTagFunctionArgType:
			return _T("FunctionArgType"); 
    case SymTagFuncDebugStart:
			return _T("FuncDebugStart"); 
    case SymTagFuncDebugEnd:
			return _T("FuncDebugEnd"); 
    case SymTagUsingNamespace:
			return _T("UsingNamespace"); 
    case SymTagVTableShape:
			return _T("VTableShape"); 
    case SymTagVTable:
			return _T("VTable"); 
    case SymTagCustom:
			return _T("Custom"); 
    case SymTagThunk:
			return _T("Thunk"); 
    case SymTagCustomType:
			return _T("CustomType"); 
    case SymTagManagedType:
			return _T("ManagedType"); 
    case SymTagDimension:
			return _T("Dimension"); 
		default: 
			return _T("Unknown"); 
	}

	return _T(""); 

}

LPCTSTR RegisterStr( ULONG RegCode ) 
{
	switch( RegCode ) 
	{
		case CV_REG_EAX: 
			return _T("EAX"); 
		case CV_REG_ECX: 
			return _T("ECX"); 
		case CV_REG_EDX: 
			return _T("EDX"); 
		case CV_REG_EBX: 
			return _T("EBX"); 
		case CV_REG_ESP: 
			return _T("ESP"); 
		case CV_REG_EBP: 
			return _T("EBP"); 
		case CV_REG_ESI: 
			return _T("ESI"); 
		case CV_REG_EDI: 
			return _T("EDI"); 
		default: 
			return _T("UNKNOWN"); 
	}
}

