Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a2efd3ac authored by Christopher Ferris's avatar Christopher Ferris
Browse files

Rewrite unwind thread handling.

This new version doesn't require any specialized thread
implementation, it uses the Current implementation to do its job.
In addition, it runs much faster when multiple threads are trying
to unwind at the same time since the global signal lock is held for
only a small amount of time. Even running through the threads one at
a time should be faster since it no longer requires two passes through
the unwound stacks.

The new code now allows multiple simultaneous unwinds of the
same thread.

Finally, add the ability to unwind from a ucontext_t passed in. This
functionality doesn't work for remote unwinds yet.

Change-Id: I4d181d7ca5ffd2acfd1686e668e6d21e36b425cb
parent b52a48af
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@

#include <inttypes.h>
#include <stdint.h>
#include <ucontext.h>

#include <string>
#include <vector>
@@ -64,7 +65,7 @@ public:
  virtual ~Backtrace();

  // Get the current stack trace and store in the backtrace_ structure.
  virtual bool Unwind(size_t num_ignore_frames);
  virtual bool Unwind(size_t num_ignore_frames, ucontext_t* context = NULL);

  // Get the function name and offset into the function given the pc.
  // If the string is empty, then no valid function name was found.
+3 −0
Original line number Diff line number Diff line
@@ -118,6 +118,9 @@ backtrace_test_shared_libraries := \
backtrace_test_shared_libraries_target := \
	libcutils \

backtrace_test_static_libraries_host := \
	libcutils \

module := backtrace_test
module_tag := debug
build_type := target
+2 −2
Original line number Diff line number Diff line
@@ -55,8 +55,8 @@ Backtrace::~Backtrace() {
  }
}

