diff --git a/src/unix/stackwalk.cpp b/src/unix/stackwalk.cpp index 7cd6ae1f35..46f11ea7c9 100644 --- a/src/unix/stackwalk.cpp +++ b/src/unix/stackwalk.cpp @@ -37,6 +37,21 @@ #include #endif // HAVE_CXA_DEMANGLE +// We don't test for this function in configure because it's present in glibc +// versions dating back to at least 2.3, i.e. in practice it's always available. +#if defined(__LINUX__) && defined(__GLIBC__) + #include + #include + + #include + #include + + #define HAVE_DLADDR1 +#endif + +namespace +{ + // ---------------------------------------------------------------------------- // tiny helper wrapper around popen/pclose() // ---------------------------------------------------------------------------- @@ -66,6 +81,8 @@ private: wxDECLARE_NO_COPY_CLASS(wxStdioPipe); }; +} // anonymous namespace + // ============================================================================ // implementation // ============================================================================ @@ -220,13 +237,171 @@ bool ReadLine(FILE* fp, unsigned long num, wxString* line) } *line = wxString::FromAscii(g_buf); - line->RemoveLast(); + line->RemoveLast(); // trailing newline return true; } +#ifndef __WXOSX__ + +// Parse a single unit of addr2line output, which spans 2 lines. +bool +ReadSingleResult(FILE* fp, unsigned long i, + wxString& name, wxString& filename, unsigned long& line) +{ + // 1st line has function name + if ( !ReadLine(fp, i, &name) ) + return false; + + if ( name == wxT("??") ) + name.clear(); + + // 2nd one -- the file/line info + if ( !ReadLine(fp, i, &filename) ) + return false; + + const size_t posColon = filename.find(wxT(':')); + if ( posColon != wxString::npos ) + { + // parse line number: note that there can be more stuff following it in + // addr2line output, e.g. "(discriminator N)", see + // http://wiki.dwarfstd.org/index.php?title=Path_Discriminators + wxString afterColon(filename, posColon + 1, wxString::npos); + const size_t posSpace = afterColon.find(' '); + if ( posSpace != wxString::npos ) + afterColon.erase(posSpace); + + if ( !afterColon.ToULong(&line) ) + line = 0; + + // remove line number from 'filename' + filename.erase(posColon); + if ( filename == wxT("??") ) + filename.clear(); + } + else + { + wxLogDebug("Unexpected addr2line format: \"%s\" - the colon is missing", + filename); + } + + return true; +} + +#endif // !__WXOSX__ + } // anonymous namespace +// When we have dladdr1() (which is always the case under Linux), we can do +// better as we can find the correct path for addr2line, allowing it to find +// the symbols in the shared libraries too. +#ifdef HAVE_DLADDR1 + +namespace +{ + +struct ModuleInfo +{ + explicit ModuleInfo(const char* name, ptrdiff_t diff) + : name(name), + diff(diff) + { + } + + // Name of the file containing this address, may be NULL. + const char* name; + + // Difference between the address in the file and in memory. + ptrdiff_t diff; +}; + +ModuleInfo GetModuleInfoFromAddr(void* addr) +{ + Dl_info info; + link_map* lm; + if ( !dladdr1(addr, &info, (void**)&lm, RTLD_DL_LINKMAP) ) + { + // Probably not worth spamming the user with even debug errors. + return ModuleInfo(NULL, 0); + } + + return ModuleInfo(info.dli_fname, lm->l_addr); +} + +} // anonymous namespace + +int wxStackWalker::InitFrames(wxStackFrame *arr, size_t n, void **addresses, char **syminfo) +{ + // We want to optimize the number of addr2line invocations, but we have to + // run it separately for each file, so find all the files first. + // + // Note that we could avoid doing all this and still use a single command + // if we could rely on having eu-addr2line which has a "-p " + // parameter, but it's not usually available, unfortunately. + + struct ModuleData + { + // The command we run to get information about the addresses, starting + // with addr2line and containing addresses at the end. + wxString command; + + // Indices of addresses corresponding to the subsequent lines of output + // of the command above. + std::vector indices; + }; + + // The key of this map is the path of the module. + std::unordered_map modulesData; + + // First pass: construct all commands to run. + for ( size_t i = 0; i < n; ++i ) + { + void* const addr = addresses[i]; + + const ModuleInfo modInfo = GetModuleInfoFromAddr(addr); + + // We can't do anything if we failed to find the file name. + if ( !modInfo.name ) + { + arr[i].Set(wxString(), wxString(), syminfo[i], i, 0, addr); + continue; + } + + ModuleData& modData = modulesData[modInfo.name]; + if ( modData.command.empty() ) + modData.command.Printf("addr2line -C -f -e \"%s\"", modInfo.name); + + modData.command += + wxString::Format(" %zx", wxPtrToUInt(addr) - modInfo.diff); + modData.indices.push_back(i); + } + + // Second pass: do run them. + wxString name, filename; + unsigned long line = 0; + + for ( const auto& it: modulesData ) + { + const ModuleData& modData = it.second; + + wxStdioPipe fp(modData.command.utf8_str(), "r"); + if ( !fp ) + return 0; + + for ( const size_t i: modData.indices ) + { + if ( !ReadSingleResult(fp, i, name, filename, line) ) + return 0; + + arr[i].Set(name, filename, syminfo[i], i, line, addresses[i]); + } + } + + return n; +} + +#else // !HAVE_DLADDR1 + 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 @@ -303,38 +478,8 @@ int wxStackWalker::InitFrames(wxStackFrame *arr, size_t n, void **addresses, cha } } #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(); - - // 2nd one -- the file/line info - if ( !ReadLine(fp, i, &filename) ) - return false; - - 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); - - // 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()); - } + if ( !ReadSingleResult(fp, i, name, filename, line) ) + return 0; #endif // __WXOSX__/!__WXOSX__ // now we've got enough info to initialize curr-th stack frame @@ -346,6 +491,8 @@ int wxStackWalker::InitFrames(wxStackFrame *arr, size_t n, void **addresses, cha return curr; } +#endif // HAVE_DLADDR1/!HAVE_DLADDR1 + void wxStackWalker::Walk(size_t skip, size_t maxDepth) { // read all frames required