JD2022-TU1/main/tools/legacy/CallStackDisplay/SymbolHelper.cpp

398 lines
15 KiB
C++

//--------------------------------------------------------------------------------------
// SymbolHelper.cpp
//
// Microsoft Game Technology Group.
// Copyright (C) Microsoft Corporation. All rights reserved.
//--------------------------------------------------------------------------------------
#include "StdAfx.h"
#include "SymbolHelper.h"
#include <xdevkit.h>
#include <string>
#include <dbghelp.h>
// We link with dbghelp.lib to make it convenient to call functions in dbghelp.dll,
// however we specify delay loading of dbghelp.dll so that we can load a specific
// version in LoadDbgHelp. This is crucial since otherwise we may get the version in
// the system directory, and then dbghelp.dll will not load symsrv.dll.
#pragma comment(lib, "dbghelp.lib")
// Name: LoadDbgHelp
// Desc: Forcibly loads DbgHelp.dll from %XEDK%\bin\win32. This is done by calling
// LoadLibrary with a fully specified path, since otherwise the version in the
// system directory will take precedence.
//--------------------------------------------------------------------------------------
BOOL LoadDbgHelp()
{
// Get the XEDK environment variable.
CHAR* xedkDir;
size_t xedkDirSize;
errno_t err = _dupenv_s( &xedkDir, &xedkDirSize, "xedk" );
if( err || !xedkDir )
{
printf( "Couldn't read xedk environment variable.\n" );
return FALSE;
}
// Create a fully specified path to the XEDK version of dbghelp.dll
// This is necessary because symsrv.dll must be in the same directory
// as dbghelp.dll, and only a fully specified path can guarantee which
// version of dbghelp.dll we load.
std::string dbgHelpPath = std::string( xedkDir ) + "\\bin\\win32\\dbghelp.dll";
// Free xedkDir
if( xedkDir )
free( xedkDir );
// Call LoadLibrary on DbgHelp.DLL with our fully specified path.
HMODULE hDbgHelp = LoadLibrary( dbgHelpPath.c_str() );
// Print an error message and return FALSE if DbgHelp.DLL didn't load.
if( !hDbgHelp )
{
printf( "ERROR: Couldn't load DbgHelp.dll from %%xedk%%\\bin\\win32.\n" );
return FALSE;
}
// DbgHelp.DLL loaded.
printf( "DbgHelp.DLL successfully loaded.\n" );
return TRUE;
}
//--------------------------------------------------------------------------------------
// Name: cbSymbol
// Desc: DbgHelp callback which can be used for printing DbgHelp diagnostics.
//--------------------------------------------------------------------------------------
static BOOL CALLBACK cbSymbol( HANDLE/*hProcess*/, ULONG ActionCode, PVOID CallbackData, PVOID /*UserContext*/ )
{
switch( ActionCode )
{
case CBA_DEBUG_INFO:
printf( "%s", ( PSTR )CallbackData );
break;
default:
return false;
}
return true;
}
//--------------------------------------------------------------------------------------
// Name: SymbolHelper constructor
// Desc: Do necessary setup for locating and loading symbols. This includes initializing
// COM, ensuring that the necessary DLLs are in the correct location, initializing
// DbgHelp and passing the XEDK symbol server path on to DbgHelp.
//--------------------------------------------------------------------------------------
SymbolHelper::SymbolHelper()
{
// Initialize COM.
if( FAILED( CoInitialize( NULL ) ) )
{
printf( "CoInitialize failed.\n" );
exit( 10 );
}
// Load DbgHelp. This must be done before any DbgHelp functions are used,
// and it only works if DbgHelp.dll is delay loaded.
if( !LoadDbgHelp() )
exit( 10 );
// Most DbgHelp functions use a 'process' handle to identify their context.
// This can be virtually any number, except zero.
m_DebugProcess = ( HANDLE )1;
// Enable DbgHelp debug messages, make sure that DbgHelp only loads symbols that
// exactly match, and do deferred symbol loading for greater efficiency.
SymSetOptions( SYMOPT_DEBUG | SYMOPT_EXACT_SYMBOLS | SYMOPT_DEFERRED_LOADS );
// Create an XboxManager object to let us get the symbol server path.
CComPtr <IXboxManager> spManager;
HRESULT hr1 = spManager.CoCreateInstance( __uuidof( XboxManager ) );
if( !SUCCEEDED( hr1 ) )
{
printf( "Couldn't create XboxManager object.\n" );
exit( 10 );
}
// Get the XEDK symbol server path.
CComBSTR bstrSymbolServerPath = NULL;
hr1 = spManager->get_SystemSymbolServerPath( &bstrSymbolServerPath );
if( !SUCCEEDED( hr1 ) )
{
printf( "Couldn't get XEDK symbol server path.\n" );
exit( 10 );
}
// Convert the symbol server path from wide characters to char.
char symbolServerPath[MAX_PATH];
sprintf_s( symbolServerPath, "%S", bstrSymbolServerPath.m_str );
// Now build up a complete symbol search path to give to DbgHelp.
// Add the XEDK symbol server to the symbol search path.
std::string fullSearchPath = symbolServerPath;
fullSearchPath += ";E:\\DepotMK\\bin";//d:\\p4-itf\\bin";
CHAR* ntSymbolPath;
size_t symbolPathSize;
errno_t err = _dupenv_s( &ntSymbolPath, &symbolPathSize, "_NT_SYMBOL_PATH" );
if( !err && ntSymbolPath )
{
fullSearchPath += ";" + std::string( ntSymbolPath );
free( ntSymbolPath );
}
// Add the current directory to the search path.
fullSearchPath += std::string( ";." );
// Pass the symbol search path on to DbgHelp.
SymInitialize( m_DebugProcess, const_cast<char*>( fullSearchPath.c_str() ), FALSE );
// Set up a callback to help debug symbol loading problems. If symsrv.dll can't be loaded
// then this will print a message to that effect.
//SymRegisterCallback( m_DebugProcess, cbSymbol, NULL );
}
//--------------------------------------------------------------------------------------
// Name: SymbolHelper destructor
// Desc: Cleanup the symbol helper class.
//--------------------------------------------------------------------------------------
SymbolHelper::~SymbolHelper()
{
// Free our DIA2 objects prior to shutting down COM.
m_ModuleList.clear();
// Shut down COM.
CoUninitialize();
}
//--------------------------------------------------------------------------------------
// Name: PrintSymbolSummary
// Desc: Lookup the specified address in the previously loaded symbol files, and print
// out a readable description of information about that address. The symbol
// lookup will only succeed if the PDB files associated with the code modules are
// still available.
//--------------------------------------------------------------------------------------
bool SymbolHelper::PrintSymbolSummary( DWORD address ,std::string& _symbolName,std::string& _fileName,int& _lineNumber)
{
bool success = false;
CHAR symbolName[1000] = {0};
ULONG displacement = 0;
CHAR filename[500] = {0};
ULONG lineNumber = 0;
// Scan through the list of loaded modules to find the one that contains the
// requested address.
for( size_t i = 0; i < m_ModuleList.size(); ++i )
{
// Find what module's address range the address falls in.
if( address > m_ModuleList[i].m_Address &&
address < m_ModuleList[i].m_Address + m_ModuleList[i].m_Size )
{
CComPtr <IDiaSession>& pSession = m_ModuleList[i].m_psession;
// Find the symbol using the virtual address--the raw address. This
// only works if you have previously told DIA where the module was
// loaded, using put_loadAddress.
// Specify SymTagPublicSymbol instead of SymTagFunction if you want
// the full decorated names.
IDiaSymbol* pFunc;
HRESULT result = pSession->findSymbolByVA( address, SymTagFunction, &pFunc );
if( SUCCEEDED( result ) && pFunc )
{
// Get the name of the function.
CComBSTR functionName = 0;
pFunc->get_name( &functionName );
if( functionName )
{
// Convert the function name from wide characters to char.
sprintf_s( symbolName, "%S", functionName.m_str );
// Get the offset of the address from the symbol's address.
ULONGLONG symbolBaseAddress;
pFunc->get_virtualAddress( &symbolBaseAddress );
displacement = address - ( ULONG )symbolBaseAddress;
success = true;
// Now try to get the filename and line number.
// Get an enumerator that corresponds to this instruction.
CComPtr <IDiaEnumLineNumbers> pLines;
const DWORD instructionSize = 4;
if( SUCCEEDED( pSession->findLinesByVA( address, instructionSize, &pLines ) ) )
{
// We could loop over all of the source lines that map to this instruction,
// but there is probably at most one, and if there are multiple source
// lines we still only want one.
CComPtr <IDiaLineNumber> pLine;
DWORD celt;
if( SUCCEEDED( pLines->Next( 1, &pLine, &celt ) ) && celt == 1 )
{
// Get the line number.
pLine->get_lineNumber( &lineNumber );
// Get the source file object, and then its name.
CComPtr <IDiaSourceFile> pSrc;
pLine->get_sourceFile( &pSrc );
CComBSTR sourceName = 0;
pSrc->get_fileName( &sourceName );
// Convert from wide characters to ASCII.
sprintf_s( filename, "%S", sourceName.m_str );
}
}
}
}
}
}
if( success )
{
// Print out the symbol name and the offset of the address from
// that symbol.
// printf( " %8X: %s+%u", address, symbolName, displacement );
// Now print out the filename/linenumber information if we have it.
if( filename[0] )
{
// Get the filename part of the path.
const CHAR* filepart = strrchr( filename, '\\' );
if( filepart )
++filepart;
else
filepart = filename;
_symbolName = symbolName;
_fileName = filename;
_lineNumber = lineNumber;
// printf( " - %s(%u)\n", filepart, lineNumber );
}
else
printf( " - error getting source hFile/line number\n" );
}
else
{
// printf( " %8X: Symbol lookup failed.\n", address );
}
return success;
}
//--------------------------------------------------------------------------------------
// Name: LoadSymbolsForModule
// Desc: Load the specified symbols using DbgHelp and DIA2. Return true for success.
//--------------------------------------------------------------------------------------
bool SymbolHelper::LoadSymbolsForModule( const VOID* baseAddress,
size_t size, DWORD/*timeStamp*/, const DM_PDB_SIGNATURE* signature )
{
// Create a DIA2 data source
CComPtr <IDiaDataSource> pSource;
HRESULT hr = CoCreateInstance( __uuidof( DiaSource ),
NULL,
CLSCTX_INPROC_SERVER,
__uuidof( IDiaDataSource ),
( void** )&pSource );
if( FAILED( hr ) )
return false;
const char* pdbPath = signature->Path;
// Ask DbgHelp to look for the PDB file. Because DbgHelp has previously been told
// to use the XEDK symbol server this should locate system debug information.
// Note: this function should ignore files whose signature doesn't match, but
// in reality it does not. Thus, if a PDB file with the correct name but wrong
// signature is found first, it will stop searching.
char resultPath[MAX_PATH];
BOOL findResult;
/* if (strstr(signature->Path, "Techno"))
{
findResult = SymFindFileInPath( m_DebugProcess, 0,
const_cast<char*>( "x360_Techno_d.pdb" ),
const_cast<GUID*>( &signature->Guid ), signature->Age, 0,
SSRVOPT_GUIDPTR,
resultPath,
NULL,
NULL );
}
else*/
{
findResult = SymFindFileInPath( m_DebugProcess, 0,
const_cast<char*>( pdbPath ),
const_cast<GUID*>( &signature->Guid ), signature->Age, 0,
SSRVOPT_GUIDPTR,
resultPath,
NULL,
NULL );
// If DbgHelp found the symbols then adjust our path.
}
if( findResult )
pdbPath = resultPath;
// Convert the filename to wide characters for use with DIA2.
wchar_t wPdbPath[ _MAX_PATH ];
size_t convertedChars;
mbstowcs_s( &convertedChars, wPdbPath, pdbPath, _TRUNCATE );
// See if there is a PDB file at the specified location, and if so,
// load it, checking the GUID and age to make sure that it is the correct
// PDB file. Note that the timeStamp is not used anymore.
hr = pSource->loadAndValidateDataFromPdb( wPdbPath,
const_cast<GUID*>( &signature->Guid ), 0, signature->Age );
if( FAILED( hr ) )
{
printf("loadAndValidateDataFromPdb failed: ");
switch (hr)
{
case E_PDB_NOT_FOUND:
printf("Failed to open the file, or the file has an invalid format.\n");
break;
case E_PDB_FORMAT:
printf("Attempted to access a file with an obsolete format.\n");
break;
case E_PDB_INVALID_SIG:
printf("Signature does not match.\n");
break;
case E_PDB_INVALID_AGE:
printf("Age does not match.\n");
break;
case E_INVALIDARG:
printf("Invalid parameter.\n");
break;
case E_UNEXPECTED:
printf("The data source has already been prepared.\n");
break;
default:
printf("unknown reason\n");
break;
}
printf("trying to load without verification...");
hr = pSource->loadDataFromPdb( wPdbPath);
if( FAILED( hr ) )
{
printf("failed\n");
return false;
}
printf("success\n");
}
// Create a session for the just loaded PDB file.
CComPtr <IDiaSession> psession;
if( FAILED( pSource->openSession( &psession ) ) )
{
return false;
}
// Tell DIA2 where the module was loaded.
psession->put_loadAddress( ( ULONG_PTR )baseAddress );
// Add this session to a list of loaded modules.
m_ModuleList.push_back( Module( ( ULONG_PTR )baseAddress, size, psession ) );
return true;
}