android_system_core/libbacktrace/BacktraceThread.cpp
Christopher Ferris 46756821c4 Rewrite libbacktrace to be all C++.
This includes removing the map_info.c source and replacing it with the
BacktraceMap class to handle all map related code.

Change all callers of libbacktrace map functionality.

Also modify the corkscrew thread code so that it doesn't need to build
the map twice (once in the corkscrew format and once in the libbacktrace
format).

Change-Id: I32865a39f83a3dd6f958fc03c2759ba47d12382e
2014-01-16 16:12:29 -08:00

219 lines
6.1 KiB
C++

/*
* 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) {
if (!thread_intf_->Init()) {
return false;
}
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;
}