/*
- * Copyright 2014 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 <limits.h>
+#include <link.h>
+
+#include <climits>
#include <cstdio>
+#include <cstdlib>
#include <iostream>
-#include <map>
-#ifdef __GNUC__
+#ifdef __GLIBCXX__
#include <ext/stdio_filebuf.h>
#include <ext/stdio_sync_filebuf.h>
#endif
-#include "folly/Conv.h"
-#include "folly/FileUtil.h"
-#include "folly/ScopeGuard.h"
-#include "folly/String.h"
+#include <folly/Conv.h>
+#include <folly/FileUtil.h>
+#include <folly/Memory.h>
+#include <folly/ScopeGuard.h>
+#include <folly/String.h>
-#include "folly/experimental/symbolizer/Elf.h"
-#include "folly/experimental/symbolizer/Dwarf.h"
-#include "folly/experimental/symbolizer/LineReader.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 {
-/**
- * Read a hex value.
- */
-uintptr_t readHex(StringPiece& sp) {
- uintptr_t val = 0;
- const char* p = sp.begin();
- for (; p != sp.end(); ++p) {
- unsigned int v;
- if (*p >= '0' && *p <= '9') {
- v = (*p - '0');
- } else if (*p >= 'a' && *p <= 'f') {
- v = (*p - 'a') + 10;
- } else if (*p >= 'A' && *p <= 'F') {
- v = (*p - 'A') + 10;
- } else {
- break;
- }
- val = (val << 4) + v;
- }
- sp.assign(p, sp.end());
- return val;
-}
-
-/**
- * Skip over non-space characters.
- */
-void skipNS(StringPiece& sp) {
- const char* p = sp.begin();
- for (; p != sp.end() && (*p != ' ' && *p != '\t'); ++p) { }
- sp.assign(p, sp.end());
-}
-
-/**
- * Skip over space and tab characters.
- */
-void skipWS(StringPiece& sp) {
- const char* p = sp.begin();
- for (; p != sp.end() && (*p == ' ' || *p == '\t'); ++p) { }
- sp.assign(p, sp.end());
-}
-
-/**
- * Parse a line from /proc/self/maps
- */
-bool parseProcMapsLine(StringPiece line,
- uintptr_t& from, uintptr_t& to,
- StringPiece& fileName) {
- // from to perm offset dev inode path
- // 00400000-00405000 r-xp 00000000 08:03 35291182 /bin/cat
- if (line.empty()) {
- return false;
- }
-
- // Remove trailing newline, if any
- if (line.back() == '\n') {
- line.pop_back();
- }
-
- // from
- from = readHex(line);
- if (line.empty() || line.front() != '-') {
- return false;
- }
- line.pop_front();
-
- // to
- to = readHex(line);
- if (line.empty() || line.front() != ' ') {
- return false;
- }
- line.pop_front();
-
- // perms
- skipNS(line);
- if (line.empty() || line.front() != ' ') {
- return false;
- }
- line.pop_front();
-
- uintptr_t fileOffset = readHex(line);
- if (line.empty() || line.front() != ' ') {
- return false;
- }
- line.pop_front();
- if (fileOffset != 0) {
- return false; // main mapping starts at 0
- }
-
- // dev
- skipNS(line);
- if (line.empty() || line.front() != ' ') {
- return false;
- }
- line.pop_front();
-
- // inode
- skipNS(line);
- if (line.empty() || line.front() != ' ') {
- return false;
- }
-
- skipWS(line);
- if (line.empty()) {
- fileName.clear();
- return true;
- }
-
- fileName = line;
- return true;
-}
-
ElfCache* defaultElfCache() {
static constexpr size_t defaultCapacity = 500;
- static ElfCache cache(defaultCapacity);
- return &cache;
+ static auto cache = new ElfCache(defaultCapacity);
+ return cache;
}
-} // namespace
+} // namespace
-void SymbolizedFrame::set(const std::shared_ptr<ElfFile>& file,
- uintptr_t address) {
+void SymbolizedFrame::set(
+ const std::shared_ptr<ElfFile>& file,
+ uintptr_t address,
+ Dwarf::LocationInfoMode mode) {
clear();
found = true;
file_ = file;
name = file->getSymbolName(sym);
- Dwarf(file.get()).findAddress(address, location);
+ Dwarf(file.get()).findAddress(address, location, mode);
}
+Symbolizer::Symbolizer(ElfCacheBase* cache, Dwarf::LocationInfoMode mode)
+ : cache_(cache ? cache : defaultElfCache()), mode_(mode) {}
-Symbolizer::Symbolizer(ElfCacheBase* cache)
- : cache_(cache ?: defaultElfCache()) {
-}
-
-void Symbolizer::symbolize(const uintptr_t* addresses,
- SymbolizedFrame* frames,
- size_t addressCount) {
+void Symbolizer::symbolize(
+ const uintptr_t* addresses,
+ SymbolizedFrame* frames,
+ size_t addrCount) {
size_t remaining = 0;
- for (size_t i = 0; i < addressCount; ++i) {
+ for (size_t i = 0; i < addrCount; ++i) {
auto& frame = frames[i];
if (!frame.found) {
++remaining;
}
}
- if (remaining == 0) { // we're done
+ if (remaining == 0) { // we're done
return;
}
- int fd = openNoInt("/proc/self/maps", O_RDONLY);
- if (fd == -1) {
+ if (_r_debug.r_version != 1) {
return;
}
- char buf[PATH_MAX + 100]; // Long enough for any line
- LineReader reader(fd, buf, sizeof(buf));
-
- while (remaining != 0) {
- StringPiece line;
- if (reader.readLine(line) != LineReader::kReading) {
- break;
- }
-
- // Parse line
- uintptr_t from;
- uintptr_t to;
- StringPiece fileName;
- if (!parseProcMapsLine(line, from, to, fileName)) {
+ 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;
}
- bool first = true;
- std::shared_ptr<ElfFile> elfFile;
+ // 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();
- // See if any addresses are here
- for (size_t i = 0; i < addressCount; ++i) {
+ for (size_t i = 0; i < addrCount && remaining != 0; ++i) {
auto& frame = frames[i];
if (frame.found) {
continue;
}
- uintptr_t address = addresses[i];
-
- if (from > address || address >= to) {
- continue;
- }
-
- // Found
- frame.found = true;
- --remaining;
+ auto const addr = addresses[i];
+ // Get the unrelocated, ELF-relative address.
+ auto const adjusted = addr - base;
- // Open the file on first use
- if (first) {
- first = false;
- elfFile = cache_->getFile(fileName);
- }
-
- if (!elfFile) {
- continue;
+ if (elfFile->getSectionContainingAddress(adjusted)) {
+ frame.set(elfFile, adjusted, mode_);
+ --remaining;
}
-
- // Undo relocation
- frame.set(elfFile, address - from);
}
}
-
- closeNoInt(fd);
}
namespace {
-const char kHexChars[] = "0123456789abcdef";
-const SymbolizePrinter::Color kAddressColor = SymbolizePrinter::Color::BLUE;
-const SymbolizePrinter::Color kFunctionColor = SymbolizePrinter::Color::PURPLE;
-const SymbolizePrinter::Color kFileColor = SymbolizePrinter::Color::DEFAULT;
-} // namespace
+constexpr char kHexChars[] = "0123456789abcdef";
+constexpr auto kAddressColor = SymbolizePrinter::Color::BLUE;
+constexpr auto kFunctionColor = SymbolizePrinter::Color::PURPLE;
+constexpr auto kFileColor = SymbolizePrinter::Color::DEFAULT;
+} // namespace
constexpr char AddressFormatter::bufTemplate[];
+constexpr std::array<const char*, SymbolizePrinter::Color::NUM>
+ SymbolizePrinter::kColorMap;
AddressFormatter::AddressFormatter() {
memcpy(buf_, bufTemplate, sizeof(buf_));
return;
}
- SCOPE_EXIT { color(Color::DEFAULT); };
+ SCOPE_EXIT {
+ color(Color::DEFAULT);
+ };
- color(kAddressColor);
+ if (!(options_ & NO_FRAME_ADDRESS)) {
+ color(kAddressColor);
- AddressFormatter formatter;
- doPrint(formatter.format(address));
+ AddressFormatter formatter;
+ doPrint(formatter.format(address));
+ }
const char padBuf[] = " ";
- folly::StringPiece pad(padBuf,
- sizeof(padBuf) - 1 - (16 - 2 * sizeof(uintptr_t)));
+ folly::StringPiece pad(
+ padBuf, sizeof(padBuf) - 1 - (16 - 2 * sizeof(uintptr_t)));
color(kFunctionColor);
if (!frame.found) {
if (!frame.name || frame.name[0] == '\0') {
doPrint(" (unknown)");
} else {
- char demangledBuf[1024];
+ char demangledBuf[2048];
demangle(frame.name, demangledBuf, sizeof(demangledBuf));
doPrint(" ");
doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf);
}
}
-namespace {
-
-const std::map<SymbolizePrinter::Color, std::string> kColorMap = {
- { SymbolizePrinter::Color::DEFAULT, "\x1B[0m" },
- { SymbolizePrinter::Color::RED, "\x1B[31m" },
- { SymbolizePrinter::Color::GREEN, "\x1B[32m" },
- { SymbolizePrinter::Color::YELLOW, "\x1B[33m" },
- { SymbolizePrinter::Color::BLUE, "\x1B[34m" },
- { SymbolizePrinter::Color::CYAN, "\x1B[36m" },
- { SymbolizePrinter::Color::WHITE, "\x1B[37m" },
- { SymbolizePrinter::Color::PURPLE, "\x1B[35m" },
-};
-
-}
-
void SymbolizePrinter::color(SymbolizePrinter::Color color) {
- if ((options_ & COLOR) == 0 &&
- ((options_ & COLOR_IF_TTY) == 0 || !isTty_)) {
+ if ((options_ & COLOR) == 0 && ((options_ & COLOR_IF_TTY) == 0 || !isTty_)) {
return;
}
- auto it = kColorMap.find(color);
- if (it == kColorMap.end()) {
+ if (static_cast<size_t>(color) >= kColorMap.size()) { // catches underflow too
return;
}
- doPrint(it->second);
+ doPrint(kColorMap[color]);
}
-void SymbolizePrinter::println(uintptr_t address,
- const SymbolizedFrame& frame) {
+void SymbolizePrinter::println(
+ uintptr_t address,
+ const SymbolizedFrame& frame) {
print(address, frame);
doPrint("\n");
}
-void SymbolizePrinter::printTerse(uintptr_t address,
- const SymbolizedFrame& frame) {
+void SymbolizePrinter::printTerse(
+ uintptr_t address,
+ const SymbolizedFrame& frame) {
if (frame.found && frame.name && frame.name[0] != '\0') {
- char demangledBuf[1024] = {0};
+ char demangledBuf[2048] = {0};
demangle(frame.name, demangledBuf, sizeof(demangledBuf));
doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf);
} else {
}
}
-void SymbolizePrinter::println(const uintptr_t* addresses,
- const SymbolizedFrame* frames,
- size_t frameCount) {
+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]);
}
return sbuf->fd();
}
}
-#endif // __GNUC__
+#endif // __GNUC__
return -1;
}
-bool isTty(int options, int fd) {
- return ((options & SymbolizePrinter::TERSE) == 0 &&
- (options & SymbolizePrinter::COLOR_IF_TTY) != 0 &&
- fd >= 0 && ::isatty(fd));
+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);
}
-} // anonymous namespace
+} // namespace
OStreamSymbolizePrinter::OStreamSymbolizePrinter(std::ostream& out, int options)
- : SymbolizePrinter(options, isTty(options, getFD(out))),
- out_(out) {
-}
+ : 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, isTty(options, fd)),
- fd_(fd),
- buffer_(bufferSize ? IOBuf::create(bufferSize) : nullptr) {
-}
+ : SymbolizePrinter(options, isColorfulTty(options, fd)),
+ fd_(fd),
+ buffer_(bufferSize ? IOBuf::create(bufferSize) : nullptr) {}
FDSymbolizePrinter::~FDSymbolizePrinter() {
flush();
}
FILESymbolizePrinter::FILESymbolizePrinter(FILE* file, int options)
- : SymbolizePrinter(options, isTty(options, fileno(file))),
- file_(file) {
-}
+ : SymbolizePrinter(options, isColorfulTty(options, fileno(file))),
+ file_(file) {}
void FILESymbolizePrinter::doPrint(StringPiece sp) {
fwrite(sp.data(), 1, sp.size(), file_);
buf_.append(sp.data(), sp.size());
}
-} // namespace symbolizer
-} // namespace folly
+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");
+ }
+ }
+}
+
+} // namespace symbolizer
+} // namespace folly