Commit e8cabb32 authored by Tudor Bosman's avatar Tudor Bosman Committed by Jordan DeLong

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
parent b94353f2
/*
* 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_ */
/*
* 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
/*
* 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_ */
/*
* 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
/*
* 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;
}
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).
/*
* 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;
}
/*
* 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_ */
This diff is collapsed.
/*
* 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_ */
/*
* 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
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "folly/experimental/symbolizer/Elf.h"
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <endian.h>
#include <fcntl.h>
#include <string>
#include <glog/logging.h>
#include "folly/Conv.h"
namespace facebook {
namespace symbolizer {
ElfFile::ElfFile()
: fd_(-1),
file_(static_cast<char*>(MAP_FAILED)),
length_(0),
baseAddress_(0) {
}
ElfFile::ElfFile(const char* name)
: fd_(open(name, O_RDONLY)),
file_(static_cast<char*>(MAP_FAILED)),
length_(0),
baseAddress_(0) {
if (fd_ == -1) {
systemError("open ", name);
}
struct stat st;
int r = fstat(fd_, &st);
if (r == -1) {
systemError("fstat");
}
length_ = st.st_size;
file_ = static_cast<char*>(
mmap(nullptr, length_, PROT_READ, MAP_SHARED, fd_, 0));
if (file_ == MAP_FAILED) {
systemError("mmap");
}
init();
}
ElfFile::~ElfFile() {
destroy();
}
ElfFile::ElfFile(ElfFile&& other)
: fd_(other.fd_),
file_(other.file_),
length_(other.length_),
baseAddress_(other.baseAddress_) {
other.fd_ = -1;
other.file_ = static_cast<char*>(MAP_FAILED);
other.length_ = 0;
other.baseAddress_ = 0;
}
ElfFile& ElfFile::operator=(ElfFile&& other) {
assert(this != &other);
destroy();
fd_ = other.fd_;
file_ = other.file_;
length_ = other.length_;
baseAddress_ = other.baseAddress_;
other.fd_ = -1;
other.file_ = static_cast<char*>(MAP_FAILED);
other.length_ = 0;
other.baseAddress_ = 0;
return *this;
}
void ElfFile::destroy() {
if (file_ != MAP_FAILED) {
munmap(file_, length_);
}
if (fd_ != -1) {
close(fd_);
}
}
void ElfFile::init() {
auto& elfHeader = this->elfHeader();
// Validate ELF magic numbers
enforce(elfHeader.e_ident[EI_MAG0] == ELFMAG0 &&
elfHeader.e_ident[EI_MAG1] == ELFMAG1 &&
elfHeader.e_ident[EI_MAG2] == ELFMAG2 &&
elfHeader.e_ident[EI_MAG3] == ELFMAG3,
"invalid ELF magic");
// Validate ELF class (32/64 bits)
#define EXPECTED_CLASS P1(ELFCLASS, __ELF_NATIVE_CLASS)
#define P1(a, b) P2(a, b)
#define P2(a, b) a ## b
enforce(elfHeader.e_ident[EI_CLASS] == EXPECTED_CLASS,
"invalid ELF class");
#undef P1
#undef P2
#undef EXPECTED_CLASS
// Validate ELF data encoding (LSB/MSB)
#if __BYTE_ORDER == __LITTLE_ENDIAN
# define EXPECTED_ENCODING ELFDATA2LSB
#elif __BYTE_ORDER == __BIG_ENDIAN
# define EXPECTED_ENCODING ELFDATA2MSB
#else
# error Unsupported byte order
#endif
enforce(elfHeader.e_ident[EI_DATA] == EXPECTED_ENCODING,
"invalid ELF encoding");
#undef EXPECTED_ENCODING
// Validate ELF version (1)
enforce(elfHeader.e_ident[EI_VERSION] == EV_CURRENT &&
elfHeader.e_version == EV_CURRENT,
"invalid ELF version");
// We only support executable and shared object files
enforce(elfHeader.e_type == ET_EXEC || elfHeader.e_type == ET_DYN,
"invalid ELF file type");
enforce(elfHeader.e_phnum != 0, "no program header!");
enforce(elfHeader.e_phentsize == sizeof(ElfW(Phdr)),
"invalid program header entry size");
enforce(elfHeader.e_shentsize == sizeof(ElfW(Shdr)),
"invalid section header entry size");
const ElfW(Phdr)* programHeader = &at<ElfW(Phdr)>(elfHeader.e_phoff);
bool foundBase = false;
for (size_t i = 0; i < elfHeader.e_phnum; programHeader++, i++) {
// Program headers are sorted by load address, so the first PT_LOAD
// header gives us the base address.
if (programHeader->p_type == PT_LOAD) {
baseAddress_ = programHeader->p_vaddr;
foundBase = true;
break;
}
}
enforce(foundBase, "could not find base address");
}
const ElfW(Shdr)* ElfFile::getSectionByIndex(size_t idx) const {
enforce(idx < elfHeader().e_shnum, "invalid section index");
return &at<ElfW(Shdr)>(elfHeader().e_shoff + idx * sizeof(ElfW(Shdr)));
}
folly::StringPiece ElfFile::getSectionBody(const ElfW(Shdr)& section) const {
return folly::StringPiece(file_ + section.sh_offset, section.sh_size);
}
void ElfFile::validateStringTable(const ElfW(Shdr)& stringTable) const {
enforce(stringTable.sh_type == SHT_STRTAB, "invalid type for string table");
const char* start = file_ + stringTable.sh_offset;
// First and last bytes must be 0
enforce(stringTable.sh_size == 0 ||
(start[0] == '\0' && start[stringTable.sh_size - 1] == '\0'),
"invalid string table");
}
const char* ElfFile::getString(const ElfW(Shdr)& stringTable, size_t offset)
const {
validateStringTable(stringTable);
enforce(offset < stringTable.sh_size, "invalid offset in string table");
return file_ + stringTable.sh_offset + offset;
}
const char* ElfFile::getSectionName(const ElfW(Shdr)& section) const {
if (elfHeader().e_shstrndx == SHN_UNDEF) {
return nullptr; // no section name string table
}
const ElfW(Shdr)& sectionNames = *getSectionByIndex(elfHeader().e_shstrndx);
return getString(sectionNames, section.sh_name);
}
const ElfW(Shdr)* ElfFile::getSectionByName(const char* name) const {
if (elfHeader().e_shstrndx == SHN_UNDEF) {
return nullptr; // no section name string table
}
// Find offset in the section name string table of the requested name
const ElfW(Shdr)& sectionNames = *getSectionByIndex(elfHeader().e_shstrndx);
const char* foundName = iterateStrings(
sectionNames,
[&] (const char* s) { return !strcmp(name, s); });
if (foundName == nullptr) {
return nullptr;
}
size_t offset = foundName - (file_ + sectionNames.sh_offset);
// Find section with the appropriate sh_name offset
const ElfW(Shdr)* foundSection = iterateSections(
[&](const ElfW(Shdr)& sh) {
if (sh.sh_name == offset) {
return true;
}
return false;
});
return foundSection;
}
ElfFile::Symbol ElfFile::getDefinitionByAddress(uintptr_t address) const {
Symbol foundSymbol {nullptr, nullptr};
auto find = [&] (const ElfW(Shdr)& section) {
enforce(section.sh_entsize == sizeof(ElfW(Sym)),
"invalid entry size in symbol table");
const ElfW(Sym)* sym = &at<ElfW(Sym)>(section.sh_offset);
const ElfW(Sym)* end = &at<ElfW(Sym)>(section.sh_offset + section.sh_size);
for (; sym != end; ++sym) {
// st_info has the same representation on 32- and 64-bit platforms
auto type = ELF32_ST_TYPE(sym->st_info);
// TODO(tudorb): Handle STT_TLS, but then we'd have to understand
// thread-local relocations. If all we're looking up is functions
// (instruction pointers), it doesn't matter, though.
if (type != STT_OBJECT && type != STT_FUNC) {
continue;
}
if (sym->st_shndx == SHN_UNDEF) {
continue; // not a definition
}
if (address >= sym->st_value && address < sym->st_value + sym->st_size) {
foundSymbol.first = &section;
foundSymbol.second = sym;
return true;
}
}
return false;
};
// Try the .dynsym section first if it exists, it's smaller.
(iterateSectionsWithType(SHT_DYNSYM, find) ||
iterateSectionsWithType(SHT_SYMTAB, find));
return foundSymbol;
}
const char* ElfFile::getSymbolName(Symbol symbol) const {
if (!symbol.first || !symbol.second) {
return nullptr;
}
if (symbol.second->st_name == 0) {
return nullptr; // symbol has no name
}
if (symbol.first->sh_link == SHN_UNDEF) {
return nullptr; // symbol table has no strings
}
return getString(*getSectionByIndex(symbol.first->sh_link),
symbol.second->st_name);
}
} // namespace symbolizer
} // namespace facebook
/*
* 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_ */
/*
* 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;
}
/*
* 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
/*
* 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_ */
/*
* 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;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment