kicad/thirdparty/sentry-native/external/libunwindstack-ndk/ThreadUnwinder.cpp

183 lines
5.9 KiB
C++
Raw Normal View History

/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include <android-base/errno_restorer.h>
#include <android-base/threads.h>
#include <unwindstack/Log.h>
#include <unwindstack/Regs.h>
#include <unwindstack/Unwinder.h>
#include "ThreadEntry.h"
namespace unwindstack {
static void SignalLogOnly(int, siginfo_t*, void*) {
android::base::ErrnoRestorer restore;
log_async_safe("pid %d, tid %d: Received a spurious thread signal\n", getpid(),
static_cast<int>(android::base::GetThreadId()));
}
static void SignalHandler(int, siginfo_t*, void* sigcontext) {
android::base::ErrnoRestorer restore;
ThreadEntry* entry = ThreadEntry::Get(android::base::GetThreadId(), false);
if (!entry) {
return;
}
entry->CopyUcontextFromSigcontext(sigcontext);
// Indicate the ucontext is now valid.
entry->Wake();
// Pause the thread until the unwind is complete. This avoids having
// the thread run ahead causing problems.
// The number indicates that we are waiting for the second Wake() call
// overall which is made by the thread requesting an unwind.
if (entry->Wait(WAIT_FOR_UNWIND_TO_COMPLETE)) {
// Do not remove the entry here because that can result in a deadlock
// if the code cannot properly send a signal to the thread under test.
entry->Wake();
} else {
// At this point, it is possible that entry has been freed, so just exit.
log_async_safe("Timed out waiting for unwind thread to indicate it completed.");
}
}
ThreadUnwinder::ThreadUnwinder(size_t max_frames)
: UnwinderFromPid(max_frames, getpid(), Regs::CurrentArch()) {}
ThreadUnwinder::ThreadUnwinder(size_t max_frames, const ThreadUnwinder* unwinder)
: UnwinderFromPid(max_frames, getpid(), Regs::CurrentArch()) {
process_memory_ = unwinder->process_memory_;
maps_ = unwinder->maps_;
jit_debug_ = unwinder->jit_debug_;
dex_files_ = unwinder->dex_files_;
initted_ = unwinder->initted_;
}
ThreadEntry* ThreadUnwinder::SendSignalToThread(int signal, pid_t tid) {
static std::mutex action_mutex;
std::lock_guard<std::mutex> guard(action_mutex);
ThreadEntry* entry = ThreadEntry::Get(tid);
entry->Lock();
struct sigaction new_action = {.sa_sigaction = SignalHandler,
.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK};
struct sigaction old_action = {};
sigemptyset(&new_action.sa_mask);
if (sigaction(signal, &new_action, &old_action) != 0) {
log_async_safe("sigaction failed: %s", strerror(errno));
ThreadEntry::Remove(entry);
last_error_.code = ERROR_SYSTEM_CALL;
return nullptr;
}
if (tgkill(getpid(), tid, signal) != 0) {
// Do not emit an error message, this might be expected. Set the
// error and let the caller decide.
if (errno == ESRCH) {
last_error_.code = ERROR_THREAD_DOES_NOT_EXIST;
} else {
last_error_.code = ERROR_SYSTEM_CALL;
}
sigaction(signal, &old_action, nullptr);
ThreadEntry::Remove(entry);
return nullptr;
}
// Wait for the thread to get the ucontext. The number indicates
// that we are waiting for the first Wake() call made by the thread.
bool wait_completed = entry->Wait(WAIT_FOR_UCONTEXT);
if (wait_completed) {
return entry;
}
if (old_action.sa_sigaction == nullptr) {
// If the wait failed, it could be that the signal could not be delivered
// within the timeout. Add a signal handler that's simply going to log
// something so that we don't crash if the signal eventually gets
// delivered. Only do this if there isn't already an action set up.
struct sigaction log_action = {.sa_sigaction = SignalLogOnly,
.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK};
sigemptyset(&log_action.sa_mask);
sigaction(signal, &log_action, nullptr);
} else {
sigaction(signal, &old_action, nullptr);
}
// Check to see if the thread has disappeared.
if (tgkill(getpid(), tid, 0) == -1 && errno == ESRCH) {
last_error_.code = ERROR_THREAD_DOES_NOT_EXIST;
} else {
last_error_.code = ERROR_THREAD_TIMEOUT;
log_async_safe("Timed out waiting for signal handler to get ucontext data.");
}
ThreadEntry::Remove(entry);
return nullptr;
}
void ThreadUnwinder::UnwindWithSignal(int signal, pid_t tid,
const std::vector<std::string>* initial_map_names_to_skip,
const std::vector<std::string>* map_suffixes_to_ignore) {
ClearErrors();
if (tid == pid_) {
last_error_.code = ERROR_UNSUPPORTED;
return;
}
if (!Init()) {
return;
}
ThreadEntry* entry = SendSignalToThread(signal, tid);
if (entry == nullptr) {
return;
}
std::unique_ptr<Regs> regs(Regs::CreateFromUcontext(Regs::CurrentArch(), entry->GetUcontext()));
SetRegs(regs.get());
UnwinderFromPid::Unwind(initial_map_names_to_skip, map_suffixes_to_ignore);
// Tell the signal handler to exit and release the entry.
entry->Wake();
// Wait for the thread to indicate it is done with the ThreadEntry.
if (!entry->Wait(WAIT_FOR_THREAD_TO_RESTART)) {
// Send a warning, but do not mark as a failure to unwind.
log_async_safe("Timed out waiting for signal handler to indicate it finished.");
}
ThreadEntry::Remove(entry);
}
} // namespace unwindstack