--- /dev/null
+/*
+ * 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_ */
+
--- /dev/null
+/*
+ * 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
+
--- /dev/null
+/*
+ * 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_ */
+
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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;
+}
+
--- /dev/null
+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).
+
--- /dev/null
+/*
+ * 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;
+}
+
--- /dev/null
+/*
+ * 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_ */
+
--- /dev/null
+/*
+ * 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
+
--- /dev/null
+/*
+ * 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_ */
+
--- /dev/null
+/*
+ * 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
+
--- /dev/null
+/*
+ * 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 = §ion;
+ 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
+
--- /dev/null
+/*
+ * 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_ */
+
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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_ */
+
--- /dev/null
+/*
+ * 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;
+}
+