blob: 93dd1d5948020d03ca00285c833dd9043e06ab86 [file] [log] [blame] [edit]
/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*-
* Copyright (c) 2024, gperftools Contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "single_stepper.h"
#if __linux__ && __x86_64__ && !defined(TCMALLOC_OMIT_SINGLE_STEPPER)
#include <errno.h>
#include <sched.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/syscall.h>
#include <ucontext.h>
#include <unistd.h>
#include <atomic>
#include "base/spinlock.h"
#include "base/static_storage.h"
#include "base/logging.h"
#include "base/cleanup.h"
#define STEPPER_SUPPORTED 1
namespace tcmalloc {
namespace {
struct SingleStepperImpl : public SingleStepper {
~SingleStepperImpl() = default;
static inline std::atomic<bool> active = {};
static inline SingleStepperImpl* active_impl = nullptr;
static constexpr uintptr_t kTF = 0x100; // Trace flag in x86 FLAGS register.
SteppingCallbackFn callback;
static void step_handler(int signo, siginfo_t* si, void* _uc) {
tcmalloc::Cleanup preserve_errno([errno_save = errno]() { errno = errno_save; });
ucontext_t* uc = static_cast<ucontext_t*>(_uc);
auto at_rip = reinterpret_cast<uint8_t*>(uc->uc_mcontext.gregs[REG_RIP]);
if (try_handle_sigtrap_blocking(at_rip, uc)) {
return;
}
if (!active.load(std::memory_order_relaxed)) {
uc->uc_mcontext.gregs[REG_EFL] &= ~kTF;
return;
}
uc->uc_mcontext.gregs[REG_EFL] |= kTF;
active_impl->callback(uc, active_impl);
}
static bool try_handle_sigtrap_blocking(uint8_t* at_rip, ucontext_t* uc) {
if (at_rip[0] != 0x0f || at_rip[1] != 0x05) {
return false;
}
// syscall instruction. Lets check if someone is about to block
// SIGTRAP. If so we must turn off single-stepping, because
// otherwise blocked SIGTRAP and pending single-stepping will kill
// the process.
auto& regs = uc->uc_mcontext.gregs;
if (regs[REG_RAX] != SYS_rt_sigprocmask) {
return false;
}
if (regs[REG_RDI] != SIG_SETMASK && regs[REG_RDI] != SIG_BLOCK) {
return false;
}
sigset_t* newmask = reinterpret_cast<sigset_t*>(regs[REG_RSI]);
if (!newmask || !sigismember(newmask, SIGTRAP)) {
return false;
}
// okay, once we detected this case, we drop single-stepping
// flag, block SIGTRAP and raise it. So that when SIGTRAP is
// eventually unblocked, we'll get back to signal hander and
// re-set single-stepping back.
regs[REG_EFL] &= ~kTF;
raise(SIGTRAP);
sigset_t* oldmask = reinterpret_cast<sigset_t*>(regs[REG_RDX]);
if (oldmask) {
*oldmask = uc->uc_sigmask;
regs[REG_RDX] = 0; // handle "get old mask" part, so we can block
// our signal
}
sigaddset(&uc->uc_sigmask, SIGTRAP);
return true;
}
void Start(SteppingCallbackFn callback) override {
CHECK(!active.load(std::memory_order_relaxed));
this->callback = callback;
active_impl = this;
// Then we prepare SIGTRAP signal handler.
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = step_handler;
sa.sa_flags = SA_RESTART | SA_SIGINFO;
CHECK(sigaction(SIGTRAP, &sa, nullptr) == 0);
}
active.store(true, std::memory_order_relaxed);
raise(SIGTRAP);
}
void Stop() override { active.store(false, std::memory_order_relaxed); }
bool IsAtLockInstruction(void* uc) override {
ucontext_t* real_uc = static_cast<ucontext_t*>(uc);
auto at_rip = reinterpret_cast<uint8_t*>(real_uc->uc_mcontext.gregs[REG_RIP]);
return (*at_rip == 0xf0); // LOCK prefix
}
};
SingleStepperImpl* GetStepper() {
static TrivialOnce once;
static StaticStorage<SingleStepperImpl> storage;
once.RunOnce([]() { storage.Construct(); });
return storage.get();
}
} // anonymous namespace
} // namespace tcmalloc
#endif
namespace tcmalloc {
SingleStepper::~SingleStepper() = default;
std::optional<SingleStepper*> SingleStepper::Get() {
#if STEPPER_SUPPORTED
return GetStepper();
#else
return std::nullopt;
#endif
}
} // namespace tcmalloc