//-------------------------------------------------------------------------------------- // SymbolHelper.cpp // // Microsoft Game Technology Group. // Copyright (C) Microsoft Corporation. All rights reserved. //-------------------------------------------------------------------------------------- #include "StdAfx.h" #include "SymbolHelper.h" #include #include #include // 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 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( 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 & 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 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 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 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 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( "x360_Techno_d.pdb" ), const_cast( &signature->Guid ), signature->Age, 0, SSRVOPT_GUIDPTR, resultPath, NULL, NULL ); } else*/ { findResult = SymFindFileInPath( m_DebugProcess, 0, const_cast( pdbPath ), const_cast( &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( &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 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; }