| /* |
| * 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 "BacktraceLog.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(), THREAD_SIGNAL) != 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(THREAD_SIGNAL, &act, &oldact) == 0) { |
| retval = TriggerUnwindOnThread(entry); |
| sigaction(THREAD_SIGNAL, &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; |
| } |