From e8cabb32411a5e4f7928496faa64cecc999dfcd7 Mon Sep 17 00:00:00 2001 From: Tudor Bosman Date: Wed, 29 Aug 2012 18:20:27 -0700 Subject: [PATCH] Move exception tracer library to folly/experimental 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 --- .../exception_tracer/ExceptionAbi.h | 61 ++ .../exception_tracer/ExceptionTracer.cpp | 191 ++++ .../exception_tracer/ExceptionTracer.h | 52 ++ .../exception_tracer/ExceptionTracerLib.cpp | 184 ++++ .../exception_tracer/ExceptionTracerTest.cpp | 98 +++ folly/experimental/exception_tracer/README | 23 + .../exception_tracer/StackTrace.c | 116 +++ .../exception_tracer/StackTrace.h | 83 ++ folly/experimental/symbolizer/Dwarf.cpp | 819 ++++++++++++++++++ folly/experimental/symbolizer/Dwarf.h | 276 ++++++ folly/experimental/symbolizer/Elf-inl.h | 65 ++ folly/experimental/symbolizer/Elf.cpp | 291 +++++++ folly/experimental/symbolizer/Elf.h | 159 ++++ folly/experimental/symbolizer/ElfUtil.cpp | 49 ++ folly/experimental/symbolizer/Symbolizer.cpp | 161 ++++ folly/experimental/symbolizer/Symbolizer.h | 61 ++ .../symbolizer/SymbolizerTest.cpp | 37 + 17 files changed, 2726 insertions(+) create mode 100644 folly/experimental/exception_tracer/ExceptionAbi.h create mode 100644 folly/experimental/exception_tracer/ExceptionTracer.cpp create mode 100644 folly/experimental/exception_tracer/ExceptionTracer.h create mode 100644 folly/experimental/exception_tracer/ExceptionTracerLib.cpp create mode 100644 folly/experimental/exception_tracer/ExceptionTracerTest.cpp create mode 100644 folly/experimental/exception_tracer/README create mode 100644 folly/experimental/exception_tracer/StackTrace.c create mode 100644 folly/experimental/exception_tracer/StackTrace.h create mode 100644 folly/experimental/symbolizer/Dwarf.cpp create mode 100644 folly/experimental/symbolizer/Dwarf.h create mode 100644 folly/experimental/symbolizer/Elf-inl.h create mode 100644 folly/experimental/symbolizer/Elf.cpp create mode 100644 folly/experimental/symbolizer/Elf.h create mode 100644 folly/experimental/symbolizer/ElfUtil.cpp create mode 100644 folly/experimental/symbolizer/Symbolizer.cpp create mode 100644 folly/experimental/symbolizer/Symbolizer.h create mode 100644 folly/experimental/symbolizer/SymbolizerTest.cpp diff --git a/folly/experimental/exception_tracer/ExceptionAbi.h b/folly/experimental/exception_tracer/ExceptionAbi.h new file mode 100644 index 00000000..6bc79e0f --- /dev/null +++ b/folly/experimental/exception_tracer/ExceptionAbi.h @@ -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 +#include + +#include + +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 index 00000000..3e417d18 --- /dev/null +++ b/folly/experimental/exception_tracer/ExceptionTracer.cpp @@ -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 +#include +#include + +#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 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 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 index 00000000..b742f190 --- /dev/null +++ b/folly/experimental/exception_tracer/ExceptionTracer.h @@ -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 +#include + +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 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 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 index 00000000..16fe73fc --- /dev/null +++ b/folly/experimental/exception_tracer/ExceptionTracerLib.cpp @@ -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 +#include +#include + +#include + +#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 index 00000000..4427c870 --- /dev/null +++ b/folly/experimental/exception_tracer/ExceptionTracerTest.cpp @@ -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 + +#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 index 00000000..50da4e98 --- /dev/null +++ b/folly/experimental/exception_tracer/README @@ -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 index 00000000..fbc4a78e --- /dev/null +++ b/folly/experimental/exception_tracer/StackTrace.c @@ -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 +#include +#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 index 00000000..f7da51bc --- /dev/null +++ b/folly/experimental/exception_tracer/StackTrace.h @@ -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 +#include + +#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 index 00000000..40e42d6c --- /dev/null +++ b/folly/experimental/symbolizer/Dwarf.cpp @@ -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 + +#include + +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 +typename std::enable_if::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(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(sp) : read(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( + 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(chunk); + is64Bit_ = (initialLength == (uint32_t)-1); + auto length = is64Bit_ ? read(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(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(sp); + case DW_FORM_block1: + return readBytes(sp, read(sp)); + case DW_FORM_block2: + return readBytes(sp, read(sp)); + case DW_FORM_block4: + return readBytes(sp, read(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(sp); + case DW_FORM_data2: // fallthrough + case DW_FORM_ref2: + return read(sp); + case DW_FORM_data4: // fallthrough + case DW_FORM_ref4: + return read(sp); + case DW_FORM_data8: // fallthrough + case DW_FORM_ref8: + return read(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(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(chunk); + enforce(version == 2, "invalid aranges version"); + + debugInfoOffset = readOffset(chunk, arangesSection.is64Bit()); + auto addressSize = read(chunk); + enforce(addressSize == sizeof(uintptr_t), "invalid address size"); + auto segmentSize = read(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(chunk); + auto length = read(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(chunk); + enforce(version >= 2 && version <= 4, "invalid info version"); + uint64_t abbrevOffset = readOffset(chunk, debugInfoSection.is64Bit()); + auto addressSize = read(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(val); + foundLineOffset = true; + break; + case DW_AT_comp_dir: + // Compilation directory + compilationDirectory = boost::get(val); + break; + case DW_AT_name: + // File name of main file being compiled + mainFileName = boost::get(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(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(header); + if (version_ == 4) { // Version 2 and 3 records don't have this + uint8_t maxOpsPerInstruction = read(header); + enforce(maxOpsPerInstruction == 1, "VLIW not supported"); + } + defaultIsStmt_ = read(header); + lineBase_ = read(header); // yes, signed + lineRange_ = read(header); + opcodeBase_ = read(header); + enforce(opcodeBase_ != 0, "invalid opcode base"); + standardOpcodeLengths_ = reinterpret_cast(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(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(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(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(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(program); + --length; + + switch (extendedOpcode) { + case DW_LNE_end_sequence: + return END; + case DW_LNE_set_address: + address_ = read(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 index 00000000..32382c36 --- /dev/null +++ b/folly/experimental/symbolizer/Dwarf.h @@ -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 + +#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 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 pair, advance sp; returns <0, 0> at end. + static DIEAbbreviation::Attribute readAttribute(folly::StringPiece& sp); + + // Read one attribute value, advance sp + typedef boost::variant 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 index 00000000..033dc76f --- /dev/null +++ b/folly/experimental/symbolizer/Elf-inl.h @@ -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 +const ElfW(Shdr)* ElfFile::iterateSections(Fn fn) const { + const ElfW(Shdr)* ptr = &at(elfHeader().e_shoff); + for (size_t i = 0; i < elfHeader().e_shnum; i++, ptr++) { + if (fn(*ptr)) { + return ptr; + } + } + + return nullptr; +} + +template +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 +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 index 00000000..b9eb372f --- /dev/null +++ b/folly/experimental/symbolizer/Elf.cpp @@ -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 +#include +#include +#include +#include + +#include + +#include + +#include "folly/Conv.h" + +namespace facebook { +namespace symbolizer { + +ElfFile::ElfFile() + : fd_(-1), + file_(static_cast(MAP_FAILED)), + length_(0), + baseAddress_(0) { +} + +ElfFile::ElfFile(const char* name) + : fd_(open(name, O_RDONLY)), + file_(static_cast(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( + 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(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(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(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(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(section.sh_offset); + const ElfW(Sym)* end = &at(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 + diff --git a/folly/experimental/symbolizer/Elf.h b/folly/experimental/symbolizer/Elf.h new file mode 100644 index 00000000..4187aa69 --- /dev/null +++ b/folly/experimental/symbolizer/Elf.h @@ -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 +#include +#include // For ElfW() + +#include +#include + +#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(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 + 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 + 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 + 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 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 + const T& at(off_t offset) const { + return *reinterpret_cast(file_ + offset); + } + + int fd_; + char* file_; // mmap() location + size_t length_; // mmap() length + + uintptr_t baseAddress_; +}; + +template +void systemError(Args... args) __attribute__((noreturn)); + +template +void systemError(Args... args) { + throw std::system_error(errno, std::system_category(), + folly::to(args...)); +} + +template +inline void enforce(bool v, Args... args) { + if (UNLIKELY(!v)) { + throw std::runtime_error(folly::to(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 index 00000000..7883aabb --- /dev/null +++ b/folly/experimental/symbolizer/ElfUtil.cpp @@ -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 + +#include +#include + +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(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 index 00000000..861b3321 --- /dev/null +++ b/folly/experimental/symbolizer/Symbolizer.cpp @@ -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 + +#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 index 00000000..0b38a5db --- /dev/null +++ b/folly/experimental/symbolizer/Symbolizer.h @@ -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 +#include +#include + +#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 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 index 00000000..8df70976 --- /dev/null +++ b/folly/experimental/symbolizer/SymbolizerTest.cpp @@ -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(main), name, location)); + LOG(INFO) << name << " " << location.file << " " << location.line << " (" + << location.mainFile << ")"; + return 0; +} + -- 2.34.1