fall back to .debug_info scan in fatal signal handler
[folly.git] / folly / experimental / symbolizer / Symbolizer.cpp
index d7f4db96898eca2463630171be651e6bbc8193c2..13fe68fa38d4ab054f7b039ccbd301a1353d0941 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Facebook, Inc.
+ * Copyright 2016 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 <cstdio>
+#include <cstdlib>
 #include <iostream>
-#include <map>
+#include <limits.h>
+#include <link.h>
+#include <unistd.h>
 
 #ifdef __GNUC__
 #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/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/Elf.h>
+#include <folly/experimental/symbolizer/Dwarf.h>
+#include <folly/experimental/symbolizer/LineReader.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());
+ElfCache* defaultElfCache() {
+  static constexpr size_t defaultCapacity = 500;
+  static auto cache = new ElfCache(defaultCapacity);
+  return cache;
 }
 
-/**
- * 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();
+}  // namespace
 
-  // perms
-  skipNS(line);
-  if (line.empty() || line.front() != ' ') {
-    return false;
-  }
-  line.pop_front();
+void SymbolizedFrame::set(const std::shared_ptr<ElfFile>& file,
+                          uintptr_t address,
+                          Dwarf::LocationInfoMode mode) {
+  clear();
+  found = true;
 
-  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;
+  address += file->getBaseAddress();
+  auto sym = file->getDefinitionByAddress(address);
+  if (!sym.first) {
+    return;
   }
-  line.pop_front();
 
-  // inode
-  skipNS(line);
-  if (line.empty() || line.front() != ' ') {
-    return false;
-  }
+  file_ = file;
+  name = file->getSymbolName(sym);
 
-  skipWS(line);
-  if (line.empty()) {
-    fileName.clear();
-    return true;
-  }
-
-  fileName = line;
-  return true;
+  Dwarf(file.get()).findAddress(address, location, mode);
 }
 
-}  // namespace
+Symbolizer::Symbolizer(ElfCacheBase* cache, Dwarf::LocationInfoMode mode)
+  : cache_(cache ?: defaultElfCache()), mode_(mode) {
+}
 
 void Symbolizer::symbolize(const uintptr_t* addresses,
                            SymbolizedFrame* frames,
-                           size_t addressCount) {
+                           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;
-      frame.name.clear();
-      frame.location = Dwarf::LocationInfo();
+      frame.clear();
     }
   }
 
@@ -171,94 +97,88 @@ void Symbolizer::symbolize(const uintptr_t* addresses,
     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));
-
-  char fileNameBuf[PATH_MAX];
-
-  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;
-    ElfFile* elfFile = nullptr;
+    // 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];
+      auto const addr = addresses[i];
+      // Get the unrelocated, ELF-relative address.
+      auto const adjusted = addr - base;
 
-      if (from > address || address >= to) {
-        continue;
+      if (elfFile->getSectionContainingAddress(adjusted)) {
+        frame.set(elfFile, adjusted, mode_);
+        --remaining;
       }
+    }
+  }
+}
 
-      // Found
-      frame.found = true;
-      --remaining;
-
-      // Open the file on first use
-      if (first) {
-        first = false;
-        if (fileCount_ < kMaxFiles &&
-            !fileName.empty() &&
-            fileName.size() < sizeof(fileNameBuf)) {
-          memcpy(fileNameBuf, fileName.data(), fileName.size());
-          fileNameBuf[fileName.size()] = '\0';
-          auto& f = files_[fileCount_++];
-          if (f.openNoThrow(fileNameBuf) != -1) {
-            elfFile = &f;
-          }
-        }
-      }
+namespace {
+constexpr char kHexChars[] = "0123456789abcdef";
+constexpr auto kAddressColor = SymbolizePrinter::Color::BLUE;
+constexpr auto kFunctionColor = SymbolizePrinter::Color::PURPLE;
+constexpr auto kFileColor = SymbolizePrinter::Color::DEFAULT;
+}  // namespace
 
-      if (!elfFile) {
-        continue;
-      }
+constexpr char AddressFormatter::bufTemplate[];
+constexpr std::array<const char*, SymbolizePrinter::Color::NUM>
+    SymbolizePrinter::kColorMap;
 
-      // Undo relocation
-      uintptr_t fileAddress = address - from + elfFile->getBaseAddress();
-      auto sym = elfFile->getDefinitionByAddress(fileAddress);
-      if (!sym.first) {
-        continue;
-      }
-      auto name = elfFile->getSymbolName(sym);
-      if (name) {
-        frame.name = name;
-      }
+AddressFormatter::AddressFormatter() {
+  memcpy(buf_, bufTemplate, sizeof(buf_));
+}
 
-      Dwarf(elfFile).findAddress(fileAddress, frame.location);
-    }
+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;
   }
 
-  closeNoInt(fd);
+  return folly::StringPiece(buf_, end);
 }
 
-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
-
 void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) {
   if (options_ & TERSE) {
     printTerse(address, frame);
@@ -267,42 +187,30 @@ void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) {
 
   SCOPE_EXIT { color(Color::DEFAULT); };
 
-  color(kAddressColor);
-  // Can't use sprintf, not async-signal-safe
-  static_assert(sizeof(uintptr_t) <= 8, "huge uintptr_t?");
-  char buf[] = "    @ 0000000000000000";
-  char* end = buf + sizeof(buf) - 1 - (16 - 2 * sizeof(uintptr_t));
+  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)));
-  char* p = end;
-  *p-- = '\0';
-  while (address != 0) {
-    *p-- = kHexChars[address & 0xf];
-    address >>= 4;
-  }
-  doPrint(folly::StringPiece(buf, end));
 
   color(kFunctionColor);
-  char mangledBuf[1024];
   if (!frame.found) {
     doPrint(" (not found)");
     return;
   }
 
-  if (frame.name.empty()) {
+  if (!frame.name || frame.name[0] == '\0') {
     doPrint(" (unknown)");
-  } else if (frame.name.size() >= sizeof(mangledBuf)) {
-    doPrint(" ");
-    doPrint(frame.name);
   } else {
-    memcpy(mangledBuf, frame.name.data(), frame.name.size());
-    mangledBuf[frame.name.size()] = '\0';
-
-    char demangledBuf[1024];
-    demangle(mangledBuf, demangledBuf, sizeof(demangledBuf));
+    char demangledBuf[2048];
+    demangle(frame.name, demangledBuf, sizeof(demangledBuf));
     doPrint(" ");
-    doPrint(demangledBuf);
+    doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf);
   }
 
   if (!(options_ & NO_FILE_AND_LINE)) {
@@ -335,31 +243,15 @@ void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) {
   }
 }
 
-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_)) {
     return;
   }
-  auto it = kColorMap.find(color);
-  if (it == kColorMap.end()) {
+  if (color < 0 || color >= kColorMap.size()) {
     return;
   }
-  doPrint(it->second);
+  doPrint(kColorMap[color]);
 }
 
 void SymbolizePrinter::println(uintptr_t address,
@@ -370,14 +262,10 @@ void SymbolizePrinter::println(uintptr_t address,
 
 void SymbolizePrinter::printTerse(uintptr_t address,
                                   const SymbolizedFrame& frame) {
-  if (frame.found) {
-    char mangledBuf[1024];
-    memcpy(mangledBuf, frame.name.data(), frame.name.size());
-    mangledBuf[frame.name.size()] = '\0';
-
-    char demangledBuf[1024] = {0};
-    demangle(mangledBuf, demangledBuf, sizeof(demangledBuf));
-    doPrint(strlen(demangledBuf) == 0 ? "(unknown)" : demangledBuf);
+  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?");
@@ -424,16 +312,20 @@ int getFD(const std::ios& stream) {
   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
 
 OStreamSymbolizePrinter::OStreamSymbolizePrinter(std::ostream& out, int options)
-  : SymbolizePrinter(options, isTty(options, getFD(out))),
+  : SymbolizePrinter(options, isColorfulTty(options, getFD(out))),
     out_(out) {
 }
 
@@ -441,17 +333,39 @@ void OStreamSymbolizePrinter::doPrint(StringPiece sp) {
   out_ << sp;
 }
 
-FDSymbolizePrinter::FDSymbolizePrinter(int fd, int options)
-  : SymbolizePrinter(options, isTty(options, fd)),
-    fd_(fd) {
+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) {
-  writeFull(fd_, sp.data(), sp.size());
+  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 {
+    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, isTty(options, fileno(file))),
+  : SymbolizePrinter(options, isColorfulTty(options, fileno(file))),
     file_(file) {
 }