]> git.saurik.com Git - wxWidgets.git/commitdiff
Applied wxStackWalker improvement part of
authorRobert Roebling <robert@roebling.de>
Sun, 12 Nov 2006 14:33:03 +0000 (14:33 +0000)
committerRobert Roebling <robert@roebling.de>
Sun, 12 Nov 2006 14:33:03 +0000 (14:33 +0000)
    item #1591718, [wxGTK] Native assert dialog and optimized stack walker

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@43346 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

docs/latex/wx/stackwalker.tex
include/wx/msw/stackwalk.h
include/wx/stackwalk.h
include/wx/unix/stackwalk.h
src/msw/stackwalk.cpp
src/unix/stackwalk.cpp

index e99b65774b0f104a01c44ec24c526a0a77ce822e..562e80eb760a9996c729fa8dc4b182e1a8eac54b 100644 (file)
@@ -82,13 +82,15 @@ This function must be overrided to process the given frame.
 
 \membersection{wxStackWalker::Walk}\label{wxstackwalkerwalk}
 
-\func{void}{Walk}{\param{size\_t }{skip = 1}}
+\func{void}{Walk}{\param{size\_t }{skip = 1}, \param{size\_t }{maxDepth = 200}}
 
 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 $\ge 1$).
 
+Up to \arg{maxDepth} frames are walked from the innermost to the outermost one.
+
 
 \membersection{wxStackWalker::WalkFromException}\label{wxstackwalkerwalkfromexception}
 
index b8ea90e7b9fc634141c8bead9afbe72c34102ead..2d99a132b6df98d6c995388b011c7e3e3242dd59 100644 (file)
@@ -90,7 +90,7 @@ public:
     // only
     wxStackWalker(const char * WXUNUSED(argv0) = NULL) { }
 
-    virtual void Walk(size_t skip = 1);
+    virtual void Walk(size_t skip = 1, size_t maxDepth = 200);
     virtual void WalkFromException();
 
 
index 298317ef7df7e6a2608f2fd4d18a924d453a3898..911d28fdf86b4e4a6604aff9e0815bee64a2ef9c 100644 (file)
@@ -130,7 +130,7 @@ public:
     // 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;
+    virtual void Walk(size_t skip = 1, size_t maxDepth = 200) = 0;
 
     // enumerate stack frames from the location of uncaught exception
     //
index eb53fac59dc501650b495d62928159948314c8da..f58e17db981dba3428ed2493ac803be9b5b2ef80 100644 (file)
 
 class WXDLLIMPEXP_BASE wxStackFrame : public wxStackFrameBase
 {
+    friend class wxStackWalker;
+
 public:
     // arguments are the stack depth of this frame, its address and the return
     // value of backtrace_symbols() for it
     //
     // NB: we don't copy syminfo pointer so it should have lifetime at least as
     //     long as ours
-    wxStackFrame(size_t level, void *address, const char *syminfo)
+    wxStackFrame(size_t level = 0, void *address = NULL, const char *syminfo = NULL)
         : wxStackFrameBase(level, address)
     {
-        m_hasName =
-        m_hasLocation = false;
-
         m_syminfo = syminfo;
     }
 
 protected:
     virtual void OnGetName();
-    virtual void OnGetLocation();
+
+    // optimized for the 2 step initialization done by wxStackWalker
+    void Set(const wxString &name, const wxString &filename, const char* syminfo,
+             size_t level, size_t numLine, void *address)
+    {
+        m_level = level;
+        m_name = name;
+        m_filename = filename;
+        m_syminfo = syminfo;
+
+        m_line = numLine;
+        m_address = address;
+    }
 
 private:
     const char *m_syminfo;
-
-    bool m_hasName,
-         m_hasLocation;
 };
 
 // ----------------------------------------------------------------------------
@@ -60,13 +68,30 @@ public:
         ms_exepath = wxString::FromAscii(argv0);
     }
 
-    virtual void Walk(size_t skip = 1);
+    ~wxStackWalker()
+    {
+        FreeStack();
+    }
+
+    virtual void Walk(size_t skip = 1, size_t maxDepth = 200);
     virtual void WalkFromException() { Walk(2); }
 
     static const wxString& GetExePath() { return ms_exepath; }
 
+
+    // these two may be used to save the stack at some point (fast operation)
+    // and then process it later (slow operation)
+    void SaveStack(size_t maxDepth);
+    void ProcessFrames(size_t skip);
+    void FreeStack();
+
 private:
+    int InitFrames(wxStackFrame *arr, size_t n, void **addresses, char **syminfo);
+
     static wxString ms_exepath;
+    static void *ms_addresses[];
+    static char **ms_symbols;
+    static int m_depth;
 };
 
 #endif // _WX_UNIX_STACKWALK_H_
index 8bdf7d4021931971216fa80aa6796d9fe01014d8..ed94c55efcb966361c015bbce28401fc6575e9f0 100644 (file)
@@ -326,7 +326,7 @@ void wxStackWalker::WalkFromException()
     WalkFrom(wxGlobalSEInformation, 0);
 }
 
