From 17e91d44edf5e6476a477a200bcd89d4327358a3 Mon Sep 17 00:00:00 2001 From: Christopher Ferris Date: Mon, 21 Oct 2013 13:30:52 -0700 Subject: Rewrite libbacktrace using C++. The old code was essentially trying to be C++ in C and was awkward. This change makes it all objects with a thin layer that C code can use. There is a C++ backtrace object that is not very useful, this code will replace it. This change also includes moving the backtrace test to a gtest, and adding coverage of all major functionality. Bug: 8410085 Change-Id: Iae0f1b09b3dd60395f71ed66010c1ea5cdd37841 --- libbacktrace/BacktraceThread.cpp | 224 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 libbacktrace/BacktraceThread.cpp (limited to 'libbacktrace/BacktraceThread.cpp') diff --git a/libbacktrace/BacktraceThread.cpp b/libbacktrace/BacktraceThread.cpp new file mode 100644 index 0000000..6c3641e --- /dev/null +++ b/libbacktrace/BacktraceThread.cpp @@ -0,0 +1,224 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include + +#include "BacktraceThread.h" +#include "thread_utils.h" + +//------------------------------------------------------------------------- +// ThreadEntry implementation. +//------------------------------------------------------------------------- +static ThreadEntry* g_list = NULL; +static pthread_mutex_t g_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_mutex); + if (g_list == this) { + g_list = next_; + } else { + if (next_) { + next_->prev_ = prev_; + } + prev_->next_ = next_; + } + pthread_mutex_unlock(&g_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_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. + ALOGW("%s::%s(): Entry for pid %d tid %d already exists.\n", + __FILE__, __FUNCTION__, pid, tid); + + pthread_mutex_unlock(&g_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_mutex); + + return entry; +} + +//------------------------------------------------------------------------- +// BacktraceThread functions. +//------------------------------------------------------------------------- +static void SignalHandler(int n __attribute__((unused)), siginfo_t* siginfo, + void* sigcontext) { + if (pthread_mutex_lock(&g_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_mutex); + if (!cur_entry) { + ALOGW("%s::%s(): Unable to find pid %d tid %d information\n", + __FILE__, __FUNCTION__, 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) + : BacktraceCurrent(impl), thread_intf_(thread_intf) { + backtrace_.tid = tid; +} + +BacktraceThread::~BacktraceThread() { +} + +void BacktraceThread::FinishUnwind() { + for (size_t i = 0; i < NumFrames(); i++) { + backtrace_frame_data_t* frame = &backtrace_.frames[i]; + + frame->map_offset = 0; + uintptr_t map_start; + frame->map_name = GetMapName(frame->pc, &map_start); + if (frame->map_name) { + frame->map_offset = frame->pc - map_start; + } + + frame->func_offset = 0; + std::string func_name = GetFunctionName(frame->pc, &frame->func_offset); + if (!func_name.empty()) { + frame->func_name = strdup(func_name.c_str()); + } + } +} + +bool BacktraceThread::TriggerUnwindOnThread(ThreadEntry* entry) { + entry->state_ = STATE_WAITING; + + if (tgkill(Pid(), Tid(), SIGURG) != 0) { + ALOGW("%s::%s(): tgkill failed %s\n", __FILE__, __FUNCTION__, strerror(errno)); + return false; + } + + // Allow up to a second for the dump to occur. + int wait_millis = 1000; + 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) { + ALOGW("%s::%s(): Cancelled dump of thread %d\n", __FILE__, __FUNCTION__, + entry->tid_); + state = STATE_CANCEL; + cancelled = true; + } else { + state = android_atomic_acquire_load(&entry->state_); + } + } + + // Wait for at most one minute for the dump to finish. + wait_millis = 60000; + while (android_atomic_acquire_load(&entry->state_) != STATE_DONE) { + if (wait_millis--) { + usleep(1000); + } else { + ALOGW("%s::%s(): Didn't finish thread unwind in 60 seconds.\n", + __FILE__, __FUNCTION__); + break; + } + } + return !cancelled; +} + +bool BacktraceThread::Unwind(size_t num_ignore_frames) { + if (!thread_intf_->Init()) { + return false; + } + + ThreadEntry* entry = ThreadEntry::AddThreadToUnwind( + thread_intf_, Pid(), Tid(), num_ignore_frames); + if (!entry) { + return false; + } + + bool retval = false; + 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 { + ALOGW("%s::%s(): sigaction failed %s\n", __FILE__, __FUNCTION__, strerror(errno)); + } + + if (retval) { + FinishUnwind(); + } + delete entry; + + return retval; +} -- cgit v1.1