]> git.saurik.com Git - wxWidgets.git/blobdiff - src/unix/stackwalk.cpp
Ensure there is valid context for DrawRectangle
[wxWidgets.git] / src / unix / stackwalk.cpp
index 9ad52db9e6a253dd8733afa415552bad40638097..d082dfd22fd5b45d4de6c565cd638b2a3e199216 100644 (file)
@@ -1,5 +1,5 @@
 /////////////////////////////////////////////////////////////////////////////
-// Name:        msw/stackwalk.cpp
+// Name:        src/unix/stackwalk.cpp
 // Purpose:     wxStackWalker implementation for Unix/glibc
 // Author:      Vadim Zeitlin
 // Modified by:
@@ -33,6 +33,7 @@
 #endif
 
 #include "wx/stackwalk.h"
+#include "wx/stdpaths.h"
 
 #include <execinfo.h>
 
@@ -66,41 +67,38 @@ public:
 private:
     FILE *m_fp;
 
-    DECLARE_NO_COPY_CLASS(wxStdioPipe)
+    wxDECLARE_NO_COPY_CLASS(wxStdioPipe);
 };
 
 // ============================================================================
 // 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
     wxString syminfo = wxString::FromAscii(m_syminfo);
-    const size_t posOpen = syminfo.find(_T('('));
+    const size_t posOpen = syminfo.find(wxT('('));
     if ( posOpen != wxString::npos )
     {
-        const size_t posPlus = syminfo.find(_T('+'), posOpen + 1);
+        const size_t posPlus = syminfo.find(wxT('+'), posOpen + 1);
         if ( posPlus != wxString::npos )
         {
-            const size_t posClose = syminfo.find(_T(')'), posPlus + 1);
+            const size_t posClose = syminfo.find(wxT(')'), posPlus + 1);
             if ( posClose != wxString::npos )
             {
                 if ( m_name.empty() )
@@ -129,103 +127,237 @@ void wxStackFrame::OnGetName()
                     m_offset = ofs;
             }
         }
+
+        m_module.assign(syminfo, posOpen);
+    }
+#ifndef __WXOSX__
+    else // not in "module(funcname+offset)" format
+    {
+        m_module = syminfo;
     }
+#endif // !__WXOSX__
+}
+
 