-void wxStackWalker::Walk(size_t skip)
+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
index ae48c30a31cbd3b0a81433e6672901879abd0923..2e6a5ccda44596af6d83e5c444e28ef0eedc56a9 100644 (file)
@@ -33,6 +33,7 @@
 #endif
 
 #include "wx/stackwalk.h"
+#include "wx/stdpaths.h"
 
 #include <execinfo.h>
 
@@ -73,23 +74,20 @@ private:
 // implementation
 // ============================================================================
 
-wxString wxStackWalker::ms_exepath;
-
 // ----------------------------------------------------------------------------
 // wxStackFrame
 // ----------------------------------------------------------------------------
 
 void wxStackFrame::OnGetName()
 {
-    if ( m_hasName )
+    if ( !m_name.empty() )
         return;
 
-    m_hasName = true;
-
-    // try addr2line first because it always gives us demangled names (even if
-    // __cxa_demangle is not available) and because it seems less error-prone
+    // we already tried addr2line in wxStackWalker::InitFrames: it always
+    // gives us demangled names (even if __cxa_demangle is not available) when
+    // the function is part of the ELF (when it's in a shared object addr2line
+    // will give "??") and because it seems less error-prone.
     // when it works, backtrace_symbols() sometimes returns incorrect results
-    OnGetLocation();
 
     // format is: "module(funcname+offset) [address]" but the part in
     // parentheses can be not present
@@ -138,102 +136,174 @@ void wxStackFrame::OnGetName()
     }
 }
 
-void wxStackFrame::OnGetLocation()
+
+// ----------------------------------------------------------------------------
+// wxStackWalker
+// ----------------------------------------------------------------------------
+
+// that many frames should be enough for everyone
+#define MAX_FRAMES          200
+
+// we need a char buffer big enough to contain a call to addr2line with
+// up to MAX_FRAMES addresses !
+// NB: %p specifier will print the pointer in hexadecimal form
+//     and thus will require 2 chars for each byte + 3 for the
+//     " 0x" prefix
+#define CHARS_PER_FRAME    (sizeof(void*) * 2 + 3)
+
+// BUFSIZE will be 2250 for 32 bit machines
+#define BUFSIZE            (50 + MAX_FRAMES*CHARS_PER_FRAME)
+
+// static data
+void *wxStackWalker::ms_addresses[MAX_FRAMES];
+char **wxStackWalker::ms_symbols = NULL;
+int wxStackWalker::m_depth = 0;
+wxString wxStackWalker::ms_exepath;
+static char g_buf[BUFSIZE];
+
+
+void wxStackWalker::SaveStack(size_t maxDepth)
+{
+    // read all frames required
+    maxDepth = wxMin(WXSIZEOF(ms_addresses)/sizeof(void*), maxDepth);
+    m_depth = backtrace(ms_addresses, maxDepth*sizeof(void*));
+    if ( !m_depth )
+        return;
+
+    ms_symbols = backtrace_symbols(ms_addresses, m_depth);
+}
+
+void wxStackWalker::ProcessFrames(size_t skip)
 {
-    if ( m_hasLocation )
+    wxStackFrame frames[MAX_FRAMES];
+
+    if (!ms_symbols || !m_depth)
         return;
 
-    m_hasLocation = true;
+    // we have 3 more "intermediate" frames which the calling code doesn't know
+    // about, account for them
+    skip += 3;
+
+    // call addr2line only once since this call may be very slow
+    // (it has to load in memory the entire EXE of this app which may be quite
+    //  big, especially if it contains debug info and is compiled statically!)
+    int towalk = InitFrames(frames, m_depth - skip, &ms_addresses[skip], &ms_symbols[skip]);
+
+    // now do user-defined operations on each frame
+    for ( int n = 0; n < towalk - (int)skip; n++ )
+        OnStackFrame(frames[n]);
+}
+
+void wxStackWalker::FreeStack()
+{
+    // ms_symbols has been allocated by backtrace_symbols() and it's the responsibility
+    // of the caller, i.e. us, to free that pointer
+    if (ms_symbols)
+        free( ms_symbols );
+    ms_symbols = NULL;
+    m_depth = 0;
+}
 
