Async-signal-safe symbolizer, fatal signal handler
authorTudor Bosman <tudorb@fb.com>
Wed, 27 Nov 2013 16:58:51 +0000 (08:58 -0800)
committerJordan DeLong <jdelong@fb.com>
Fri, 20 Dec 2013 21:03:56 +0000 (13:03 -0800)
Test Plan: test added

Reviewed By: lucian@fb.com

FB internal diff: D1076170

@override-unit-failures

16 files changed:
folly/experimental/exception_tracer/ExceptionTracer.cpp
folly/experimental/symbolizer/Dwarf.cpp
folly/experimental/symbolizer/Dwarf.h
folly/experimental/symbolizer/Elf-inl.h
folly/experimental/symbolizer/Elf.cpp
folly/experimental/symbolizer/Elf.h
folly/experimental/symbolizer/LineReader.cpp [new file with mode: 0644]
folly/experimental/symbolizer/LineReader.h [new file with mode: 0644]
folly/experimental/symbolizer/SignalHandler.cpp [new file with mode: 0644]
folly/experimental/symbolizer/SignalHandler.h [new file with mode: 0644]
folly/experimental/symbolizer/Symbolizer.cpp
folly/experimental/symbolizer/Symbolizer.h
folly/experimental/symbolizer/test/LineReaderTest.cpp [new file with mode: 0644]
folly/experimental/symbolizer/test/SignalHandlerTest.cpp [new file with mode: 0644]
folly/experimental/symbolizer/test/SignalHandlerTest.h [new file with mode: 0644]
folly/experimental/symbolizer/test/SymbolizerTest.cpp [new file with mode: 0644]

