From dc32cc00e80972223767be339f9318b803f4a2ae Mon Sep 17 00:00:00 2001 From: Magnus Jedvert Date: Sun, 20 Jan 2019 11:46:32 +0100 Subject: [PATCH] Android: Add helper methods for printing native stack traces This CL adds utility functions to unwind the stack for a given thread on Android ARM devices. This works on top of unwind.h and unwinds native (C++) stack traces only. Unwinding a thread from another thread is done by overriding the signal handler with a custom function and then interrupting the specific thread. Bug: webrtc:10168 Change-Id: If5adffd3a6bb57bf502168743e09a7eefc292bf3 Reviewed-on: https://webrtc-review.googlesource.com/c/118141 Commit-Queue: Magnus Jedvert Reviewed-by: Tommi Cr-Commit-Position: refs/heads/master@{#26328} --- sdk/android/BUILD.gn | 16 ++ .../native_api/stacktrace/stacktrace.cc | 231 ++++++++++++++++++ .../native_api/stacktrace/stacktrace.h | 42 ++++ .../stacktrace/stacktrace_unittest.cc | 187 ++++++++++++++ 4 files changed, 476 insertions(+) create mode 100644 sdk/android/native_api/stacktrace/stacktrace.cc create mode 100644 sdk/android/native_api/stacktrace/stacktrace.h create mode 100644 sdk/android/native_unittests/stacktrace/stacktrace_unittest.cc diff --git a/sdk/android/BUILD.gn b/sdk/android/BUILD.gn index 60b55179f1..534865ec11 100644 --- a/sdk/android/BUILD.gn +++ b/sdk/android/BUILD.gn @@ -68,6 +68,7 @@ if (is_android) { ":native_api_codecs", ":native_api_jni", ":native_api_peerconnection", + ":native_api_stacktrace", ":native_api_video", ] } @@ -980,6 +981,19 @@ if (is_android) { ] } + # API for capturing and printing native stacktraces. + rtc_static_library("native_api_stacktrace") { + visibility = [ "*" ] + sources = [ + "native_api/stacktrace/stacktrace.cc", + "native_api/stacktrace/stacktrace.h", + ] + + deps = [ + "../../rtc_base", + ] + } + # API for creating C++ wrapper implementations of api/mediastreaminterface.h # video interfaces from their Java equivalents. rtc_static_library("native_api_video") { @@ -1512,6 +1526,7 @@ if (is_android) { "native_unittests/codecs/wrapper_unittest.cc", "native_unittests/java_types_unittest.cc", "native_unittests/peerconnection/peer_connection_factory_unittest.cc", + "native_unittests/stacktrace/stacktrace_unittest.cc", "native_unittests/test_jni_onload.cc", "native_unittests/video/video_source_unittest.cc", ] @@ -1537,6 +1552,7 @@ if (is_android) { ":native_api_codecs", ":native_api_jni", ":native_api_peerconnection", + ":native_api_stacktrace", ":native_api_video", ":opensles_audio_device_module", ":video_jni", diff --git a/sdk/android/native_api/stacktrace/stacktrace.cc b/sdk/android/native_api/stacktrace/stacktrace.cc new file mode 100644 index 0000000000..8268ff549e --- /dev/null +++ b/sdk/android/native_api/stacktrace/stacktrace.cc @@ -0,0 +1,231 @@ +/* + * Copyright 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "sdk/android/native_api/stacktrace/stacktrace.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ptrace.h is polluting the namespace. Clean up to avoid conflicts with rtc. +#if defined(DS) +#undef DS +#endif + +#include "rtc_base/critical_section.h" +#include "rtc_base/logging.h" +#include "rtc_base/strings/string_builder.h" + +namespace webrtc { + +namespace { + +// Maximum stack trace depth we allow before aborting. +constexpr size_t kMaxStackSize = 100; +// Signal that will be used to interrupt threads. SIGURG ("Urgent condition on +// socket") is chosen because Android does not set up a specific handler for +// this signal. +constexpr int kSignal = SIGURG; + +// Note: This class is only meant for use within this file, and for the +// simplified use case of a single Wait() and a single Signal(), followed by +// discarding the object (never reused). +// This is a replacement of rtc::Event that is async-safe and doesn't use +// pthread api. This is necessary since signal handlers cannot allocate memory +// or use pthread api. This class is ported from Chromium. +class AsyncSafeWaitableEvent { + public: + AsyncSafeWaitableEvent() { + std::atomic_store_explicit(&futex_, 0, std::memory_order_release); + } + + ~AsyncSafeWaitableEvent() {} + + // Returns false in the event of an error and errno is set to indicate the + // cause of the error. + 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) { + int res = syscall(SYS_futex, &futex_, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, + nullptr, nullptr, 0); + if (std::atomic_load_explicit(&futex_, std::memory_order_acquire) != 0) + return true; + if (res != 0) + return false; + } + } + + void Signal() { + std::atomic_store_explicit(&futex_, 1, std::memory_order_release); + syscall(SYS_futex, &futex_, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1, nullptr, + nullptr, 0); + } + + private: + std::atomic futex_; +}; + +// Struct to store the arguments to the signal handler. +struct SignalHandlerOutputState { + // This event is signalled when signal handler is done executing. + AsyncSafeWaitableEvent signal_handler_finish_event; + // Running counter of array index below. + size_t stack_size_counter = 0; + // Array storing the stack trace. + uintptr_t addresses[kMaxStackSize]; +}; + +// Global lock to ensure only one thread gets interrupted at a time. +rtc::GlobalLockPod g_signal_handler_lock; +// Argument passed to the ThreadSignalHandler() from the sampling thread to the +// sampled (stopped) thread. This value is set just before sending signal to the +// thread and reset when handler is done. +SignalHandlerOutputState* volatile g_signal_handler_output_state; + +// This function is called iteratively for each stack trace element and stores +// the element in the array from |unwind_output_state|. +_Unwind_Reason_Code UnwindBacktrace(struct _Unwind_Context* unwind_context, + void* unwind_output_state) { + SignalHandlerOutputState* const output_state = + static_cast(unwind_output_state); + + // Avoid overflowing the stack trace array. + if (output_state->stack_size_counter >= kMaxStackSize) + return _URC_END_OF_STACK; + + // Store the instruction pointer in the array. Subtract 2 since we want to get + // the call instruction pointer, not the return address which is the + // instruction after. + output_state->addresses[output_state->stack_size_counter] = + _Unwind_GetIP(unwind_context) - 2; + ++output_state->stack_size_counter; + + return _URC_NO_REASON; +} + +// This signal handler is exectued on the interrupted thread. +void SignalHandler(int signum, siginfo_t* info, void* ptr) { + _Unwind_Backtrace(&UnwindBacktrace, g_signal_handler_output_state); + g_signal_handler_output_state->signal_handler_finish_event.Signal(); +} + +// Temporarily change the signal handler to a function that records a raw stack +// trace and interrupt the given tid. This function will block until the output +// thread stack trace has been stored in |params|. The return value is an error +// string on failure and null on success. +const char* CaptureRawStacktrace(int pid, + int tid, + SignalHandlerOutputState* params) { + // This function is under a global lock since we are changing the signal + // handler and using global state for the output. The lock is to ensure only + // one thread at a time gets captured. The lock also means we need to be very + // careful with what statements we put in this function, and we should even + // avoid logging here. + struct sigaction act; + struct sigaction old_act; + memset(&act, 0, sizeof(act)); + act.sa_sigaction = &SignalHandler; + act.sa_flags = SA_RESTART | SA_SIGINFO; + sigemptyset(&act.sa_mask); + + rtc::GlobalLockScope ls(&g_signal_handler_lock); + g_signal_handler_output_state = params; + + if (sigaction(kSignal, &act, &old_act) != 0) + return "Failed to change signal action"; + + // Interrupt the thread which will execute SignalHandler() on the given + // thread. + if (tgkill(pid, tid, kSignal) != 0) + return "Failed to interrupt thread"; + + // Wait until the thread is done recording its stack trace. + if (!params->signal_handler_finish_event.Wait()) + return "Failed to wait for thread to finish stack trace"; + + // Restore previous signal handler. + sigaction(kSignal, &old_act, /* old_act= */ nullptr); + + return nullptr; +} + +} // namespace + +std::vector GetStackTrace(int tid) { + // Only a thread itself can unwind its stack, so we will interrupt the given + // tid with a custom signal handler in order to unwind its stack. The stack + // will be recorded to |params| through the use of the global pointer + // |g_signal_handler_param|. + SignalHandlerOutputState params; + + const char* error_string = CaptureRawStacktrace(getpid(), tid, ¶ms); + if (error_string != nullptr) { + RTC_LOG(LS_ERROR) << error_string << ". tid: " << tid + << ". errno: " << errno; + return {}; + } + + if (params.stack_size_counter >= kMaxStackSize) + RTC_LOG(LS_WARNING) << "Stack trace for thread " << tid << " was truncated"; + + // Translate addresses into symbolic information using dladdr(). + std::vector stack_trace; + for (size_t i = 0; i < params.stack_size_counter; ++i) { + const uintptr_t address = params.addresses[i]; + + Dl_info dl_info = {}; + if (!dladdr(reinterpret_cast(address), &dl_info)) { + RTC_LOG(LS_WARNING) + << "Could not translate address to symbolic information for address " + << address << " at stack depth " << i; + continue; + } + + StackTraceElement stack_trace_element; + stack_trace_element.shared_object_path = dl_info.dli_fname; + stack_trace_element.relative_address = static_cast( + address - reinterpret_cast(dl_info.dli_fbase)); + stack_trace_element.symbol_name = dl_info.dli_sname; + + stack_trace.push_back(stack_trace_element); + } + + return stack_trace; +} + +std::string StackTraceToString( + const std::vector& stack_trace) { + rtc::StringBuilder string_builder; + + for (size_t i = 0; i < stack_trace.size(); ++i) { + const StackTraceElement& stack_trace_element = stack_trace[i]; + string_builder.AppendFormat( + "#%02zu pc %08x %s", i, + static_cast(stack_trace_element.relative_address), + stack_trace_element.shared_object_path); + // The symbol name is only available for unstripped .so files. + if (stack_trace_element.symbol_name != nullptr) + string_builder.AppendFormat(" %s", stack_trace_element.symbol_name); + + string_builder.AppendFormat("\n"); + } + + return string_builder.Release(); +} + +} // namespace webrtc diff --git a/sdk/android/native_api/stacktrace/stacktrace.h b/sdk/android/native_api/stacktrace/stacktrace.h new file mode 100644 index 0000000000..1fbf21b347 --- /dev/null +++ b/sdk/android/native_api/stacktrace/stacktrace.h @@ -0,0 +1,42 @@ +/* + * Copyright 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef SDK_ANDROID_NATIVE_API_STACKTRACE_STACKTRACE_H_ +#define SDK_ANDROID_NATIVE_API_STACKTRACE_STACKTRACE_H_ + +#include +#include + +namespace webrtc { + +struct StackTraceElement { + // Pathname of shared object (.so file) that contains address. + const char* shared_object_path; + // Execution address relative to the .so base address. This matches the + // addresses you get with "nm", "objdump", and "ndk-stack", as long as the + // code is compiled with position-independent code. Android requires + // position-independent code since Lollipop. + uint32_t relative_address; + // Name of symbol whose definition overlaps the address. This value is null + // when symbol names are stripped. + const char* symbol_name; +}; + +// Utility to unwind stack for a given thread on Android ARM devices. This works +// on top of unwind.h and unwinds native (C++) stack traces only. +std::vector GetStackTrace(int tid); + +// Get a string representation of the stack trace in a format ndk-stack accepts. +std::string StackTraceToString( + const std::vector& stack_trace); + +} // namespace webrtc + +#endif // SDK_ANDROID_NATIVE_API_STACKTRACE_STACKTRACE_H_ diff --git a/sdk/android/native_unittests/stacktrace/stacktrace_unittest.cc b/sdk/android/native_unittests/stacktrace/stacktrace_unittest.cc new file mode 100644 index 0000000000..4268fdaf79 --- /dev/null +++ b/sdk/android/native_unittests/stacktrace/stacktrace_unittest.cc @@ -0,0 +1,187 @@ +/* + * Copyright 2019 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "sdk/android/native_api/stacktrace/stacktrace.h" +#include +#include +#include "absl/memory/memory.h" +#include "rtc_base/criticalsection.h" +#include "rtc_base/event.h" +#include "rtc_base/logging.h" +#include "rtc_base/platform_thread.h" +#include "rtc_base/strings/string_builder.h" +#include "test/gtest.h" + +namespace webrtc { +namespace test { + +// Returns the execution address relative to the .so base address. This matches +// the addresses we get from GetStacktrace(). +uint32_t GetCurrentRelativeExecutionAddress() { + void* pc = __builtin_return_address(0); + Dl_info dl_info = {}; + const bool success = dladdr(pc, &dl_info); + EXPECT_TRUE(success); + return static_cast(reinterpret_cast(pc) - + reinterpret_cast(dl_info.dli_fbase)); +} + +// Returns true if any of the stack trace element is inside the specified +// region. +bool StackTraceContainsRange(const std::vector& stack_trace, + uintptr_t pc_low, + uintptr_t pc_high) { + for (const StackTraceElement& stack_trace_element : stack_trace) { + if (pc_low <= stack_trace_element.relative_address && + pc_high >= stack_trace_element.relative_address) { + return true; + } + } + return false; +} + +class DeadlockInterface { + public: + virtual ~DeadlockInterface() {} + + // This function should deadlock until Release() is called. + virtual void Deadlock() = 0; + + // This function should release the thread stuck in Deadlock(). + virtual void Release() = 0; +}; + +struct ThreadParams { + volatile int tid; + // Signaled when the deadlock region is entered. + rtc::Event deadlock_start_event; + DeadlockInterface* volatile deadlock_impl; + // Defines an address range within the deadlock will occur. + volatile uint32_t deadlock_region_start_address; + volatile uint32_t deadlock_region_end_address; + // Signaled when the deadlock is done. + rtc::Event deadlock_done_event; +}; + +class RtcEventDeadlock : public DeadlockInterface { + private: + void Deadlock() override { event.Wait(rtc::Event::kForever); } + void Release() override { event.Set(); } + + rtc::Event event; +}; + +class RtcCriticalSectionDeadlock : public DeadlockInterface { + public: + RtcCriticalSectionDeadlock() + : critscope_(absl::make_unique(&crit_)) {} + + private: + void Deadlock() override { rtc::CritScope lock(&crit_); } + + void Release() override { critscope_.reset(); } + + rtc::CriticalSection crit_; + std::unique_ptr critscope_; +}; + +class SpinDeadlock : public DeadlockInterface { + public: + SpinDeadlock() : is_deadlocked_(true) {} + + private: + void Deadlock() override { + while (is_deadlocked_) { + } + } + + void Release() override { is_deadlocked_ = false; } + + std::atomic is_deadlocked_; +}; + +class SleepDeadlock : public DeadlockInterface { + private: + void Deadlock() override { sleep(1000000); } + + void Release() override { + // The interrupt itself will break free from the sleep. + } +}; + +// This is the function that is exectued by the thread that will deadlock and +// have its stacktrace captured. +void ThreadFunction(void* void_params) { + ThreadParams* params = static_cast(void_params); + params->tid = gettid(); + + params->deadlock_region_start_address = GetCurrentRelativeExecutionAddress(); + params->deadlock_start_event.Set(); + params->deadlock_impl->Deadlock(); + params->deadlock_region_end_address = GetCurrentRelativeExecutionAddress(); + + params->deadlock_done_event.Set(); +} + +void TestStacktrace(std::unique_ptr deadlock_impl) { + // Set params that will be sent to other thread. + ThreadParams params; + params.deadlock_impl = deadlock_impl.get(); + + // Spawn thread. + rtc::PlatformThread thread(&ThreadFunction, ¶ms, "StacktraceTest"); + thread.Start(); + + // Wait until the thread has entered the deadlock region. + params.deadlock_start_event.Wait(rtc::Event::kForever); + + // Acquire the stack trace of the thread which should now be deadlocking. + std::vector stack_trace = GetStackTrace(params.tid); + + // Release the deadlock so that the thread can continue. + deadlock_impl->Release(); + + // Wait until the thread has left the deadlock. + params.deadlock_done_event.Wait(rtc::Event::kForever); + + // Assert that the stack trace contains the deadlock region. + EXPECT_TRUE(StackTraceContainsRange(stack_trace, + params.deadlock_region_start_address, + params.deadlock_region_end_address)) + << "Deadlock region: [" + << rtc::ToHex(params.deadlock_region_start_address) << ", " + << rtc::ToHex(params.deadlock_region_end_address) + << "] not contained in: " << StackTraceToString(stack_trace); + + thread.Stop(); +} + +TEST(Stacktrace, TestSpinLock) { + TestStacktrace(absl::make_unique()); +} + +TEST(Stacktrace, TestSleep) { + TestStacktrace(absl::make_unique()); +} + +// Stack traces originating from kernel space does not include user space stack +// traces for ARM 32. +#ifdef WEBRTC_ARCH_ARM64 +TEST(Stacktrace, TestRtcEvent) { + TestStacktrace(absl::make_unique()); +} + +TEST(Stacktrace, TestRtcCriticalSection) { + TestStacktrace(absl::make_unique()); +} +#endif + +} // namespace test +} // namespace webrtc