+int wxStackWalker::InitFrames(wxStackFrame *arr, size_t n, void **addresses, char **syminfo)
+{
     // we need to launch addr2line tool to get this information and we need to
     // have the program name for this
     wxString exepath = wxStackWalker::GetExePath();
     if ( exepath.empty() )
     {
-        if ( !wxTheApp || !wxTheApp->argv )
-            return;
-        exepath = wxTheApp->argv[0];
+        exepath = wxStandardPaths::Get().GetExecutablePath();
+        if ( exepath.empty() )
+        {
+            wxLogDebug(wxT("Cannot parse stack frame because the executable ")
+                       wxT("path could not be detected"));
+            return 0;
+        }
     }
 
-    wxStdioPipe fp(wxString::Format(_T("addr2line -C -f -e \"%s\" %p"),
-                                    exepath.c_str(), m_address).mb_str(),
-                   "r");
-
-    if ( !fp )
-        return;
-
-    // parse addr2line output
-    char buf[1024];
-    if ( !fgets(buf, WXSIZEOF(buf), fp) )
+    // build the (long) command line for executing addr2line in an optimized way
+    // (e.g. use always chars, even in Unicode build: popen() always takes chars)
+    int len = snprintf(g_buf, BUFSIZE, "addr2line -C -f -e \"%s\"", exepath.mb_str());
+    len = (len <= 0) ? strlen(g_buf) : len;     // in case snprintf() is broken
+    for (size_t i=0; i<n; i++)
     {
-        wxLogDebug(_T("Empty addr2line output?"));
-        return;
+        snprintf(&g_buf[len], BUFSIZE - len, " %p", addresses[i]);
+        len = strlen(g_buf);
     }
 
-    // 1st line has function name
-    if ( GetName().empty() )
-    {
-        m_name = wxString::FromAscii(buf);
-        m_name.RemoveLast(); // trailing newline
+    //wxLogDebug(wxT("piping the command '%s'"), g_buf);  // for debug only
 
-        if ( m_name == _T("??") )
-            m_name.clear();
-    }
+    wxStdioPipe fp(g_buf, "r");
+    if ( !fp )
+        return 0;
 
-    // 2nd one -- the file/line info
-    if ( fgets(buf, WXSIZEOF(buf), fp) )
+    // parse addr2line output (should be exactly 2 lines for each address)
+    // reusing the g_buf used for building the command line above
+    wxString name, filename;
+    unsigned long line, curr=0;
+    for (size_t i=0; i<n; i++)
     {
-        wxString output(wxString::FromAscii(buf));
-        output.RemoveLast();
+        // 1st line has function name
+        if ( fgets(g_buf, WXSIZEOF(g_buf), fp) )
+        {
+            name = wxString::FromAscii(g_buf);
+            name.RemoveLast(); // trailing newline
 
-        const size_t posColon = output.find(_T(':'));
-        if ( posColon != wxString::npos )
+            if ( name == _T("??") )
+                name.clear();
+        }
+        else
+        {
+            wxLogDebug(_T("cannot read addr2line output for %d-th stack frame!"), i);
+            return false;
+        }
+
+        // 2nd one -- the file/line info
+        if ( fgets(g_buf, WXSIZEOF(g_buf), fp) )
         {
-            m_filename.assign(output, 0, posColon);
-            if ( m_filename == _T("??") )
+            filename = wxString::FromAscii(g_buf);
+            filename.RemoveLast();
+
+            const size_t posColon = filename.find(_T(':'));
+            if ( posColon != wxString::npos )
             {
-                m_filename.clear();
+                // parse line number
+                if ( !wxString(filename, posColon + 1, wxString::npos).
+                        ToULong(&line) )
+                    line = 0;
+
+                // remove line number from 'filename'
+                filename.erase(posColon);
+                if ( filename == _T("??") )
+                    filename.clear();
             }
             else
             {
-                unsigned long line;
-                if ( wxString(output, posColon + 1, wxString::npos).
-                        ToULong(&line) )
-                    m_line = line;
+                wxLogDebug(_T("Unexpected addr2line format: \"%s\" - ")
+                           _T("the semicolon is missing"),
+                           filename.c_str());
             }
         }
-        else
+
+        if (!name.empty() || !filename.empty())
         {
-            wxLogDebug(_T("Unexpected addr2line format: \"%s\""),
-                       output.c_str());
+            // now we've got enough info to initialize curr-th stack frame:
+            arr[curr++].Set(name, filename, syminfo[i], i, line, addresses[i]);
         }
     }
-}
 
-// ----------------------------------------------------------------------------
-// wxStackWalker
-// ----------------------------------------------------------------------------
+    return curr;
+}
 
-void wxStackWalker::Walk(size_t skip)
+void wxStackWalker::Walk(size_t skip, size_t maxDepth)
 {
-    // that many frames should be enough for everyone
-    void *addresses[200];
+    // read all frames required
+    SaveStack(maxDepth);
 
-    int depth = backtrace(addresses, WXSIZEOF(addresses));
-    if ( !depth )
-        return;
-
-    char **symbols = backtrace_symbols(addresses, depth);
+    // process them
+    ProcessFrames(skip);
 
-    // we have 3 more "intermediate" frames which the calling code doesn't know
-    // about., account for them
-    skip += 3;
-
-    for ( int n = skip; n < depth; n++ )
-    {
-        wxStackFrame frame(n - skip, addresses[n], symbols[n]);
-        OnStackFrame(frame);
-    }
+    // cleanup
+    FreeStack();
 }
 
 #endif // wxUSE_STACKWALKER