bool Backtrace::Unwind(size_t num_ignore_frames) {
  return impl_->Unwind(num_ignore_frames);
bool Backtrace::Unwind(size_t num_ignore_frames, ucontext_t* ucontext) {
  return impl_->Unwind(num_ignore_frames, ucontext);
}

extern "C" char* __cxa_demangle(const char* mangled, char* buf, size_t* len,
+2 −1
Original line number Diff line number Diff line
@@ -21,12 +21,13 @@
#include <backtrace/BacktraceMap.h>

#include <sys/types.h>
#include <ucontext.h>

class BacktraceImpl {
public:
  virtual ~BacktraceImpl() { }

  virtual bool Unwind(size_t num_ignore_frames) = 0;
  virtual bool Unwind(size_t num_ignore_frames, ucontext_t* ucontext) = 0;

  // The name returned is not demangled, Backtrace::GetFunctionName()
  // takes care of demangling the name.
+129 −141
Original line number Diff line number Diff line
@@ -16,10 +16,15 @@

#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <linux/futex.h>
#include <pthread.h>
#include <signal.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <ucontext.h>

#include <cutils/atomic.h>

@@ -27,190 +32,173 @@
#include "BacktraceThread.h"
#include "thread_utils.h"

static inline int futex(volatile int* uaddr, int op, int val, const struct timespec* ts, volatile int* uaddr2, int val3) {
  return syscall(__NR_futex, uaddr, op, val, ts, uaddr2, val3);
}

//-------------------------------------------------------------------------
// 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::list_ = NULL;
pthread_mutex_t ThreadEntry::list_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) {
// Assumes that ThreadEntry::list_mutex_ has already been locked before
// creating a ThreadEntry object.
ThreadEntry::ThreadEntry(pid_t pid, pid_t tid)
    : pid_(pid), tid_(tid), futex_(0), ref_count_(1), mutex_(PTHREAD_MUTEX_INITIALIZER), next_(ThreadEntry::list_), prev_(NULL) {
  // Add ourselves to the list.
  if (ThreadEntry::list_) {
    ThreadEntry::list_->prev_ = this;
  }
  ThreadEntry::list_ = this;
}

ThreadEntry::~ThreadEntry() {
  pthread_mutex_lock(&g_entry_mutex);
  if (g_list == this) {
    g_list = next;
  } else {
    if (next) {
      next->prev = prev;
ThreadEntry* ThreadEntry::Get(pid_t pid, pid_t tid, bool create) {
  pthread_mutex_lock(&ThreadEntry::list_mutex_);
  ThreadEntry* entry = list_;
  while (entry != NULL) {
    if (entry->Match(pid, tid)) {
      break;
    }
    prev->next = next;
    entry = entry->next_;
  }
  pthread_mutex_unlock(&g_entry_mutex);

  next = NULL;
  prev = NULL;
  if (!entry) {
    if (create) {
      entry = new ThreadEntry(pid, tid);
    }
  } else {
    entry->ref_count_++;
  }
  pthread_mutex_unlock(&ThreadEntry::list_mutex_);

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);
  return entry;
}

  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);
void ThreadEntry::Remove(ThreadEntry* entry) {
  pthread_mutex_unlock(&entry->mutex_);

      pthread_mutex_unlock(&g_entry_mutex);
      return NULL;
  pthread_mutex_lock(&ThreadEntry::list_mutex_);
  if (--entry->ref_count_ == 0) {
    delete entry;
  }
    cur_entry = cur_entry->next;
  pthread_mutex_unlock(&ThreadEntry::list_mutex_);
}

  // Add the entry to the list.
  entry->next = g_list;
  if (g_list) {
    g_list->prev = entry;
// Assumes that ThreadEntry::list_mutex_ has already been locked before
// deleting a ThreadEntry object.
ThreadEntry::~ThreadEntry() {
  if (list_ == this) {
    list_ = next_;
  } else {
    if (next_) {
      next_->prev_ = prev_;
    }
  g_list = entry;
  pthread_mutex_unlock(&g_entry_mutex);

  return entry;
    prev_->next_ = next_;
  }

//-------------------------------------------------------------------------
// 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;
  next_ = NULL;
  prev_ = NULL;
}

    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);
void ThreadEntry::Wait(int value) {
  timespec ts;
  ts.tv_sec = 10;
  ts.tv_nsec = 0;
  errno = 0;
  futex(&futex_, FUTEX_WAIT, value, &ts, NULL, 0);
  if (errno != 0 && errno != EWOULDBLOCK) {
    BACK_LOGW("futex wait failed, futex = %d: %s", futex_, strerror(errno));
  }
}

BacktraceThread::BacktraceThread(
    BacktraceImpl* impl, BacktraceThreadInterface* thread_intf, pid_t tid,
    BacktraceMap* map)
    : BacktraceCurrent(impl, map), thread_intf_(thread_intf) {
  tid_ = tid;
void ThreadEntry::Wake() {
  futex_++;
  futex(&futex_, FUTEX_WAKE, INT_MAX, NULL, NULL, 0);
}

BacktraceThread::~BacktraceThread() {
//-------------------------------------------------------------------------
// BacktraceThread functions.
//-------------------------------------------------------------------------
static pthread_mutex_t g_sigaction_mutex = PTHREAD_MUTEX_INITIALIZER;

static void SignalHandler(int, siginfo_t*, void* sigcontext) {
  ThreadEntry* entry = ThreadEntry::Get(getpid(), gettid(), false);
  if (!entry) {
    BACK_LOGW("Unable to find pid %d tid %d information", getpid(), gettid());
    return;
  }

void BacktraceThread::FinishUnwind() {
  for (std::vector<backtrace_frame_data_t>::iterator it = frames_.begin();
       it != frames_.end(); ++it) {
    it->map = FindMap(it->pc);
  entry->CopyUcontext(reinterpret_cast<ucontext_t*>(sigcontext));

    it->func_offset = 0;
    it->func_name = GetFunctionName(it->pc, &it->func_offset);
  }
}
  // Indicate the ucontext is now valid.
  entry->Wake();

bool BacktraceThread::TriggerUnwindOnThread(ThreadEntry* entry) {
  entry->state = STATE_WAITING;
  // Pause the thread until the unwind is complete. This avoids having
  // the thread run ahead causing problems.
  entry->Wait(1);

  if (tgkill(Pid(), Tid(), THREAD_SIGNAL) != 0) {
    BACK_LOGW("tgkill failed %s", strerror(errno));
    return false;
  ThreadEntry::Remove(entry);
}

  // 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;
    }
BacktraceThread::BacktraceThread(BacktraceImpl* impl, pid_t tid, BacktraceMap* map)
    : BacktraceCurrent(impl, map) {
  tid_ = tid;
}

  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);
    }
BacktraceThread::~BacktraceThread() {
}

  // 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, ucontext_t* ucontext) {
  if (ucontext) {
    // Unwind using an already existing ucontext.
    return impl_->Unwind(num_ignore_frames, ucontext);
  }

bool BacktraceThread::Unwind(size_t num_ignore_frames) {
  ThreadEntry* entry = ThreadEntry::AddThreadToUnwind(
      thread_intf_, Pid(), Tid(), num_ignore_frames);
  if (!entry) {
  // Prevent multiple threads trying to set the trigger action on different
  // threads at the same time.
  if (pthread_mutex_lock(&g_sigaction_mutex) < 0) {
    BACK_LOGW("sigaction failed: %s", strerror(errno));
    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) {
  ThreadEntry* entry = ThreadEntry::Get(Pid(), Tid());
  entry->Lock();

  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 {
  if (sigaction(THREAD_SIGNAL, &act, &oldact) != 0) {
    BACK_LOGW("sigaction failed %s", strerror(errno));
    }
    entry->Unlock();
    ThreadEntry::Remove(entry);
    pthread_mutex_unlock(&g_sigaction_mutex);
  } else {
    BACK_LOGW("unable to acquire sigaction mutex.");
    return false;
  }

  if (retval) {
    FinishUnwind();
  if (tgkill(Pid(), Tid(), THREAD_SIGNAL) != 0) {
    BACK_LOGW("tgkill %d failed: %s", Tid(), strerror(errno));
    sigaction(THREAD_SIGNAL, &oldact, NULL);
    entry->Unlock();
    ThreadEntry::Remove(entry);
    pthread_mutex_unlock(&g_sigaction_mutex);
    return false;
  }
  delete entry;

  return retval;
  // Wait for the thread to get the ucontext.
  entry->Wait(0);

  // After the thread has received the signal, allow other unwinders to
  // continue.
  sigaction(THREAD_SIGNAL, &oldact, NULL);
  pthread_mutex_unlock(&g_sigaction_mutex);

  bool unwind_done = impl_->Unwind(num_ignore_frames, entry->GetUcontext());

  // Tell the signal handler to exit and release the entry.
  entry->Wake();

  return unwind_done;
}
Loading