Commit 5acc01c2 authored by Nick Wolchko's avatar Nick Wolchko Committed by Facebook GitHub Bot

split smart exception tracer read + register code

Summary:
Split the SmartExceptionTracer so that code can depend on the reading
functionality without also registering the throw hooks at the same time. This
is because hooks during throwing an exception can be expensive for some users.
Splitting it this way allows a library to try to print an async trace if it's
available, but still letting the end application decide if it wants to enable
the feature or not.

Reviewed By: yfeldblum, ot

Differential Revision: D32766294

fbshipit-source-id: 1ffd86d026fc2c18dfa547cf2d169f6491a877cc
parent 5c672ce7
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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/ExceptionTracerLib.h>
#include <folly/experimental/exception_tracer/SmartExceptionTracerSingleton.h>
#include <folly/experimental/symbolizer/Symbolizer.h>
#if FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
#if defined(__GLIBCXX__)
namespace folly::exception_tracer {
namespace {
void metaDeleter(void* ex) noexcept {
auto syncMeta = detail::getMetaMap().withWLock([ex](auto& locked) noexcept {
auto iter = locked.find(ex);
auto ret = std::move(iter->second);
locked.erase(iter);
return ret;
});
// If the thrown object was allocated statically it may not have a deleter.
auto meta = syncMeta->wlock();
if (meta->deleter) {
meta->deleter(ex);
}
}
// This callback runs when an exception is thrown so we can grab the stack
// trace. To manage the lifetime of the stack trace we override the deleter with
// our own wrapper.
void throwCallback(
void* ex, std::type_info*, void (**deleter)(void*)) noexcept {
// Make this code reentrant safe in case we throw an exception while
// handling an exception. Thread local variables are zero initialized.
static thread_local bool handlingThrow;
if (handlingThrow) {
return;
}
SCOPE_EXIT { handlingThrow = false; };
handlingThrow = true;
// This can allocate memory potentially causing problems in an OOM
// situation so we catch and short circuit.
try {
auto newMeta = std::make_unique<detail::SynchronizedExceptionMeta>();
newMeta->withWLock([&deleter](auto& lockedMeta) {
// Override the deleter with our custom one and capture the old one.
lockedMeta.deleter = std::exchange(*deleter, metaDeleter);
ssize_t n = folly::symbolizer::getStackTrace(
lockedMeta.trace.addresses, kMaxFrames);
if (n != -1) {
lockedMeta.trace.frameCount = n;
}
ssize_t nAsync = folly::symbolizer::getAsyncStackTraceSafe(
lockedMeta.traceAsync.addresses, kMaxFrames);
if (nAsync != -1) {
lockedMeta.traceAsync.frameCount = nAsync;
}
});
auto oldMeta = detail::getMetaMap().withWLock([ex, &newMeta](auto& wlock) {
return std::exchange(wlock[ex], std::move(newMeta));
});
DCHECK(oldMeta == nullptr);
} catch (const std::bad_alloc&) {
}
}
struct Initialize {
Initialize() {
registerCxaThrowCallback(throwCallback);
detail::setSmartExceptionTracerHookEnabled(true);
}
};
Initialize initialize;
} // namespace
} // namespace folly::exception_tracer
#endif // defined(__GLIBCXX__)
#endif // FOLLY_HAVE_ELF && FOLLY_HAVE_DWARF
......@@ -22,6 +22,7 @@
#include <folly/Synchronized.h>
#include <folly/container/F14Map.h>
#include <folly/experimental/exception_tracer/ExceptionTracerLib.h>
#include <folly/experimental/exception_tracer/SmartExceptionTracerSingleton.h>
#include <folly/experimental/exception_tracer/StackTrace.h>
#include <folly/experimental/symbolizer/Symbolizer.h>
......@@ -33,87 +34,7 @@ namespace folly {
namespace exception_tracer {
namespace {
struct ExceptionMeta {
void (*deleter)(void*);
// normal stack trace
StackTrace trace;
// async stack trace
StackTrace traceAsync;
};
using SynchronizedExceptionMeta = folly::Synchronized<ExceptionMeta>;
auto& getMetaMap() {
// Leaky Meyers Singleton
static Indestructible<Synchronized<
folly::F14FastMap<void*, std::unique_ptr<SynchronizedExceptionMeta>>>>
meta;
return *meta;
}
void metaDeleter(void* ex) noexcept {
auto syncMeta = getMetaMap().withWLock([ex](auto& locked) noexcept {
auto iter = locked.find(ex);
auto ret = std::move(iter->second);
locked.erase(iter);
return ret;
});
// If the thrown object was allocated statically it may not have a deleter.
auto meta = syncMeta->wlock();
if (meta->deleter) {
meta->deleter(ex);
}
}
// This callback runs when an exception is thrown so we can grab the stack
// trace. To manage the lifetime of the stack trace we override the deleter with
// our own wrapper.
void throwCallback(
void* ex, std::type_info*, void (**deleter)(void*)) noexcept {
// Make this code reentrant safe in case we throw an exception while
// handling an exception. Thread local variables are zero initialized.
static thread_local bool handlingThrow;
if (handlingThrow) {
return;
}
SCOPE_EXIT { handlingThrow = false; };
handlingThrow = true;
// This can allocate memory potentially causing problems in an OOM
// situation so we catch and short circuit.
try {
auto newMeta = std::make_unique<SynchronizedExceptionMeta>();
newMeta->withWLock([&deleter](auto& lockedMeta) {
// Override the deleter with our custom one and capture the old one.
lockedMeta.deleter = std::exchange(*deleter, metaDeleter);
ssize_t n =
symbolizer::getStackTrace(lockedMeta.trace.addresses, kMaxFrames);
if (n != -1) {
lockedMeta.trace.frameCount = n;
}
ssize_t nAsync = symbolizer::getAsyncStackTraceSafe(
lockedMeta.traceAsync.addresses, kMaxFrames);
if (nAsync != -1) {
lockedMeta.traceAsync.frameCount = nAsync;
}
});
auto oldMeta = getMetaMap().withWLock([ex, &newMeta](auto& wlock) {
return std::exchange(wlock[ex], std::move(newMeta));
});
DCHECK(oldMeta == nullptr);
} catch (const std::bad_alloc&) {
}
}
struct Initialize {
Initialize() { registerCxaThrowCallback(throwCallback); }
};
Initialize initialize;
std::atomic_bool loggedMessage{false};
// ExceptionMetaFunc takes a `const ExceptionMeta&` and
// returns a pair of iterators to represent a range of addresses for
......@@ -121,11 +42,18 @@ Initialize initialize;
template <typename ExceptionMetaFunc>
ExceptionInfo getTraceWithFunc(
const std::exception& ex, ExceptionMetaFunc func) {
if (!detail::isSmartExceptionTracerHookEnabled() &&
!loggedMessage.load(std::memory_order_relaxed)) {
LOG(WARNING)
<< "Smart exception tracer library not linked, stack traces not available";
loggedMessage = true;
}
ExceptionInfo info;
info.type = &typeid(ex);
auto rlockedMeta = getMetaMap().withRLock(
auto rlockedMeta = detail::getMetaMap().withRLock(
[&](const auto& locked) noexcept
-> SynchronizedExceptionMeta::RLockedPtr {
-> detail::SynchronizedExceptionMeta::RLockedPtr {
auto* meta = get_ptr(locked, (void*)&ex);
// If we can't find the exception, return an empty stack trace.
if (!meta) {
......@@ -169,13 +97,13 @@ ExceptionInfo getTraceWithFunc(
return ExceptionInfo();
}
auto getAsyncStackTraceItPair(const ExceptionMeta& meta) {
auto getAsyncStackTraceItPair(const detail::ExceptionMeta& meta) {
return std::make_pair(
meta.traceAsync.addresses,
meta.traceAsync.addresses + meta.traceAsync.frameCount);
}
auto getNormalStackTraceItPair(const ExceptionMeta& meta) {
auto getNormalStackTraceItPair(const detail::ExceptionMeta& meta) {
return std::make_pair(
meta.trace.addresses, meta.trace.addresses + meta.trace.frameCount);
}
......
......@@ -23,6 +23,10 @@
#if defined(__GLIBCXX__)
// These functions report stack traces if available.
// To enable collecting stack traces your binary must also include the
// smart_exception_stack_trace_hooks target.
namespace folly {
namespace exception_tracer {
......
#include <folly/experimental/exception_tracer/SmartExceptionTracerSingleton.h>
namespace folly::exception_tracer::detail {
Synchronized<F14FastMap<void*, std::unique_ptr<SynchronizedExceptionMeta>>>&
getMetaMap() {
// Leaky Meyers Singleton
static Indestructible<Synchronized<
F14FastMap<void*, std::unique_ptr<SynchronizedExceptionMeta>>>>
meta;
return *meta;
}
static std::atomic_bool hookEnabled{false};
bool isSmartExceptionTracerHookEnabled() {
return hookEnabled.load(std::memory_order_relaxed);
}
void setSmartExceptionTracerHookEnabled(bool enabled) {
hookEnabled = enabled;
}
} // namespace folly::exception_tracer::detail
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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.
*/
#pragma once
#include <folly/Synchronized.h>
#include <folly/container/F14Map.h>
#include <folly/experimental/exception_tracer/StackTrace.h>
namespace folly::exception_tracer::detail {
struct ExceptionMeta {
void (*deleter)(void*);
// normal stack trace
StackTrace trace;
// async stack trace
StackTrace traceAsync;
};
using SynchronizedExceptionMeta = folly::Synchronized<ExceptionMeta>;
Synchronized<F14FastMap<void*, std::unique_ptr<SynchronizedExceptionMeta>>>&
getMetaMap();
bool isSmartExceptionTracerHookEnabled();
void setSmartExceptionTracerHookEnabled(bool enabled);
} // namespace folly::exception_tracer::detail
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