From: Vadim Zeitlin Date: Mon, 17 Jan 2005 01:23:20 +0000 (+0000) Subject: added wxStackWalker class X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/7beafee9423f4cfa2fb08c8c5ff579f884314c79?ds=inline added wxStackWalker class git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@31416 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- diff --git a/include/wx/msw/stackwalk.h b/include/wx/msw/stackwalk.h new file mode 100644 index 0000000000..b003181bc8 --- /dev/null +++ b/include/wx/msw/stackwalk.h @@ -0,0 +1,99 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/msw/stackwalk.h +// Purpose: wxStackWalker for MSW +// Author: Vadim Zeitlin +// Modified by: +// Created: 2005-01-08 +// RCS-ID: $Id$ +// Copyright: (c) 2005 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_MSW_STACKWALK_H_ +#define _WX_MSW_STACKWALK_H_ + +// these structs are declared in windows headers +struct _CONTEXT; +struct _EXCEPTION_POINTERS; + +// and these in dbghelp.h +struct _SYMBOL_INFO; + +// ---------------------------------------------------------------------------- +// wxStackFrame +// ---------------------------------------------------------------------------- + +class WXDLLIMPEXP_BASE wxStackFrame : public wxStackFrameBase +{ +private: + wxStackFrame *ConstCast() const + { return wx_const_cast(wxStackFrame *, this); } + + size_t DoGetParamCount() const { return m_paramTypes.GetCount(); } + +public: + wxStackFrame(size_t level, void *address, size_t addrFrame) + : wxStackFrameBase(level, address) + { + m_hasName = + m_hasLocation = false; + + m_addrFrame = addrFrame; + } + + virtual size_t GetParamCount() const + { + ConstCast()->OnGetParam(); + return DoGetParamCount(); + } + + virtual bool + GetParam(size_t n, wxString *type, wxString *name, wxString *value) const; + + // callback used by OnGetParam(), don't call directly + void OnParam(_SYMBOL_INFO *pSymInfo); + +protected: + virtual void OnGetName(); + virtual void OnGetLocation(); + + void OnGetParam(); + + + // helper for debug API: it wants to have addresses as DWORDs + size_t GetSymAddr() const + { + return wx_reinterpret_cast(size_t, m_address); + } + +private: + bool m_hasName, + m_hasLocation; + + size_t m_addrFrame; + + wxArrayString m_paramTypes, + m_paramNames, + m_paramValues; +}; + +// ---------------------------------------------------------------------------- +// wxStackWalker +// ---------------------------------------------------------------------------- + +class WXDLLIMPEXP_BASE wxStackWalker : public wxStackWalkerBase +{ +public: + wxStackWalker() { } + + virtual void Walk(size_t skip = 1); + virtual void WalkFromException(); + + + // enumerate stack frames from the given context + void WalkFrom(const _CONTEXT *ctx, size_t skip = 1); + void WalkFrom(const _EXCEPTION_POINTERS *ep, size_t skip = 1); +}; + +#endif // _WX_MSW_STACKWALK_H_ + diff --git a/include/wx/stackwalk.h b/include/wx/stackwalk.h new file mode 100644 index 0000000000..5e8beacc91 --- /dev/null +++ b/include/wx/stackwalk.h @@ -0,0 +1,144 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/wx/stackwalk.h +// Purpose: wxStackWalker and related classes, common part +// Author: Vadim Zeitlin +// Modified by: +// Created: 2005-01-07 +// RCS-ID: $Id$ +// Copyright: (c) 2004 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_STACKWALK_H_ +#define _WX_STACKWALK_H_ + +#include "wx/defs.h" + +#if wxUSE_STACKWALKER + +class WXDLLIMPEXP_BASE wxStackFrame; + +// ---------------------------------------------------------------------------- +// wxStackFrame: a single stack level +// ---------------------------------------------------------------------------- + +class WXDLLIMPEXP_BASE wxStackFrameBase +{ +private: + // put this inline function here so that it is defined before use + wxStackFrameBase *ConstCast() const + { return wx_const_cast(wxStackFrameBase *, this); } + +public: + wxStackFrameBase(size_t level, void *address = NULL) + { + m_level = level; + + m_line = + m_offset = 0; + + m_address = address; + } + + // get the level of this frame (deepest/innermost one is 0) + size_t GetLevel() const { return m_level; } + + // return the address of this frame + void *GetAddress() const { return m_address; } + + + // return the unmangled (if possible) name of the function containing this + // frame + wxString GetName() const { ConstCast()->OnGetName(); return m_name; } + + // return the instruction pointer offset from the start of the function + size_t GetOffset() const { ConstCast()->OnGetName(); return m_offset; } + + + // return true if we have the filename and line number for this frame + bool HasSourceLocation() const { return !GetFileName().empty(); } + + // return the name of the file containing this frame, empty if + // unavailable (typically because debug info is missing) + wxString GetFileName() const + { ConstCast()->OnGetLocation(); return m_filename; } + + // return the line number of this frame, 0 if unavailable + size_t GetLine() const { ConstCast()->OnGetLocation(); return m_line; } + + + // return the number of parameters of this function (may return 0 if we + // can't retrieve the parameters info even although the function does have + // parameters) + virtual size_t GetParamCount() const { return 0; } + + // get the name, type and value (in text form) of the given parameter + // + // any pointer may be NULL + // + // return true if at least some values could be retrieved + virtual bool GetParam(size_t WXUNUSED(n), + wxString * WXUNUSED(type), + wxString * WXUNUSED(name), + wxString * WXUNUSED(value)) const + { + return false; + } + + + // although this class is not supposed to be used polymorphically, give it + // a virtual dtor to silence compiler warnings + virtual ~wxStackFrameBase() { } + +protected: + // hooks for derived classes to initialize some fields on demand + virtual void OnGetName() { } + virtual void OnGetLocation() { } + + + // fields are protected, not private, so that OnGetXXX() could modify them + // directly + size_t m_level; + + wxString m_name, + m_filename; + size_t m_line; + + void *m_address; + size_t m_offset; +}; + +// ---------------------------------------------------------------------------- +// wxStackWalker: class for enumerating stack frames +// ---------------------------------------------------------------------------- + +class WXDLLIMPEXP_BASE wxStackWalkerBase +{ +public: + // ctor does nothing, use Walk() to walk the stack + wxStackWalkerBase() { } + + // enumerate stack frames from the current location, skipping the initial + // number of them (this can be useful when Walk() is called from some known + // location and you don't want to see the first few frames anyhow; also + // notice that Walk() frame itself is not included if skip >= 1) + virtual void Walk(size_t skip = 1) = 0; + + // enumerate stack frames from the location of uncaught exception + // + // this version can only be called from wxApp::OnFatalException() + virtual void WalkFromException() = 0; + +protected: + // this function must be overrided to process the given frame + virtual void OnStackFrame(const wxStackFrame& frame) = 0; +}; + +#ifdef __WXMSW__ + #include "wx/msw/stackwalk.h" +#endif + +#endif // wxUSE_STACKWALKER + +#endif // _WX_STACKWALK_H_ + diff --git a/src/msw/stackwalk.cpp b/src/msw/stackwalk.cpp new file mode 100644 index 0000000000..3872361952 --- /dev/null +++ b/src/msw/stackwalk.cpp @@ -0,0 +1,348 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: msw/stackwalk.cpp +// Purpose: wxStackWalker implementation for Win32 +// Author: Vadim Zeitlin +// Modified by: +// Created: 2005-01-08 +// RCS-ID: $Id$ +// Copyright: (c) 2003-2005 Vadim Zeitlin +// Licence: wxWindows licence +///////////////////////////////////////////////////////////////////////////// + +// ============================================================================ +// declarations +// ============================================================================ + +// ---------------------------------------------------------------------------- +// headers +// ---------------------------------------------------------------------------- + +#include "wx/wxprec.h" + +#ifdef __BORLANDC__ + #pragma hdrstop +#endif + +#if wxUSE_STACKWALKER + +#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(symbolBuffer); + 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(_T("")); + + 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 + // except handler there returning EXCEPTION_CONTINUE_EXECUTION, but we'd be + // left in an inconsistent state, so deal with it explicitely 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(_T("")); + } +} + +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() ) + { + wxLogError(_("Failed to get stack backtrace:\n%s"), + wxDbgHelpDLL::GetErrorMessage().c_str()); + } + + // 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 + for ( size_t nLevel = 0; ; 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) +{ + // 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 + +// TODO: implement stubs + +#endif // wxUSE_DBGHELP/!wxUSE_DBGHELP + +#endif // wxUSE_STACKWALKER +