X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/7a3ba35d6394d838bed80004b4c021e04566317c..0c9dd3b69bd0b3804d9b4c703364730770f5a723:/src/unix/stackwalk.cpp?ds=inline diff --git a/src/unix/stackwalk.cpp b/src/unix/stackwalk.cpp index 36a85a7ac8..f724816e9c 100644 --- a/src/unix/stackwalk.cpp +++ b/src/unix/stackwalk.cpp @@ -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> @@ -40,38 +41,64 @@ #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() ) @@ -100,92 +127,236 @@ 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 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; +} + +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