Revert "Android: Add helper methods for printing native stack traces"
This reverts commit dc32cc00e80972223767be339f9318b803f4a2ae. Reason for revert: Seems to be breaking Chromium FYI, https://www.google.com/url?q=https://ci.chromium.org/p/chromium/builders/luci.chromium.webrtc.fyi/WebRTC%2520Chromium%2520FYI%2520Android%2520Tests%2520%2528dbg%2529%2520%2528K%2520Nexus5%2529/1807&sa=D&source=hangouts&ust=1548149426180000&usg=AFQjCNGh9aBqv2wNE12D8-6rn9-AJR-cNg Original change's description: > 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 <magjed@webrtc.org> > Reviewed-by: Tommi <tommi@webrtc.org> > Cr-Commit-Position: refs/heads/master@{#26328} TBR=magjed@webrtc.org,tommi@webrtc.org Change-Id: I6e01f9226ef60777cb422baeab042bce8944f9ed No-Presubmit: true No-Tree-Checks: true No-Try: true Bug: webrtc:10168 Reviewed-on: https://webrtc-review.googlesource.com/c/118683 Reviewed-by: Magnus Jedvert <magjed@webrtc.org> Commit-Queue: Magnus Jedvert <magjed@webrtc.org> Cr-Commit-Position: refs/heads/master@{#26336}
This commit is contained in:

committed by
Commit Bot

parent
d7bc09705b
commit
700615fb1c
@ -68,7 +68,6 @@ if (is_android) {
|
||||
":native_api_codecs",
|
||||
":native_api_jni",
|
||||
":native_api_peerconnection",
|
||||
":native_api_stacktrace",
|
||||
":native_api_video",
|
||||
]
|
||||
}
|
||||
@ -981,19 +980,6 @@ 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") {
|
||||
@ -1526,7 +1512,6 @@ 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",
|
||||
]
|
||||
@ -1552,7 +1537,6 @@ if (is_android) {
|
||||
":native_api_codecs",
|
||||
":native_api_jni",
|
||||
":native_api_peerconnection",
|
||||
":native_api_stacktrace",
|
||||
":native_api_video",
|
||||
":opensles_audio_device_module",
|
||||
":video_jni",
|
||||
|
@ -1,231 +0,0 @@
|
||||
/*
|
||||
* 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 <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <linux/futex.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/ucontext.h>
|
||||
#include <syscall.h>
|
||||
#include <ucontext.h>
|
||||
#include <unistd.h>
|
||||
#include <unwind.h>
|
||||
#include <atomic>
|
||||
|
||||
// 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<int> 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<SignalHandlerOutputState*>(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<StackTraceElement> 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<StackTraceElement> 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<void*>(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<uint32_t>(
|
||||
address - reinterpret_cast<uintptr_t>(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<StackTraceElement>& 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<uint32_t>(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
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* 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 <string>
|
||||
#include <vector>
|
||||
|
||||
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<StackTraceElement> GetStackTrace(int tid);
|
||||
|
||||
// Get a string representation of the stack trace in a format ndk-stack accepts.
|
||||
std::string StackTraceToString(
|
||||
const std::vector<StackTraceElement>& stack_trace);
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // SDK_ANDROID_NATIVE_API_STACKTRACE_STACKTRACE_H_
|
@ -1,187 +0,0 @@
|
||||
/*
|
||||
* 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 <dlfcn.h>
|
||||
#include <vector>
|
||||
#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<uint32_t>(reinterpret_cast<uintptr_t>(pc) -
|
||||
reinterpret_cast<uintptr_t>(dl_info.dli_fbase));
|
||||
}
|
||||
|
||||
// Returns true if any of the stack trace element is inside the specified
|
||||
// region.
|
||||
bool StackTraceContainsRange(const std::vector<StackTraceElement>& 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<rtc::CritScope>(&crit_)) {}
|
||||
|
||||
private:
|
||||
void Deadlock() override { rtc::CritScope lock(&crit_); }
|
||||
|
||||
void Release() override { critscope_.reset(); }
|
||||
|
||||
rtc::CriticalSection crit_;
|
||||
std::unique_ptr<rtc::CritScope> 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<bool> 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<ThreadParams*>(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<DeadlockInterface> 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<StackTraceElement> 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<SpinDeadlock>());
|
||||
}
|
||||
|
||||
TEST(Stacktrace, TestSleep) {
|
||||
TestStacktrace(absl::make_unique<SleepDeadlock>());
|
||||
}
|
||||
|
||||
// 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<RtcEventDeadlock>());
|
||||
}
|
||||
|
||||
TEST(Stacktrace, TestRtcCriticalSection) {
|
||||
TestStacktrace(absl::make_unique<RtcCriticalSectionDeadlock>());
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
Reference in New Issue
Block a user