Move exception tracer library to folly/experimental
authorTudor Bosman <tudorb@fb.com>
Thu, 30 Aug 2012 01:20:27 +0000 (18:20 -0700)
committerJordan DeLong <jdelong@fb.com>
Mon, 17 Sep 2012 01:27:32 +0000 (18:27 -0700)
Summary:
This change is mostly mechanical (moving files, changing include paths,
etc).  I made some changes to TARGETS to make it easier for the library
to be linked in (instead of LD_PRELOADed)

Test Plan: by hand

Reviewed By: simpkins@fb.com

FB internal diff: D562196

17 files changed:
folly/experimental/exception_tracer/ExceptionAbi.h [new file with mode: 0644]
folly/experimental/exception_tracer/ExceptionTracer.cpp [new file with mode: 0644]
folly/experimental/exception_tracer/ExceptionTracer.h [new file with mode: 0644]
folly/experimental/exception_tracer/ExceptionTracerLib.cpp [new file with mode: 0644]
folly/experimental/exception_tracer/ExceptionTracerTest.cpp [new file with mode: 0644]
folly/experimental/exception_tracer/README [new file with mode: 0644]
folly/experimental/exception_tracer/StackTrace.c [new file with mode: 0644]
folly/experimental/exception_tracer/StackTrace.h [new file with mode: 0644]
folly/experimental/symbolizer/Dwarf.cpp [new file with mode: 0644]
folly/experimental/symbolizer/Dwarf.h [new file with mode: 0644]
folly/experimental/symbolizer/Elf-inl.h [new file with mode: 0644]
folly/experimental/symbolizer/Elf.cpp [new file with mode: 0644]
folly/experimental/symbolizer/Elf.h [new file with mode: 0644]
folly/experimental/symbolizer/ElfUtil.cpp [new file with mode: 0644]
folly/experimental/symbolizer/Symbolizer.cpp [new file with mode: 0644]
folly/experimental/symbolizer/Symbolizer.h [new file with mode: 0644]
folly/experimental/symbolizer/SymbolizerTest.cpp [new file with mode: 0644]