-    m_module.assign(syminfo, posOpen);
+// ----------------------------------------------------------------------------
+// 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 wxStackFrame::OnGetLocation()
+void wxStackWalker::ProcessFrames(size_t skip)
 {
-    if ( m_hasLocation )
+    wxStackFrame frames[MAX_FRAMES];
+
+    if (!ms_symbols || !m_depth)
         return;
 
-    m_hasLocation = true;
+    // we are another level down from Walk(), so adjust the number of stack
+    // frames to skip accordingly
+    skip += 1;
+
+    // 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 numFrames = InitFrames(frames, m_depth - skip,
+                               &ms_addresses[skip], &ms_symbols[skip]);
+
+    // now do user-defined operations on each frame
+    for ( int n = 0; n < numFrames; 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;
+}
+
+namespace
+{
+
+// Helper function to read a line from the file and return it without the
+// trailing newline. Line number is only used for error reporting.
+bool ReadLine(FILE* fp, unsigned long num, wxString* line)
+{
+    if ( !fgets(g_buf, WXSIZEOF(g_buf), fp) )
+    {
+        wxLogDebug(wxS("cannot read address information for stack frame #%lu"),
+                   num);
+        return false;
+    }
+
+    *line = wxString::FromAscii(g_buf);
+    line->RemoveLast();
+
+    return true;
+}
 
+} // anonymous namespace
+
+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 command line for executing addr2line or atos under OS X using
+    // char* directly to avoid the conversions from Unicode
+#ifdef __WXOSX__
+    int len = snprintf(g_buf, BUFSIZE, "atos -p %d", (int)getpid());
+#else
+    int len = snprintf(g_buf, BUFSIZE, "addr2line -C -f -e \"%s\"", (const char*) exepath.mb_str());
+#endif
+    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() )
+    //wxLogDebug(wxT("piping the command '%s'"), g_buf);  // for debug only
+
+    wxStdioPipe fp(g_buf, "r");
+    if ( !fp )
+        return 0;
+
+    // parse the output reusing the same buffer to avoid any big memory
+    // allocations which could fail if our program is in a bad state
+    wxString name, filename;
+    unsigned long line = 0,
+                  curr = 0;
+    for  ( size_t i = 0; i < n; i++ )
     {
-        m_name = wxString::FromAscii(buf);
-        m_name.RemoveLast(); // trailing newline
+#ifdef __WXOSX__
+        wxString buffer;
+        if ( !ReadLine(fp, i, &buffer) )
+            return false;
+
+        line = 0;
+        filename.clear();
+
+        // We can get back either the string in the following format:
+        //
+        //      func(args) (in module) (file:line)
+        //
+        // or just the same address back if it couldn't be resolved.
+        const size_t posIn = buffer.find("(in ");
+        if ( posIn != wxString::npos )
+        {
+            name.assign(buffer, 0, posIn);
 
-        if ( m_name == _T("??") )
-            m_name.clear();
-    }
+            size_t posAt = buffer.find(") (", posIn + 3);
+            if ( posAt != wxString::npos )
+            {
+                posAt += 3; // Skip ") ("
 
-    // 2nd one -- the file/line info
-    if ( fgets(buf, WXSIZEOF(buf), fp) )
-    {
-        wxString output(wxString::FromAscii(buf));
-        output.RemoveLast();
+                // Discard the two last characters which are ")\n"
+                wxString location(buffer, posAt, buffer.length() - posAt - 2);
+
+                wxString linenum;
+                filename = location.BeforeFirst(':', &linenum);
+                if ( !linenum.empty() )
+                    linenum.ToULong(&line);
+            }
+        }
+#else // !__WXOSX__
+        // 1st line has function name
+        if ( !ReadLine(fp, i, &name) )
+            return false;
+
+        name = wxString::FromAscii(g_buf);
+        name.RemoveLast(); // trailing newline
+
+        if ( name == wxT("??") )
+            name.clear();
 
-        const size_t posColon = output.find(_T(':'));
+        // 2nd one -- the file/line info
+        if ( !ReadLine(fp, i, &filename) )
+            return false;
+
+        const size_t posColon = filename.find(wxT(':'));
         if ( posColon != wxString::npos )
         {
-            m_filename.assign(output, 0, posColon);
-            if ( m_filename == _T("??") )
-            {
-                m_filename.clear();
-            }
-            else
-            {
-                unsigned long line;
-                if ( wxString(output, posColon + 1, wxString::npos).
-                        ToULong(&line) )
-                    m_line = line;
-            }
+            // parse line number (it's ok if it fails, this will just leave
+            // line at its current, invalid, 0 value)
+            wxString(filename, posColon + 1, wxString::npos).ToULong(&line);
+
+            // remove line number from 'filename'
+            filename.erase(posColon);
+            if ( filename == wxT("??") )
+                filename.clear();
         }
         else
         {
-            wxLogDebug(_T("Unexpected addr2line format: \"%s\""),
-                       output.c_str());
+            wxLogDebug(wxT("Unexpected addr2line format: \"%s\" - ")
+                       wxT("the semicolon is missing"),
+                       filename.c_str());
         }
+#endif // __WXOSX__/!__WXOSX__
+
+        // now we've got enough info to initialize curr-th stack frame
+        // (at worst, only addresses[i] and syminfo[i] have been initialized,
+        //  but wxStackFrame::OnGetName may still be able to get function name):
+        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);
 
-    for ( int n = 0; n < depth; n++ )
-    {
-        wxStackFrame frame(n, addresses[n], symbols[n]);
-        OnStackFrame(frame);
-    }
+    // cleanup
+    FreeStack();
 }
 
 #endif // wxUSE_STACKWALKER