/*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2012-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* limitations under the License.
*/
+#include <folly/experimental/symbolizer/Symbolizer.h>
-#include "folly/experimental/symbolizer/Symbolizer.h"
+#include <link.h>
-#include <boost/regex.hpp>
-#include <glog/logging.h>
+#include <climits>
+#include <cstdio>
+#include <cstdlib>
+#include <iostream>
-#include "folly/experimental/symbolizer/Elf.h"
-#include "folly/experimental/symbolizer/Dwarf.h"
-#include "folly/Range.h"
-#include "folly/FBString.h"
-#include "folly/String.h"
-#include "folly/experimental/Gen.h"
-#include "folly/experimental/FileGen.h"
-#include "folly/experimental/StringGen.h"
+#ifdef __GLIBCXX__
+#include <ext/stdio_filebuf.h>
+#include <ext/stdio_sync_filebuf.h>
+#endif
+
+#include <folly/Conv.h>
+#include <folly/FileUtil.h>
+#include <folly/Memory.h>
+#include <folly/ScopeGuard.h>
+#include <folly/String.h>
+
+#include <folly/experimental/symbolizer/Dwarf.h>
+#include <folly/experimental/symbolizer/Elf.h>
+#include <folly/experimental/symbolizer/LineReader.h>
+#include <folly/portability/Unistd.h>
+
+/*
+ * This is declared in `link.h' on Linux platforms, but apparently not on the
+ * Mac version of the file. It's harmless to declare again, in any case.
+ *
+ * Note that declaring it with `extern "C"` results in linkage conflicts.
+ */
+extern struct r_debug _r_debug;
namespace folly {
namespace symbolizer {
namespace {
-StringPiece sp(const boost::csub_match& m) {
- return StringPiece(m.first, m.second);
-}
-
-uint64_t fromHex(StringPiece s) {
- // Make a copy; we need a null-terminated string for strtoull
- fbstring str(s.data(), s.size());
- const char* p = str.c_str();
- char* end;
- uint64_t val = strtoull(p, &end, 16);
- CHECK(*p != '\0' && *end == '\0');
- return val;
-}
-
-struct MappedFile {
- uintptr_t begin;
- uintptr_t end;
- std::string name;
-};
-
-} // namespace
-
-bool Symbolizer::symbolize(uintptr_t address, StringPiece& symbolName,
- Dwarf::LocationInfo& location) {
- symbolName.clear();
- location = Dwarf::LocationInfo();
-
- // Entry in /proc/self/maps
- static const boost::regex mapLineRegex(
- "([[:xdigit:]]+)-([[:xdigit:]]+)" // from-to
- "\\s+"
- "[\\w-]+" // permissions
- "\\s+"
- "([[:xdigit:]]+)" // offset
- "\\s+"
- "[[:xdigit:]]+:[[:xdigit:]]+" // device, minor:major
- "\\s+"
- "\\d+" // inode
- "\\s*"
- "(.*)"); // file name
-
- boost::cmatch match;
-
- MappedFile foundFile;
- bool error = gen::byLine("/proc/self/maps") |
- [&] (StringPiece line) -> bool {
- CHECK(boost::regex_match(line.begin(), line.end(), match, mapLineRegex));
- uint64_t begin = fromHex(sp(match[1]));
- uint64_t end = fromHex(sp(match[2]));
- uint64_t fileOffset = fromHex(sp(match[3]));
- if (fileOffset != 0) {
- return true; // main mapping starts at 0
+
+ElfCache* defaultElfCache() {
+ static constexpr size_t defaultCapacity = 500;
+ static auto cache = new ElfCache(defaultCapacity);
+ return cache;
+}
+
+} // namespace
+
+void SymbolizedFrame::set(
+ const std::shared_ptr<ElfFile>& file,
+ uintptr_t address,
+ Dwarf::LocationInfoMode mode) {
+ clear();
+ found = true;
+
+ address += file->getBaseAddress();
+ auto sym = file->getDefinitionByAddress(address);
+ if (!sym.first) {
+ return;
+ }
+
+ file_ = file;
+ name = file->getSymbolName(sym);
+
+ Dwarf(file.get()).findAddress(address, location, mode);
+}
+
+Symbolizer::Symbolizer(ElfCacheBase* cache, Dwarf::LocationInfoMode mode)
+ : cache_(cache ? cache : defaultElfCache()), mode_(mode) {}
+
+void Symbolizer::symbolize(
+ const uintptr_t* addresses,
+ SymbolizedFrame* frames,
+ size_t addrCount) {
+ size_t remaining = 0;
+ for (size_t i = 0; i < addrCount; ++i) {
+ auto& frame = frames[i];
+ if (!frame.found) {
+ ++remaining;
+ frame.clear();
+ }
+ }
+
+ if (remaining == 0) { // we're done
+ return;
+ }
+
+ if (_r_debug.r_version != 1) {
+ return;
+ }
+
+ char selfPath[PATH_MAX + 8];
+ ssize_t selfSize;
+ if ((selfSize = readlink("/proc/self/exe", selfPath, PATH_MAX + 1)) == -1) {
+ // Something has gone terribly wrong.
+ return;
+ }
+ selfPath[selfSize] = '\0';
+
+ for (auto lmap = _r_debug.r_map; lmap != nullptr && remaining != 0;
+ lmap = lmap->l_next) {
+ // The empty string is used in place of the filename for the link_map
+ // corresponding to the running executable. Additionally, the `l_addr' is
+ // 0 and the link_map appears to be first in the list---but none of this
+ // behavior appears to be documented, so checking for the empty string is
+ // as good as anything.
+ auto const objPath = lmap->l_name[0] != '\0' ? lmap->l_name : selfPath;
+
+ auto const elfFile = cache_->getFile(objPath);
+ if (!elfFile) {
+ continue;
+ }
+
+ // Get the address at which the object is loaded. We have to use the ELF
+ // header for the running executable, since its `l_addr' is zero, but we
+ // should use `l_addr' for everything else---in particular, if the object
+ // is position-independent, getBaseAddress() (which is p_vaddr) will be 0.
+ auto const base =
+ lmap->l_addr != 0 ? lmap->l_addr : elfFile->getBaseAddress();
+
+ for (size_t i = 0; i < addrCount && remaining != 0; ++i) {
+ auto& frame = frames[i];
+ if (frame.found) {
+ continue;
}
- if (begin <= address && address < end) {
- foundFile.begin = begin;
- foundFile.end = end;
- foundFile.name.assign(match[4].first, match[4].second);
- return false;
+ auto const addr = addresses[i];
+ // Get the unrelocated, ELF-relative address.
+ auto const adjusted = addr - base;
+
+ if (elfFile->getSectionContainingAddress(adjusted)) {
+ frame.set(elfFile, adjusted, mode_);
+ --remaining;
}
+ }
+ }
+}
+
+namespace {
+constexpr char kHexChars[] = "0123456789abcdef";
+constexpr auto kAddressColor = SymbolizePrinter::Color::BLUE;
+constexpr auto kFunctionColor = SymbolizePrinter::Color::PURPLE;
+constexpr auto kFileColor = SymbolizePrinter::Color::DEFAULT;
+} // namespace
- return true;
- };
+constexpr char AddressFormatter::bufTemplate[];
+constexpr std::array<const char*, SymbolizePrinter::Color::NUM>
+ SymbolizePrinter::kColorMap;
- if (error) {
- return false;
+AddressFormatter::AddressFormatter() {
+ memcpy(buf_, bufTemplate, sizeof(buf_));
+}
+
+folly::StringPiece AddressFormatter::format(uintptr_t address) {
+ // Can't use sprintf, not async-signal-safe
+ static_assert(sizeof(uintptr_t) <= 8, "huge uintptr_t?");
+ char* end = buf_ + sizeof(buf_) - 1 - (16 - 2 * sizeof(uintptr_t));
+ char* p = end;
+ *p-- = '\0';
+ while (address != 0) {
+ *p-- = kHexChars[address & 0xf];
+ address >>= 4;
}
- auto& elfFile = getFile(foundFile.name);
- // Undo relocation
- uintptr_t origAddress = address - foundFile.begin + elfFile.getBaseAddress();
+ return folly::StringPiece(buf_, end);
+}
- auto sym = elfFile.getDefinitionByAddress(origAddress);
- if (!sym.first) {
- return false;
+void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) {
+ if (options_ & TERSE) {
+ printTerse(address, frame);
+ return;
+ }
+
+ SCOPE_EXIT {
+ color(Color::DEFAULT);
+ };
+
+ if (!(options_ & NO_FRAME_ADDRESS)) {
+ color(kAddressColor);
+
+ AddressFormatter formatter;
+ doPrint(formatter.format(address));
+ }
+
+ const char padBuf[] = " ";
+ folly::StringPiece pad(
+ padBuf, sizeof(padBuf) - 1 - (16 - 2 * sizeof(uintptr_t)));
+
+ color(kFunctionColor);
+ if (!frame.found) {
+ doPrint(" (not found)");
+ return;
+ }
+
+ if (!frame.name || frame.name[0] == '\0') {
+ doPrint(" (unknown)");
+ } else {
+ char demangledBuf[2048];
+ demangle(frame.name, demangledBuf, sizeof(demangledBuf));
+ doPrint(" ");
+ doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf);
}
- auto name = elfFile.getSymbolName(sym);
- if (name) {
- symbolName = name;
+ if (!(options_ & NO_FILE_AND_LINE)) {
+ color(kFileColor);
+ char fileBuf[PATH_MAX];
+ fileBuf[0] = '\0';
+ if (frame.location.hasFileAndLine) {
+ frame.location.file.toBuffer(fileBuf, sizeof(fileBuf));
+ doPrint("\n");
+ doPrint(pad);
+ doPrint(fileBuf);
+
+ char buf[22];
+ uint32_t n = uint64ToBufferUnsafe(frame.location.line, buf);
+ doPrint(":");
+ doPrint(StringPiece(buf, n));
+ }
+
+ if (frame.location.hasMainFile) {
+ char mainFileBuf[PATH_MAX];
+ mainFileBuf[0] = '\0';
+ frame.location.mainFile.toBuffer(mainFileBuf, sizeof(mainFileBuf));
+ if (!frame.location.hasFileAndLine || strcmp(fileBuf, mainFileBuf)) {
+ doPrint("\n");
+ doPrint(pad);
+ doPrint("-> ");
+ doPrint(mainFileBuf);
+ }
+ }
}
+}
+
+void SymbolizePrinter::color(SymbolizePrinter::Color color) {
+ if ((options_ & COLOR) == 0 && ((options_ & COLOR_IF_TTY) == 0 || !isTty_)) {
+ return;
+ }
+ if (static_cast<size_t>(color) >= kColorMap.size()) { // catches underflow too
+ return;
+ }
+ doPrint(kColorMap[color]);
+}
- Dwarf(&elfFile).findAddress(origAddress, location);
- return true;
+void SymbolizePrinter::println(
+ uintptr_t address,
+ const SymbolizedFrame& frame) {
+ print(address, frame);
+ doPrint("\n");
}
-ElfFile& Symbolizer::getFile(const std::string& name) {
- auto pos = elfFiles_.find(name);
- if (pos != elfFiles_.end()) {
- return pos->second;
+void SymbolizePrinter::printTerse(
+ uintptr_t address,
+ const SymbolizedFrame& frame) {
+ if (frame.found && frame.name && frame.name[0] != '\0') {
+ char demangledBuf[2048] = {0};
+ demangle(frame.name, demangledBuf, sizeof(demangledBuf));
+ doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf);
+ } else {
+ // Can't use sprintf, not async-signal-safe
+ static_assert(sizeof(uintptr_t) <= 8, "huge uintptr_t?");
+ char buf[] = "0x0000000000000000";
+ char* end = buf + sizeof(buf) - 1 - (16 - 2 * sizeof(uintptr_t));
+ char* p = end;
+ *p-- = '\0';
+ while (address != 0) {
+ *p-- = kHexChars[address & 0xf];
+ address >>= 4;
+ }
+ doPrint(StringPiece(buf, end));
}
+}
- return elfFiles_.insert(
- std::make_pair(name, ElfFile(name.c_str()))).first->second;
+void SymbolizePrinter::println(
+ const uintptr_t* addresses,
+ const SymbolizedFrame* frames,
+ size_t frameCount) {
+ for (size_t i = 0; i < frameCount; ++i) {
+ println(addresses[i], frames[i]);
+ }
}
-void Symbolizer::write(std::ostream& out, uintptr_t address,
- StringPiece symbolName,
- const Dwarf::LocationInfo& location) {
- char buf[20];
- sprintf(buf, "%#18jx", address);
- out << " @ " << buf;
+namespace {
- if (!symbolName.empty()) {
- out << " " << demangle(symbolName.toString().c_str());
+int getFD(const std::ios& stream) {
+#ifdef __GNUC__
+ std::streambuf* buf = stream.rdbuf();
+ using namespace __gnu_cxx;
- std::string file;
- if (location.hasFileAndLine) {
- file = location.file.toString();
- out << " " << file << ":" << location.line;
+ {
+ auto sbuf = dynamic_cast<stdio_sync_filebuf<char>*>(buf);
+ if (sbuf) {
+ return fileno(sbuf->file());
}
+ }
+ {
+ auto sbuf = dynamic_cast<stdio_filebuf<char>*>(buf);
+ if (sbuf) {
+ return sbuf->fd();
+ }
+ }
+#endif // __GNUC__
+ return -1;
+}
- std::string mainFile;
- if (location.hasMainFile) {
- mainFile = location.mainFile.toString();
- if (!location.hasFileAndLine || file != mainFile) {
- out << "\n (compiling "
- << location.mainFile << ")";
- }
+bool isColorfulTty(int options, int fd) {
+ if ((options & SymbolizePrinter::TERSE) != 0 ||
+ (options & SymbolizePrinter::COLOR_IF_TTY) == 0 || fd < 0 ||
+ !::isatty(fd)) {
+ return false;
+ }
+ auto term = ::getenv("TERM");
+ return !(term == nullptr || term[0] == '\0' || strcmp(term, "dumb") == 0);
+}
+
+} // namespace
+
+OStreamSymbolizePrinter::OStreamSymbolizePrinter(std::ostream& out, int options)
+ : SymbolizePrinter(options, isColorfulTty(options, getFD(out))),
+ out_(out) {}
+
+void OStreamSymbolizePrinter::doPrint(StringPiece sp) {
+ out_ << sp;
+}
+
+FDSymbolizePrinter::FDSymbolizePrinter(int fd, int options, size_t bufferSize)
+ : SymbolizePrinter(options, isColorfulTty(options, fd)),
+ fd_(fd),
+ buffer_(bufferSize ? IOBuf::create(bufferSize) : nullptr) {}
+
+FDSymbolizePrinter::~FDSymbolizePrinter() {
+ flush();
+}
+
+void FDSymbolizePrinter::doPrint(StringPiece sp) {
+ if (buffer_) {
+ if (sp.size() > buffer_->tailroom()) {
+ flush();
+ writeFull(fd_, sp.data(), sp.size());
+ } else {
+ memcpy(buffer_->writableTail(), sp.data(), sp.size());
+ buffer_->append(sp.size());
}
} else {
- out << " (unknown)";
+ writeFull(fd_, sp.data(), sp.size());
+ }
+}
+
+void FDSymbolizePrinter::flush() {
+ if (buffer_ && !buffer_->empty()) {
+ writeFull(fd_, buffer_->data(), buffer_->length());
+ buffer_->clear();
+ }
+}
+
+FILESymbolizePrinter::FILESymbolizePrinter(FILE* file, int options)
+ : SymbolizePrinter(options, isColorfulTty(options, fileno(file))),
+ file_(file) {}
+
+void FILESymbolizePrinter::doPrint(StringPiece sp) {
+ fwrite(sp.data(), 1, sp.size(), file_);
+}
+
+void StringSymbolizePrinter::doPrint(StringPiece sp) {
+ buf_.append(sp.data(), sp.size());
+}
+
+StackTracePrinter::StackTracePrinter(size_t minSignalSafeElfCacheSize, int fd)
+ : fd_(fd),
+ elfCache_(std::max(countLoadedElfFiles(), minSignalSafeElfCacheSize)),
+ printer_(
+ fd,
+ SymbolizePrinter::COLOR_IF_TTY,
+ size_t(64) << 10), // 64KiB
+ addresses_(std::make_unique<FrameArray<kMaxStackTraceDepth>>()) {}
+
+void StackTracePrinter::flush() {
+ printer_.flush();
+ fsyncNoInt(fd_);
+}
+
+void StackTracePrinter::printStackTrace(bool symbolize) {
+ SCOPE_EXIT {
+ flush();
+ };
+
+ // Skip the getStackTrace frame
+ if (!getStackTraceSafe(*addresses_)) {
+ print("(error retrieving stack trace)\n");
+ } else if (symbolize) {
+ // Do our best to populate location info, process is going to terminate,
+ // so performance isn't critical.
+ Symbolizer symbolizer(&elfCache_, Dwarf::LocationInfoMode::FULL);
+ symbolizer.symbolize(*addresses_);
+
+ // Skip the top 2 frames:
+ // getStackTraceSafe
+ // StackTracePrinter::printStackTrace (here)
+ //
+ // Leaving signalHandler on the stack for clarity, I think.
+ printer_.println(*addresses_, 2);
+ } else {
+ print("(safe mode, symbolizer not available)\n");
+ AddressFormatter formatter;
+ for (size_t i = 0; i < addresses_->frameCount; ++i) {
+ print(formatter.format(addresses_->addresses[i]));
+ print("\n");
+ }
}
- out << "\n";
}
-} // namespace symbolizer
-} // namespace folly
+} // namespace symbolizer
+} // namespace folly