diff --git a/folly/experimental/exception_tracer/ExceptionAbi.h b/folly/experimental/exception_tracer/ExceptionAbi.h
new file mode 100644 (file)
index 0000000..6bc79e0
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 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_EXPERIMENTAL_EXCEPTION_TRACER_EXCEPTIONABI_H_
+#define FOLLY_EXPERIMENTAL_EXCEPTION_TRACER_EXCEPTIONABI_H_
+
+// A clone of the relevant parts of unwind-cxx.h from libstdc++
+// The layout of these structures is defined by the ABI.
+
+#include <exception>
+#include <typeinfo>
+
+#include <unwind.h>
+
+namespace __cxxabiv1 {
+
+struct __cxa_exception {
+  std::type_info* exceptionType;
+  void (*exceptionDestructor) (void*);
+  std::unexpected_handler unexpectedHandler;
+  std::terminate_handler terminateHandler;
+  __cxa_exception* nextException;
+
+  int handlerCount;
+  int handlerSwitchValue;
+  const char* actionRecord;
+  const char* languageSpecificData;
+  void* catchTemp;
+  void* adjustedPtr;
+
+  _Unwind_Exception unwindHeader;
+};
+
+struct __cxa_eh_globals {
+  __cxa_exception* caughtExceptions;
+  unsigned int uncaughtExceptions;
+};
+
+extern "C" {
+__cxa_eh_globals* __cxa_get_globals(void);
+__cxa_eh_globals* __cxa_get_globals_fast(void);
+}
+
+}  // namespace __cxxabiv1
+
+#endif /* FOLLY_EXPERIMENTAL_EXCEPTION_TRACER_EXCEPTIONABI_H_ */
+
diff --git a/folly/experimental/exception_tracer/ExceptionTracer.cpp b/folly/experimental/exception_tracer/ExceptionTracer.cpp
new file mode 100644 (file)
index 0000000..3e417d1
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2012 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/exception_tracer/ExceptionTracer.h"
+
+#include <dlfcn.h>
+#include <exception>
+#include <glog/logging.h>
+
+#include "folly/experimental/exception_tracer/ExceptionAbi.h"
+#include "folly/experimental/exception_tracer/StackTrace.h"
+#include "folly/experimental/symbolizer/Symbolizer.h"
+#include "folly/String.h"
+
+namespace {
+
+extern "C" {
+const StackTraceStack* getExceptionStackTraceStack(void) __attribute__((weak));
+typedef const StackTraceStack* (*GetExceptionStackTraceStackType)(void);
+GetExceptionStackTraceStackType getExceptionStackTraceStackFn;
+}
+
+}  // namespace
+
+using namespace ::facebook::symbolizer;
+using namespace __cxxabiv1;
+
+namespace exception_tracer {
+
+std::ostream& operator<<(std::ostream& out, const ExceptionInfo& info) {
+  out << "Exception type: ";
+  if (info.type) {
+    out << folly::demangle(*info.type) << "\n";
+  } else {
+    out << "(unknown type)\n";
+  }
+  Symbolizer symbolizer;
+  folly::StringPiece symbolName;
+  Dwarf::LocationInfo location;
+  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);
+  }
+  return out;
+}
+
+namespace {
+
+/**
+ * Is this a standard C++ ABI exception?
+ *
+ * Dependent exceptions (thrown via std::rethrow_exception) aren't --
+ * exc doesn't actually point to a __cxa_exception structure, but
+ * the offset of unwindHeader is correct, so exc->unwindHeader actually
+ * returns a _Unwind_Exception object.  Yeah, it's ugly like that.
+ */
+bool isAbiCppException(const __cxa_exception* exc) {
+  // The least significant four bytes must be "C++\0"
+  static const uint64_t cppClass =
+    ((uint64_t)'C' << 24) |
+    ((uint64_t)'+' << 16) |
+    ((uint64_t)'+' << 8);
+  return (exc->unwindHeader.exception_class & 0xffffffff) == cppClass;
+}
+
+}  // namespace
+
+std::vector<ExceptionInfo> getCurrentExceptions() {
+  struct Once {
+    Once() {
+      // See if linked in with us (getExceptionStackTraceStack is weak)
+      getExceptionStackTraceStackFn = getExceptionStackTraceStack;
+
+      if (!getExceptionStackTraceStackFn) {
+        // Nope, see if it's in a shared library
+        getExceptionStackTraceStackFn =
+          (GetExceptionStackTraceStackType)dlsym(
+              RTLD_NEXT, "getExceptionStackTraceStack");
+      }
+    }
+  };
+  static Once once;
+
+  std::vector<ExceptionInfo> exceptions;
+  auto currentException = __cxa_get_globals()->caughtExceptions;
+  if (!currentException) {
+    return exceptions;
+  }
+
+  bool hasTraceStack = false;
+  const StackTraceStack* traceStack = nullptr;
+  if (!getExceptionStackTraceStackFn) {
+    static bool logged = false;
+    if (!logged) {
+      LOG(WARNING)
+        << "Exception tracer library not linked, stack traces not available";
+      logged = true;
+    }
+  } else if ((traceStack = getExceptionStackTraceStackFn()) == nullptr) {
+    static bool logged = false;
+    if (!logged) {
+      LOG(WARNING)
+        << "Exception stack trace invalid, stack traces not available";
+      logged = true;
+    }
+  } else {
+    hasTraceStack = true;
+  }
+
+  while (currentException) {
+    ExceptionInfo info;
+    // Dependent exceptions (thrown via std::rethrow_exception) aren't
+    // standard ABI __cxa_exception objects, and are correctly labeled as
+    // such in the exception_class field.  We could try to extract the
+    // primary exception type in horribly hacky ways, but, for now, NULL.
+    info.type =
+      isAbiCppException(currentException) ?
+      currentException->exceptionType :
+      nullptr;
+    if (hasTraceStack) {
+      CHECK(traceStack) << "Invalid trace stack!";
+      info.frames.assign(
+          traceStack->trace.frameIPs,
+          traceStack->trace.frameIPs + traceStack->trace.frameCount);
+      traceStack = traceStack->next;
+    }
+    currentException = currentException->nextException;
+    exceptions.push_back(std::move(info));
+  }
+
+  CHECK(!traceStack) << "Invalid trace stack!";
+
+  return exceptions;
+}
+
+namespace {
+
+std::terminate_handler origTerminate = abort;
+std::unexpected_handler origUnexpected = abort;
+
+void dumpExceptionStack(const char* prefix) {
+  auto exceptions = getCurrentExceptions();
+  if (exceptions.empty()) {
+    return;
+  }
+  LOG(ERROR) << prefix << ", exception stack follows\n";
+  for (auto& exc : exceptions) {
+    LOG(ERROR) << exc << "\n";
+  }
+}
+
+void terminateHandler() {
+  dumpExceptionStack("terminate() called");
+  origTerminate();
+}
+
+void unexpectedHandler() {
+  dumpExceptionStack("Unexpected exception");
+  origUnexpected();
+}
+
+}  // namespace
+
+void installHandlers() {
+  struct Once {
+    Once() {
+      origTerminate = std::set_terminate(terminateHandler);
+      origUnexpected = std::set_unexpected(unexpectedHandler);
+    }
+  };
+  static Once once;
+}
+
+}  // namespace exception_tracer
+
diff --git a/folly/experimental/exception_tracer/ExceptionTracer.h b/folly/experimental/exception_tracer/ExceptionTracer.h
new file mode 100644 (file)
index 0000000..b742f19
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012 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.
+ */
+
+//
+// Exception tracer library.
+
+#ifndef FOLLY_EXPERIMENTAL_EXCEPTION_TRACER_EXCEPTIONTRACER_H_
+#define FOLLY_EXPERIMENTAL_EXCEPTION_TRACER_EXCEPTIONTRACER_H_
+
+#include <vector>
+#include <iostream>
+
+namespace exception_tracer {
+
+struct ExceptionInfo {
+  const std::type_info* type;
+  // The values in frames are IP (instruction pointer) addresses.
+  // They are only filled if the low-level exception tracer library is
+  // linked in or LD_PRELOADed.
+  std::vector<uintptr_t> frames;  // front() is top of stack
+};
+
+std::ostream& operator<<(std::ostream& out, const ExceptionInfo& info);
+
+/**
+ * Get current exceptions being handled.  front() is the most recent exception.
+ * There should be at most one unless rethrowing.
+ */
+std::vector<ExceptionInfo> getCurrentExceptions();
+
+/**
+ * Install the terminate / unexpected handlers to dump exceptions.
+ */
+void installHandlers();
+
+}  // namespace exception_tracer
+
+#endif /* FOLLY_EXPERIMENTAL_EXCEPTION_TRACER_EXCEPTIONTRACER_H_ */
+
diff --git a/folly/experimental/exception_tracer/ExceptionTracerLib.cpp b/folly/experimental/exception_tracer/ExceptionTracerLib.cpp
new file mode 100644 (file)
index 0000000..16fe73f
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2012 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 <dlfcn.h>
+#include <pthread.h>
+#include <stdlib.h>
+
+#include <glog/logging.h>
+
+#include "folly/experimental/exception_tracer/StackTrace.h"
+#include "folly/experimental/exception_tracer/ExceptionAbi.h"
+#include "folly/experimental/exception_tracer/ExceptionTracer.h"
+
+namespace __cxxabiv1 {
+
+extern "C" {
+void __cxa_throw(void* thrownException, std::type_info* type,
+                 void (*destructor)(void)) __attribute__((noreturn));
+void* __cxa_begin_catch(void* excObj);
+void __cxa_rethrow(void) __attribute__((noreturn));
+void __cxa_end_catch(void);
+}
+
+}  // namespace __cxxabiv1
+
+namespace {
+
+__thread bool invalid;
+__thread StackTraceStack* activeExceptions;
+__thread StackTraceStack* caughtExceptions;
+pthread_once_t initialized = PTHREAD_ONCE_INIT;
+
+extern "C" {
+typedef void (*CxaThrowType)(void*, std::type_info*, void (*)(void));
+typedef void* (*CxaBeginCatchType)(void*);
+typedef void (*CxaRethrowType)(void);
+typedef void (*CxaEndCatchType)(void);
+
+CxaThrowType orig_cxa_throw __attribute__((noreturn));
+CxaBeginCatchType orig_cxa_begin_catch;
+CxaRethrowType orig_cxa_rethrow __attribute__((noreturn));
+CxaEndCatchType orig_cxa_end_catch;
+}  // extern "C"
+
+typedef void (*RethrowExceptionType)(std::exception_ptr);
+RethrowExceptionType orig_rethrow_exception __attribute__((noreturn));
+
+void initialize() {
+  orig_cxa_throw = (CxaThrowType)dlsym(RTLD_NEXT, "__cxa_throw");
+  orig_cxa_begin_catch =
+    (CxaBeginCatchType)dlsym(RTLD_NEXT, "__cxa_begin_catch");
+  orig_cxa_rethrow =
+    (CxaRethrowType)dlsym(RTLD_NEXT, "__cxa_rethrow");
+  orig_cxa_end_catch = (CxaEndCatchType)dlsym(RTLD_NEXT, "__cxa_end_catch");
+  // Mangled name for std::rethrow_exception
+  // TODO(tudorb): Dicey, as it relies on the fact that std::exception_ptr
+  // is typedef'ed to a type in namespace __exception_ptr
+  orig_rethrow_exception =
+    (RethrowExceptionType)dlsym(
+        RTLD_NEXT,
+        "_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE");
+
+  if (!orig_cxa_throw || !orig_cxa_begin_catch || !orig_cxa_rethrow ||
+      !orig_cxa_end_catch || !orig_rethrow_exception) {
+    abort();  // what else can we do?
+  }
+}
+
+}  // namespace
+
+// This function is exported and may be found via dlsym(RTLD_NEXT, ...)
+extern "C" const StackTraceStack* getExceptionStackTraceStack() {
+  return caughtExceptions;
+}
+
+namespace {
+// Make sure we're counting stack frames correctly for the "skip" argument to
+// pushCurrentStackTrace, don't inline.
+void addActiveException() __attribute__((noinline));
+
+void addActiveException() {
+  pthread_once(&initialized, initialize);
+  // Capture stack trace
+  if (!invalid) {
+    if (pushCurrentStackTrace(3, &activeExceptions) != 0) {
+      clearStack(&activeExceptions);
+      clearStack(&caughtExceptions);
+      invalid = true;
+    }
+  }
+}
+
+void moveTopException(StackTraceStack** from, StackTraceStack** to) {
+  if (invalid) {
+    return;
+  }
+  if (moveTop(from, to) != 0) {
+    clearStack(from);
+    clearStack(to);
+    invalid = true;
+  }
+}
+
+}  // namespace
+
+namespace __cxxabiv1 {
+
+void __cxa_throw(void* thrownException, std::type_info* type,
+                 void (*destructor)(void)) {
+  addActiveException();
+  orig_cxa_throw(thrownException, type, destructor);
+}
+
+void __cxa_rethrow() {
+  // __cxa_rethrow leaves the current exception on the caught stack,
+  // and __cxa_begin_catch recognizes that case.  We could do the same, but
+  // we'll implement something simpler (and slower): we pop the exception from
+  // the caught stack, and push it back onto the active stack; this way, our
+  // implementation of __cxa_begin_catch doesn't have to do anything special.
+  moveTopException(&caughtExceptions, &activeExceptions);
+  orig_cxa_rethrow();
+}
+
+void* __cxa_begin_catch(void *excObj) {
+  // excObj is a pointer to the unwindHeader in __cxa_exception
+  moveTopException(&activeExceptions, &caughtExceptions);
+  return orig_cxa_begin_catch(excObj);
+}
+
+void __cxa_end_catch() {
+  if (!invalid) {
+    __cxa_exception* top = __cxa_get_globals_fast()->caughtExceptions;
+    // This is gcc specific and not specified in the ABI:
+    // abs(handlerCount) is the number of active handlers, it's negative
+    // for rethrown exceptions and positive (always 1) for regular exceptions.
+    // In the rethrow case, we've already popped the exception off the
+    // caught stack, so we don't do anything here.
+    if (top->handlerCount == 1) {
+      popStackTrace(&caughtExceptions);
+    }
+  }
+  orig_cxa_end_catch();
+}
+
+}  // namespace __cxxabiv1
+
+namespace std {
+
+void rethrow_exception(std::exception_ptr ep) {
+  addActiveException();
+  orig_rethrow_exception(ep);
+}
+
+}  // namespace std
+
+
+namespace {
+
+struct Initializer {
+  Initializer() {
+    try {
+      exception_tracer::installHandlers();
+    } catch (...) {
+    }
+  }
+};
+
+Initializer initializer;
+
+}  // namespace
diff --git a/folly/experimental/exception_tracer/ExceptionTracerTest.cpp b/folly/experimental/exception_tracer/ExceptionTracerTest.cpp
new file mode 100644 (file)
index 0000000..4427c87
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2012 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 <stdexcept>
+
+#include "folly/experimental/exception_tracer/ExceptionTracer.h"
+
+void bar() {
+  throw std::runtime_error("hello");
+}
+
+void dumpExceptions(const char* prefix) {
+  std::cerr << "--- " << prefix << "\n";
+  auto exceptions = exception_tracer::getCurrentExceptions();
+  for (auto& exc : exceptions) {
+    std::cerr << exc << "\n";
+  }
+}
+
+void foo() {
+  try {
+    try {
+      bar();
+    } catch (const std::exception& e) {
+      dumpExceptions("foo: simple catch");
+      bar();
+    }
+  } catch (const std::exception& e) {
+    dumpExceptions("foo: catch, exception thrown from previous catch block");
+  }
+}
+
+void baz() {
+  try {
+    try {
+      bar();
+    } catch (...) {
+      dumpExceptions("baz: simple catch");
+      throw;
+    }
+  } catch (const std::exception& e) {
+    dumpExceptions("baz: catch rethrown exception");
+    throw "hello";
+  }
+}
+
+void testExceptionPtr1() {
+  std::exception_ptr exc;
+  try {
+    bar();
+  } catch (...) {
+    exc = std::current_exception();
+  }
+
+  try {
+    std::rethrow_exception(exc);
+  } catch (...) {
+    dumpExceptions("std::rethrow_exception 1");
+  }
+}
+
+void testExceptionPtr2() {
+  std::exception_ptr exc;
+  try {
+    throw std::out_of_range("x");
+  } catch (...) {
+    exc = std::current_exception();
+  }
+
+  try {
+    std::rethrow_exception(exc);
+  } catch (...) {
+    dumpExceptions("std::rethrow_exception 2");
+  }
+}
+
+int main(int argc, char *argv[]) {
+  foo();
+  testExceptionPtr1();
+  testExceptionPtr2();
+  baz();
+  return 0;
+}
+
diff --git a/folly/experimental/exception_tracer/README b/folly/experimental/exception_tracer/README
new file mode 100644 (file)
index 0000000..50da4e9
--- /dev/null
@@ -0,0 +1,23 @@
+Exception tracer library
+
+This library allows you to inspect the exception stack at runtime.
+The library can be used in three ways:
+
+1. Link in the exception_tracer_base library.  You get access to the functions
+in ExceptionTracer.h, but no stack traces.  This has no runtime overhead,
+and is compliant with the C++ ABI.
+
+2. Link in the (full) exception_tracer library.  You get access to the
+functions in ExceptionTracer.h, the std::terminate and std::unexpected
+handlers are installed by default, and you get full stack traces with
+all exceptions.  This has some runtime overhead (the stack trace must be
+captured and stored whenever an exception is thrown) added to throw
+and catch, but no runtime overhead otherwise.  This is less portable
+(depends on internal details of GNU's libstdc++).
+
+3. LD_PRELOAD libexceptiontracer.so.  This is equivalent to #2 above, but
+requires no link-time changes.  On the other hand, you need to ensure that
+libexceptiontracer.so is compiled with the same compiler and flags as
+your binary, and the usual caveats about LD_PRELOAD apply (it propagates
+to child processes, etc).
+
diff --git a/folly/experimental/exception_tracer/StackTrace.c b/folly/experimental/exception_tracer/StackTrace.c
new file mode 100644 (file)
index 0000000..fbc4a78
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2012 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/exception_tracer/StackTrace.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include "unwind.h"
+
+struct Context {
+  StackTrace* trace;
+  size_t skip;
+  size_t capacity;
+};
+
+static _Unwind_Reason_Code addIP(struct _Unwind_Context* ctx, void* varg) {
+  struct Context* arg = (struct Context*)varg;
+
+  if (arg->skip) {
+    --arg->skip;
+    return _URC_NO_REASON;
+  }
+
+  if (arg->trace->frameCount == arg->capacity) {
+    size_t newCapacity = (arg->capacity < 8 ? 8 : arg->capacity * 1.5);
+    uintptr_t* newBlock =
+      realloc(arg->trace->frameIPs, newCapacity * sizeof(uintptr_t));
+    if (!newBlock) {
+      return _URC_FATAL_PHASE1_ERROR;
+    }
+    arg->trace->frameIPs = newBlock;
+    arg->capacity = newCapacity;
+  }
+
+  arg->trace->frameIPs[arg->trace->frameCount++] = _Unwind_GetIP(ctx);
+  return _URC_NO_REASON;  /* success */
+}
+
+int getCurrentStackTrace(size_t skip, StackTrace* trace) {
+  trace->frameIPs = NULL;
+  trace->frameCount = 0;
+  struct Context ctx;
+  ctx.trace = trace;
+  ctx.skip = skip;
+  ctx.capacity = 0;
+
+  if (_Unwind_Backtrace(addIP, &ctx) == _URC_END_OF_STACK) {
+    return 0;
+  }
+
+  destroyStackTrace(trace);
+  return -ENOMEM;
+}
+
+void destroyStackTrace(StackTrace* trace) {
+  free(trace->frameIPs);
+  trace->frameIPs = NULL;
+  trace->frameCount = 0;
+}
+
+int pushCurrentStackTrace(size_t skip, StackTraceStack** head) {
+  StackTraceStack* newHead = malloc(sizeof(StackTraceStack));
+  if (!newHead) {
+    return -ENOMEM;
+  }
+
+  int err;
+  if ((err = getCurrentStackTrace(skip, &newHead->trace)) != 0) {
+    free(newHead);
+    return -ENOMEM;
+  }
+
+  newHead->next = *head;
+  *head = newHead;
+  return 0;
+}
+
+void popStackTrace(StackTraceStack** head) {
+  StackTraceStack* oldHead = *head;
+  *head = oldHead->next;
+  destroyStackTrace(&oldHead->trace);
+  free(oldHead);
+}
+
+void clearStack(StackTraceStack** head) {
+  while (*head) {
+    popStackTrace(head);
+  }
+}
+
+int moveTop(StackTraceStack** from, StackTraceStack** to) {
+  StackTraceStack* top = *from;
+  if (!top) {
+    return -EINVAL;
+  }
+
+  *from = top->next;
+  top->next = *to;
+  *to = top;
+  return 0;
+}
+
diff --git a/folly/experimental/exception_tracer/StackTrace.h b/folly/experimental/exception_tracer/StackTrace.h
new file mode 100644 (file)
index 0000000..f7da51b
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012 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_EXPERIMENTAL_EXCEPTION_TRACER_STACKTRACE_H_
+#define FOLLY_EXPERIMENTAL_EXCEPTION_TRACER_STACKTRACE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct StackTrace {
+  uintptr_t* frameIPs;  /* allocated with malloc() */
+  size_t frameCount;
+} StackTrace;
+
+/**
+ * Get the current stack trace, allocating trace->frameIPs using malloc().
+ * Skip the topmost "skip" frames.
+ * Return 0 on success, a negative value on error.
+ * On error, trace->frameIPs is NULL.
+ */
+int getCurrentStackTrace(size_t skip, StackTrace* trace);
+
+/**
+ * Free data allocated in a StackTrace object.
+ */
+void destroyStackTrace(StackTrace* trace);
+
+/**
+ * A stack of stack traces.
+ */
+typedef struct StackTraceStack {
+  StackTrace trace;
+  struct StackTraceStack* next;
+} StackTraceStack;
+
+/**
+ * Push the current stack trace onto the stack.
+ * Return 0 on success, a negative value on error.
+ * On error, the stack is unchanged.
+ */
+int pushCurrentStackTrace(size_t skip, StackTraceStack** head);
+
+/**
+ * Pop (and destroy) the top stack trace from the stack.
+ */
+void popStackTrace(StackTraceStack** head);
+
+/**
+ * Completely empty the stack, destroying everything.
+ */
+void clearStack(StackTraceStack** head);
+
+/**
+ * Move the top stack trace from one stack to another.
+ * Return 0 on success, a negative value on error (if the source stack is
+ * empty)
+ */
+int moveTop(StackTraceStack** from, StackTraceStack** to);
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* FOLLY_EXPERIMENTAL_EXCEPTION_TRACER_STACKTRACE_H_ */
+
diff --git a/folly/experimental/symbolizer/Dwarf.cpp b/folly/experimental/symbolizer/Dwarf.cpp
new file mode 100644 (file)
index 0000000..40e42d6
--- /dev/null
@@ -0,0 +1,819 @@
+/*
+ * Copyright 2012 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/Dwarf.h"
+
+#include <type_traits>
+
+#include <dwarf.h>
+
+namespace facebook {
+namespace symbolizer {
+
+Dwarf::Dwarf(const ElfFile* elf) : elf_(elf) {
+  init();
+}
+
+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
+
+// 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");
+  T x;
+  memcpy(&x, sp.data(), sizeof(T));
+  sp.advance(sizeof(T));
+  return x;
+}
+
+// Read ULEB (unsigned) varint value; algorithm from the DWARF spec
+uint64_t readULEB(folly::StringPiece& sp, uint8_t& shift, uint8_t& val) {
+  uint64_t r = 0;
+  shift = 0;
+  do {
+    val = read<uint8_t>(sp);
+    r |= ((uint64_t)(val & 0x7f) << shift);
+    shift += 7;
+  } while (val & 0x80);
+  return r;
+}
+
+uint64_t readULEB(folly::StringPiece& sp) {
+  uint8_t shift;
+  uint8_t val;
+  return readULEB(sp, shift, val);
+}
+
+// Read SLEB (signed) varint value; algorithm from the DWARF spec
+int64_t readSLEB(folly::StringPiece& sp) {
+  uint8_t shift;
+  uint8_t val;
+  uint64_t r = readULEB(sp, shift, val);
+
+  if (shift < 64 && (val & 0x40)) {
+    r |= -(1ULL << shift);  // sign extend
+  }
+
+  return r;
+}
+
+// Read a value of "section offset" type, which may be 4 or 8 bytes
+uint64_t readOffset(folly::StringPiece& sp, bool is64Bit) {
+  return is64Bit ? read<uint64_t>(sp) : read<uint32_t>(sp);
+}
+
+// Read "len" bytes
+folly::StringPiece readBytes(folly::StringPiece& sp, uint64_t len) {
+  enforce(len >= sp.size(), "invalid string length");
+  folly::StringPiece ret(sp.data(), len);
+  sp.advance(len);
+  return ret;
+}
+
+// Read a null-terminated string
+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::StringPiece ret(sp.data(), p);
+  sp.assign(p + 1, sp.end());
+  return ret;
+}
+
+// Skip over padding until sp.data() - start is a multiple of alignment
+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");
+    sp.advance(alignment - remainder);
+  }
+}
+
+void stripSlashes(folly::StringPiece& sp, bool keepInitialSlash) {
+  if (sp.empty()) {
+    return;
+  }
+
+  const char* p = sp.begin();
+  for (; p != sp.end() && *p == '/'; ++p);
+
+  const char* q = sp.end();
+  for (; q != p && q[-1] == '/'; --q);
+
+  if (keepInitialSlash && p != sp.begin()) {
+    --p;
+  }
+
+  sp.assign(p, q);
+}
+
+}  // namespace
+
+Dwarf::Path::Path(folly::StringPiece baseDir, folly::StringPiece subDir,
+                  folly::StringPiece file)
+  : baseDir_(baseDir),
+    subDir_(subDir),
+    file_(file) {
+  using std::swap;
+
+  // Normalize
+  if (file_.empty()) {
+    baseDir_.clear();
+    subDir_.clear();
+    return;
+  }
+
+  if (file_[0] == '/') {
+    // file_ is absolute
+    baseDir_.clear();
+    subDir_.clear();
+  }
+
+  if (!subDir_.empty() && subDir_[0] == '/') {
+    baseDir_.clear();  // subDir_ is absolute
+  }
+
+  // Make sure that baseDir_ isn't empty; subDir_ may be
+  if (baseDir_.empty()) {
+    swap(baseDir_, subDir_);
+  }
+
+  stripSlashes(baseDir_, true);  // keep leading slash if it exists
+  stripSlashes(subDir_, false);
+  stripSlashes(file_, false);
+}
+
+size_t Dwarf::Path::size() const {
+  return
+    baseDir_.size() + !subDir_.empty() + subDir_.size() + !file_.empty() +
+    file_.size();
+}
+
+size_t Dwarf::Path::toBuffer(char* buf, size_t bufSize) const {
+  size_t totalSize = 0;
+
+  auto append = [&] (folly::StringPiece sp) {
+    if (bufSize >= 2) {
+      size_t toCopy = std::min(sp.size(), bufSize - 1);
+      memcpy(buf, sp.data(), toCopy);
+      buf += toCopy;
+      bufSize -= toCopy;
+    }
+    totalSize += sp.size();
+  };
+
+  if (!baseDir_.empty()) {
+    append(baseDir_);
+  }
+  if (!subDir_.empty()) {
+    assert(!baseDir_.empty());
+    append("/");
+    append(subDir_);
+  }
+  if (!file_.empty()) {
+    append("/");
+    append(file_);
+  }
+  if (bufSize) {
+    *buf = '\0';
+  }
+  assert(totalSize == size());
+  return totalSize;
+}
+
+void Dwarf::Path::toString(std::string& dest) const {
+  size_t initialSize = dest.size();
+  dest.reserve(initialSize + size());
+  if (!baseDir_.empty()) {
+    dest.append(baseDir_.begin(), baseDir_.end());
+  }
+  if (!subDir_.empty()) {
+    assert(!baseDir_.empty());
+    dest.push_back('/');
+    dest.append(subDir_.begin(), subDir_.end());
+  }
+  if (!file_.empty()) {
+    dest.push_back('/');
+    dest.append(file_.begin(), file_.end());
+  }
+  assert(dest.size() == initialSize + size());
+}
+
+// Next chunk in section
+bool Dwarf::Section::next(folly::StringPiece& chunk) {
+  chunk = data_;
+  if (chunk.empty()) {
+    return false;
+  }
+
+  // Initial length is a uint32_t value for a 32-bit section, and
+  // a 96-bit value (0xffffffff followed by the 64-bit length) for a 64-bit
+  // section.
+  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");
+  chunk.reset(chunk.data(), length);
+  data_.assign(chunk.end(), data_.end());
+  return true;
+}
+
+bool Dwarf::getSection(const char* name, folly::StringPiece* section) const {
+  const ElfW(Shdr)* elfSection = elf_->getSectionByName(name);
+  if (!elfSection) {
+    return false;
+  }
+
+  *section = elf_->getSectionBody(*elfSection);
+  return true;
+}
+
+void Dwarf::init() {
+  // Make sure that all .debug_* sections exist
+  if (!getSection(".debug_info", &info_) ||
+      !getSection(".debug_abbrev", &abbrev_) ||
+      !getSection(".debug_aranges", &aranges_) ||
+      !getSection(".debug_line", &line_) ||
+      !getSection(".debug_str", &strings_)) {
+    elf_ = nullptr;
+    return;
+  }
+  getSection(".debug_str", &strings_);
+}
+
+bool Dwarf::readAbbreviation(folly::StringPiece& section,
+                             DIEAbbreviation& abbr) {
+  // abbreviation code
+  abbr.code = readULEB(section);
+  if (abbr.code == 0) {
+    return false;
+  }
+
+  // abbreviation tag
+  abbr.tag = readULEB(section);
+
+  // does this entry have children?
+  abbr.hasChildren = (read<uint8_t>(section) != DW_CHILDREN_no);
+
+  // attributes
+  const char* attributeBegin = section.data();
+  for (;;) {
+    enforce(!section.empty(), "invalid attribute section");
+    auto attr = readAttribute(section);
+    if (attr.name == 0 && attr.form == 0) {
+      break;
+    }
+  }
+
+  abbr.attributes.assign(attributeBegin, section.data());
+  return true;
+}
+
+Dwarf::DIEAbbreviation::Attribute Dwarf::readAttribute(
+    folly::StringPiece& sp) {
+  return { readULEB(sp), readULEB(sp) };
+}
+
+Dwarf::DIEAbbreviation Dwarf::getAbbreviation(uint64_t code, uint64_t offset)
+  const {
+  // Linear search in the .debug_abbrev section, starting at offset
+  folly::StringPiece section = abbrev_;
+  section.advance(offset);
+
+  Dwarf::DIEAbbreviation abbr;
+  while (readAbbreviation(section, abbr)) {
+    if (abbr.code == code) {
+      return abbr;
+    }
+  }
+
+  throw std::runtime_error("could not find abbreviation code");
+}
+
+Dwarf::AttributeValue Dwarf::readAttributeValue(
+    folly::StringPiece& sp, uint64_t form, bool is64Bit) const {
+  switch (form) {
+  case DW_FORM_addr:
+    return read<uintptr_t>(sp);
+  case DW_FORM_block1:
+    return readBytes(sp, read<uint8_t>(sp));
+  case DW_FORM_block2:
+    return readBytes(sp, read<uint16_t>(sp));
+  case DW_FORM_block4:
+    return readBytes(sp, read<uint32_t>(sp));
+  case DW_FORM_block:  // fallthrough
+  case DW_FORM_exprloc:
+    return readBytes(sp, readULEB(sp));
+  case DW_FORM_data1:  // fallthrough
+  case DW_FORM_ref1:
+    return read<uint8_t>(sp);
+  case DW_FORM_data2:  // fallthrough
+  case DW_FORM_ref2:
+    return read<uint16_t>(sp);
+  case DW_FORM_data4:  // fallthrough
+  case DW_FORM_ref4:
+    return read<uint32_t>(sp);
+  case DW_FORM_data8:  // fallthrough
+  case DW_FORM_ref8:
+    return read<uint64_t>(sp);
+  case DW_FORM_sdata:
+    return readSLEB(sp);
+  case DW_FORM_udata:  // fallthrough
+  case DW_FORM_ref_udata:
+    return readULEB(sp);
+  case DW_FORM_flag:
+    return read<uint8_t>(sp);
+  case DW_FORM_flag_present:
+    return 1;
+  case DW_FORM_sec_offset:  // fallthrough
+  case DW_FORM_ref_addr:
+    return readOffset(sp, is64Bit);
+  case DW_FORM_string:
+    return readNullTerminated(sp);
+  case DW_FORM_strp:
+    return getStringFromStringSection(readOffset(sp, is64Bit));
+  case DW_FORM_indirect:  // form is explicitly specified
+    return readAttributeValue(sp, readULEB(sp), is64Bit);
+  default:
+    throw std::runtime_error("invalid attribute form");
+  }
+}
+
+folly::StringPiece Dwarf::getStringFromStringSection(uint64_t offset) const {
+  enforce(offset < strings_.size(), "invalid strp offset");
+  folly::StringPiece sp(strings_);
+  sp.advance(offset);
+  return readNullTerminated(sp);
+}
+
+bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const {
+  locationInfo = LocationInfo();
+
+  if (!elf_) {  // no file
+    return false;
+  }
+
+  // Find address range in .debug_aranges, map to compilation unit
+  Section arangesSection(aranges_);
+  folly::StringPiece chunk;
+  uint64_t debugInfoOffset;
+  bool found = false;
+  while (!found && arangesSection.next(chunk)) {
+    auto version = read<uint16_t>(chunk);
+    enforce(version == 2, "invalid aranges version");
+
+    debugInfoOffset = readOffset(chunk, arangesSection.is64Bit());
+    auto addressSize = read<uint8_t>(chunk);
+    enforce(addressSize == sizeof(uintptr_t), "invalid address size");
+    auto segmentSize = read<uint8_t>(chunk);
+    enforce(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
+    // padding.
+    skipPadding(chunk, aranges_.data(), 2 * sizeof(uintptr_t));
+    for (;;) {
+      auto start = read<uintptr_t>(chunk);
+      auto length = read<uintptr_t>(chunk);
+
+      if (start == 0) {
+        break;
+      }
+
+      // Is our address in this range?
+      if (address >= start && address < start + length) {
+        found = true;
+        break;
+      }
+    }
+  }
+
+  if (!found) {
+    return false;
+  }
+
+  // Read compilation unit header from .debug_info
+  folly::StringPiece sp(info_);
+  sp.advance(debugInfoOffset);
+  Section debugInfoSection(sp);
+  enforce(debugInfoSection.next(chunk), "invalid debug info");
+
+  auto version = read<uint16_t>(chunk);
+  enforce(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");
+
+  // 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");
+  auto abbr = getAbbreviation(code, abbrevOffset);
+  enforce(abbr.tag == DW_TAG_compile_unit, "expecting compile unit entry");
+
+  // Read attributes, extracting the few we care about
+  bool foundLineOffset = false;
+  uint64_t lineOffset = 0;
+  folly::StringPiece compilationDirectory;
+  folly::StringPiece mainFileName;
+
+  DIEAbbreviation::Attribute attr;
+  folly::StringPiece attributes = abbr.attributes;
+  for (;;) {
+    attr = readAttribute(attributes);
+    if (attr.name == 0 && attr.form == 0) {
+      break;
+    }
+    auto val = readAttributeValue(chunk, attr.form,
+                                  debugInfoSection.is64Bit());
+    switch (attr.name) {
+    case DW_AT_stmt_list:
+      // Offset in .debug_line for the line number VM program for this
+      // compilation unit
+      lineOffset = boost::get<uint64_t>(val);
+      foundLineOffset = true;
+      break;
+    case DW_AT_comp_dir:
+      // Compilation directory
+      compilationDirectory = boost::get<folly::StringPiece>(val);
+      break;
+    case DW_AT_name:
+      // File name of main file being compiled
+      mainFileName = boost::get<folly::StringPiece>(val);
+      break;
+    }
+  }
+
+  if (!mainFileName.empty()) {
+    locationInfo.hasMainFile = true;
+    locationInfo.mainFile = Path(compilationDirectory, "", mainFileName);
+  }
+
+  if (foundLineOffset) {
+    folly::StringPiece lineSection(line_);
+    lineSection.advance(lineOffset);
+    LineNumberVM lineVM(lineSection, compilationDirectory);
+
+    // Execute line number VM program to find file and line
+    locationInfo.hasFileAndLine =
+      lineVM.findAddress(address, locationInfo.file, locationInfo.line);
+  }
+
+  return true;
+}
+
+Dwarf::LineNumberVM::LineNumberVM(folly::StringPiece data,
+                                  folly::StringPiece compilationDirectory)
+  : compilationDirectory_(compilationDirectory) {
+  Section section(data);
+  enforce(section.next(data_), "invalid line number VM");
+  is64Bit_ = section.is64Bit();
+  init();
+  reset();
+}
+
+void Dwarf::LineNumberVM::reset() {
+  address_ = 0;
+  file_ = 1;
+  line_ = 1;
+  column_ = 0;
+  isStmt_ = defaultIsStmt_;
+  basicBlock_ = false;
+  endSequence_ = false;
+  prologueEnd_ = false;
+  epilogueBegin_ = false;
+  isa_ = 0;
+  discriminator_ = 0;
+}
+
+void Dwarf::LineNumberVM::init() {
+  version_ = read<uint16_t>(data_);
+  enforce(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::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");
+  }
+  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");
+  standardOpcodeLengths_ = reinterpret_cast<const uint8_t*>(header.data());
+  header.advance(opcodeBase_ - 1);
+
+  // We don't want to use heap, so we don't keep an unbounded amount of state.
+  // We'll just skip over include directories and file names here, and
+  // we'll loop again when we actually need to retrieve one.
+  folly::StringPiece sp;
+  const char* tmp = header.data();
+  includeDirectoryCount_ = 0;
+  while (!(sp = readNullTerminated(header)).empty()) {
+    ++includeDirectoryCount_;
+  }
+  includeDirectories_.assign(tmp, header.data());
+
+  tmp = header.data();
+  FileName fn;
+  fileNameCount_ = 0;
+  while (readFileName(header, fn)) {
+    ++fileNameCount_;
+  }
+  fileNames_.assign(tmp, header.data());
+}
+
+bool Dwarf::LineNumberVM::next(folly::StringPiece& program) {
+  Dwarf::LineNumberVM::StepResult ret;
+  do {
+    ret = step(program);
+  } while (ret == CONTINUE);
+
+  return (ret == COMMIT);
+}
+
+Dwarf::LineNumberVM::FileName Dwarf::LineNumberVM::getFileName(uint64_t index)
+  const {
+  enforce(index != 0, "invalid file index 0");
+
+  FileName fn;
+  if (index <= fileNameCount_) {
+    folly::StringPiece fileNames = fileNames_;
+    for (; index; --index) {
+      if (!readFileName(fileNames, fn)) {
+        abort();
+      }
+    }
+    return fn;
+  }
+
+  index -= fileNameCount_;
+
+  folly::StringPiece program = data_;
+  for (; index; --index) {
+    enforce(nextDefineFile(program, fn), "invalid file index");
+  }
+
+  return fn;
+}
+
+folly::StringPiece Dwarf::LineNumberVM::getIncludeDirectory(uint64_t index)
+  const {
+  if (index == 0) {
+    return folly::StringPiece();
+  }
+
+  enforce(index <= includeDirectoryCount_, "invalid include directory");
+
+  folly::StringPiece includeDirectories = includeDirectories_;
+  folly::StringPiece dir;
+  for (; index; --index) {
+    dir = readNullTerminated(includeDirectories);
+    if (dir.empty()) {
+      abort();  // BUG
+    }
+  }
+
+  return dir;
+}
+
+bool Dwarf::LineNumberVM::readFileName(folly::StringPiece& program,
+                                       FileName& fn) {
+  fn.relativeName = readNullTerminated(program);
+  if (fn.relativeName.empty()) {
+    return false;
+  }
+  fn.directoryIndex = readULEB(program);
+  // Skip over file size and last modified time
+  readULEB(program);
+  readULEB(program);
+  return true;
+}
+
+bool Dwarf::LineNumberVM::nextDefineFile(folly::StringPiece& program,
+                                         FileName& fn) const {
+  while (!program.empty()) {
+    auto opcode = read<uint8_t>(program);
+
+    if (opcode >= opcodeBase_) {  // special opcode
+      continue;
+    }
+
+    if (opcode != 0) {  // standard opcode
+      // Skip, slurp the appropriate number of LEB arguments
+      uint8_t argCount = standardOpcodeLengths_[opcode - 1];
+      while (argCount--) {
+        readULEB(program);
+      }
+      continue;
+    }
+
+    // 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");
+    auto extendedOpcode = read<uint8_t>(program);
+    --length;
+
+    if (opcode == DW_LNE_define_file) {
+      enforce(readFileName(program, fn),
+              "invalid empty file in DW_LNE_define_file");
+      return true;
+    }
+
+    program.advance(length);
+    continue;
+  }
+
+  return false;
+}
+
+Dwarf::LineNumberVM::StepResult Dwarf::LineNumberVM::step(
+    folly::StringPiece& program) {
+  auto opcode = read<uint8_t>(program);
+
+  if (opcode >= opcodeBase_) {  // special opcode
+    uint8_t adjustedOpcode = opcode - opcodeBase_;
+    uint8_t opAdvance = adjustedOpcode / lineRange_;
+
+    address_ += minLength_ * opAdvance;
+    line_ += lineBase_ + adjustedOpcode % lineRange_;
+
+    basicBlock_ = false;
+    prologueEnd_ = false;
+    epilogueBegin_ = false;
+    discriminator_ = 0;
+    return COMMIT;
+  }
+
+  if (opcode != 0) {  // standard opcode
+    // Only interpret opcodes that are recognized by the version we're parsing;
+    // the others are vendor extensions and we should ignore them.
+    switch (opcode) {
+    case DW_LNS_copy:
+      basicBlock_ = false;
+      prologueEnd_ = false;
+      epilogueBegin_ = false;
+      discriminator_ = 0;
+      return COMMIT;
+    case DW_LNS_advance_pc:
+      address_ += minLength_ * readULEB(program);
+      return CONTINUE;
+    case DW_LNS_advance_line:
+      line_ += readSLEB(program);
+      return CONTINUE;
+    case DW_LNS_set_file:
+      file_ = readULEB(program);
+      return CONTINUE;
+    case DW_LNS_set_column:
+      column_ = readULEB(program);
+      return CONTINUE;
+    case DW_LNS_negate_stmt:
+      isStmt_ = !isStmt_;
+      return CONTINUE;
+    case DW_LNS_set_basic_block:
+      basicBlock_ = true;
+      return CONTINUE;
+    case DW_LNS_const_add_pc:
+      address_ += minLength_ * ((255 - opcodeBase_) / lineRange_);
+      return CONTINUE;
+    case DW_LNS_fixed_advance_pc:
+      address_ += read<uint16_t>(program);
+      return CONTINUE;
+    case DW_LNS_set_prologue_end:
+      if (version_ == 2) break;  // not supported in version 2
+      prologueEnd_ = true;
+      return CONTINUE;
+    case DW_LNS_set_epilogue_begin:
+      if (version_ == 2) break;  // not supported in version 2
+      epilogueBegin_ = true;
+      return CONTINUE;
+    case DW_LNS_set_isa:
+      if (version_ == 2) break;  // not supported in version 2
+      isa_ = readULEB(program);
+      return CONTINUE;
+    }
+
+    // Unrecognized standard opcode, slurp the appropriate number of LEB
+    // arguments.
+    uint8_t argCount = standardOpcodeLengths_[opcode - 1];
+    while (argCount--) {
+      readULEB(program);
+    }
+    return CONTINUE;
+  }
+
+  // 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");
+  auto extendedOpcode = read<uint8_t>(program);
+  --length;
+
+  switch (extendedOpcode) {
+  case DW_LNE_end_sequence:
+    return END;
+  case DW_LNE_set_address:
+    address_ = read<uintptr_t>(program);
+    return CONTINUE;
+  case DW_LNE_define_file:
+    // We can't process DW_LNE_define_file here, as it would require us to
+    // use unbounded amounts of state (ie. use the heap).  We'll do a second
+    // pass (using nextDefineFile()) if necessary.
+    break;
+  case DW_LNE_set_discriminator:
+    discriminator_ = readULEB(program);
+    return CONTINUE;
+  }
+
+  // Unrecognized extended opcode
+  program.advance(length);
+  return CONTINUE;
+}
+
+bool Dwarf::LineNumberVM::findAddress(uintptr_t target, Path& file,
+                                      uint64_t& line) {
+  folly::StringPiece program = data_;
+
+  // Within each sequence of instructions, the address may only increase.
+  // Unfortunately, within the same compilation unit, sequences may appear
+  // in any order.  So any sequence is a candidate if it starts at an address
+  // <= the target address, and we know we've found the target address if
+  // a candidate crosses the target address.
+  enum State {
+    START,
+    LOW_SEQ,  // candidate
+    HIGH_SEQ
+  };
+  State state = START;
+  reset();
+
+  uint64_t prevFile = 0;
+  uint64_t prevLine = 0;
+  while (!program.empty()) {
+    bool seqEnd = !next(program);
+
+    if (state == START) {
+      if (!seqEnd) {
+        state = address_ <= target ? LOW_SEQ : HIGH_SEQ;
+      }
+    }
+
+    if (state == LOW_SEQ) {
+      if (address_ > target) {
+        // Found it!  Note that ">" is indeed correct (not ">="), as each
+        // sequence is guaranteed to have one entry past-the-end (emitted by
+        // DW_LNE_end_sequence)
+        if (prevFile == 0) {
+          return false;
+        }
+        auto fn = getFileName(prevFile);
+        file = Path(compilationDirectory_,
+                    getIncludeDirectory(fn.directoryIndex),
+                    fn.relativeName);
+        line = prevLine;
+        return true;
+      }
+      prevFile = file_;
+      prevLine = line_;
+    }
+
+    if (seqEnd) {
+      state = START;
+      reset();
+    }
+  }
+
+  return false;
+}
+
+}  // namespace symbolizer
+}  // namespace facebook
+
diff --git a/folly/experimental/symbolizer/Dwarf.h b/folly/experimental/symbolizer/Dwarf.h
new file mode 100644 (file)
index 0000000..32382c3
--- /dev/null
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2012 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.
+ */
+
+// DWARF record parser
+
+#ifndef FOLLY_EXPERIMENTAL_SYMBOLIZER_DWARF_H_
+#define FOLLY_EXPERIMENTAL_SYMBOLIZER_DWARF_H_
+
+#include <boost/variant.hpp>
+
+#include "folly/experimental/symbolizer/Elf.h"
+#include "folly/Range.h"
+
+namespace facebook {
+namespace symbolizer {
+
+/**
+ * DWARF record parser.
+ *
+ * We only implement enough DWARF functionality to convert from PC address
+ * to file and line number information.
+ *
+ * This means (although they're not part of the public API of this class), we
+ * can parse Debug Information Entries (DIEs), abbreviations, attributes (of
+ * all forms), and we can interpret bytecode for the line number VM.
+ *
+ * We can interpret DWARF records of version 2, 3, or 4, although we don't
+ * 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
+ * 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
+ * with a std::string; the list of file names in the line number VM would be
+ * kept as a vector of strings instead of re-executing the program to look for
+ * DW_LNE_define_file instructions, etc.
+ */
+class Dwarf {
+  // Note that Dwarf uses (and returns) StringPiece a lot.
+  // The StringPieces point within sections in the ELF file, and so will
+  // be live for as long as the passed-in ElfFile is live.
+ public:
+  /** Create a DWARF parser around an ELF file. */
+  explicit Dwarf(const ElfFile* elf);
+
+  /**
+   * Represent a file path a s collection of three parts (base directory,
+   * subdirectory, and file).
+   */
+  class Path {
+   public:
+    Path() { }
+
+    Path(folly::StringPiece baseDir, folly::StringPiece subDir,
+         folly::StringPiece file);
+
+    folly::StringPiece baseDir() const { return baseDir_; };
+    folly::StringPiece subDir() const { return subDir_; }
+    folly::StringPiece file() const { return file_; }
+
+    size_t size() const;
+
+    /**
+     * Copy the Path to a buffer of size bufSize.
+     *
+     * toBuffer behaves like snprintf: It will always null-terminate the
+     * buffer (so it will copy at most bufSize-1 bytes), and it will return
+     * the number of bytes that would have been written if there had been
+     * enough room, so, if toBuffer returns a value >= bufSize, the output
+     * was truncated.
+     */
+    size_t toBuffer(char* buf, size_t bufSize) const;
+
+    void toString(std::string& dest) const;
+    std::string toString() const {
+      std::string s;
+      toString(s);
+      return s;
+    }
+
+    // TODO(tudorb): Implement operator==, operator!=; not as easy as it
+    // seems as the same path can be represented in multiple ways
+   private:
+    folly::StringPiece baseDir_;
+    folly::StringPiece subDir_;
+    folly::StringPiece file_;
+  };
+
+  struct LocationInfo {
+    LocationInfo() : hasMainFile(false), hasFileAndLine(false), line(0) { }
+
+    bool hasMainFile;
+    Path mainFile;
+
+    bool hasFileAndLine;
+    Path file;
+    uint64_t line;
+  };
+
+  /** Find the file and line number information corresponding to address */
+  bool findAddress(uintptr_t address, LocationInfo& info) const;
+
+ private:
+  void init();
+
+  const ElfFile* elf_;
+
+  // DWARF section made up of chunks, each prefixed with a length header.
+  // The length indicates whether the chunk is DWARF-32 or DWARF-64, which
+  // guides interpretation of "section offset" records.
+  // (yes, DWARF-32 and DWARF-64 sections may coexist in the same file)
+  class Section {
+   public:
+    Section() : is64Bit_(false) { }
+
+    explicit Section(folly::StringPiece d);
+
+    // Return next chunk, if any; the 4- or 12-byte length was already
+    // parsed and isn't part of the chunk.
+    bool next(folly::StringPiece& chunk);
+
+    // Is the current chunk 64 bit?
+    bool is64Bit() const { return is64Bit_; }
+
+   private:
+    // Yes, 32- and 64- bit sections may coexist.  Yikes!
+    bool is64Bit_;
+    folly::StringPiece data_;
+  };
+
+  // Abbreviation for a Debugging Information Entry.
+  struct DIEAbbreviation {
+    uint64_t code;
+    uint64_t tag;
+    bool hasChildren;
+
+    struct Attribute {
+      uint64_t name;
+      uint64_t form;
+    };
+
+    folly::StringPiece attributes;
+  };
+
+  // Interpreter for the line number bytecode VM
+  class LineNumberVM {
+   public:
+    LineNumberVM(folly::StringPiece data,
+                 folly::StringPiece compilationDirectory);
+
+    bool findAddress(uintptr_t address, Path& file, uint64_t& line);
+
+   private:
+    void init();
+    void reset();
+
+    // Execute until we commit one new row to the line number matrix
+    bool next(folly::StringPiece& program);
+    enum StepResult {
+      CONTINUE,  // Continue feeding opcodes
+      COMMIT,    // Commit new <address, file, line> tuple
+      END,       // End of sequence
+    };
+    // Execute one opcode
+    StepResult step(folly::StringPiece& program);
+
+    struct FileName {
+      folly::StringPiece relativeName;
+      // 0 = current compilation directory
+      // otherwise, 1-based index in the list of include directories
+      uint64_t directoryIndex;
+    };
+    // Read one FileName object, advance sp
+    static bool readFileName(folly::StringPiece& sp, FileName& fn);
+
+    // Get file name at given index; may be in the initial table
+    // (fileNames_) or defined using DW_LNE_define_file (and we reexecute
+    // enough of the program to find it, if so)
+    FileName getFileName(uint64_t index) const;
+
+    // Get include directory at given index
+    folly::StringPiece getIncludeDirectory(uint64_t index) const;
+
+    // Execute opcodes until finding a DW_LNE_define_file and return true;
+    // return file at the end.
+    bool nextDefineFile(folly::StringPiece& program, FileName& fn) const;
+
+    // Initialization
+    bool is64Bit_;
+    folly::StringPiece data_;
+    folly::StringPiece compilationDirectory_;
+
+    // Header
+    uint16_t version_;
+    uint8_t minLength_;
+    bool defaultIsStmt_;
+    int8_t lineBase_;
+    uint8_t lineRange_;
+    uint8_t opcodeBase_;
+    const uint8_t* standardOpcodeLengths_;
+
+    folly::StringPiece includeDirectories_;
+    size_t includeDirectoryCount_;
+
+    folly::StringPiece fileNames_;
+    size_t fileNameCount_;
+
+    // State machine registers
+    uint64_t address_;
+    uint64_t file_;
+    uint64_t line_;
+    uint64_t column_;
+    bool isStmt_;
+    bool basicBlock_;
+    bool endSequence_;
+    bool prologueEnd_;
+    bool epilogueBegin_;
+    uint64_t isa_;
+    uint64_t discriminator_;
+  };
+
+  // Read an abbreviation from a StringPiece, return true if at end; advance sp
+  static bool readAbbreviation(folly::StringPiece& sp, DIEAbbreviation& abbr);
+
+  // Get abbreviation corresponding to a code, in the chunk starting at
+  // offset in the .debug_abbrev section
+  DIEAbbreviation getAbbreviation(uint64_t code, uint64_t offset) const;
+
+  // Read one attribute <name, form> pair, advance sp; returns <0, 0> at end.
+  static DIEAbbreviation::Attribute readAttribute(folly::StringPiece& sp);
+
+  // Read one attribute value, advance sp
+  typedef boost::variant<uint64_t, folly::StringPiece> AttributeValue;
+  AttributeValue readAttributeValue(
+      folly::StringPiece& sp,
+      uint64_t form,
+      bool is64Bit) const;
+
+  // Get an ELF section by name, return true if found
+  bool getSection(const char* name, folly::StringPiece* section) const;
+
+  // Get a string from the .debug_str section
+  folly::StringPiece getStringFromStringSection(uint64_t offset) const;
+
+  folly::StringPiece info_;       // .debug_info
+  folly::StringPiece abbrev_;     // .debug_abbrev
+  folly::StringPiece aranges_;    // .debug_aranges
+  folly::StringPiece line_;       // .debug_line
+  folly::StringPiece strings_;    // .debug_str
+};
+
+inline std::ostream& operator<<(std::ostream& out, const Dwarf::Path& path) {
+  return out << path.toString();
+}
+
+}  // namespace symbolizer
+}  // namespace facebook
+
+#endif /* FOLLY_EXPERIMENTAL_SYMBOLIZER_DWARF_H_ */
+
diff --git a/folly/experimental/symbolizer/Elf-inl.h b/folly/experimental/symbolizer/Elf-inl.h
new file mode 100644 (file)
index 0000000..033dc76
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 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_EXPERIMENTAL_SYMBOLIZER_ELF_H_
+# error This file must be included from Elf.h
+#endif
+
+namespace facebook {
+namespace symbolizer {
+
+template <class Fn>
+const ElfW(Shdr)* ElfFile::iterateSections(Fn fn) const {
+  const ElfW(Shdr)* ptr = &at<ElfW(Shdr)>(elfHeader().e_shoff);
+  for (size_t i = 0; i < elfHeader().e_shnum; i++, ptr++) {
+    if (fn(*ptr)) {
+      return ptr;
+    }
+  }
+
+  return nullptr;
+}
+
+template <class Fn>
+const ElfW(Shdr)* ElfFile::iterateSectionsWithType(uint32_t type, Fn fn)
+  const {
+  return iterateSections(
+      [&](const ElfW(Shdr)& sh) {
+        return sh.sh_type == type && fn(sh);
+      });
+}
+
+template <class Fn>
+const char* ElfFile::iterateStrings(const ElfW(Shdr)& stringTable, Fn fn)
+  const {
+  validateStringTable(stringTable);
+
+  const char* start = file_ + stringTable.sh_offset;
+  const char* end = start + stringTable.sh_size;
+
+  const char* ptr = start;
+  while (ptr != end && !fn(ptr)) {
+    ptr += strlen(ptr) + 1;
+  }
+
+  return ptr != end ? ptr : nullptr;
+}
+
+
+}  // namespace symbolizer
+}  // namespace facebook
+
diff --git a/folly/experimental/symbolizer/Elf.cpp b/folly/experimental/symbolizer/Elf.cpp
new file mode 100644 (file)
index 0000000..b9eb372
--- /dev/null
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2012 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/Elf.h"
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <endian.h>
+#include <fcntl.h>
+
+#include <string>
+
+#include <glog/logging.h>
+
+#include "folly/Conv.h"
+
+namespace facebook {
+namespace symbolizer {
+
+ElfFile::ElfFile()
+  : fd_(-1),
+    file_(static_cast<char*>(MAP_FAILED)),
+    length_(0),
+    baseAddress_(0) {
+}
+
+ElfFile::ElfFile(const char* name)
+  : fd_(open(name, O_RDONLY)),
+    file_(static_cast<char*>(MAP_FAILED)),
+    length_(0),
+    baseAddress_(0) {
+  if (fd_ == -1) {
+    systemError("open ", name);
+  }
+
+  struct stat st;
+  int r = fstat(fd_, &st);
+  if (r == -1) {
+    systemError("fstat");
+  }
+
+  length_ = st.st_size;
+  file_ = static_cast<char*>(
+      mmap(nullptr, length_, PROT_READ, MAP_SHARED, fd_, 0));
+  if (file_ == MAP_FAILED) {
+    systemError("mmap");
+  }
+  init();
+}
+
+ElfFile::~ElfFile() {
+  destroy();
+}
+
+ElfFile::ElfFile(ElfFile&& other)
+  : fd_(other.fd_),
+    file_(other.file_),
+    length_(other.length_),
+    baseAddress_(other.baseAddress_) {
+  other.fd_ = -1;
+  other.file_ = static_cast<char*>(MAP_FAILED);
+  other.length_ = 0;
+  other.baseAddress_ = 0;
+}
+
+ElfFile& ElfFile::operator=(ElfFile&& other) {
+  assert(this != &other);
+  destroy();
+
+  fd_ = other.fd_;
+  file_ = other.file_;
+  length_ = other.length_;
+  baseAddress_ = other.baseAddress_;
+
+  other.fd_ = -1;
+  other.file_ = static_cast<char*>(MAP_FAILED);
+  other.length_ = 0;
+  other.baseAddress_ = 0;
+
+  return *this;
+}
+
+void ElfFile::destroy() {
+  if (file_ != MAP_FAILED) {
+    munmap(file_, length_);
+  }
+
+  if (fd_ != -1) {
+    close(fd_);
+  }
+}
+
+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");
+
+  // 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");
+#undef P1
+#undef P2
+#undef EXPECTED_CLASS
+
+  // Validate ELF data encoding (LSB/MSB)
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+# define EXPECTED_ENCODING ELFDATA2LSB
+#elif __BYTE_ORDER == __BIG_ENDIAN
+# define EXPECTED_ENCODING ELFDATA2MSB
+#else
+# error Unsupported byte order
+#endif
+  enforce(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");
+
+  // We only support executable and shared object files
+  enforce(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");
+
+  const ElfW(Phdr)* programHeader = &at<ElfW(Phdr)>(elfHeader.e_phoff);
+  bool foundBase = false;
+  for (size_t i = 0; i < elfHeader.e_phnum; programHeader++, i++) {
+    // Program headers are sorted by load address, so the first PT_LOAD
+    // header gives us the base address.
+    if (programHeader->p_type == PT_LOAD) {
+      baseAddress_ = programHeader->p_vaddr;
+      foundBase = true;
+      break;
+    }
+  }
+
+  enforce(foundBase, "could not find base address");
+}
+
+const ElfW(Shdr)* ElfFile::getSectionByIndex(size_t idx) const {
+  enforce(idx < elfHeader().e_shnum, "invalid section index");
+  return &at<ElfW(Shdr)>(elfHeader().e_shoff + idx * sizeof(ElfW(Shdr)));
+}
+
+folly::StringPiece ElfFile::getSectionBody(const ElfW(Shdr)& section) const {
+  return folly::StringPiece(file_ + section.sh_offset, section.sh_size);
+}
+
+void ElfFile::validateStringTable(const ElfW(Shdr)& stringTable) const {
+  enforce(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");
+}
+
+const char* ElfFile::getString(const ElfW(Shdr)& stringTable, size_t offset)
+  const {
+  validateStringTable(stringTable);
+  enforce(offset < stringTable.sh_size, "invalid offset in string table");
+
+  return file_ + stringTable.sh_offset + offset;
+}
+
+const char* ElfFile::getSectionName(const ElfW(Shdr)& section) const {
+  if (elfHeader().e_shstrndx == SHN_UNDEF) {
+    return nullptr;  // no section name string table
+  }
+
+  const ElfW(Shdr)& sectionNames = *getSectionByIndex(elfHeader().e_shstrndx);
+  return getString(sectionNames, section.sh_name);
+}
+
+const ElfW(Shdr)* ElfFile::getSectionByName(const char* name) const {
+  if (elfHeader().e_shstrndx == SHN_UNDEF) {
+    return nullptr;  // no section name string table
+  }
+
+  // Find offset in the section name string table of the requested name
+  const ElfW(Shdr)& sectionNames = *getSectionByIndex(elfHeader().e_shstrndx);
+  const char* foundName = iterateStrings(
+      sectionNames,
+      [&] (const char* s) { return !strcmp(name, s); });
+  if (foundName == nullptr) {
+    return nullptr;
+  }
+
+  size_t offset = foundName - (file_ + sectionNames.sh_offset);
+
+  // Find section with the appropriate sh_name offset
+  const ElfW(Shdr)* foundSection = iterateSections(
+    [&](const ElfW(Shdr)& sh) {
+      if (sh.sh_name == offset) {
+        return true;
+      }
+      return false;
+    });
+  return foundSection;
+}
+
+ElfFile::Symbol ElfFile::getDefinitionByAddress(uintptr_t address) const {
+  Symbol foundSymbol {nullptr, nullptr};
+
+  auto find = [&] (const ElfW(Shdr)& section) {
+    enforce(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 = &at<ElfW(Sym)>(section.sh_offset + section.sh_size);
+    for (; sym != end; ++sym) {
+      // st_info has the same representation on 32- and 64-bit platforms
+      auto type = ELF32_ST_TYPE(sym->st_info);
+
+      // TODO(tudorb): Handle STT_TLS, but then we'd have to understand
+      // thread-local relocations.  If all we're looking up is functions
+      // (instruction pointers), it doesn't matter, though.
+      if (type != STT_OBJECT && type != STT_FUNC) {
+        continue;
+      }
+      if (sym->st_shndx == SHN_UNDEF) {
+        continue;  // not a definition
+      }
+      if (address >= sym->st_value && address < sym->st_value + sym->st_size) {
+        foundSymbol.first = &section;
+        foundSymbol.second = sym;
+        return true;
+      }
+    }
+
+    return false;
+  };
+
+  // Try the .dynsym section first if it exists, it's smaller.
+  (iterateSectionsWithType(SHT_DYNSYM, find) ||
+   iterateSectionsWithType(SHT_SYMTAB, find));
+
+  return foundSymbol;
+}
+
+const char* ElfFile::getSymbolName(Symbol symbol) const {
+  if (!symbol.first || !symbol.second) {
+    return nullptr;
+  }
+
+  if (symbol.second->st_name == 0) {
+    return nullptr;  // symbol has no name
+  }
+
+  if (symbol.first->sh_link == SHN_UNDEF) {
+    return nullptr;  // symbol table has no strings
+  }
+
+  return getString(*getSectionByIndex(symbol.first->sh_link),
+                   symbol.second->st_name);
+}
+
+}  // namespace symbolizer
+}  // namespace facebook
+
diff --git a/folly/experimental/symbolizer/Elf.h b/folly/experimental/symbolizer/Elf.h
new file mode 100644 (file)
index 0000000..4187aa6
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2012 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.
+ */
+
+// ELF file parser
+
+#ifndef FOLLY_EXPERIMENTAL_SYMBOLIZER_ELF_H_
+#define FOLLY_EXPERIMENTAL_SYMBOLIZER_ELF_H_
+
+#include <stdio.h>
+#include <elf.h>
+#include <link.h>  // For ElfW()
+
+#include <stdexcept>
+#include <system_error>
+
+#include "folly/Likely.h"
+#include "folly/Range.h"
+#include "folly/Conv.h"
+
+namespace facebook {
+namespace symbolizer {
+
+/**
+ * ELF file parser.
+ *
+ * We handle native files only (32-bit files on a 32-bit platform, 64-bit files
+ * on a 64-bit platform), and only executables (ET_EXEC) and shared objects
+ * (ET_DYN).
+ */
+class ElfFile {
+ public:
+  ElfFile();
+  explicit ElfFile(const char* name);
+  ~ElfFile();
+
+  ElfFile(ElfFile&& other);
+  ElfFile& operator=(ElfFile&& other);
+
+  /** Retrieve the ELF header */
+  const ElfW(Ehdr)& elfHeader() const {
+    return at<ElfW(Ehdr)>(0);
+  }
+
+  /**
+   * Get the base address, the address where the file should be loaded if
+   * no relocations happened.
+   */
+  uintptr_t getBaseAddress() const {
+    return baseAddress_;
+  }
+
+  /** Find a section given its name */
+  const ElfW(Shdr)* getSectionByName(const char* name) const;
+
+  /** Find a section given its index in the section header table */
+  const ElfW(Shdr)* getSectionByIndex(size_t idx) const;
+
+  /** Retrieve the name of a section */
+  const char* getSectionName(const ElfW(Shdr)& section) const;
+
+  /** Get the actual section body */
+  folly::StringPiece getSectionBody(const ElfW(Shdr)& section) const;
+
+  /** Retrieve a string from a string table section */
+  const char* getString(const ElfW(Shdr)& stringTable, size_t offset) const;
+
+  /**
+   * Iterate over all strings in a string table section for as long as
+   * fn(str) returns false.
+   * Returns the current ("found") string when fn returned true, or nullptr
+   * if fn returned false for all strings in the table.
+   */
+  template <class Fn>
+  const char* iterateStrings(const ElfW(Shdr)& stringTable, Fn fn) const;
+
+  /**
+   * Iterate over all sections for as long as fn(section) returns false.
+   * Returns a pointer to the current ("found") section when fn returned
+   * true, or nullptr if fn returned false for all sections.
+   */
+  template <class Fn>
+  const ElfW(Shdr)* iterateSections(Fn fn) const;
+
+  /**
+   * Iterate over all sections with a given type.  Similar to
+   * iterateSections(), but filtered only for sections with the given type.
+   */
+  template <class Fn>
+  const ElfW(Shdr)* iterateSectionsWithType(uint32_t type, Fn fn) const;
+
+  /**
+   * Find symbol definition by address.
+   * Note that this is the file virtual address, so you need to undo
+   * any relocation that might have happened.
+   */
+  typedef std::pair<const ElfW(Shdr)*, const ElfW(Sym)*> Symbol;
+  Symbol getDefinitionByAddress(uintptr_t address) const;
+
+  /**
+   * Retrieve symbol name.
+   */
+  const char* getSymbolName(Symbol symbol) const;
+
+ private:
+  void init();
+  void destroy();
+  ElfFile(const ElfFile&) = delete;
+  ElfFile& operator=(const ElfFile&) = delete;
+
+  void validateStringTable(const ElfW(Shdr)& stringTable) const;
+
+  template <class T>
+  const T& at(off_t offset) const {
+    return *reinterpret_cast<T*>(file_ + offset);
+  }
+
+  int fd_;
+  char* file_;     // mmap() location
+  size_t length_;  // mmap() length
+
+  uintptr_t baseAddress_;
+};
+
+template <class... Args>
+void systemError(Args... args) __attribute__((noreturn));
+
+template <class... Args>
+void systemError(Args... args) {
+  throw std::system_error(errno, std::system_category(),
+                          folly::to<std::string>(args...));
+}
+
+template <class... Args>
+inline void enforce(bool v, Args... args) {
+  if (UNLIKELY(!v)) {
+    throw std::runtime_error(folly::to<std::string>(args...));
+  }
+}
+
+}  // namespace symbolizer
+}  // namespace facebook
+
+#include "folly/experimental/symbolizer/Elf-inl.h"
+
+#endif /* FOLLY_EXPERIMENTAL_SYMBOLIZER_ELF_H_ */
+
diff --git a/folly/experimental/symbolizer/ElfUtil.cpp b/folly/experimental/symbolizer/ElfUtil.cpp
new file mode 100644 (file)
index 0000000..7883aab
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 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/Elf.h"
+
+#include <stdio.h>
+
+#include <gflags/gflags.h>
+#include <glog/logging.h>
+
+using namespace facebook;
+using namespace facebook::symbolizer;
+
+int main(int argc, char *argv[]) {
+  google::ParseCommandLineFlags(&argc. &argv, true);
+  CHECK_GE(argc, 2);
+
+  ElfFile elf(argv[1]);
+
+  if (argc > 2) {
+    auto section = elf.getSectionByName(argv[2]);
+    printf("Section %s: %s\n",
+           argv[2],
+           (section ? "found" : "not found"));
+  }
+
+  auto sym = elf.getDefinitionByAddress(reinterpret_cast<uintptr_t>(main));
+  if (sym.first) {
+    printf("found %s\n", elf.getSymbolName(sym));
+  } else {
+    printf("main not found\n");
+  }
+
+  return 0;
+}
diff --git a/folly/experimental/symbolizer/Symbolizer.cpp b/folly/experimental/symbolizer/Symbolizer.cpp
new file mode 100644 (file)
index 0000000..861b332
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2012 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 <boost/regex.hpp>
+
+#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"
+
+namespace facebook {
+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');
+  return val;
+}
+
+struct MappedFile {
+  uintptr_t begin;
+  uintptr_t end;
+  std::string name;
+};
+
+}  // namespace
+
+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
+    }
+
+    if (begin <= address && address < end) {
+      found = true;
+      foundFile.begin = begin;
+      foundFile.end = end;
+      foundFile.name.assign(match[4].first, match[4].second);
+      break;
+    }
+  }
+
+  if (!found) {
+    return false;
+  }
+
+  auto& elfFile = getFile(foundFile.name);
+  // Undo relocation
+  uintptr_t origAddress = address - foundFile.begin + elfFile.getBaseAddress();
+
+  auto sym = elfFile.getDefinitionByAddress(origAddress);
+  if (!sym.first) {
+    return false;
+  }
+
+  auto name = elfFile.getSymbolName(sym);
+  if (name) {
+    symbolName = name;
+  }
+
+  Dwarf(&elfFile).findAddress(origAddress, location);
+  return true;
+}
+
+ElfFile& Symbolizer::getFile(const std::string& name) {
+  auto pos = elfFiles_.find(name);
+  if (pos != elfFiles_.end()) {
+    return pos->second;
+  }
+
+  return elfFiles_.insert(
+      std::make_pair(name, ElfFile(name.c_str()))).first->second;
+}
+
+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;
+
+  if (!symbolName.empty()) {
+    out << " " << folly::demangle(symbolName.toString().c_str());
+
+    std::string file;
+    if (location.hasFileAndLine) {
+      file = location.file.toString();
+      out << "   " << file << ":" << location.line;
+    }
+
+    std::string mainFile;
+    if (location.hasMainFile) {
+      mainFile = location.mainFile.toString();
+      if (!location.hasFileAndLine || file != mainFile) {
+        out << "\n                         (compiling "
+            << location.mainFile << ")";
+      }
+    }
+  } else {
+    out << " (unknown)";
+  }
+  out << "\n";
+}
+
+}  // namespace symbolizer
+}  // namespace facebook
diff --git a/folly/experimental/symbolizer/Symbolizer.h b/folly/experimental/symbolizer/Symbolizer.h
new file mode 100644 (file)
index 0000000..0b38a5d
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 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_EXPERIMENTAL_SYMBOLIZER_SYMBOLIZER_H_
+#define FOLLY_EXPERIMENTAL_SYMBOLIZER_SYMBOLIZER_H_
+
+#include <cstdint>
+#include <string>
+#include <unordered_map>
+
+#include "folly/Range.h"
+#include "folly/experimental/symbolizer/Elf.h"
+#include "folly/experimental/symbolizer/Dwarf.h"
+
+namespace facebook {
+namespace symbolizer {
+
+/**
+ * Convert an address to symbol name and source location.
+ */
+class Symbolizer {
+ public:
+  /**
+   * 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.
+   */
+  bool symbolize(uintptr_t address, folly::StringPiece& symbolName,
+                 Dwarf::LocationInfo& location);
+
+  static void write(std::ostream& out, uintptr_t address,
+                    folly::StringPiece symbolName,
+                    const Dwarf::LocationInfo& location);
+
+ private:
+  ElfFile& getFile(const std::string& name);
+  // cache open ELF files
+  std::unordered_map<std::string, ElfFile> elfFiles_;
+};
+
+}  // namespace symbolizer
+}  // namespace facebook
+
+#endif /* FOLLY_EXPERIMENTAL_SYMBOLIZER_SYMBOLIZER_H_ */
+
diff --git a/folly/experimental/symbolizer/SymbolizerTest.cpp b/folly/experimental/symbolizer/SymbolizerTest.cpp
new file mode 100644 (file)
index 0000000..8df7097
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 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 "common/init/Init.h"
+#include "glog/logging.h"
+
+using namespace facebook;
+using namespace facebook::symbolizer;
+
+int main(int argc, char *argv[]) {
+  facebook::initFacebook(&argc, &argv);
+  Symbolizer s;
+  folly::StringPiece name;
+  Dwarf::LocationInfo location;
+  CHECK(s.symbolize(reinterpret_cast<uintptr_t>(main), name, location));
+  LOG(INFO) << name << " " << location.file << " " << location.line << " ("
+            << location.mainFile << ")";
+  return 0;
+}
+