///////////////////////////////////////////////////////////////////////////// // Name: msw/stackwalk.cpp // Purpose: wxStackWalker implementation for Win32 // Author: Vadim Zeitlin // Modified by: // Created: 2005-01-08 // RCS-ID: $Id: stackwalk.cpp 47767 2007-07-28 00:16:55Z VZ $ // Copyright: (c) 2003-2005 Vadim Zeitlin // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // ============================================================================ // declarations // ============================================================================ // ---------------------------------------------------------------------------- // headers // ---------------------------------------------------------------------------- #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #if wxUSE_STACKWALKER #ifndef WX_PRECOMP #include "wx/string.h" #endif #include "wx/stackwalk.h" #include "wx/msw/debughlp.h" #if wxUSE_DBGHELP // ============================================================================ // implementation // ============================================================================ // ---------------------------------------------------------------------------- // wxStackFrame // ---------------------------------------------------------------------------- void wxStackFrame::OnGetName() { if ( m_hasName ) return; m_hasName = true; // get the name of the function for this stack frame entry static const size_t MAX_NAME_LEN = 1024; BYTE symbolBuffer[sizeof(SYMBOL_INFO) + MAX_NAME_LEN]; wxZeroMemory(symbolBuffer); PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)symbolBuffer; pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); pSymbol->MaxNameLen = MAX_NAME_LEN; DWORD64 symDisplacement = 0; if ( !wxDbgHelpDLL::SymFromAddr ( ::GetCurrentProcess(), GetSymAddr(), &symDisplacement, pSymbol ) ) { wxDbgHelpDLL::LogError(_T("SymFromAddr")); return; } m_name = wxString::FromAscii(pSymbol->Name); m_offset = symDisplacement; } void wxStackFrame::OnGetLocation() { if ( m_hasLocation ) return; m_hasLocation = true; // get the source line for this stack frame entry IMAGEHLP_LINE lineInfo = { sizeof(IMAGEHLP_LINE) }; DWORD dwLineDisplacement; if ( !wxDbgHelpDLL::SymGetLineFromAddr ( ::GetCurrentProcess(), GetSymAddr(), &dwLineDisplacement, &lineInfo ) ) { // it is normal that we don't have source info for some symbols, // notably all the ones from the system DLLs... //wxDbgHelpDLL::LogError(_T("SymGetLineFromAddr")); return; } m_filename = wxString::FromAscii(lineInfo.FileName); m_line = lineInfo.LineNumber; } bool wxStackFrame::GetParam(size_t n, wxString *type, wxString *name, wxString *value) const { if ( !DoGetParamCount() ) ConstCast()->OnGetParam(); if ( n >= DoGetParamCount() ) return false; if ( type ) *type = m_paramTypes[n]; if ( name ) *name = m_paramNames[n]; if ( value ) *value = m_paramValues[n]; return true; } void wxStackFrame::OnParam(PSYMBOL_INFO pSymInfo) { m_paramTypes.Add(wxEmptyString); m_paramNames.Add(wxString::FromAscii(pSymInfo->Name)); // if symbol information is corrupted and we crash, the exception is going // to be ignored when we're called from WalkFromException() because of the // exception handler there returning EXCEPTION_CONTINUE_EXECUTION, but we'd // be left in an inconsistent state, so deal with it explicitly here (even // if normally we should never crash, of course...) #ifdef _CPPUNWIND try #else __try #endif { // as it is a parameter (and not a global var), it is always offset by // the frame address DWORD_PTR pValue = m_addrFrame + pSymInfo->Address; m_paramValues.Add(wxDbgHelpDLL::DumpSymbol(pSymInfo, (void *)pValue)); } #ifdef _CPPUNWIND catch ( ... ) #else __except ( EXCEPTION_EXECUTE_HANDLER ) #endif { m_paramValues.Add(wxEmptyString); } } BOOL CALLBACK EnumSymbolsProc(PSYMBOL_INFO pSymInfo, ULONG WXUNUSED(SymSize), PVOID data) { wxStackFrame *frame = wx_static_cast(wxStackFrame *, data); // we're only interested in parameters if ( pSymInfo->Flags & IMAGEHLP_SYMBOL_INFO_PARAMETER ) { frame->OnParam(pSymInfo); } // return true to continue enumeration, false would have stopped it return TRUE; } void wxStackFrame::OnGetParam() { // use SymSetContext to get just the locals/params for this frame IMAGEHLP_STACK_FRAME imagehlpStackFrame; wxZeroMemory(imagehlpStackFrame); imagehlpStackFrame.InstructionOffset = GetSymAddr(); if ( !wxDbgHelpDLL::SymSetContext ( ::GetCurrentProcess(), &imagehlpStackFrame, 0 // unused ) ) { // for symbols from kernel DLL we might not have access to their // address, this is not a real error if ( ::GetLastError() != ERROR_INVALID_ADDRESS ) { wxDbgHelpDLL::LogError(_T("SymSetContext")); } return; } if ( !wxDbgHelpDLL::SymEnumSymbols ( ::GetCurrentProcess(), NULL, // DLL base: use current context NULL, // no mask, get all symbols EnumSymbolsProc, // callback this // data to pass to it ) ) { wxDbgHelpDLL::LogError(_T("SymEnumSymbols")); } } // ---------------------------------------------------------------------------- // wxStackWalker // ---------------------------------------------------------------------------- void wxStackWalker::WalkFrom(const CONTEXT *pCtx, size_t skip) { if ( !wxDbgHelpDLL::Init() ) { // don't log a user-visible error message here because the stack trace // is only needed for debugging/diagnostics anyhow and we shouldn't // confuse the user by complaining that we couldn't generate it wxLogDebug(_T("Failed to get stack backtrace: %s"), wxDbgHelpDLL::GetErrorMessage().c_str()); return; } // according to MSDN, the first parameter should be just a unique value and // not process handle (although the parameter is prototyped as "HANDLE // hProcess") and actually it advises to use the process id and not handle // for Win9x, but then we need to use the same value in StackWalk() call // below which should be a real handle... so this is what we use const HANDLE hProcess = ::GetCurrentProcess(); if ( !wxDbgHelpDLL::SymInitialize ( hProcess, NULL, // use default symbol search path TRUE // load symbols for all loaded modules ) ) { wxDbgHelpDLL::LogError(_T("SymInitialize")); return; } CONTEXT ctx = *pCtx; // will be modified by StackWalk() DWORD dwMachineType; // initialize the initial frame: currently we can do it for x86 only STACKFRAME sf; wxZeroMemory(sf); #ifdef _M_IX86 sf.AddrPC.Offset = ctx.Eip; sf.AddrPC.Mode = AddrModeFlat; sf.AddrStack.Offset = ctx.Esp; sf.AddrStack.Mode = AddrModeFlat; sf.AddrFrame.Offset = ctx.Ebp; sf.AddrFrame.Mode = AddrModeFlat; dwMachineType = IMAGE_FILE_MACHINE_I386; #else #error "Need to initialize STACKFRAME on non x86" #endif // _M_IX86 // iterate over all stack frames (but stop after 200 to avoid entering // infinite loop if the stack is corrupted) for ( size_t nLevel = 0; nLevel < 200; nLevel++ ) { // get the next stack frame if ( !wxDbgHelpDLL::StackWalk ( dwMachineType, hProcess, ::GetCurrentThread(), &sf, &ctx, NULL, // read memory function (default) wxDbgHelpDLL::SymFunctionTableAccess, wxDbgHelpDLL::SymGetModuleBase, NULL // address translator for 16 bit ) ) { if ( ::GetLastError() ) wxDbgHelpDLL::LogError(_T("StackWalk")); break; } // don't show this frame itself in the output if ( nLevel >= skip ) { wxStackFrame frame(nLevel - skip, (void *)sf.AddrPC.Offset, sf.AddrFrame.Offset); OnStackFrame(frame); } } // this results in crashes inside ntdll.dll when called from // exception handler ... #if 0 if ( !wxDbgHelpDLL::SymCleanup(hProcess) ) { wxDbgHelpDLL::LogError(_T("SymCleanup")); } #endif } void wxStackWalker::WalkFrom(const _EXCEPTION_POINTERS *ep, size_t skip) { WalkFrom(ep->ContextRecord, skip); } void wxStackWalker::WalkFromException() { extern EXCEPTION_POINTERS *wxGlobalSEInformation; wxCHECK_RET( wxGlobalSEInformation, _T("wxStackWalker::WalkFromException() can only be called from wxApp::OnFatalException()") ); // don't skip any frames, the first one is where we crashed WalkFrom(wxGlobalSEInformation, 0); } void wxStackWalker::Walk(size_t skip, size_t WXUNUSED(maxDepth)) { // to get a CONTEXT for the current location, simply force an exception and // get EXCEPTION_POINTERS from it // // note: // 1. we additionally skip RaiseException() and WalkFromException() frames // 2. explicit cast to EXCEPTION_POINTERS is needed with VC7.1 even if it // shouldn't have been according to the docs __try { RaiseException(0x1976, 0, 0, NULL); } __except( WalkFrom((EXCEPTION_POINTERS *)GetExceptionInformation(), skip + 2), EXCEPTION_CONTINUE_EXECUTION ) { // never executed because of WalkFromException() return value } } #else // !wxUSE_DBGHELP // ============================================================================ // stubs // ============================================================================ // ---------------------------------------------------------------------------- // wxStackFrame // ---------------------------------------------------------------------------- void wxStackFrame::OnGetName() { } void wxStackFrame::OnGetLocation() { } bool wxStackFrame::GetParam(size_t WXUNUSED(n), wxString * WXUNUSED(type), wxString * WXUNUSED(name), wxString * WXUNUSED(value)) const { return false; } void wxStackFrame::OnParam(_SYMBOL_INFO * WXUNUSED(pSymInfo)) { } void wxStackFrame::OnGetParam() { } // ---------------------------------------------------------------------------- // wxStackWalker // ---------------------------------------------------------------------------- void wxStackWalker::WalkFrom(const CONTEXT * WXUNUSED(pCtx), size_t WXUNUSED(skip)) { } void wxStackWalker::WalkFrom(const _EXCEPTION_POINTERS * WXUNUSED(ep), size_t WXUNUSED(skip)) { } void wxStackWalker::WalkFromException() { } void wxStackWalker::Walk(size_t WXUNUSED(skip), size_t WXUNUSED(maxDepth)) { } #endif // wxUSE_DBGHELP/!wxUSE_DBGHELP #endif // wxUSE_STACKWALKER