diff options
Diffstat (limited to 'libbacktrace/BacktraceThread.cpp')
-rw-r--r-- | libbacktrace/BacktraceThread.cpp | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/libbacktrace/BacktraceThread.cpp b/libbacktrace/BacktraceThread.cpp new file mode 100644 index 0000000..5ffe516 --- /dev/null +++ b/libbacktrace/BacktraceThread.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2013 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 <inttypes.h> +#include <pthread.h> +#include <signal.h> +#include <string.h> +#include <sys/types.h> + +#include <cutils/atomic.h> + +#include "BacktraceThread.h" +#include "thread_utils.h" + +//------------------------------------------------------------------------- +// ThreadEntry implementation. +//------------------------------------------------------------------------- +static ThreadEntry* g_list = NULL; +static pthread_mutex_t g_entry_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t g_sigaction_mutex = PTHREAD_MUTEX_INITIALIZER; + +ThreadEntry::ThreadEntry( + BacktraceThreadInterface* intf, pid_t pid, pid_t tid, size_t num_ignore_frames) + : thread_intf(intf), pid(pid), tid(tid), next(NULL), prev(NULL), + state(STATE_WAITING), num_ignore_frames(num_ignore_frames) { +} + +ThreadEntry::~ThreadEntry() { + pthread_mutex_lock(&g_entry_mutex); + if (g_list == this) { + g_list = next; + } else { + if (next) { + next->prev = prev; + } + prev->next = next; + } + pthread_mutex_unlock(&g_entry_mutex); + + next = NULL; + prev = NULL; +} + +ThreadEntry* ThreadEntry::AddThreadToUnwind( + BacktraceThreadInterface* intf, pid_t pid, pid_t tid, size_t num_ignore_frames) { + ThreadEntry* entry = new ThreadEntry(intf, pid, tid, num_ignore_frames); + + pthread_mutex_lock(&g_entry_mutex); + ThreadEntry* cur_entry = g_list; + while (cur_entry != NULL) { + if (cur_entry->Match(pid, tid)) { + // There is already an entry for this pid/tid, this is bad. + BACK_LOGW("Entry for pid %d tid %d already exists.", pid, tid); + + pthread_mutex_unlock(&g_entry_mutex); + return NULL; + } + cur_entry = cur_entry->next; + } + + // Add the entry to the list. + entry->next = g_list; + if (g_list) { + g_list->prev = entry; + } + g_list = entry; + pthread_mutex_unlock(&g_entry_mutex); + + return entry; +} + +//------------------------------------------------------------------------- +// BacktraceThread functions. +//------------------------------------------------------------------------- +static void SignalHandler(int n __attribute__((unused)), siginfo_t* siginfo, + void* sigcontext) { + if (pthread_mutex_lock(&g_entry_mutex) == 0) { + pid_t pid = getpid(); + pid_t tid = gettid(); + ThreadEntry* cur_entry = g_list; + while (cur_entry) { + if (cur_entry->Match(pid, tid)) { + break; + } + cur_entry = cur_entry->next; + } + pthread_mutex_unlock(&g_entry_mutex); + if (!cur_entry) { + BACK_LOGW("Unable to find pid %d tid %d information", pid, tid); + return; + } + + if (android_atomic_acquire_cas(STATE_WAITING, STATE_DUMPING, &cur_entry->state) == 0) { + cur_entry->thread_intf->ThreadUnwind(siginfo, sigcontext, + cur_entry->num_ignore_frames); + } + android_atomic_release_store(STATE_DONE, &cur_entry->state); + } +} + +BacktraceThread::BacktraceThread( + BacktraceImpl* impl, BacktraceThreadInterface* thread_intf, pid_t tid, + BacktraceMap* map) + : BacktraceCurrent(impl, map), thread_intf_(thread_intf) { + tid_ = tid; +} + +BacktraceThread::~BacktraceThread() { +} + +void BacktraceThread::FinishUnwind() { + for (std::vector<backtrace_frame_data_t>::iterator it = frames_.begin(); + it != frames_.end(); ++it) { + it->map = FindMap(it->pc); + + it->func_offset = 0; + it->func_name = GetFunctionName(it->pc, &it->func_offset); + } +} + +bool BacktraceThread::TriggerUnwindOnThread(ThreadEntry* entry) { + entry->state = STATE_WAITING; + + if (tgkill(Pid(), Tid(), SIGURG) != 0) { + BACK_LOGW("tgkill failed %s", strerror(errno)); + return false; + } + + // Allow up to ten seconds for the dump to start. + int wait_millis = 10000; + int32_t state; + while (true) { + state = android_atomic_acquire_load(&entry->state); + if (state != STATE_WAITING) { + break; + } + if (wait_millis--) { + usleep(1000); + } else { + break; + } + } + + bool cancelled = false; + if (state == STATE_WAITING) { + if (android_atomic_acquire_cas(state, STATE_CANCEL, &entry->state) == 0) { + BACK_LOGW("Cancelled dump of thread %d", entry->tid); + state = STATE_CANCEL; + cancelled = true; + } else { + state = android_atomic_acquire_load(&entry->state); + } + } + + // Wait for at most ten seconds for the cancel or dump to finish. + wait_millis = 10000; + while (android_atomic_acquire_load(&entry->state) != STATE_DONE) { + if (wait_millis--) { + usleep(1000); + } else { + BACK_LOGW("Didn't finish thread unwind in 60 seconds."); + break; + } + } + return !cancelled; +} + +bool BacktraceThread::Unwind(size_t num_ignore_frames) { + ThreadEntry* entry = ThreadEntry::AddThreadToUnwind( + thread_intf_, Pid(), Tid(), num_ignore_frames); + if (!entry) { + return false; + } + + // Prevent multiple threads trying to set the trigger action on different + // threads at the same time. + bool retval = false; + if (pthread_mutex_lock(&g_sigaction_mutex) == 0) { + struct sigaction act, oldact; + memset(&act, 0, sizeof(act)); + act.sa_sigaction = SignalHandler; + act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK; + sigemptyset(&act.sa_mask); + if (sigaction(SIGURG, &act, &oldact) == 0) { + retval = TriggerUnwindOnThread(entry); + sigaction(SIGURG, &oldact, NULL); + } else { + BACK_LOGW("sigaction failed %s", strerror(errno)); + } + pthread_mutex_unlock(&g_sigaction_mutex); + } else { + BACK_LOGW("unable to acquire sigaction mutex."); + } + + if (retval) { + FinishUnwind(); + } + delete entry; + + return retval; +} |