blob: e0bab24a9d3642c5fa03d982110b2a2333936e9d [file] [log] [blame]
/*
* 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;
}