Harden failure signal handler in the face of memory corruptions
[folly.git] / folly / experimental / symbolizer / Symbolizer.cpp
index 861b3321875b6094a6d6d48622693873f783a5e0..e2e090adfa11b31ccb71f2320c162c103fec4cf9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Facebook, Inc.
+ * Copyright 2014 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 <boost/regex.hpp>
+#include <limits.h>
+#include <cstdio>
+#include <iostream>
+#include <map>
+
+#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/experimental/symbolizer/Elf.h"
 #include "folly/experimental/symbolizer/Dwarf.h"
-#include "glog/logging.h"
-#include "folly/Range.h"
-#include "folly/FBString.h"
-#include "folly/String.h"
-#include "folly/experimental/io/Stream.h"
+#include "folly/experimental/symbolizer/LineReader.h"
 
-namespace facebook {
+
+namespace folly {
 namespace symbolizer {
 
 namespace {
-folly::StringPiece sp(const boost::csub_match& m) {
-  return folly::StringPiece(m.first, m.second);
-}
 
-uint64_t fromHex(folly::StringPiece s) {
-  // Make a copy; we need a null-terminated string for strtoull
-  folly::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');
+/**
+ * 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;
 }
 
-struct MappedFile {
-  uintptr_t begin;
-  uintptr_t end;
-  std::string name;
-};
+/**
+ * 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());
+}
 
-}  // namespace
+/**
+ * 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());
+}
 
-bool Symbolizer::symbolize(uintptr_t address, folly::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 found = false;
-  for (auto& byteLine : folly::byLine("/proc/self/maps")) {
-    folly::StringPiece line(byteLine);
-    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) {
-      continue;  // main mapping starts at 0
-    }
+/**
+ * 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;
+  }
 
-    if (begin <= address && address < end) {
-      found = true;
-      foundFile.begin = begin;
-      foundFile.end = end;
-      foundFile.name.assign(match[4].first, match[4].second);
-      break;
-    }
+  // Remove trailing newline, if any
+  if (line.back() == '\n') {
+    line.pop_back();
   }
 
-  if (!found) {
+  // from
+  from = readHex(line);
+  if (line.empty() || line.front() != '-') {
     return false;
   }
+  line.pop_front();
 
-  auto& elfFile = getFile(foundFile.name);
-  // Undo relocation
-  uintptr_t origAddress = address - foundFile.begin + elfFile.getBaseAddress();
+  // 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();
 
-  auto sym = elfFile.getDefinitionByAddress(origAddress);
-  if (!sym.first) {
+  // inode
+  skipNS(line);
+  if (line.empty() || line.front() != ' ') {
     return false;
   }
 
-  auto name = elfFile.getSymbolName(sym);
-  if (name) {
-    symbolName = name;
+  skipWS(line);
+  if (line.empty()) {
+    fileName.clear();
+    return true;
   }
 
-  Dwarf(&elfFile).findAddress(origAddress, location);
+  fileName = line;
   return true;
 }
 
-ElfFile& Symbolizer::getFile(const std::string& name) {
-  auto pos = elfFiles_.find(name);
-  if (pos != elfFiles_.end()) {
-    return pos->second;
+ElfCache* defaultElfCache() {
+  static constexpr size_t defaultCapacity = 500;
+  static ElfCache cache(defaultCapacity);
+  return &cache;
+}
+
+}  // namespace
+
+Symbolizer::Symbolizer(ElfCacheBase* cache)
+  : cache_(cache ?: defaultElfCache()) {
+}
+
+void Symbolizer::symbolize(const uintptr_t* addresses,
+                           SymbolizedFrame* frames,
+                           size_t addressCount) {
+  size_t remaining = 0;
+  for (size_t i = 0; i < addressCount; ++i) {
+    auto& frame = frames[i];
+    if (!frame.found) {
+      ++remaining;
+      frame.name.clear();
+      frame.location = Dwarf::LocationInfo();
+    }
+  }
+
+  if (remaining == 0) {  // we're done
+    return;
   }
 
-  return elfFiles_.insert(
-      std::make_pair(name, ElfFile(name.c_str()))).first->second;
+  int fd = openNoInt("/proc/self/maps", O_RDONLY);
+  if (fd == -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)) {
+      continue;
+    }
+
+    bool first = true;
+    std::shared_ptr<ElfFile> elfFile;
+
+    // See if any addresses are here
+    for (size_t i = 0; i < addressCount; ++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;
+
+      // Open the file on first use
+      if (first) {
+        first = false;
+        elfFile = cache_->getFile(fileName);
+      }
+
+      if (!elfFile) {
+        continue;
+      }
+
+      // 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;
+      }
+
+      Dwarf(elfFile.get()).findAddress(fileAddress, frame.location);
+    }
+  }
+
+  closeNoInt(fd);
 }
 
-void Symbolizer::write(std::ostream& out, uintptr_t address,
-                       folly::StringPiece symbolName,
-                       const Dwarf::LocationInfo& location) {
-  char buf[20];
-  sprintf(buf, "%#18jx", address);
-  out << "    @ " << buf;
+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 AddressFormatter::bufTemplate[];
+
+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;
+  }
+
+  return folly::StringPiece(buf_, end);
+}
+
+void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) {
+  if (options_ & TERSE) {
+    printTerse(address, frame);
+    return;
+  }
+
+  SCOPE_EXIT { color(Color::DEFAULT); };
+
+  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);
+  char mangledBuf[1024];
+  if (!frame.found) {
+    doPrint(" (not found)");
+    return;
+  }
+
+  if (frame.name.empty()) {
+    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));
+    doPrint(" ");
+    doPrint(demangledBuf);
+  }
 
-  if (!symbolName.empty()) {
-    out << " " << folly::demangle(symbolName.toString().c_str());
+  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);
 
-    std::string file;
-    if (location.hasFileAndLine) {
-      file = location.file.toString();
-      out << "   " << file << ":" << location.line;
+      char buf[22];
+      uint32_t n = uint64ToBufferUnsafe(frame.location.line, buf);
+      doPrint(":");
+      doPrint(StringPiece(buf, n));
     }
 
-    std::string mainFile;
-    if (location.hasMainFile) {
-      mainFile = location.mainFile.toString();
-      if (!location.hasFileAndLine || file != mainFile) {
-        out << "\n                         (compiling "
-            << location.mainFile << ")";
+    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);
       }
     }
+  }
+}
+
+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()) {
+    return;
+  }
+  doPrint(it->second);
+}
+
+void SymbolizePrinter::println(uintptr_t address,
+                               const SymbolizedFrame& frame) {
+  print(address, frame);
+  doPrint("\n");
+}
+
+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);
   } else {
-    out << " (unknown)";
+    // 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));
+  }
+}
+
+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]);
+  }
+}
+
+namespace {
+
+int getFD(const std::ios& stream) {
+#ifdef __GNUC__
+  std::streambuf* buf = stream.rdbuf();
+  using namespace __gnu_cxx;
+
+  {
+    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();
+    }
   }
-  out << "\n";
+#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));
+}
+
+}  // anonymous namespace
+
+OStreamSymbolizePrinter::OStreamSymbolizePrinter(std::ostream& out, int options)
+  : SymbolizePrinter(options, isTty(options, getFD(out))),
+    out_(out) {
+}
+
+void OStreamSymbolizePrinter::doPrint(StringPiece sp) {
+  out_ << sp;
+}
+
+FDSymbolizePrinter::FDSymbolizePrinter(int fd, int options)
+  : SymbolizePrinter(options, isTty(options, fd)),
+    fd_(fd) {
+}
+
+void FDSymbolizePrinter::doPrint(StringPiece sp) {
+  writeFull(fd_, sp.data(), sp.size());
+}
+
+FILESymbolizePrinter::FILESymbolizePrinter(FILE* file, int options)
+  : SymbolizePrinter(options, isTty(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());
 }
 
 }  // namespace symbolizer
-}  // namespace facebook
+}  // namespace folly