From a82c22998b76c7109366e8aea5e1fc90154ce749 Mon Sep 17 00:00:00 2001
From: Robert Roebling <robert@roebling.de>
Date: Sun, 12 Nov 2006 14:33:03 +0000
Subject: [PATCH]   Applied wxStackWalker improvement part of     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 |   4 +-
 include/wx/msw/stackwalk.h    |   2 +-
 include/wx/stackwalk.h        |   2 +-
 include/wx/unix/stackwalk.h   |  43 +++++--
 src/msw/stackwalk.cpp         |   2 +-
 src/unix/stackwalk.cpp        | 212 ++++++++++++++++++++++------------
 6 files changed, 181 insertions(+), 84 deletions(-)

diff --git a/docs/latex/wx/stackwalker.tex b/docs/latex/wx/stackwalker.tex
index e99b65774b..562e80eb76 100644
--- a/docs/latex/wx/stackwalker.tex
+++ b/docs/latex/wx/stackwalker.tex
@@ -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}
 
diff --git a/include/wx/msw/stackwalk.h b/include/wx/msw/stackwalk.h
index b8ea90e7b9..2d99a132b6 100644
--- a/include/wx/msw/stackwalk.h
+++ b/include/wx/msw/stackwalk.h
@@ -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();
 
 
diff --git a/include/wx/stackwalk.h b/include/wx/stackwalk.h
index 298317ef7d..911d28fdf8 100644
--- a/include/wx/stackwalk.h
+++ b/include/wx/stackwalk.h
@@ -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
     //
diff --git a/include/wx/unix/stackwalk.h b/include/wx/unix/stackwalk.h
index eb53fac59d..f58e17db98 100644
--- a/include/wx/unix/stackwalk.h
+++ b/include/wx/unix/stackwalk.h
@@ -18,30 +18,38 @@
 
 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_
diff --git a/src/msw/stackwalk.cpp b/src/msw/stackwalk.cpp
index 8bdf7d4021..ed94c55efc 100644
--- a/src/msw/stackwalk.cpp
+++ b/src/msw/stackwalk.cpp
@@ -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
diff --git a/src/unix/stackwalk.cpp b/src/unix/stackwalk.cpp
index ae48c30a31..2e6a5ccda4 100644
--- a/src/unix/stackwalk.cpp
+++ b/src/unix/stackwalk.cpp
@@ -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
-- 
2.50.0

