mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-14 06:10:40 +01:00

We have seen crashes originating from derefencing nullptrs in this code, for unknown reasons. This CL adds null checks to protect against this. The stacktraces will be missing or truncated when this happens. Bug: b/147338449 Change-Id: Ieb006f0f8dec4f9621e4df2e2c1a9641f086df86 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/173593 Reviewed-by: Sami Kalliomäki <sakal@webrtc.org> Commit-Queue: Magnus Jedvert <magjed@webrtc.org> Cr-Commit-Position: refs/heads/master@{#31079}
254 lines
9 KiB
C++
254 lines
9 KiB
C++
/*
|
|
* 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 "absl/base/attributes.h"
|
|
#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.
|
|
ABSL_CONST_INIT rtc::GlobalLock 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);
|
|
|
|
// Abort if output state is corrupt.
|
|
if (output_state == nullptr)
|
|
return _URC_END_OF_STACK;
|
|
|
|
// 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) {
|
|
// This should have been set by the thread requesting the stack trace.
|
|
SignalHandlerOutputState* signal_handler_output_state =
|
|
g_signal_handler_output_state;
|
|
if (signal_handler_output_state != nullptr) {
|
|
_Unwind_Backtrace(&UnwindBacktrace, signal_handler_output_state);
|
|
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;
|
|
}
|
|
|
|
// Translate addresses into symbolic information using dladdr().
|
|
std::vector<StackTraceElement> FormatStackTrace(
|
|
const SignalHandlerOutputState& params) {
|
|
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;
|
|
}
|
|
|
|
} // 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";
|
|
}
|
|
return FormatStackTrace(params);
|
|
}
|
|
|
|
std::vector<StackTraceElement> GetStackTrace() {
|
|
SignalHandlerOutputState params;
|
|
_Unwind_Backtrace(&UnwindBacktrace, ¶ms);
|
|
if (params.stack_size_counter >= kMaxStackSize) {
|
|
RTC_LOG(LS_WARNING) << "Stack trace was truncated";
|
|
}
|
|
return FormatStackTrace(params);
|
|
}
|
|
|
|
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
|