// Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/profiler/stack_copier_signal.h" #include #include #include #include #include #include #include "base/memory/raw_ptr.h" #include "base/notreached.h" #include "base/profiler/register_context.h" #include "base/profiler/stack_buffer.h" #include "base/profiler/suspendable_thread_delegate.h" #include "base/time/time_override.h" #include "base/trace_event/base_tracing.h" #include "build/build_config.h" #include "third_party/abseil-cpp/absl/types/optional.h" namespace base { namespace { // Waitable event implementation with futex and without DCHECK(s), since signal // handlers cannot allocate memory or use pthread api. class AsyncSafeWaitableEvent { public: AsyncSafeWaitableEvent() { futex_.store(0, std::memory_order_release); } ~AsyncSafeWaitableEvent() {} bool Wait() { // futex() can wake up spuriously if this memory address was previously used // for a pthread mutex. So, also check the condition. while (true) { long res = syscall(SYS_futex, futex_int_ptr(), FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, nullptr, nullptr, 0); if (futex_.load(std::memory_order_acquire) != 0) return true; if (res != 0) return false; } } void Signal() { futex_.store(1, std::memory_order_release); syscall(SYS_futex, futex_int_ptr(), FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1, nullptr, nullptr, 0); } private: // Provides a pointer to the atomic's storage. std::atomic_int has standard // layout so its address can be used for the pointer as long as it only // contains the int. int* futex_int_ptr() { static_assert(sizeof(futex_) == sizeof(int), "Expected std::atomic_int to be the same size as int"); return reinterpret_cast(&futex_); } std::atomic_int futex_{0}; }; // Scoped signal event that calls Signal on the AsyncSafeWaitableEvent at // destructor. class ScopedEventSignaller { public: ScopedEventSignaller(AsyncSafeWaitableEvent* event) : event_(event) {} ~ScopedEventSignaller() { event_->Signal(); } private: raw_ptr event_; }; // Struct to store the arguments to the signal handler. struct HandlerParams { uintptr_t stack_base_address; // The event is signalled when signal handler is done executing. raw_ptr event; // Return values: // Successfully copied the stack segment. raw_ptr success; // The thread context of the leaf function. raw_ptr context; // Buffer to copy the stack segment. raw_ptr stack_buffer; raw_ptr stack_copy_bottom; // The timestamp when the stack was copied. raw_ptr> maybe_timestamp; // The delegate provided to the StackCopier. raw_ptr stack_copier_delegate; }; // Pointer to the parameters to be "passed" to the CopyStackSignalHandler() from // the sampling thread to the sampled (stopped) thread. This value is set just // before sending the signal to the thread and reset when the handler is done. std::atomic g_handler_params; // CopyStackSignalHandler is invoked on the stopped thread and records the // thread's stack and register context at the time the signal was received. This // function may only call reentrant code. void CopyStackSignalHandler(int n, siginfo_t* siginfo, void* sigcontext) { HandlerParams* params = g_handler_params.load(std::memory_order_acquire); // MaybeTimeTicksNowIgnoringOverride() is implemented in terms of // clock_gettime on Linux, which is signal safe per the signal-safety(7) man // page, but is not garanteed to succeed, in which case absl::nullopt is // returned. TimeTicks::Now() can't be used because it expects clock_gettime // to always succeed and is thus not signal-safe. *params->maybe_timestamp = subtle::MaybeTimeTicksNowIgnoringOverride(); ScopedEventSignaller e(params->event); *params->success = false; const ucontext_t* ucontext = static_cast(sigcontext); std::memcpy(params->context, &ucontext->uc_mcontext, sizeof(mcontext_t)); const uintptr_t bottom = RegisterContextStackPointer(params->context); const uintptr_t top = params->stack_base_address; if ((top - bottom) > params->stack_buffer->size()) { // The stack exceeds the size of the allocated buffer. The buffer is sized // such that this shouldn't happen under typical execution so we can safely // punt in this situation. return; } params->stack_copier_delegate->OnStackCopy(); *params->stack_copy_bottom = StackCopierSignal::CopyStackContentsAndRewritePointers( reinterpret_cast(bottom), reinterpret_cast(top), StackBuffer::kPlatformStackAlignment, params->stack_buffer->buffer()); *params->success = true; } // Sets the global handler params for the signal handler function. class ScopedSetSignalHandlerParams { public: ScopedSetSignalHandlerParams(HandlerParams* params) { g_handler_params.store(params, std::memory_order_release); } ~ScopedSetSignalHandlerParams() { g_handler_params.store(nullptr, std::memory_order_release); } }; class ScopedSigaction { public: ScopedSigaction(int signal, struct sigaction* action, struct sigaction* original_action) : signal_(signal), action_(action), original_action_(original_action), succeeded_(sigaction(signal, action, original_action) == 0) {} bool succeeded() const { return succeeded_; } ~ScopedSigaction() { if (!succeeded_) return; bool reset_succeeded = sigaction(signal_, original_action_, action_) == 0; DCHECK(reset_succeeded); } private: const int signal_; const raw_ptr action_; const raw_ptr original_action_; const bool succeeded_; }; } // namespace StackCopierSignal::StackCopierSignal( std::unique_ptr thread_delegate) : thread_delegate_(std::move(thread_delegate)) {} StackCopierSignal::~StackCopierSignal() = default; bool StackCopierSignal::CopyStack(StackBuffer* stack_buffer, uintptr_t* stack_top, TimeTicks* timestamp, RegisterContext* thread_context, Delegate* delegate) { AsyncSafeWaitableEvent wait_event; bool copied = false; const uint8_t* stack_copy_bottom = nullptr; const uintptr_t stack_base_address = thread_delegate_->GetStackBaseAddress(); absl::optional maybe_timestamp; HandlerParams params = {stack_base_address, &wait_event, &copied, thread_context, stack_buffer, &stack_copy_bottom, &maybe_timestamp, delegate}; { ScopedSetSignalHandlerParams scoped_handler_params(¶ms); // Set the signal handler for the thread to the stack copy function. struct sigaction action; struct sigaction original_action; memset(&action, 0, sizeof(action)); action.sa_sigaction = CopyStackSignalHandler; action.sa_flags = SA_RESTART | SA_SIGINFO; sigemptyset(&action.sa_mask); TRACE_EVENT_BEGIN0(TRACE_DISABLED_BY_DEFAULT("cpu_profiler.debug"), "StackCopierSignal copy stack"); // SIGURG is chosen here because we observe no crashes with this signal and // neither Chrome or the AOSP sets up a special handler for this signal. ScopedSigaction scoped_sigaction(SIGURG, &action, &original_action); if (!scoped_sigaction.succeeded()) return false; if (syscall(SYS_tgkill, getpid(), thread_delegate_->GetThreadId(), SIGURG) != 0) { NOTREACHED(); return false; } bool finished_waiting = wait_event.Wait(); TRACE_EVENT_END0(TRACE_DISABLED_BY_DEFAULT("cpu_profiler.debug"), "StackCopierSignal copy stack"); if (!finished_waiting) { NOTREACHED(); return false; } // Ideally, an accurate timestamp is captured while the sampled thread is // paused. In rare cases, this may fail, in which case we resort to // capturing an delayed timestamp here instead. if (maybe_timestamp.has_value()) *timestamp = maybe_timestamp.value(); else { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cpu_profiler.debug"), "Fallback on TimeTicks::Now()"); *timestamp = TimeTicks::Now(); } } const uintptr_t bottom = RegisterContextStackPointer(params.context); for (uintptr_t* reg : thread_delegate_->GetRegistersToRewrite(thread_context)) { *reg = StackCopierSignal::RewritePointerIfInOriginalStack( reinterpret_cast(bottom), reinterpret_cast(stack_base_address), stack_copy_bottom, *reg); } *stack_top = reinterpret_cast(stack_copy_bottom) + (stack_base_address - bottom); return copied; } } // namespace base