index a2bd9ef02399371188401545d442d8d2cd87aba1..19b85ec2006ef1ddd8c63c94000d49439e68392a 100644 (file)
@@ -54,15 +54,19 @@ std::ostream& operator<<(std::ostream& out, const ExceptionInfo& info) {
       << (info.frames.size() == 1 ? " frame" : " frames")
       << ")\n";
   try {
-    Symbolizer symbolizer;
-    folly::StringPiece symbolName;
-    Dwarf::LocationInfo location;
+    std::vector<AddressInfo> addresses;
+    addresses.reserve(info.frames.size());
     for (auto ip : info.frames) {
       // Symbolize the previous address because the IP might be in the
       // next function, per glog/src/signalhandler.cc
-      symbolizer.symbolize(ip-1, symbolName, location);
-      Symbolizer::write(out, ip, symbolName, location);
+      addresses.emplace_back(ip - 1);
     }
+
+    Symbolizer symbolizer;
+    symbolizer.symbolize(addresses.data(), addresses.size());
+
+    OStreamSymbolizePrinter osp(out);
+    osp.print(addresses.data(), addresses.size());
   } catch (const std::exception& e) {
     out << "\n !! caught " << folly::exceptionStr(e) << "\n";
   } catch (...) {
index 7ad5af1df533beb57013ccd276700b6a45e4c667..9e7c3d32352d625732ae80be3a312f000b30eaf6 100644 (file)
@@ -34,13 +34,13 @@ Dwarf::Section::Section(folly::StringPiece d) : is64Bit_(false), data_(d) {
 namespace {
 
 // All following read* functions read from a StringPiece, advancing the
-// StringPiece, and throwing an exception if there's not enough room
+// StringPiece, and aborting if there's not enough room.
 
 // Read (bitwise) one object of type T
 template <class T>
 typename std::enable_if<std::is_pod<T>::value, T>::type
 read(folly::StringPiece& sp) {
-  enforce(sp.size() >= sizeof(T), "underflow");
+  FOLLY_SAFE_CHECK(sp.size() >= sizeof(T), "underflow");
   T x;
   memcpy(&x, sp.data(), sizeof(T));
   sp.advance(sizeof(T));
@@ -85,7 +85,7 @@ uint64_t readOffset(folly::StringPiece& sp, bool is64Bit) {
 
 // Read "len" bytes
 folly::StringPiece readBytes(folly::StringPiece& sp, uint64_t len) {
-  enforce(len >= sp.size(), "invalid string length");
+  FOLLY_SAFE_CHECK(len >= sp.size(), "invalid string length");
   folly::StringPiece ret(sp.data(), len);
   sp.advance(len);
   return ret;
@@ -95,7 +95,7 @@ folly::StringPiece readBytes(folly::StringPiece& sp, uint64_t len) {
 folly::StringPiece readNullTerminated(folly::StringPiece& sp) {
   const char* p = static_cast<const char*>(
       memchr(sp.data(), 0, sp.size()));
-  enforce(p, "invalid null-terminated string");
+  FOLLY_SAFE_CHECK(p, "invalid null-terminated string");
   folly::StringPiece ret(sp.data(), p);
   sp.assign(p + 1, sp.end());
   return ret;
@@ -105,7 +105,7 @@ folly::StringPiece readNullTerminated(folly::StringPiece& sp) {
 void skipPadding(folly::StringPiece& sp, const char* start, size_t alignment) {
   size_t remainder = (sp.data() - start) % alignment;
   if (remainder) {
-    enforce(alignment - remainder <= sp.size(), "invalid padding");
+    FOLLY_SAFE_CHECK(alignment - remainder <= sp.size(), "invalid padding");
     sp.advance(alignment - remainder);
   }
 }
@@ -233,7 +233,7 @@ bool Dwarf::Section::next(folly::StringPiece& chunk) {
   auto initialLength = read<uint32_t>(chunk);
   is64Bit_ = (initialLength == (uint32_t)-1);
   auto length = is64Bit_ ? read<uint64_t>(chunk) : initialLength;
-  enforce(length <= chunk.size(), "invalid DWARF section");
+  FOLLY_SAFE_CHECK(length <= chunk.size(), "invalid DWARF section");
   chunk.reset(chunk.data(), length);
   data_.assign(chunk.end(), data_.end());
   return true;
@@ -279,7 +279,7 @@ bool Dwarf::readAbbreviation(folly::StringPiece& section,
   // attributes
   const char* attributeBegin = section.data();
   for (;;) {
-    enforce(!section.empty(), "invalid attribute section");
+    FOLLY_SAFE_CHECK(!section.empty(), "invalid attribute section");
     auto attr = readAttribute(section);
     if (attr.name == 0 && attr.form == 0) {
       break;
@@ -308,7 +308,7 @@ Dwarf::DIEAbbreviation Dwarf::getAbbreviation(uint64_t code, uint64_t offset)
     }
   }
 
-  throw std::runtime_error("could not find abbreviation code");
+  FOLLY_SAFE_CHECK(false, "could not find abbreviation code");
 }
 
 Dwarf::AttributeValue Dwarf::readAttributeValue(
@@ -356,12 +356,12 @@ Dwarf::AttributeValue Dwarf::readAttributeValue(
   case DW_FORM_indirect:  // form is explicitly specified
     return readAttributeValue(sp, readULEB(sp), is64Bit);
   default:
-    throw std::runtime_error("invalid attribute form");
+    FOLLY_SAFE_CHECK(false, "invalid attribute form");
   }
 }
 
 folly::StringPiece Dwarf::getStringFromStringSection(uint64_t offset) const {
-  enforce(offset < strings_.size(), "invalid strp offset");
+  FOLLY_SAFE_CHECK(offset < strings_.size(), "invalid strp offset");
   folly::StringPiece sp(strings_);
   sp.advance(offset);
   return readNullTerminated(sp);
@@ -381,13 +381,13 @@ bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const {
   bool found = false;
   while (!found && arangesSection.next(chunk)) {
     auto version = read<uint16_t>(chunk);
-    enforce(version == 2, "invalid aranges version");
+    FOLLY_SAFE_CHECK(version == 2, "invalid aranges version");
 
     debugInfoOffset = readOffset(chunk, arangesSection.is64Bit());
     auto addressSize = read<uint8_t>(chunk);
-    enforce(addressSize == sizeof(uintptr_t), "invalid address size");
+    FOLLY_SAFE_CHECK(addressSize == sizeof(uintptr_t), "invalid address size");
     auto segmentSize = read<uint8_t>(chunk);
-    enforce(segmentSize == 0, "segmented architecture not supported");
+    FOLLY_SAFE_CHECK(segmentSize == 0, "segmented architecture not supported");
 
     // Padded to a multiple of 2 addresses.
     // Strangely enough, this is the only place in the DWARF spec that requires
@@ -417,21 +417,22 @@ bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const {
   folly::StringPiece sp(info_);
   sp.advance(debugInfoOffset);
   Section debugInfoSection(sp);
-  enforce(debugInfoSection.next(chunk), "invalid debug info");
+  FOLLY_SAFE_CHECK(debugInfoSection.next(chunk), "invalid debug info");
 
   auto version = read<uint16_t>(chunk);
-  enforce(version >= 2 && version <= 4, "invalid info version");
+  FOLLY_SAFE_CHECK(version >= 2 && version <= 4, "invalid info version");
   uint64_t abbrevOffset = readOffset(chunk, debugInfoSection.is64Bit());
   auto addressSize = read<uint8_t>(chunk);
-  enforce(addressSize == sizeof(uintptr_t), "invalid address size");
+  FOLLY_SAFE_CHECK(addressSize == sizeof(uintptr_t), "invalid address size");
 
   // We survived so far.  The first (and only) DIE should be
   // DW_TAG_compile_unit
   // TODO(tudorb): Handle DW_TAG_partial_unit?
   auto code = readULEB(chunk);
-  enforce(code != 0, "invalid code");
+  FOLLY_SAFE_CHECK(code != 0, "invalid code");
   auto abbr = getAbbreviation(code, abbrevOffset);
-  enforce(abbr.tag == DW_TAG_compile_unit, "expecting compile unit entry");
+  FOLLY_SAFE_CHECK(abbr.tag == DW_TAG_compile_unit,
+                   "expecting compile unit entry");
 
   // Read attributes, extracting the few we care about
   bool foundLineOffset = false;
@@ -488,7 +489,7 @@ Dwarf::LineNumberVM::LineNumberVM(folly::StringPiece data,
                                   folly::StringPiece compilationDirectory)
   : compilationDirectory_(compilationDirectory) {
   Section section(data);
-  enforce(section.next(data_), "invalid line number VM");
+  FOLLY_SAFE_CHECK(section.next(data_), "invalid line number VM");
   is64Bit_ = section.is64Bit();
   init();
   reset();
@@ -510,23 +511,24 @@ void Dwarf::LineNumberVM::reset() {
 
 void Dwarf::LineNumberVM::init() {
   version_ = read<uint16_t>(data_);
-  enforce(version_ >= 2 && version_ <= 4, "invalid version in line number VM");
+  FOLLY_SAFE_CHECK(version_ >= 2 && version_ <= 4,
+                   "invalid version in line number VM");
   uint64_t headerLength = readOffset(data_, is64Bit_);
-  enforce(headerLength <= data_.size(),
-          "invalid line number VM header length");
+  FOLLY_SAFE_CHECK(headerLength <= data_.size(),
+                   "invalid line number VM header length");
   folly::StringPiece header(data_.data(), headerLength);
   data_.assign(header.end(), data_.end());
 
   minLength_ = read<uint8_t>(header);
   if (version_ == 4) {  // Version 2 and 3 records don't have this
     uint8_t maxOpsPerInstruction = read<uint8_t>(header);
-    enforce(maxOpsPerInstruction == 1, "VLIW not supported");
+    FOLLY_SAFE_CHECK(maxOpsPerInstruction == 1, "VLIW not supported");
   }
   defaultIsStmt_ = read<uint8_t>(header);
   lineBase_ = read<int8_t>(header);  // yes, signed
   lineRange_ = read<uint8_t>(header);
   opcodeBase_ = read<uint8_t>(header);
-  enforce(opcodeBase_ != 0, "invalid opcode base");
+  FOLLY_SAFE_CHECK(opcodeBase_ != 0, "invalid opcode base");
   standardOpcodeLengths_ = reinterpret_cast<const uint8_t*>(header.data());
   header.advance(opcodeBase_ - 1);
 
@@ -561,7 +563,7 @@ bool Dwarf::LineNumberVM::next(folly::StringPiece& program) {
 
 Dwarf::LineNumberVM::FileName Dwarf::LineNumberVM::getFileName(uint64_t index)
   const {
-  enforce(index != 0, "invalid file index 0");
+  FOLLY_SAFE_CHECK(index != 0, "invalid file index 0");
 
   FileName fn;
   if (index <= fileNameCount_) {
@@ -578,7 +580,7 @@ Dwarf::LineNumberVM::FileName Dwarf::LineNumberVM::getFileName(uint64_t index)
 
   folly::StringPiece program = data_;
   for (; index; --index) {
-    enforce(nextDefineFile(program, fn), "invalid file index");
+    FOLLY_SAFE_CHECK(nextDefineFile(program, fn), "invalid file index");
   }
 
   return fn;
@@ -590,7 +592,8 @@ folly::StringPiece Dwarf::LineNumberVM::getIncludeDirectory(uint64_t index)
     return folly::StringPiece();
   }
 
-  enforce(index <= includeDirectoryCount_, "invalid include directory");
+  FOLLY_SAFE_CHECK(index <= includeDirectoryCount_,
+                   "invalid include directory");
 
   folly::StringPiece includeDirectories = includeDirectories_;
   folly::StringPiece dir;
@@ -638,13 +641,13 @@ bool Dwarf::LineNumberVM::nextDefineFile(folly::StringPiece& program,
     // Extended opcode
     auto length = readULEB(program);
     // the opcode itself should be included in the length, so length >= 1
-    enforce(length != 0, "invalid extended opcode length");
+    FOLLY_SAFE_CHECK(length != 0, "invalid extended opcode length");
     read<uint8_t>(program); // extended opcode
     --length;
 
     if (opcode == DW_LNE_define_file) {
-      enforce(readFileName(program, fn),
-              "invalid empty file in DW_LNE_define_file");
+      FOLLY_SAFE_CHECK(readFileName(program, fn),
+                       "invalid empty file in DW_LNE_define_file");
       return true;
     }
 
@@ -733,7 +736,7 @@ Dwarf::LineNumberVM::StepResult Dwarf::LineNumberVM::step(
   // Extended opcode
   auto length = readULEB(program);
   // the opcode itself should be included in the length, so length >= 1
-  enforce(length != 0, "invalid extende opcode length");
+  FOLLY_SAFE_CHECK(length != 0, "invalid extended opcode length");
   auto extendedOpcode = read<uint8_t>(program);
   --length;
 
index 3b2615012cddad27e56a2b9d752e485abe7901d8..4a14f24cd304e43e791a365e6b85e7fecac103cd 100644 (file)
@@ -41,9 +41,8 @@ namespace symbolizer {
  * actually support many of the version 4 features (such as VLIW, multiple
  * operations per instruction)
  *
- * Note that the DWARF record parser does not allocate heap memory at all
- * during normal operation (it might in the error case, as throwing exceptions
- * uses the heap).  This is on purpose: you can use the parser from
+ * Note that the DWARF record parser does not allocate heap memory at all.
+ * This is on purpose: you can use the parser from
  * memory-constrained situations (such as an exception handler for
  * std::out_of_memory)  If it weren't for this requirement, some things would
  * be much simpler: the Path class would be unnecessary and would be replaced
index cc4f51bd1cae72283f3afda0744ed941ee35ddb0..66f3b94e628f19d07e958a78d636d683b50eeddf 100644 (file)
@@ -62,8 +62,8 @@ const char* ElfFile::iterateStrings(const ElfW(Shdr)& stringTable, Fn fn)
 template <class Fn>
 const ElfW(Sym)* ElfFile::iterateSymbols(const ElfW(Shdr)& section, Fn fn)
   const {
-  enforce(section.sh_entsize == sizeof(ElfW(Sym)),
-          "invalid entry size in symbol table");
+  FOLLY_SAFE_CHECK(section.sh_entsize == sizeof(ElfW(Sym)),
+                   "invalid entry size in symbol table");
 
   const ElfW(Sym)* sym = &at<ElfW(Sym)>(section.sh_offset);
   const ElfW(Sym)* end = sym + (section.sh_size / section.sh_entsize);
index 33e20ddefa11745d28c13a4222c8903ccdef49f2..818b5188fa7091eb380e7644bb04cb4d4230c47e 100644 (file)
@@ -32,7 +32,7 @@
 namespace folly {
 namespace symbolizer {
 
-ElfFile::ElfFile()
+ElfFile::ElfFile() noexcept
   : fd_(-1),
     file_(static_cast<char*>(MAP_FAILED)),
     length_(0),
@@ -40,18 +40,33 @@ ElfFile::ElfFile()
 }
 
 ElfFile::ElfFile(const char* name, bool readOnly)
-  : fd_(open(name, (readOnly) ? O_RDONLY : O_RDWR)),
+  : fd_(-1),
     file_(static_cast<char*>(MAP_FAILED)),
     length_(0),
     baseAddress_(0) {
+  open(name, readOnly);
+}
+
+void ElfFile::open(const char* name, bool readOnly) {
+  const char* msg = "";
+  int r = openNoThrow(name, readOnly, &msg);
+  folly::checkUnixError(r, msg);
+}
+
+int ElfFile::openNoThrow(const char* name, bool readOnly, const char** msg)
+  noexcept {
+  FOLLY_SAFE_CHECK(fd_ == -1, "File already open");
+  fd_ = ::open(name, readOnly ? O_RDONLY : O_RDWR);
   if (fd_ == -1) {
-    folly::throwSystemError("open ", name);
+    if (msg) *msg = "open";
+    return -1;
   }
 
   struct stat st;
   int r = fstat(fd_, &st);
   if (r == -1) {
-    folly::throwSystemError("fstat");
+    if (msg) *msg = "fstat";
+    return -1;
   }
 
   length_ = st.st_size;
@@ -61,9 +76,11 @@ ElfFile::ElfFile(const char* name, bool readOnly)
   }
   file_ = static_cast<char*>(mmap(nullptr, length_, prot, MAP_SHARED, fd_, 0));
   if (file_ == MAP_FAILED) {
-    folly::throwSystemError("mmap");
+    if (msg) *msg = "mmap";
+    return -1;
   }
   init();
+  return 0;
 }
 
 ElfFile::~ElfFile() {
@@ -112,18 +129,18 @@ void ElfFile::init() {
   auto& elfHeader = this->elfHeader();
 
   // Validate ELF magic numbers
-  enforce(elfHeader.e_ident[EI_MAG0] == ELFMAG0 &&
-          elfHeader.e_ident[EI_MAG1] == ELFMAG1 &&
-          elfHeader.e_ident[EI_MAG2] == ELFMAG2 &&
-          elfHeader.e_ident[EI_MAG3] == ELFMAG3,
-          "invalid ELF magic");
+  FOLLY_SAFE_CHECK(elfHeader.e_ident[EI_MAG0] == ELFMAG0 &&
+                   elfHeader.e_ident[EI_MAG1] == ELFMAG1 &&
+                   elfHeader.e_ident[EI_MAG2] == ELFMAG2 &&
+                   elfHeader.e_ident[EI_MAG3] == ELFMAG3,
+                   "invalid ELF magic");
 
   // Validate ELF class (32/64 bits)
 #define EXPECTED_CLASS P1(ELFCLASS, __ELF_NATIVE_CLASS)
 #define P1(a, b) P2(a, b)
 #define P2(a, b) a ## b
-  enforce(elfHeader.e_ident[EI_CLASS] == EXPECTED_CLASS,
-          "invalid ELF class");
+  FOLLY_SAFE_CHECK(elfHeader.e_ident[EI_CLASS] == EXPECTED_CLASS,
+                   "invalid ELF class");
 #undef P1
 #undef P2
 #undef EXPECTED_CLASS
@@ -136,24 +153,24 @@ void ElfFile::init() {
 #else
 # error Unsupported byte order
 #endif
-  enforce(elfHeader.e_ident[EI_DATA] == EXPECTED_ENCODING,
-          "invalid ELF encoding");
+  FOLLY_SAFE_CHECK(elfHeader.e_ident[EI_DATA] == EXPECTED_ENCODING,
+                   "invalid ELF encoding");
 #undef EXPECTED_ENCODING
 
   // Validate ELF version (1)
-  enforce(elfHeader.e_ident[EI_VERSION] == EV_CURRENT &&
-          elfHeader.e_version == EV_CURRENT,
-          "invalid ELF version");
+  FOLLY_SAFE_CHECK(elfHeader.e_ident[EI_VERSION] == EV_CURRENT &&
+                   elfHeader.e_version == EV_CURRENT,
+                   "invalid ELF version");
 
   // We only support executable and shared object files
-  enforce(elfHeader.e_type == ET_EXEC || elfHeader.e_type == ET_DYN,
-          "invalid ELF file type");
+  FOLLY_SAFE_CHECK(elfHeader.e_type == ET_EXEC || elfHeader.e_type == ET_DYN,
+                   "invalid ELF file type");
 
-  enforce(elfHeader.e_phnum != 0, "no program header!");
-  enforce(elfHeader.e_phentsize == sizeof(ElfW(Phdr)),
-          "invalid program header entry size");
-  enforce(elfHeader.e_shentsize == sizeof(ElfW(Shdr)),
-          "invalid section header entry size");
+  FOLLY_SAFE_CHECK(elfHeader.e_phnum != 0, "no program header!");
+  FOLLY_SAFE_CHECK(elfHeader.e_phentsize == sizeof(ElfW(Phdr)),
+                   "invalid program header entry size");
+  FOLLY_SAFE_CHECK(elfHeader.e_shentsize == sizeof(ElfW(Shdr)),
+                   "invalid section header entry size");
 
   const ElfW(Phdr)* programHeader = &at<ElfW(Phdr)>(elfHeader.e_phoff);
   bool foundBase = false;
@@ -167,11 +184,11 @@ void ElfFile::init() {
     }
   }
 
-  enforce(foundBase, "could not find base address");
+  FOLLY_SAFE_CHECK(foundBase, "could not find base address");
 }
 
 const ElfW(Shdr)* ElfFile::getSectionByIndex(size_t idx) const {
-  enforce(idx < elfHeader().e_shnum, "invalid section index");
+  FOLLY_SAFE_CHECK(idx < elfHeader().e_shnum, "invalid section index");
   return &at<ElfW(Shdr)>(elfHeader().e_shoff + idx * sizeof(ElfW(Shdr)));
 }
 
@@ -180,19 +197,21 @@ folly::StringPiece ElfFile::getSectionBody(const ElfW(Shdr)& section) const {
 }
 
 void ElfFile::validateStringTable(const ElfW(Shdr)& stringTable) const {
-  enforce(stringTable.sh_type == SHT_STRTAB, "invalid type for string table");
+  FOLLY_SAFE_CHECK(stringTable.sh_type == SHT_STRTAB,
+                   "invalid type for string table");
 
   const char* start = file_ + stringTable.sh_offset;
   // First and last bytes must be 0
-  enforce(stringTable.sh_size == 0 ||
-          (start[0] == '\0' && start[stringTable.sh_size - 1] == '\0'),
-          "invalid string table");
+  FOLLY_SAFE_CHECK(stringTable.sh_size == 0 ||
+                   (start[0] == '\0' && start[stringTable.sh_size - 1] == '\0'),
+                   "invalid string table");
 }
 
 const char* ElfFile::getString(const ElfW(Shdr)& stringTable, size_t offset)
   const {
   validateStringTable(stringTable);
-  enforce(offset < stringTable.sh_size, "invalid offset in string table");
+  FOLLY_SAFE_CHECK(offset < stringTable.sh_size,
+                   "invalid offset in string table");
 
   return file_ + stringTable.sh_offset + offset;
 }
index 256e359c1efc9bae347189bbd4c4a72580b00246..4ad99df9a065ee768e45cb3b21a743f90a79c2aa 100644 (file)
 #include <stdexcept>
 #include <system_error>
 
+#include "folly/Conv.h"
 #include "folly/Likely.h"
 #include "folly/Range.h"
-#include "folly/Conv.h"
+#include "folly/SafeAssert.h"
 
 namespace folly {
 namespace symbolizer {
 
-template <class... Args>
-inline void enforce(bool v, Args... args) {
-  if (UNLIKELY(!v)) {
-    throw std::runtime_error(folly::to<std::string>(args...));
-  }
-}
-
 /**
  * ELF file parser.
  *
@@ -49,8 +43,20 @@ inline void enforce(bool v, Args... args) {
  */
 class ElfFile {
  public:
-  ElfFile();
+  ElfFile() noexcept;
+
+  // Note: may throw, call openNoThrow() explicitly if you don't want to throw
   explicit ElfFile(const char* name, bool readOnly=true);
+
+  // Open the ELF file.
+  // Returns 0 on success, -1 (and sets errno) on failure and (if msg is not
+  // NULL) sets *msg to a static string indicating what failed.
+  int openNoThrow(const char* name, bool readOnly=true,
+                  const char** msg=nullptr) noexcept;
+
+  // Open the ELF file. Throws on error.
+  void open(const char* name, bool readOnly=true);
+
   ~ElfFile();
 
   ElfFile(ElfFile&& other);
@@ -124,6 +130,8 @@ class ElfFile {
    * Find symbol definition by address.
    * Note that this is the file virtual address, so you need to undo
    * any relocation that might have happened.
+   *
+   * Returns {nullptr, nullptr} if not found.
    */
   typedef std::pair<const ElfW(Shdr)*, const ElfW(Sym)*> Symbol;
   Symbol getDefinitionByAddress(uintptr_t address) const;
@@ -133,6 +141,8 @@ class ElfFile {
    *
    * If a symbol with this name cannot be found, a <nullptr, nullptr> Symbol
    * will be returned. This is O(N) in the number of symbols in the file.
+   *
+   * Returns {nullptr, nullptr} if not found.
    */
   Symbol getSymbolByName(const char* name) const;
 
@@ -142,7 +152,7 @@ class ElfFile {
   template <class T>
   const T& getSymbolValue(const ElfW(Sym)* symbol) const {
     const ElfW(Shdr)* section = getSectionByIndex(symbol->st_shndx);
-    enforce(section, "Symbol's section index is invalid");
+    FOLLY_SAFE_CHECK(section, "Symbol's section index is invalid");
 
     return valueAt<T>(*section, symbol->st_value);
   }
@@ -161,7 +171,7 @@ class ElfFile {
   template <class T>
   const T& getAddressValue(const ElfW(Addr) addr) const {
     const ElfW(Shdr)* section = getSectionContainingAddress(addr);
-    enforce(section, "Address does not refer to existing section");
+    FOLLY_SAFE_CHECK(section, "Address does not refer to existing section");
 
     return valueAt<T>(*section, addr);
   }
@@ -185,8 +195,8 @@ class ElfFile {
   template <class T>
   const typename std::enable_if<std::is_pod<T>::value, T>::type&
   at(ElfW(Off) offset) const {
-    enforce(offset + sizeof(T) <= length_,
-            "Offset is not contained within our mmapped file");
+    FOLLY_SAFE_CHECK(offset + sizeof(T) <= length_,
+                     "Offset is not contained within our mmapped file");
 
     return *reinterpret_cast<T*>(file_ + offset);
   }
@@ -202,11 +212,13 @@ class ElfFile {
     // TODO: For other file types, st_value holds a file offset directly. Since
     //       I don't have a use-case for that right now, just assert that
     //       nobody wants this. We can always add it later.
-    enforce(elfHeader().e_type == ET_EXEC || elfHeader().e_type == ET_DYN,
-            "Only exectuables and shared objects are supported");
-    enforce(addr >= section.sh_addr &&
-            (addr + sizeof(T)) <= (section.sh_addr + section.sh_size),
-            "Address is not contained within the provided segment");
+    FOLLY_SAFE_CHECK(
+        elfHeader().e_type == ET_EXEC || elfHeader().e_type == ET_DYN,
+        "Only exectuables and shared objects are supported");
+    FOLLY_SAFE_CHECK(
+        addr >= section.sh_addr &&
+        (addr + sizeof(T)) <= (section.sh_addr + section.sh_size),
+        "Address is not contained within the provided segment");
 
     return at<T>(section.sh_offset + (addr - section.sh_addr));
   }
diff --git a/folly/experimental/symbolizer/LineReader.cpp b/folly/experimental/symbolizer/LineReader.cpp
new file mode 100644 (file)
index 0000000..ee30339
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "folly/experimental/symbolizer/LineReader.h"
+
+#include <cstring>
+
+#include "folly/FileUtil.h"
+
+namespace folly { namespace symbolizer {
+
+LineReader::LineReader(int fd, char* buf, size_t bufSize)
+  : fd_(fd),
+    buf_(buf),
+    bufEnd_(buf_ + bufSize),
+    bol_(buf),
+    eol_(buf),
+    end_(buf),
+    state_(kReading) {
+}
+
+LineReader::State LineReader::readLine(StringPiece& line) {
+  bol_ = eol_;  // Start past what we already returned
+  for (;;) {
+    // Search for newline
+    char* newline = static_cast<char*>(memchr(eol_, '\n', end_ - eol_));
+    if (newline) {
+      eol_ = newline + 1;
+      break;
+    } else if (state_ != kReading || (bol_ == buf_ && end_ == bufEnd_)) {
+      // If the buffer is full with one line (line too long), or we're
+      // at the end of the file, return what we have.
+      eol_ = end_;
+      break;
+    }
+
+    // We don't have a full line in the buffer, but we have room to read.
+    // Move to the beginning of the buffer.
+    memmove(buf_, eol_, end_ - eol_);
+    end_ -= (eol_ - buf_);
+    bol_ = buf_;
+    eol_ = end_;
+
+    // Refill
+    ssize_t available = bufEnd_ - end_;
+    ssize_t n = readFull(fd_, end_, available);
+    if (n < 0) {
+      state_ = kError;
+      n = 0;
+    } else if (n < available) {
+      state_ = kEof;
+    }
+    end_ += n;
+  }
+
+  line.assign(bol_, eol_);
+  return eol_ != bol_ ? kReading : state_;
+}
+
+}}  // namespaces
+
diff --git a/folly/experimental/symbolizer/LineReader.h b/folly/experimental/symbolizer/LineReader.h
new file mode 100644 (file)
index 0000000..2527f63
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_SYMBOLIZER_LINEREADER_H_
+#define FOLLY_SYMBOLIZER_LINEREADER_H_
+
+#include <cstddef>
+
+#include <boost/noncopyable.hpp>
+
+#include "folly/Range.h"
+
+namespace folly { namespace symbolizer {
+
+/**
+ * Async-signal-safe line reader.
+ */
+class LineReader : private boost::noncopyable {
+ public:
+  /**
+   * Create a line reader that reads into a user-provided buffer (of size
+   * bufSize).
+   */
+  LineReader(int fd, char* buf, size_t bufSize);
+
+  enum State {
+    kReading,
+    kEof,
+    kError
+  };
+  /**
+   * Read the next line from the file.
+   *
+   * If the line is at most bufSize characters long, including the trailing
+   * newline, it will be returned (including the trailing newline).
+   *
+   * If the line is longer than bufSize, we return the first bufSize bytes
+   * (which won't include a trailing newline) and then continue from that
+   * point onwards.
+   *
+   * The lines returned are not null-terminated.
+   *
+   * Returns kReading with a valid line, kEof if at end of file, or kError
+   * if a read error was encountered.
+   *
+   * Example:
+   *   bufSize = 10
+   *   input has "hello world\n"
+   *   The first call returns "hello worl"
+   *   The second call returns "d\n"
+   */
+  State readLine(StringPiece& line);
+
+ private:
+  int const fd_;
+  char* const buf_;
+  char* const bufEnd_;
+
+  // buf_ <= bol_ <= eol_ <= end_ <= bufEnd_
+  //
+  // [buf_, end_): current buffer contents (read from file)
+  //
+  // [buf_, bol_): free (already processed, can be discarded)
+  // [bol_, eol_): current line, including \n if it exists, eol_ points
+  //               1 character past the \n
+  // [eol_, end_): read, unprocessed
+  // [end_, bufEnd_): free
+
+  char* bol_;
+  char* eol_;
+  char* end_;
+  State state_;
+};
+
+}}  // namespaces
+
+#endif /* FOLLY_SYMBOLIZER_LINEREADER_H_ */
+
diff --git a/folly/experimental/symbolizer/SignalHandler.cpp b/folly/experimental/symbolizer/SignalHandler.cpp
new file mode 100644 (file)
index 0000000..f66d013
--- /dev/null
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This is heavily inspired by the signal handler from google-glog
+
+#include "folly/experimental/symbolizer/SignalHandler.h"
+
+#include <sys/types.h>
+#include <atomic>
+#include <ctime>
+#include <mutex>
+#include <pthread.h>
+#include <signal.h>
+#include <unistd.h>
+#include <vector>
+
+#include <glog/logging.h>
+
+#include "folly/Conv.h"
+#include "folly/FileUtil.h"
+#include "folly/Portability.h"
+#include "folly/ScopeGuard.h"
+#include "folly/experimental/symbolizer/Symbolizer.h"
+
+namespace folly { namespace symbolizer {
+
+namespace {
+
+/**
+ * Fatal signal handler registry.
+ */
+class FatalSignalCallbackRegistry {
+ public:
+  typedef std::function<void()> Func;
+
+  FatalSignalCallbackRegistry();
+
+  void add(Func func);
+  void markInstalled();
+  void run();
+
+ private:
+  std::atomic<bool> installed_;
+  std::mutex mutex_;
+  std::vector<Func> handlers_;
+};
+
+FatalSignalCallbackRegistry::FatalSignalCallbackRegistry()
+  : installed_(false) {
+}
+
+void FatalSignalCallbackRegistry::add(Func func) {
+  std::lock_guard<std::mutex> lock(mutex_);
+  CHECK(!installed_)
+    << "FatalSignalCallbackRegistry::add may not be used "
+       "after installing the signal handlers.";
+  handlers_.push_back(std::move(func));
+}
+
+void FatalSignalCallbackRegistry::markInstalled() {
+  std::lock_guard<std::mutex> lock(mutex_);
+  CHECK(!installed_.exchange(true))
+    << "FatalSignalCallbackRegistry::markInstalled must be called "
+    << "at most once";
+}
+
+void FatalSignalCallbackRegistry::run() {
+  if (!installed_) {
+    return;  // Shouldn't happen
+  }
+
+  for (auto& fn : handlers_) {
+    fn();
+  }
+}
+
+// Leak it so we don't have to worry about destruction order
+FatalSignalCallbackRegistry* gFatalSignalCallbackRegistry =
+  new FatalSignalCallbackRegistry;
+
+struct {
+  int number;
+  const char* name;
+  struct sigaction oldAction;
+} kFatalSignals[] = {
+  { SIGSEGV, "SIGSEGV" },
+  { SIGILL,  "SIGILL"  },
+  { SIGFPE,  "SIGFPE"  },
+  { SIGABRT, "SIGABRT" },
+  { SIGBUS,  "SIGBUS"  },
+  { SIGTERM, "SIGTERM" },
+  { 0,       nullptr   }
+};
+
+void callPreviousSignalHandler(int signum) {
+  // Restore disposition to old disposition, then kill ourselves with the same
+  // signal. The signal will be blocked until we return from our handler,
+  // then it will invoke the default handler and abort.
+  for (auto p = kFatalSignals; p->name; ++p) {
+    if (p->number == signum) {
+      sigaction(signum, &p->oldAction, nullptr);
+      return;
+    }
+  }
+
+  // Not one of the signals we know about. Oh well. Reset to default.
+  struct sigaction sa;
+  memset(&sa, 0, sizeof(sa));
+  sa.sa_handler = SIG_DFL;
+  sigaction(signum, &sa, nullptr);
+  raise(signum);
+}
+
+void printDec(uint64_t val) {
+  char buf[20];
+  uint32_t n = uint64ToBufferUnsafe(val, buf);
+  writeFull(STDERR_FILENO, buf, n);
+}
+
+const char kHexChars[] = "0123456789abcdef";
+void printHex(uint64_t val) {
+  // TODO(tudorb): Add this to folly/Conv.h
+  char buf[2 + 2 * sizeof(uint64_t)];  // "0x" prefix, 2 digits for each byte
+
+  char* end = buf + sizeof(buf);
+  char* p = end;
+  do {
+    *--p = kHexChars[val & 0x0f];
+    val >>= 4;
+  } while (val != 0);
+  *--p = 'x';
+  *--p = '0';
+
+  writeFull(STDERR_FILENO, p, end - p);
+}
+
+void print(StringPiece sp) {
+  writeFull(STDERR_FILENO, sp.data(), sp.size());
+}
+
+void dumpTimeInfo() {
+  SCOPE_EXIT { fsyncNoInt(STDERR_FILENO); };
+  time_t now = time(nullptr);
+  print("*** Aborted at ");
+  printDec(now);
+  print(" (Unix time, try 'date -d @");
+  printDec(now);
+  print("') ***\n");
+}
+
+void dumpSignalInfo(int signum, siginfo_t* siginfo) {
+  SCOPE_EXIT { fsyncNoInt(STDERR_FILENO); };
+  // Get the signal name, if possible.
+  const char* name = nullptr;
+  for (auto p = kFatalSignals; p->name; ++p) {
+    if (p->number == signum) {
+      name = p->name;
+      break;
+    }
+  }
+
+  print("*** Signal ");
+  printDec(signum);
+  if (name) {
+    print(" (");
+    print(name);
+    print(")");
+  }
+
+  print(" (");
+  printHex(reinterpret_cast<uint64_t>(siginfo->si_addr));
+  print(") received by PID ");
+  printDec(getpid());
+  print(" (TID ");
+  printHex((uint64_t)pthread_self());
+  print("), stack trace: ***\n");
+}
+
+void dumpStackTrace() {
+  SCOPE_EXIT { fsyncNoInt(STDERR_FILENO); };
+  // Get and symbolize stack trace
+  constexpr size_t kMaxStackTraceDepth = 100;
+  AddressInfo addresses[kMaxStackTraceDepth];
+
+  // Skip the getStackTrace frame
+  ssize_t stackTraceDepth = getStackTrace(addresses, kMaxStackTraceDepth, 1);
+  if (stackTraceDepth < 0) {
+    print("(error retrieving stack trace)\n");
+  } else {
+    Symbolizer symbolizer;
+    symbolizer.symbolize(addresses, stackTraceDepth);
+
+    FDSymbolizePrinter printer(STDERR_FILENO);
+    printer.print(addresses, stackTraceDepth);
+  }
+}
+
+std::atomic<pthread_t*> gSignalThread;
+
+// Here be dragons.
+void innerSignalHandler(int signum, siginfo_t* info, void* uctx) {
+  // First, let's only let one thread in here at a time.
+  pthread_t myId = pthread_self();
+
+  pthread_t* prevSignalThread = nullptr;
+  while (!gSignalThread.compare_exchange_strong(prevSignalThread, &myId)) {
+    if (pthread_equal(*prevSignalThread, myId)) {
+      print("Entered fatal signal handler recursively. We're in trouble.\n");
+      return;
+    }
+
+    // Wait a while, try again.
+    timespec ts;
+    ts.tv_sec = 0;
+    ts.tv_nsec = 100L * 1000 * 1000;  // 100ms
+    nanosleep(&ts, nullptr);
+
+    prevSignalThread = nullptr;
+  }
+
+  dumpTimeInfo();
+  dumpSignalInfo(signum, info);
+  dumpStackTrace();
+
+  // Run user callbacks
+  gFatalSignalCallbackRegistry->run();
+}
+
+void signalHandler(int signum, siginfo_t* info, void* uctx) {
+  SCOPE_EXIT { fsyncNoInt(STDERR_FILENO); };
+  try {
+    innerSignalHandler(signum, info, uctx);
+  } catch (...) {
+    // Ignore any exceptions. What? Exceptions?
+    print("Exception in innerSignalHandler!\n");
+  }
+
+  gSignalThread = nullptr;
+  // Kill ourselves with the previous handler.
+  callPreviousSignalHandler(signum);
+}
+
+}  // namespace
+
+void addFatalSignalCallback(std::function<void()> handler) {
+  gFatalSignalCallbackRegistry->add(std::move(handler));
+}
+
+namespace {
+
+std::atomic<bool> gAlreadyInstalled;
+
+}  // namespace
+
+void installFatalSignalHandler() {
+  if (gAlreadyInstalled.exchange(true)) {
+    // Already done.
+    return;
+  }
+
+  gFatalSignalCallbackRegistry->markInstalled();
+
+  struct sigaction sa;
+  memset(&sa, 0, sizeof(sa));
+  sigemptyset(&sa.sa_mask);
+  sa.sa_flags |= SA_SIGINFO;
+  sa.sa_sigaction = &signalHandler;
+
+  for (auto p = kFatalSignals; p->name; ++p) {
+    CHECK_ERR(sigaction(p->number, &sa, &p->oldAction));
+  }
+}
+
+}}  // namespaces
+
diff --git a/folly/experimental/symbolizer/SignalHandler.h b/folly/experimental/symbolizer/SignalHandler.h
new file mode 100644 (file)
index 0000000..2c13b19
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_SYMBOLIZER_SIGNALHANDLER_H_
+#define FOLLY_SYMBOLIZER_SIGNALHANDLER_H_
+
+#include <functional>
+
+namespace folly { namespace symbolizer {
+
+/**
+ * Install handler for fatal signals. The list of signals being handled is in
+ * SignalHandler.cpp.
+ *
+ * The handler will dump signal and time information followed by a stack trace
+ * to stderr, and then call the callbacks registered below.
+ */
+void installFatalSignalHandler();
+
+
+/**
+ * Add a callback to be run when receiving a fatal signal. They will also
+ * be called by LOG(FATAL) and abort() (as those raise SIGABRT internally).
+ *
+ * These callbacks must be async-signal-safe, so don't even think of using
+ * LOG(...) or printf or malloc / new or doing anything even remotely fun.
+ *
+ * All these fatal callback must be added before calling
+ * installFatalSignalHandler().
+ */
+void addFatalSignalCallback(std::function<void()> callback);
+
+
+}}  // namespaces
+
+#endif /* FOLLY_SYMBOLIZER_SIGNALHANDLER_H_ */
+
index 57bea3bf6746a3748475be332589dcbde3bbd9d4..79345c8b6eee49670e697560f53f62d019f2943a 100644 (file)
  * limitations under the License.
  */
 
+// Must be first to ensure that UNW_LOCAL_ONLY is defined
+#define UNW_LOCAL_ONLY 1
+#include <libunwind.h>
 
 #include "folly/experimental/symbolizer/Symbolizer.h"
 
-#include <boost/regex.hpp>
-#include <glog/logging.h>
+#include <limits.h>
+
+#include "folly/Conv.h"
+#include "folly/FileUtil.h"
+#include "folly/String.h"
 
 #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"
+#include "folly/experimental/symbolizer/LineReader.h"
 
 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');
+/**
+ * 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, 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
-      }
+/**
+ * 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) {
-        foundFile.begin = begin;
-        foundFile.end = end;
-        foundFile.name.assign(match[4].first, match[4].second);
-        return false;
-      }
+  // Remove trailing newline, if any
+  if (line.back() == '\n') {
+    line.pop_back();
+  }
 
-      return true;
-    };
+  // from
+  from = readHex(line);
+  if (line.empty() || line.front() != '-') {
+    return false;
+  }
+  line.pop_front();
 
-  if (error) {
+  // to
+  to = 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();
+  // perms
+  skipNS(line);
+  if (line.empty() || line.front() != ' ') {
+    return false;
+  }
+  line.pop_front();
 
-  auto sym = elfFile.getDefinitionByAddress(origAddress);
-  if (!sym.first) {
+  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
+  }
 
-  auto name = elfFile.getSymbolName(sym);
-  if (name) {
-    symbolName = name;
+  // dev
+  skipNS(line);
+  if (line.empty() || line.front() != ' ') {
+    return false;
   }
+  line.pop_front();
 
-  Dwarf(&elfFile).findAddress(origAddress, location);
+  // inode
+  skipNS(line);
+  if (line.empty() || line.front() != ' ') {
+    return false;
+  }
+
+  skipWS(line);
+  if (line.empty()) {
+    fileName.clear();
+    return true;
+  }
+
+  fileName = line;
   return true;
 }
 
-ElfFile& Symbolizer::getFile(const std::string& name) {
-  auto pos = elfFiles_.find(name);
-  if (pos != elfFiles_.end()) {
-    return pos->second;
+}  // namespace
+
+ssize_t getStackTrace(AddressInfo* addresses,
+                      size_t maxAddresses,
+                      size_t skip) {
+  unw_context_t uctx;
+  int r = unw_getcontext(&uctx);
+  if (r < 0) {
+    return -1;
   }
 
-  return elfFiles_.insert(
-      std::make_pair(name, ElfFile(name.c_str()))).first->second;
+  unw_cursor_t cursor;
+  size_t idx = 0;
+  bool first = true;
+  while (idx < maxAddresses) {
+    if (first) {
+      first = false;
+      r = unw_init_local(&cursor, &uctx);
+    } else {
+      r = unw_step(&cursor);
+      if (r == 0) {
+        break;
+      }
+    }
+    if (r < 0) {
+      return -1;
+    }
+
+    if (skip != 0) {
+      --skip;
+      continue;
+    }
+    unw_word_t ip;
+    int rr = unw_get_reg(&cursor, UNW_REG_IP, &ip);
+    if (rr < 0) {
+      return -1;
+    }
+
+    // If error, assume not a signal frame
+    rr = unw_is_signal_frame(&cursor);
+
+    addresses[idx++] = AddressInfo(ip, (rr > 0));
+  }
+
+  if (r < 0) {
+    return -1;
+  }
+
+  return idx;
 }
 
-void Symbolizer::write(std::ostream& out, uintptr_t address,
-                       StringPiece symbolName,
-                       const Dwarf::LocationInfo& location) {
-  char buf[20];
-  sprintf(buf, "%#18jx", address);
-  out << "    @ " << buf;
+void Symbolizer::symbolize(AddressInfo* addresses, size_t addressCount) {
+  size_t remaining = 0;
+  for (size_t i = 0; i < addressCount; ++i) {
+    auto& ainfo = addresses[i];
+    if (!ainfo.found) {
+      ++remaining;
+      ainfo.name.clear();
+      ainfo.location = Dwarf::LocationInfo();
+    }
+  }
+
+  if (remaining == 0) {  // we're done
+    return;
+  }
+
+  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));
 
-  if (!symbolName.empty()) {
-    out << " " << demangle(symbolName.toString().c_str());
+  char fileNameBuf[PATH_MAX];
 
-    std::string file;
-    if (location.hasFileAndLine) {
-      file = location.file.toString();
-      out << "   " << file << ":" << location.line;
+  while (remaining != 0) {
+    StringPiece line;
+    if (reader.readLine(line) != LineReader::kReading) {
+      break;
     }
 
-    std::string mainFile;
-    if (location.hasMainFile) {
-      mainFile = location.mainFile.toString();
-      if (!location.hasFileAndLine || file != mainFile) {
-        out << "\n                         (compiling "
-            << location.mainFile << ")";
+    // Parse line
+    uintptr_t from;
+    uintptr_t to;
+    StringPiece fileName;
+    if (!parseProcMapsLine(line, from, to, fileName)) {
+      continue;
+    }
+
+    bool first = true;
+    ElfFile* elfFile = nullptr;
+
+    // See if any addresses are here
+    for (size_t i = 0; i < addressCount; ++i) {
+      auto& ainfo = addresses[i];
+      if (ainfo.found) {
+        continue;
+      }
+
+      uintptr_t address = ainfo.address;
+
+      // If the next address (closer to the top of the stack) was a signal
+      // frame, then this is the *resume* address, which is the address
+      // after the location where the signal was caught. This might be in
+      // the next function, so subtract 1 before symbolizing.
+      if (i != 0 && addresses[i-1].isSignalFrame) {
+        --address;
+      }
+
+      if (from > address || address >= to) {
+        continue;
+      }
+
+      // Found
+      ainfo.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;
+          }
+        }
+      }
+
+      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) {
+        ainfo.name = name;
+      }
+
+      Dwarf(elfFile).findAddress(fileAddress, ainfo.location);
     }
+  }
+
+  closeNoInt(fd);
+}
+
+namespace {
+const char kHexChars[] = "0123456789abcdef";
+}  // namespace
+
+void SymbolizePrinter::print(const AddressInfo& ainfo) {
+  uintptr_t address = ainfo.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* p = end;
+  *p-- = '\0';
+  while (address != 0) {
+    *p-- = kHexChars[address & 0xf];
+    address >>= 4;
+  }
+  doPrint(folly::StringPiece(buf, end));
+
+  char mangledBuf[1024];
+  if (!ainfo.found) {
+    doPrint(" (not found)\n");
+    return;
+  }
+
+  if (ainfo.name.empty()) {
+    doPrint(" (unknown)\n");
+  } else if (ainfo.name.size() >= sizeof(mangledBuf)) {
+    doPrint(" ");
+    doPrint(ainfo.name);
+    doPrint("\n");
   } else {
-    out << " (unknown)";
+    memcpy(mangledBuf, ainfo.name.data(), ainfo.name.size());
+    mangledBuf[ainfo.name.size()] = '\0';
+
+    char demangledBuf[1024];
+    demangle(mangledBuf, demangledBuf, sizeof(demangledBuf));
+    doPrint(" ");
+    doPrint(demangledBuf);
+    doPrint("\n");
+  }
+
+  char fileBuf[PATH_MAX];
+  fileBuf[0] = '\0';
+  if (ainfo.location.hasFileAndLine) {
+    ainfo.location.file.toBuffer(fileBuf, sizeof(fileBuf));
+    doPrint(pad);
+    doPrint(fileBuf);
+
+    char buf[22];
+    uint32_t n = uint64ToBufferUnsafe(ainfo.location.line, buf);
+    doPrint(":");
+    doPrint(StringPiece(buf, n));
+    doPrint("\n");
   }
-  out << "\n";
+
+  if (ainfo.location.hasMainFile) {
+    char mainFileBuf[PATH_MAX];
+    mainFileBuf[0] = '\0';
+    ainfo.location.mainFile.toBuffer(mainFileBuf, sizeof(mainFileBuf));
+    if (!ainfo.location.hasFileAndLine || strcmp(fileBuf, mainFileBuf)) {
+      doPrint(pad);
+      doPrint("-> ");
+      doPrint(mainFileBuf);
+      doPrint("\n");
+    }
+  }
+}
+
+void SymbolizePrinter::print(const AddressInfo* addresses,
+                             size_t addressCount) {
+  for (size_t i = 0; i < addressCount; ++i) {
+    auto& ainfo = addresses[i];
+    print(ainfo);
+  }
+}
+
+void OStreamSymbolizePrinter::doPrint(StringPiece sp) {
+  out_ << sp;
+}
+
+void FDSymbolizePrinter::doPrint(StringPiece sp) {
+  writeFull(fd_, sp.data(), sp.size());
+}
+
+std::ostream& operator<<(std::ostream& out, const AddressInfo& ainfo) {
+  OStreamSymbolizePrinter osp(out);
+  osp.print(ainfo);
+  return out;
 }
 
 }  // namespace symbolizer
index 243626227a736e081477cf28c9944b30d63c9d70..307ca252dcd54fd6bf02bd7074a6ef99a5f61597 100644 (file)
@@ -30,30 +30,102 @@ namespace folly {
 namespace symbolizer {
 
 /**
- * Convert an address to symbol name and source location.
+ * Address information: symbol name and location.
+ *
+ * Note that both name and location are references in the Symbolizer object,
+ * which must outlive this AddressInfo object.
+ */
+struct AddressInfo {
+  /* implicit */ AddressInfo(uintptr_t a=0, bool sf=false)
+    : address(a),
+      isSignalFrame(sf),
+      found(false) { }
+  uintptr_t address;
+  bool isSignalFrame;
+  bool found;
+  StringPiece name;
+  Dwarf::LocationInfo location;
+};
+
+/**
+ * Get the current stack trace into addresses, which has room for at least
+ * maxAddresses entries. Skip the first (topmost) skip entries.
+ * Returns the number of entries in addresses on success, -1 on failure.
  */
+ssize_t getStackTrace(AddressInfo* addresses,
+                      size_t maxAddresses,
+                      size_t skip=0);
+
 class Symbolizer {
  public:
+  Symbolizer() : fileCount_(0) { }
+
+  /**
+   * Symbolize given addresses.
+   */
+  void symbolize(AddressInfo* addresses, size_t addressCount);
+
   /**
-   * Symbolize an instruction pointer address, returning the symbol name
-   * and file/line number information.
-   *
-   * The returned StringPiece objects are valid for the lifetime of
-   * this Symbolizer object.
+   * Shortcut to symbolize one address.
    */
-  bool symbolize(uintptr_t address, folly::StringPiece& symbolName,
-                 Dwarf::LocationInfo& location);
+  bool symbolize(AddressInfo& address) {
+    symbolize(&address, 1);
+    return address.found;
+  }
+
+ private:
+  // We can't allocate memory, so we'll preallocate room.
+  // "1023 shared libraries should be enough for everyone"
+  static constexpr size_t kMaxFiles = 1024;
+  size_t fileCount_;
+  ElfFile files_[kMaxFiles];
+};
 
-  static void write(std::ostream& out, uintptr_t address,
-                    folly::StringPiece symbolName,
-                    const Dwarf::LocationInfo& location);
+/**
+ * Print a list of symbolized addresses. Base class.
+ */
+class SymbolizePrinter {
+ public:
+  void print(const AddressInfo& ainfo);
+  void print(const AddressInfo* addresses, size_t addressCount);
 
+  virtual ~SymbolizePrinter() { }
  private:
-  ElfFile& getFile(const std::string& name);
-  // cache open ELF files
-  std::unordered_map<std::string, ElfFile> elfFiles_;
+  virtual void doPrint(StringPiece sp) = 0;
 };
 
+/**
+ * Print a list of symbolized addresses to a stream.
+ * Not reentrant. Do not use from signal handling code.
+ */
+class OStreamSymbolizePrinter : public SymbolizePrinter {
+ public:
+  explicit OStreamSymbolizePrinter(std::ostream& out) : out_(out) { }
+ private:
+  void doPrint(StringPiece sp) override;
+  std::ostream& out_;
+};
+
+/**
+ * Print a list of symbolized addresses to a file descriptor.
+ * Ignores errors. Async-signal-safe.
+ */
+class FDSymbolizePrinter : public SymbolizePrinter {
+ public:
+  explicit FDSymbolizePrinter(int fd) : fd_(fd) { }
+ private:
+  void doPrint(StringPiece sp) override;
+  int fd_;
+};
+
+/**
+ * Print an AddressInfo to a stream. Note that the Symbolizer that
+ * symbolized the address must outlive the AddressInfo. Just like
+ * OStreamSymbolizePrinter (which it uses internally), this is not
+ * reentrant; do not use from signal handling code.
+ */
+std::ostream& operator<<(std::ostream& out, const AddressInfo& ainfo);
+
 }  // namespace symbolizer
 }  // namespace folly
 
diff --git a/folly/experimental/symbolizer/test/LineReaderTest.cpp b/folly/experimental/symbolizer/test/LineReaderTest.cpp
new file mode 100644 (file)
index 0000000..01a3dd3
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "folly/experimental/symbolizer/LineReader.h"
+
+#include <glog/logging.h>
+#include <gtest/gtest.h>
+
+#include "folly/FileUtil.h"
+#include "folly/experimental/TestUtil.h"
+
+namespace folly { namespace symbolizer { namespace test {
+
+using folly::test::TemporaryFile;
+
+void writeAll(int fd, const char* str) {
+  ssize_t n = strlen(str);
+  CHECK_EQ(n, writeFull(fd, str, n));
+}
+
+void expect(LineReader& lr, const char* expected) {
+  StringPiece line;
+  size_t expectedLen = strlen(expected);
+  EXPECT_EQ(expectedLen != 0 ? LineReader::kReading : LineReader::kEof,
+            lr.readLine(line));
+  EXPECT_EQ(expectedLen, line.size());
+  EXPECT_EQ(std::string(expected, expectedLen), line.str());
+}
+
+TEST(LineReader, Simple) {
+  TemporaryFile file;
+  int fd = file.fd();
+  writeAll(fd,
+           "Meow\n"
+           "Hello world\n"
+           "This is a long line. It is longer than the other lines.\n"
+           "\n"
+           "Incomplete last line");
+
+  {
+    CHECK_ERR(lseek(fd, 0, SEEK_SET));
+    char buf[10];
+    LineReader lr(fd, buf, sizeof(buf));
+    expect(lr, "Meow\n");
+    expect(lr, "Hello worl");
+    expect(lr, "d\n");
+    expect(lr, "This is a ");
+    expect(lr, "long line.");
+    expect(lr, " It is lon");
+    expect(lr, "ger than t");
+    expect(lr, "he other l");
+    expect(lr, "ines.\n");
+    expect(lr, "\n");
+    expect(lr, "Incomplete");
+    expect(lr, " last line");
+    expect(lr, "");
+  }
+
+  {
+    CHECK_ERR(lseek(fd, 0, SEEK_SET));
+    char buf[80];
+    LineReader lr(fd, buf, sizeof(buf));
+    expect(lr, "Meow\n");
+    expect(lr, "Hello world\n");
+    expect(lr, "This is a long line. It is longer than the other lines.\n");
+    expect(lr, "\n");
+    expect(lr, "Incomplete last line");
+    expect(lr, "");
+  }
+}
+
+}}}  // namespaces
+
diff --git a/folly/experimental/symbolizer/test/SignalHandlerTest.cpp b/folly/experimental/symbolizer/test/SignalHandlerTest.cpp
new file mode 100644 (file)
index 0000000..89f8159
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "folly/experimental/symbolizer/test/SignalHandlerTest.h"
+#include "folly/experimental/symbolizer/SignalHandler.h"
+
+#include <gtest/gtest.h>
+
+#include "folly/FileUtil.h"
+#include "folly/Range.h"
+
+namespace folly { namespace symbolizer { namespace test {
+
+namespace {
+
+void print(StringPiece sp) {
+  writeFull(STDERR_FILENO, sp.data(), sp.size());
+}
+
+
+void callback1() {
+  print("Callback1\n");
+}
+
+void callback2() {
+  print("Callback2\n");
+}
+
+}  // namespace
+
+TEST(SignalHandler, Simple) {
+  addFatalSignalCallback(callback1);
+  addFatalSignalCallback(callback2);
+  installFatalSignalHandler();
+
+  EXPECT_DEATH(
+      failHard(),
+      "^\\*\\*\\* Aborted at [0-9]+ \\(Unix time, try 'date -d @[0-9]+'\\) "
+      "\\*\\*\\*\n"
+      "\\*\\*\\* Signal 11 \\(SIGSEGV\\) \\(0x2a\\) received by PID [0-9]+ "
+      "\\(TID 0x[0-9a-f]+\\), stack trace: \\*\\*\\*\n"
+      ".*\n"
+      "    @ [0-9a-f]+ folly::symbolizer::test::SignalHandler_Simple_Test"
+      "::TestBody\\(\\)\n"
+      ".*\n"
+      "    @ [0-9a-f]+ main\n"
+      ".*\n"
+      "Callback1\n"
+      "Callback2\n"
+      );
+}
+
+
+}}}  // namespaces
diff --git a/folly/experimental/symbolizer/test/SignalHandlerTest.h b/folly/experimental/symbolizer/test/SignalHandlerTest.h
new file mode 100644 (file)
index 0000000..3eefbd0
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FOLLY_SYMBOLIZER_TEST_SIGNALHANDLERTEST_H_
+#define FOLLY_SYMBOLIZER_TEST_SIGNALHANDLERTEST_H_
+
+namespace folly { namespace symbolizer { namespace test {
+
+inline void failHard() {
+  *(volatile char*)42;  // SIGSEGV
+}
+
+}}}  // namespaces
+
+#endif /* FOLLY_SYMBOLIZER_TEST_SIGNALHANDLERTEST_H_ */
+
diff --git a/folly/experimental/symbolizer/test/SymbolizerTest.cpp b/folly/experimental/symbolizer/test/SymbolizerTest.cpp
new file mode 100644 (file)
index 0000000..0ff9cfd
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "folly/experimental/symbolizer/Symbolizer.h"
+
+#include <gtest/gtest.h>
+
+#include "folly/Range.h"
+#include "folly/String.h"
+
+namespace folly { namespace symbolizer { namespace test {
+
+void foo() {
+}
+
+TEST(Symbolizer, Single) {
+  AddressInfo a(reinterpret_cast<uintptr_t>(foo));
+  Symbolizer symbolizer;
+  ASSERT_TRUE(symbolizer.symbolize(a));
+  EXPECT_EQ("folly::symbolizer::test::foo()", demangle(a.name.str().c_str()));
+
+  auto path = a.location.file.toString();
+  folly::StringPiece basename(path);
+  auto pos = basename.rfind('/');
+  if (pos != folly::StringPiece::npos) {
+    basename.advance(pos + 1);
+  }
+  EXPECT_EQ("SymbolizerTest.cpp", basename.str());
+}
+
+}}}  // namespaces