/////////////////////////////////////////////////////////////////////////////
-// Name: msw/stackwalk.cpp
+// Name: src/unix/stackwalk.cpp
// Purpose: wxStackWalker implementation for Unix/glibc
// Author: Vadim Zeitlin
// Modified by:
// Created: 2005-01-18
-// RCS-ID: $Id$
// Copyright: (c) 2005 Vadim Zeitlin <vadim@wxwindows.org>
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
#endif
#include "wx/stackwalk.h"
+#include "wx/stdpaths.h"
#include <execinfo.h>
#include <cxxabi.h>
#endif // HAVE_CXA_DEMANGLE
+// ----------------------------------------------------------------------------
+// tiny helper wrapper around popen/pclose()
+// ----------------------------------------------------------------------------
+
+class wxStdioPipe
+{
+public:
+ // ctor parameters are passed to popen()
+ wxStdioPipe(const char *command, const char *type)
+ {
+ m_fp = popen(command, type);
+ }
+
+ // conversion to stdio FILE
+ operator FILE *() const { return m_fp; }
+
+ // dtor closes the pipe
+ ~wxStdioPipe()
+ {
+ if ( m_fp )
+ pclose(m_fp);
+ }
+
+private:
+ FILE *m_fp;
+
+ 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() )
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;
+ }
}
- wxArrayString output;
- wxLogNull noLog;
- if ( wxExecute(wxString::Format(_T("addr2line -C -f -e \"%s\" %p"),
- exepath.c_str(),
- m_address), output) == 0 )
+ // 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++)
{
- if ( output.GetCount() != 2 )
- {
- wxLogDebug(_T("Unexpected addr2line output."));
- }
- else // 1st line has function name, 2nd one -- the file/line info
+ snprintf(&g_buf[len], BUFSIZE - len, " %p", addresses[i]);
+ len = strlen(g_buf);
+ }
+
+ //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++ )
+ {
+#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 )
{
- if ( GetName().empty() )
- {
- m_name = output[0];
- if ( m_name == _T("??") )
- m_name.clear();
- }
+ name.assign(buffer, 0, posIn);
- const size_t posColon = output[1].find(_T(':'));
- if ( posColon != wxString::npos )
- {
- m_filename.assign(output[1], 0, posColon);
- if ( m_filename == _T("??") )
- {
- m_filename.clear();
- }
- else
- {
- unsigned long line;
- if ( wxString(output[1], posColon + 1, wxString::npos).
- ToULong(&line) )
- m_line = line;
- }
- }
- else
+ size_t posAt = buffer.find(") (", posIn + 3);
+ if ( posAt != wxString::npos )
{
- wxLogDebug(_T("Unexpected addr2line format: \"%s\""),
- output[1].c_str());
+ posAt += 3; // Skip ") ("
+
+ // 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;
-// ----------------------------------------------------------------------------
-// wxStackWalker
-// ----------------------------------------------------------------------------
+ name = wxString::FromAscii(g_buf);
+ name.RemoveLast(); // trailing newline
-void wxStackWalker::Walk(size_t skip)
-{
- // that many frames should be enough for everyone
- void *addresses[200];
+ if ( name == wxT("??") )
+ name.clear();
- int depth = backtrace(addresses, WXSIZEOF(addresses));
- if ( !depth )
- return;
+ // 2nd one -- the file/line info
+ if ( !ReadLine(fp, i, &filename) )
+ return false;
- char **symbols = backtrace_symbols(addresses, depth);
+ const size_t posColon = filename.find(wxT(':'));
+ if ( posColon != wxString::npos )
+ {
+ // 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);
- for ( int n = 0; n < depth; n++ )
- {
- wxStackFrame frame(n, addresses[n], symbols[n]);
- OnStackFrame(frame);
+ // remove line number from 'filename'
+ filename.erase(posColon);
+ if ( filename == wxT("??") )
+ filename.clear();
+ }
+ else
+ {
+ 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]);
}
+
+ return curr;
+}
+
+void wxStackWalker::Walk(size_t skip, size_t maxDepth)
+{
+ // read all frames required
+ SaveStack(maxDepth);
+
+ // process them
+ ProcessFrames(skip);
+
+ // cleanup
+ FreeStack();
}
#endif // wxUSE_STACKWALKER