Do not barf on invalid ELF files
[folly.git] / folly / experimental / symbolizer / Symbolizer.cpp
index 563abb754e29a02bb94eb6c417509040e061e646..535193324fa0fa328838376d7dcef7c8b3508220 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 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 <folly/experimental/symbolizer/Symbolizer.h>
 
 #include <limits.h>
+#include <cstdio>
+#include <iostream>
+#include <map>
 
-#include "folly/Conv.h"
-#include "folly/FileUtil.h"
-#include "folly/String.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/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"
 
 namespace folly {
 namespace symbolizer {
@@ -142,8 +152,36 @@ bool parseProcMapsLine(StringPiece line,
   return true;
 }
 
+ElfCache* defaultElfCache() {
+  static constexpr size_t defaultCapacity = 500;
+  static ElfCache cache(defaultCapacity);
+  return &cache;
+}
+
 }  // namespace
 
+void SymbolizedFrame::set(const std::shared_ptr<ElfFile>& file,
+                          uintptr_t address) {
+  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);
+}
+
+
+Symbolizer::Symbolizer(ElfCacheBase* cache)
+  : cache_(cache ?: defaultElfCache()) {
+}
+
 void Symbolizer::symbolize(const uintptr_t* addresses,
                            SymbolizedFrame* frames,
                            size_t addressCount) {
@@ -152,8 +190,7 @@ void Symbolizer::symbolize(const uintptr_t* addresses,
     auto& frame = frames[i];
     if (!frame.found) {
       ++remaining;
-      frame.name.clear();
-      frame.location = Dwarf::LocationInfo();
+      frame.clear();
     }
   }
 
@@ -169,8 +206,6 @@ void Symbolizer::symbolize(const uintptr_t* addresses,
   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) {
@@ -186,7 +221,7 @@ void Symbolizer::symbolize(const uintptr_t* addresses,
     }
 
     bool first = true;
-    ElfFile* elfFile = nullptr;
+    std::shared_ptr<ElfFile> elfFile;
 
     // See if any addresses are here
     for (size_t i = 0; i < addressCount; ++i) {
@@ -208,16 +243,7 @@ void Symbolizer::symbolize(const uintptr_t* addresses,
       // 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;
-          }
-        }
+        elfFile = cache_->getFile(fileName);
       }
 
       if (!elfFile) {
@@ -225,17 +251,7 @@ void Symbolizer::symbolize(const uintptr_t* addresses,
       }
 
       // 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).findAddress(fileAddress, frame.location);
+      frame.set(elfFile, address - from);
     }
   }
 
@@ -244,88 +260,237 @@ void Symbolizer::symbolize(const uintptr_t* addresses,
 
 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) {
+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 buf[] = "    @ 0000000000000000";
-  char* end = buf + sizeof(buf) - 1 - (16 - 2 * sizeof(uintptr_t));
-  const char padBuf[] = "                       ";
-  folly::StringPiece pad(padBuf,
-                         sizeof(padBuf) - 1 - (16 - 2 * sizeof(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;
   }
-  doPrint(folly::StringPiece(buf, end));
 
-  char mangledBuf[1024];
+  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);
   if (!frame.found) {
-    doPrint(" (not found)\n");
+    doPrint(" (not found)");
     return;
   }
 
-  if (frame.name.empty()) {
-    doPrint(" (unknown)\n");
-  } else if (frame.name.size() >= sizeof(mangledBuf)) {
-    doPrint(" ");
-    doPrint(frame.name);
-    doPrint("\n");
+  if (!frame.name || frame.name[0] == '\0') {
+    doPrint(" (unknown)");
   } else {
-    memcpy(mangledBuf, frame.name.data(), frame.name.size());
-    mangledBuf[frame.name.size()] = '\0';
-
     char demangledBuf[1024];
-    demangle(mangledBuf, demangledBuf, sizeof(demangledBuf));
+    demangle(frame.name, demangledBuf, sizeof(demangledBuf));
     doPrint(" ");
-    doPrint(demangledBuf);
-    doPrint("\n");
+    doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf);
+  }
+
+  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);
+      }
+    }
   }
+}
+
+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" },
+};
 
-  char fileBuf[PATH_MAX];
-  fileBuf[0] = '\0';
-  if (frame.location.hasFileAndLine) {
-    frame.location.file.toBuffer(fileBuf, sizeof(fileBuf));
-    doPrint(pad);
-    doPrint(fileBuf);
-
-    char buf[22];
-    uint32_t n = uint64ToBufferUnsafe(frame.location.line, buf);
-    doPrint(":");
-    doPrint(StringPiece(buf, n));
-    doPrint("\n");
+}
+
+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);
+}
 
-  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(pad);
-      doPrint("-> ");
-      doPrint(mainFileBuf);
-      doPrint("\n");
+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 && frame.name && frame.name[0] != '\0') {
+    char demangledBuf[1024] = {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));
   }
 }
 
-void SymbolizePrinter::print(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) {
-    print(addresses[i], frames[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();
+    }
+  }
+#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, size_t bufferSize)
+  : SymbolizePrinter(options, isTty(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))),
+    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