225 lines
6.2 KiB
C++
225 lines
6.2 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 <cutils/log.h>
|
||
|
|
||
|
#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;
|
||
|
}
|