/////////////////////////////////////////////////////////////////////////////// // // SymbolEngine.cpp // // Author: Oleg Starodumov (www.debuginfo.com) // // This file contains the implementation of CSymbolEngine class, // which implements a simple DbgHelp-based symbol engine // // /////////////////////////////////////////////////////////////////////////////// // Include files // // The following is needed to use Unicode functions of DbgHelp in Unicode build #ifdef UNICODE #define DBGHELP_TRANSLATE_TCHAR #endif #include #include #include #include #include #include typedef std::basic_string TString; // must be defined before including SymboleEngine.h #include "SymbolEngine.h" /////////////////////////////////////////////////////////////////////////////// // Directives // #pragma comment( lib, "dbghelp.lib" ) /////////////////////////////////////////////////////////////////////////////// // Helper classes // // Wrapper for SYMBOL_INFO_PACKAGE structure // struct CSymbolInfoPackage : public SYMBOL_INFO_PACKAGE { CSymbolInfoPackage() { si.SizeOfStruct = sizeof(SYMBOL_INFO); si.MaxNameLen = sizeof(name) / sizeof(TCHAR); } }; // Wrapper for IMAGEHLP_LINE64 structure // struct CImageHlpLine64 : public IMAGEHLP_LINE64 { CImageHlpLine64() { SizeOfStruct = sizeof(IMAGEHLP_LINE64); } }; /////////////////////////////////////////////////////////////////////////////// // Function declarations // // Notification callback BOOL CALLBACK DebugInfoCallback( HANDLE hProcess, ULONG ActionCode, ULONG64 CallbackData, ULONG64 UserContext ); /////////////////////////////////////////////////////////////////////////////// // CSymbolEngine - constructors / destructor // CSymbolEngine::CSymbolEngine() : m_hProcess( NULL ), m_LastError( 0 ) { } CSymbolEngine::~CSymbolEngine() { Close(); } /////////////////////////////////////////////////////////////////////////////// // CSymbolEngine - initialization / cleanup operations // bool CSymbolEngine::Init( HANDLE hProcess, PCTSTR SearchPath, bool Invade, bool Notify ) { // Check parameters and preconditions if( m_hProcess != NULL ) { _ASSERTE( !_T("Already initialized.") ); if( hProcess != m_hProcess ) { m_LastError = ERROR_INVALID_FUNCTION; return false; } else { return true; } } // Call SymInitialize if( !SymInitialize( hProcess, SearchPath, Invade ? TRUE : FALSE ) ) { m_LastError = GetLastError(); _ASSERTE( !_T("SymInitialize failed.") ); return false; } // Register the notification callback, if requested if( Notify ) { if( !SymRegisterCallback64( hProcess, DebugInfoCallback, (ULONG64)this ) ) { m_LastError = GetLastError(); _ASSERTE( !_T("SymInitialize failed.") ); return false; } } // Complete m_hProcess = hProcess; return true; } void CSymbolEngine::Close() { if( m_hProcess != NULL ) { if( !SymCleanup( m_hProcess ) ) { m_LastError = GetLastError(); _ASSERTE( !_T("SymCleanup failed.") ); } m_hProcess = NULL; } } /////////////////////////////////////////////////////////////////////////////// // CSymbolEngine - module operations // DWORD64 CSymbolEngine::LoadModuleSymbols( HANDLE hFile, const TString& ImageName, DWORD64 ModBase, DWORD ModSize ) { // Check preconditions if( m_hProcess == NULL ) { _ASSERTE( !_T("Symbol engine is not yet initialized.") ); m_LastError = ERROR_INVALID_FUNCTION; return 0; } // In Unicode build, ImageName parameter should be translated to ANSI #ifdef _UNICODE char* pImageName = 0; if( !ImageName.empty() ) { size_t BufSize = 2 * ImageName.length(); pImageName = (char*)_alloca( BufSize + 2 ); size_t res = wcstombs( pImageName, ImageName.c_str(), BufSize ); pImageName[BufSize] = 0; if( res == -1 ) { _ASSERTE( !_T("Module name has bad format.") ); m_LastError = ERROR_INVALID_PARAMETER; return false; } } #else const char* pImageName = ImageName.empty() ? 0 : ImageName.c_str(); #endif //_UNICODE // Load symbols for the module DWORD64 rv = SymLoadModule64( m_hProcess, hFile, pImageName, NULL, ModBase, ModSize ); if( rv == 0 ) { m_LastError = GetLastError(); _ASSERTE( !_T("SymLoadModule64() failed.") ); return 0; } // Complete return rv; } DWORD64 CSymbolEngine::LoadModuleSymbols( const TString& ImageName, DWORD64 ModBase, DWORD ModSize ) { // Check parameters if( ImageName.empty() ) { _ASSERTE( !_T("Empty module name.") ); m_LastError = ERROR_INVALID_PARAMETER; return false; } // Delegate the work to the more generic function return LoadModuleSymbols( NULL, ImageName, ModBase, ModSize ); } DWORD64 CSymbolEngine::LoadModuleSymbols( HANDLE hFile, DWORD64 ModBase, DWORD ModSize ) { // Check parameters if( ( hFile == NULL ) || ( hFile == INVALID_HANDLE_VALUE ) ) { _ASSERTE( !_T("Invalid file handle.") ); m_LastError = ERROR_INVALID_PARAMETER; return false; } // Delegate the work to the more generic function return LoadModuleSymbols( hFile, TString(), ModBase, ModSize ); } bool CSymbolEngine::UnloadModuleSymbols( DWORD64 ModBase ) { // Check preconditions if( m_hProcess == NULL ) { _ASSERTE( !_T("Symbol engine is not yet initialized.") ); m_LastError = ERROR_INVALID_FUNCTION; return false; } if( ModBase == 0 ) { _ASSERTE( !_T("Module base address is null.") ); m_LastError = ERROR_INVALID_PARAMETER; return false; } // Unload symbols if( !SymUnloadModule64( m_hProcess, ModBase ) ) { m_LastError = GetLastError(); _ASSERTE( !_T("SymUnloadModule64() failed.") ); return false; } // Complete return true; } bool CSymbolEngine::GetModuleInfo( DWORD64 Addr, IMAGEHLP_MODULE64& Info ) { // Check preconditions if( m_hProcess == NULL ) { _ASSERTE( !_T("Symbol engine is not yet initialized.") ); m_LastError = ERROR_INVALID_FUNCTION; return false; } // Obtain module information memset( &Info, 0, sizeof(Info) ); Info.SizeOfStruct = sizeof(Info); if( !SymGetModuleInfo64( m_hProcess, Addr, &Info ) ) { m_LastError = GetLastError(); _ASSERTE( !_T("SymGetModuleInfo64() failed.") ); return false; } return true; } /////////////////////////////////////////////////////////////////////////////// // CSymbolEngine - symbol operations // bool CSymbolEngine::FindSymbolByAddress( DWORD64 Address, TString& Name, DWORD64& Displacement ) { // Check preconditions if( m_hProcess == NULL ) { _ASSERTE( !_T("Symbol engine is not yet initialized.") ); m_LastError = ERROR_INVALID_FUNCTION; return false; } // Look up the symbol CSymbolInfoPackage sip; // it contains SYMBOL_INFO structure plus additional // space for the name of the symbol DWORD64 Disp = 0; if( !SymFromAddr( m_hProcess, Address, &Disp, &sip.si ) ) { // Failed, but do not assert here - it is usually normal when a symbol is not found m_LastError = GetLastError(); return false; } Name = sip.si.Name; Displacement = Disp; return true; } bool CSymbolEngine::FindLineByAddress( DWORD64 Address, TString& File, DWORD& Line, DWORD64& Displacement ) { // Check preconditions if( m_hProcess == NULL ) { _ASSERTE( !_T("Symbol engine is not yet initialized.") ); m_LastError = ERROR_INVALID_FUNCTION; return false; } // Look up the line CImageHlpLine64 LineInfo; DWORD Disp = 0; if( !SymGetLineFromAddr64( m_hProcess, Address, &Disp, &LineInfo ) ) { // Failed, but do not assert here - it is usually normal when a line is not found m_LastError = GetLastError(); return false; } File = LineInfo.FileName; Line = LineInfo.LineNumber; Displacement = Disp; return true; } /////////////////////////////////////////////////////////////////////////////// // CSymbolEngine - stack walking operations // // Turn off optimizations to make sure that frame pointer is present #pragma optimize ( "", off ) bool CSymbolEngine::StackWalk( CSymbolEngine::FrameColl_t& Frames, int FramesToSkip, HANDLE hThread, CONTEXT* pContext ) { // Cleanup m_LastError = 0; Frames.clear(); if( FramesToSkip < 0 ) FramesToSkip = 0; // Collect the data needed by StackWalk64 // Machine type DWORD MachineType = 0; // Stack frame STACKFRAME64 StackFrame; memset( &StackFrame, 0, sizeof(StackFrame) ); // Architecture-specific initialization #ifdef _M_IX86 // Machine type MachineType = IMAGE_FILE_MACHINE_I386; // STACKFRAME64 structure if( pContext != 0 ) { StackFrame.AddrPC.Offset = pContext->Eip; StackFrame.AddrPC.Mode = AddrModeFlat; StackFrame.AddrStack.Offset = pContext->Esp; StackFrame.AddrStack.Mode = AddrModeFlat; StackFrame.AddrFrame.Offset = pContext->Ebp; StackFrame.AddrFrame.Mode = AddrModeFlat; } else { unsigned long InstPtr; unsigned long StackPtr; unsigned long BasePtr; __asm { call x x: pop [InstPtr] mov [StackPtr], esp mov [BasePtr], ebp } StackFrame.AddrPC.Offset = InstPtr; StackFrame.AddrPC.Mode = AddrModeFlat; StackFrame.AddrStack.Offset = StackPtr; StackFrame.AddrStack.Mode = AddrModeFlat; StackFrame.AddrFrame.Offset = BasePtr; StackFrame.AddrFrame.Mode = AddrModeFlat; } #else #error This architecture is not supported. #endif //_M_IX86 // Walk the stack while( 1 ) { // Call StackWalk64 if( !StackWalk64( MachineType, // Machine architecture type m_hProcess, // Process handle hThread, // Thread handle &StackFrame, // Stack frame pContext, // Thread context (not required for x86) 0, // Read memory function - not used SymFunctionTableAccess64, // Function table access function (FPO access on x86) SymGetModuleBase64, // Function that can determine module base for the given address 0 // Address translation function - not used ) ) { // StackWalk64 failed m_LastError = GetLastError(); break; } // Check the stack frame bool bSaveFrame = true; if( StackFrame.AddrPC.Offset == 0 ) { // Do not save it bSaveFrame = false; } if( StackFrame.AddrPC.Offset == StackFrame.AddrReturn.Offset ) { // Do not save it bSaveFrame = false; } if( FramesToSkip > 0 ) { bSaveFrame = false; FramesToSkip--; } // Save the stack frame if( bSaveFrame ) { CStackFrame NewFrame( StackFrame.AddrPC.Offset, StackFrame.AddrReturn.Offset, StackFrame.AddrFrame.Offset ); Frames.push_back( NewFrame ); } // Proceed to the next frame } // Complete return ( Frames.size() > 0 ); } // Turn the optimizations on again #pragma optimize ( "", on ) /////////////////////////////////////////////////////////////////////////////// // CSymbolEngine - option control operations // DWORD CSymbolEngine::GetOptions() const { return SymGetOptions(); } void CSymbolEngine::SetOptions( DWORD Options ) { SymSetOptions( Options ); } void CSymbolEngine::AddOptions( DWORD Options ) { DWORD CurOptions = GetOptions(); CurOptions |= Options; SetOptions( CurOptions ); } /////////////////////////////////////////////////////////////////////////////// // Notification callback // BOOL CALLBACK DebugInfoCallback ( HANDLE /* hProcess */ , ULONG ActionCode, ULONG64 CallbackData, ULONG64 UserContext ) { // Note: This function should return TRUE only if it handles the event, // otherwise it must return FALSE (see documentation). CSymbolEngine* pEngine = (CSymbolEngine*)UserContext; if( pEngine == 0 ) { _ASSERTE( !_T("Engine pointer is null.") ); return FALSE; } if( ActionCode == CBA_DEBUG_INFO ) { if( CallbackData != 0 ) { _ASSERTE( !::IsBadStringPtr( (const TCHAR*)CallbackData, UINT_MAX ) ); pEngine->OnEngineNotify( TString( (const TCHAR*)CallbackData ) ); return TRUE; } } return FALSE; }