cpp/profiler/SignalHandler.cpp (195 lines of code) (raw):
/**
* Copyright 2004-present, 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 "SignalHandler.h"
#include <dlfcn.h>
#include <stdexcept>
#include <string>
#include <system_error>
#include <abort_with_reason.h>
namespace facebook {
namespace profilo {
namespace profiler {
std::atomic<SignalHandler*>
SignalHandler::globalRegisteredSignalHandlers[NSIG]{};
std::once_flag SignalHandler::globalPhaserInit{};
phaser_t SignalHandler::globalPhaser{};
void SignalHandler::UniversalHandler(
int signum,
siginfo_t* siginfo,
void* ucontext) {
// Sequencing:
// We enter the global phaser first, then we enter the local phaser,
// then we exit the global phaser, and then we exit the local phaser.
auto phase = phaser_enter(&globalPhaser);
HandlerScope scope = SignalHandler::EnterHandler(signum);
phaser_exit(&globalPhaser, phase);
if (!scope.IsEnabled()) {
scope.CallPreviousHandler(signum, siginfo, ucontext);
return;
}
scope.handler_.handler_(std::move(scope), signum, siginfo, ucontext);
}
SignalHandler& SignalHandler::Initialize(int signum, HandlerPtr handler) {
if (handler == nullptr) {
throw std::invalid_argument(
"null HandlerPtr is not allowed, use Disable() instead");
}
// Initialize on first use
std::call_once(globalPhaserInit, [] {
if (phaser_init(&globalPhaser)) {
throw std::logic_error("Could not initialize global phaser");
}
});
while (true) {
auto* lookup = globalRegisteredSignalHandlers[signum].load();
auto* instance = new SignalHandler(signum, handler);
if (lookup != nullptr) {
// Propagate the "original" sigaction to our new handler.
instance->old_sigaction_ = lookup->old_sigaction_;
}
if (globalRegisteredSignalHandlers[signum].compare_exchange_strong(
lookup, instance)) {
if (lookup == nullptr) {
// No previous SignalHandler instance, must take over the signal.
SignalHandler::AndroidAwareSigaction(
signum, SignalHandler::UniversalHandler, &instance->old_sigaction_);
} else {
// Have a previous SignalHandler instance, we must wait for users to
// finish first.
//
// Sequencing:
//
// Reads:
// Global phaser [ ]
// Atomic load []
// Local phaser [ ]
// ^ ^ ^
// 1 2 3
//
// After we've replaced the value in the global list, concurrent users
// fall in one of 3 cases:
// 1. They've read the new value (nothing to do)
// 2. They've read the old value but have not entered the old instance's
// phaser yet
// 3. They've read the old value and have entered the old instance's
// phaser
//
phaser_drain(&globalPhaser);
// After this point, no concurrent users that saw the old value can be
// in the global phaser without also being in the old instance's local
// phaser.
phaser_drain(&lookup->phaser_);
// After this point, no concurrent user can have a reference to the old
// instance, so it is safe to delete it.
delete lookup;
}
return *instance;
} else {
// Someone beat us to it, try again by re-reading lookup and
// re-verifying its state.
delete instance;
continue;
}
}
}
SignalHandler::SignalHandler(int signum, HandlerPtr handler)
: signum_(signum),
handler_(handler),
data_(nullptr),
phaser_(),
enabled_(false),
old_sigaction_() {
if (phaser_init(&phaser_)) {
throw std::runtime_error("Could not initialize phaser");
}
}
SignalHandler::~SignalHandler() {
phaser_destroy(&phaser_);
}
void SignalHandler::Enable() {
enabled_.store(true, std::memory_order_relaxed);
}
void SignalHandler::Disable() {
enabled_.store(false, std::memory_order_relaxed);
phaser_drain(&phaser_);
}
SignalHandler::HandlerScope SignalHandler::EnterHandler(int signum) {
auto* handler = globalRegisteredSignalHandlers[signum].load();
if (!handler) {
abortWithReason("EnterHandler call but no registered SignalHandler");
}
return SignalHandler::HandlerScope(*handler);
}
void SignalHandler::CallPreviousHandler(
int signum,
siginfo_t* info,
void* ucontext) {
sigset_t oldsigs;
// Actually calls sigchain's sigprocmask wrapper but that's okay.
// The sigchain wrapper only really cares about not masking signals
// that sigchain has special handlers for. That type of mask modification
// is perfectly okay with us.
if (sigprocmask(SIG_SETMASK, &old_sigaction_.sa_mask, &oldsigs)) {
abortWithReason("Cannot change signal mask");
}
if (old_sigaction_.sa_flags & SA_SIGINFO) {
if (old_sigaction_.sa_sigaction != nullptr) {
old_sigaction_.sa_sigaction(signum, info, ucontext);
}
} else if (old_sigaction_.sa_handler != nullptr) {
if (old_sigaction_.sa_handler != SIG_DFL &&
old_sigaction_.sa_handler != SIG_IGN) {
old_sigaction_.sa_handler(signum);
}
}
if (sigprocmask(SIG_SETMASK, &oldsigs, nullptr)) {
abortWithReason("Cannot restore signal mask");
}
}
void SignalHandler::AndroidAwareSigaction(
int signum,
SigactionPtr handler,
struct sigaction* oldact) {
#ifdef __ANDROID__
//
// On Android, we need to look up sigaction64 or sigaction directly from libc
// or otherwise we'll get the wrappers from sigchain.
//
// These wrappers do not work for our purposes because they run art's signal
// handling before our handler and that signal handling can be misled to
// believe it's in art code when in fact it's in SamplingProfiler unwinding.
//
// Further, we must use sigaction64 if available as otherwise we may hit a bug
// in bionic where sigaction`libc calls sigaction64`sigchain.
// See commit 11623dd60dd0f531fbc1cbf108680ba850acaf2f in AOSP.
//
static struct {
int (*libc_sigaction64)(
int,
const struct sigaction64*,
struct sigaction64*) = nullptr;
int (*libc_sigemptyset64)(sigset64_t*) = nullptr;
int (*libc_sigismember64)(sigset64_t*, int) = nullptr;
int (*libc_sigaction)(int, const struct sigaction*, struct sigaction*) =
nullptr;
bool lookups_complete = false;
} signal_state;
if (!signal_state.lookups_complete) {
auto libc = dlopen("libc.so", RTLD_LOCAL);
if (!libc) {
std::string error("Missing libc.so: ");
throw std::runtime_error(error + dlerror());
}
signal_state.libc_sigaction64 =
reinterpret_cast<decltype(signal_state.libc_sigaction64)>(
dlsym(libc, "sigaction64"));
if (signal_state.libc_sigaction64) {
signal_state.libc_sigemptyset64 =
reinterpret_cast<decltype(signal_state.libc_sigemptyset64)>(
dlsym(libc, "sigemptyset64"));
signal_state.libc_sigismember64 =
reinterpret_cast<decltype(signal_state.libc_sigismember64)>(
dlsym(libc, "sigismember64"));
} else {
signal_state.libc_sigaction =
reinterpret_cast<decltype(signal_state.libc_sigaction)>(
dlsym(libc, "sigaction"));
}
signal_state.lookups_complete = true;
dlclose(libc);
}
int result = 0;
if (signal_state.libc_sigaction64) {
//
// sigaction64 is available.
// Convert from struct sigaction to struct sigaction64 and back
// and call it directly.
//
// Note that the conversion from sigset64_t to sigset_t is lossy,
// we lose real-time signals!
//
struct sigaction64 action64 {
.sa_sigaction = handler, .sa_flags = kSignalHandlerFlags,
};
signal_state.libc_sigemptyset64(&action64.sa_mask);
struct sigaction64 oldaction64;
result = signal_state.libc_sigaction64(signum, &action64, &oldaction64);
struct sigaction oldaction {
.sa_flags = oldaction64.sa_flags,
};
if (oldaction.sa_flags & SA_SIGINFO) {
oldaction.sa_sigaction = oldaction64.sa_sigaction;
} else {
oldaction.sa_handler = oldaction64.sa_handler;
}
sigemptyset(&oldaction.sa_mask);
for (int i = 0; i < NSIG; i++) {
if (signal_state.libc_sigismember64(&oldaction64.sa_mask, i)) {
sigaddset(&oldaction.sa_mask, i);
}
}
*oldact = oldaction;
} else {
struct sigaction action {
.sa_sigaction = handler, .sa_flags = kSignalHandlerFlags,
};
sigemptyset(&action.sa_mask);
result = signal_state.libc_sigaction(signum, &action, oldact);
}
if (result != 0) {
throw std::system_error(errno, std::system_category());
}
#else // not __ANDROID__
struct sigaction action {};
action.sa_flags = kSignalHandlerFlags;
action.sa_sigaction = handler;
sigemptyset(&action.sa_mask);
if (sigaction(signum, &action, oldact)) {
throw std::system_error(errno, std::system_category());
}
#endif
}
} // namespace profiler
} // namespace profilo
} // namespace facebook