implement pthread mutex deadlock detection
this works by building a directed graph of acquired
pthread mutexes and making sure there are no loops in
that graph.
this feature is enabled with:
setprop debug.libc.pthread 1
when a potential deadlock is detected, a large warning is
output to the log with appropriate back traces.
currently disabled at compile-time. set PTHREAD_DEBUG_ENABLED=1
to enable.
Change-Id: I916eed2319599e8aaf8f229d3f18a8ddbec3aa8a
diff --git a/libc/bionic/libc_init_dynamic.c b/libc/bionic/libc_init_dynamic.c
index 4bb2a81..1c8480c 100644
--- a/libc/bionic/libc_init_dynamic.c
+++ b/libc/bionic/libc_init_dynamic.c
@@ -76,6 +76,12 @@
__libc_init_common(elfdata);
+ /* Setup pthread routines accordingly to the environment.
+ * Requires system properties
+ */
+ extern void pthread_debug_init(void);
+ pthread_debug_init();
+
/* Setup malloc routines accordingly to the environment.
* Requires system properties
*/
diff --git a/libc/bionic/pthread.c b/libc/bionic/pthread.c
index 3a74375..16d4998 100644
--- a/libc/bionic/pthread.c
+++ b/libc/bionic/pthread.c
@@ -51,6 +51,9 @@
#include <stdio.h>
#include <bionic_pthread.h>
+extern void pthread_debug_mutex_lock_check(pthread_mutex_t *mutex);
+extern void pthread_debug_mutex_unlock_check(pthread_mutex_t *mutex);
+
extern int __pthread_clone(int (*fn)(void*), void *child_stack, int flags, void *arg);
extern void _exit_with_stack_teardown(void * stackBase, int stackSize, int retCode);
extern void _exit_thread(int retCode);
@@ -897,20 +900,6 @@
return 0;
}
-int pthread_mutex_destroy(pthread_mutex_t *mutex)
-{
- int ret;
-
- /* use trylock to ensure that the mutex value is
- * valid and is not already locked. */
- ret = pthread_mutex_trylock(mutex);
- if (ret != 0)
- return ret;
-
- mutex->value = 0xdead10cc;
- return 0;
-}
-
/*
* Lock a non-recursive mutex.
@@ -1073,7 +1062,8 @@
_normal_unlock(&__recursive_lock, 0);
}
-int pthread_mutex_lock(pthread_mutex_t *mutex)
+__LIBC_HIDDEN__
+int pthread_mutex_lock_impl(pthread_mutex_t *mutex)
{
int mvalue, mtype, tid, new_lock_type, shared;
@@ -1134,8 +1124,21 @@
return 0;
}
+int pthread_mutex_lock(pthread_mutex_t *mutex)
+{
+ int err = pthread_mutex_lock_impl(mutex);
+#ifdef PTHREAD_DEBUG
+ if (PTHREAD_DEBUG_ENABLED) {
+ if (!err) {
+ pthread_debug_mutex_lock_check(mutex);
+ }
+ }
+#endif
+ return err;
+}
-int pthread_mutex_unlock(pthread_mutex_t *mutex)
+__LIBC_HIDDEN__
+int pthread_mutex_unlock_impl(pthread_mutex_t *mutex)
{
int mvalue, mtype, tid, oldv, shared;
@@ -1175,8 +1178,18 @@
return 0;
}
+int pthread_mutex_unlock(pthread_mutex_t *mutex)
+{
+#ifdef PTHREAD_DEBUG
+ if (PTHREAD_DEBUG_ENABLED) {
+ pthread_debug_mutex_unlock_check(mutex);
+ }
+#endif
+ return pthread_mutex_unlock_impl(mutex);
+}
-int pthread_mutex_trylock(pthread_mutex_t *mutex)
+__LIBC_HIDDEN__
+int pthread_mutex_trylock_impl(pthread_mutex_t *mutex)
{
int mvalue, mtype, tid, oldv, shared;
@@ -1219,6 +1232,18 @@
return 0;
}
+int pthread_mutex_trylock(pthread_mutex_t *mutex)
+{
+ int err = pthread_mutex_trylock_impl(mutex);
+#ifdef PTHREAD_DEBUG
+ if (PTHREAD_DEBUG_ENABLED) {
+ if (!err) {
+ pthread_debug_mutex_lock_check(mutex);
+ }
+ }
+#endif
+ return err;
+}
/* initialize 'ts' with the difference between 'abstime' and the current time
* according to 'clock'. Returns -1 if abstime already expired, or 0 otherwise.
@@ -1254,7 +1279,8 @@
}
}
-int pthread_mutex_lock_timeout_np(pthread_mutex_t *mutex, unsigned msecs)
+__LIBC_HIDDEN__
+int pthread_mutex_lock_timeout_np_impl(pthread_mutex_t *mutex, unsigned msecs)
{
clockid_t clock = CLOCK_MONOTONIC;
struct timespec abstime;
@@ -1339,6 +1365,35 @@
return 0;
}
+int pthread_mutex_lock_timeout_np(pthread_mutex_t *mutex, unsigned msecs)
+{
+ int err = pthread_mutex_lock_timeout_np_impl(mutex, msecs);
+#ifdef PTHREAD_DEBUG
+ if (PTHREAD_DEBUG_ENABLED) {
+ if (!err) {
+ pthread_debug_mutex_lock_check(mutex);
+ }
+ }
+#endif
+ return err;
+}
+
+int pthread_mutex_destroy(pthread_mutex_t *mutex)
+{
+ int ret;
+
+ /* use trylock to ensure that the mutex value is
+ * valid and is not already locked. */
+ ret = pthread_mutex_trylock_impl(mutex);
+ if (ret != 0)
+ return ret;
+
+ mutex->value = 0xdead10cc;
+ return 0;
+}
+
+
+
int pthread_condattr_init(pthread_condattr_t *attr)
{
if (attr == NULL)
diff --git a/libc/bionic/pthread_debug.c b/libc/bionic/pthread_debug.c
new file mode 100644
index 0000000..7ee208c
--- /dev/null
+++ b/libc/bionic/pthread_debug.c
@@ -0,0 +1,903 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/atomics.h>
+#include <sys/system_properties.h>
+#include <sys/mman.h>
+
+#if HAVE_DLADDR
+#include <dlfcn.h>
+#endif
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unwind.h>
+#include <unistd.h>
+
+#include "logd.h"
+#include "bionic_tls.h"
+
+/*
+ * ===========================================================================
+ * Deadlock prediction
+ * ===========================================================================
+ */
+/*
+The idea is to predict the possibility of deadlock by recording the order
+in which locks are acquired. If we see an attempt to acquire a lock
+out of order, we can identify the locks and offending code.
+
+To make this work, we need to keep track of the locks held by each thread,
+and create history trees for each lock. When a thread tries to acquire
+a new lock, we walk through the "history children" of the lock, looking
+for a match with locks the thread already holds. If we find a match,
+it means the thread has made a request that could result in a deadlock.
+
+To support recursive locks, we always allow re-locking a currently-held
+lock, and maintain a recursion depth count.
+
+An ASCII-art example, where letters represent locks:
+
+ A
+ /|\
+ / | \
+ B | D
+ \ |
+ \|
+ C
+
+The above is the tree we'd have after handling lock synchronization
+sequences "ABC", "AC", "AD". A has three children, {B, C, D}. C is also
+a child of B. (The lines represent pointers between parent and child.
+Every node can have multiple parents and multiple children.)
+
+If we hold AC, and want to lock B, we recursively search through B's
+children to see if A or C appears. It does, so we reject the attempt.
+(A straightforward way to implement it: add a link from C to B, then
+determine whether the graph starting at B contains a cycle.)
+
+If we hold AC and want to lock D, we would succeed, creating a new link
+from C to D.
+
+Updates to MutexInfo structs are only allowed for the thread that holds
+the lock, so we actually do most of our deadlock prediction work after
+the lock has been acquired.
+*/
+
+// =============================================================================
+// log functions
+// =============================================================================
+
+#define LOGD(format, ...) \
+ __libc_android_log_print(ANDROID_LOG_DEBUG, \
+ "pthread_debug", (format), ##__VA_ARGS__ )
+
+#define LOGW(format, ...) \
+ __libc_android_log_print(ANDROID_LOG_WARN, \
+ "pthread_debug", (format), ##__VA_ARGS__ )
+
+#define LOGE(format, ...) \
+ __libc_android_log_print(ANDROID_LOG_ERROR, \
+ "pthread_debug", (format), ##__VA_ARGS__ )
+
+#define LOGI(format, ...) \
+ __libc_android_log_print(ANDROID_LOG_INFO, \
+ "pthread_debug", (format), ##__VA_ARGS__ )
+
+static const char* const kStartBanner =
+ "===============================================================";
+
+static const char* const kEndBanner =
+ "===============================================================";
+
+extern char* __progname;
+
+// =============================================================================
+// map info functions
+// =============================================================================
+
+typedef struct mapinfo {
+ struct mapinfo *next;
+ unsigned start;
+ unsigned end;
+ char name[];
+} mapinfo;
+
+static mapinfo* sMapInfo = NULL;
+
+static mapinfo *parse_maps_line(char *line)
+{
+ mapinfo *mi;
+ int len = strlen(line);
+
+ if(len < 1) return 0;
+ line[--len] = 0;
+
+ if(len < 50) return 0;
+ if(line[20] != 'x') return 0;
+
+ mi = malloc(sizeof(mapinfo) + (len - 47));
+ if(mi == 0) return 0;
+
+ mi->start = strtoul(line, 0, 16);
+ mi->end = strtoul(line + 9, 0, 16);
+ /* To be filled in parse_elf_info if the mapped section starts with
+ * elf_header
+ */
+ mi->next = 0;
+ strcpy(mi->name, line + 49);
+
+ return mi;
+}
+
+static mapinfo *init_mapinfo(int pid)
+{
+ struct mapinfo *milist = NULL;
+ char data[1024];
+ sprintf(data, "/proc/%d/maps", pid);
+ FILE *fp = fopen(data, "r");
+ if(fp) {
+ while(fgets(data, sizeof(data), fp)) {
+ mapinfo *mi = parse_maps_line(data);
+ if(mi) {
+ mi->next = milist;
+ milist = mi;
+ }
+ }
+ fclose(fp);
+ }
+
+ return milist;
+}
+
+static void deinit_mapinfo(mapinfo *mi)
+{
+ mapinfo *del;
+ while(mi) {
+ del = mi;
+ mi = mi->next;
+ free(del);
+ }
+}
+
+/* Find the containing map info for the pc */
+static const mapinfo *pc_to_mapinfo(mapinfo *mi, unsigned pc, unsigned *rel_pc)
+{
+ *rel_pc = pc;
+ while(mi) {
+ if((pc >= mi->start) && (pc < mi->end)){
+ // Only calculate the relative offset for shared libraries
+ if (strstr(mi->name, ".so")) {
+ *rel_pc -= mi->start;
+ }
+ return mi;
+ }
+ mi = mi->next;
+ }
+ return NULL;
+}
+
+// =============================================================================
+// stack trace functions
+// =============================================================================
+
+#define STACK_TRACE_DEPTH 16
+
+typedef struct
+{
+ size_t count;
+ intptr_t* addrs;
+} stack_crawl_state_t;
+
+/* depends how the system includes define this */
+#ifdef HAVE_UNWIND_CONTEXT_STRUCT
+typedef struct _Unwind_Context __unwind_context;
+#else
+typedef _Unwind_Context __unwind_context;
+#endif
+
+static _Unwind_Reason_Code trace_function(__unwind_context *context, void *arg)
+{
+ stack_crawl_state_t* state = (stack_crawl_state_t*)arg;
+ if (state->count) {
+ intptr_t ip = (intptr_t)_Unwind_GetIP(context);
+ if (ip) {
+ state->addrs[0] = ip;
+ state->addrs++;
+ state->count--;
+ return _URC_NO_REASON;
+ }
+ }
+ /*
+ * If we run out of space to record the address or 0 has been seen, stop
+ * unwinding the stack.
+ */
+ return _URC_END_OF_STACK;
+}
+
+static inline
+int get_backtrace(intptr_t* addrs, size_t max_entries)
+{
+ stack_crawl_state_t state;
+ state.count = max_entries;
+ state.addrs = (intptr_t*)addrs;
+ _Unwind_Backtrace(trace_function, (void*)&state);
+ return max_entries - state.count;
+}
+
+static void log_backtrace(intptr_t* addrs, size_t c)
+{
+ int index = 0;
+ size_t i;
+ for (i=0 ; i<c; i++) {
+ unsigned int relpc;
+ void* offset = 0;
+ const char* symbol = NULL;
+
+#if HAVE_DLADDR
+ Dl_info info;
+ if (dladdr((void*)addrs[i], &info)) {
+ offset = info.dli_saddr;
+ symbol = info.dli_sname;
+ }
+#endif
+
+ if (symbol || index>0 || !HAVE_DLADDR) {
+ /*
+ * this test is a bit sketchy, but it allows us to skip the
+ * stack trace entries due to this debugging code. it works
+ * because those don't have a symbol (they're not exported)
+ */
+ mapinfo const* mi = pc_to_mapinfo(sMapInfo, addrs[i], &relpc);
+ char const* soname = mi ? mi->name : NULL;
+#if HAVE_DLADDR
+ if (!soname)
+ soname = info.dli_fname;
+#endif
+ if (!soname)
+ soname = "unknown";
+
+ if (symbol) {
+ LOGW(" "
+ "#%02d pc %08lx %s (%s+0x%x)",
+ index, relpc, soname, symbol,
+ addrs[i] - (intptr_t)offset);
+ } else {
+ LOGW(" "
+ "#%02d pc %08lx %s",
+ index, relpc, soname);
+ }
+ index++;
+ }
+ }
+}
+
+/****************************************************************************/
+
+/*
+ * level <= 0 : deadlock prediction disabled
+ * level 1 : deadlock prediction enabled, w/o call stacks
+ * level 2 : deadlock prediction enabled w/ call stacks
+ */
+#define CAPTURE_CALLSTACK 2
+static int sPthreadDebugLevel = 0;
+static pid_t sPthreadDebugDisabledThread = -1;
+static pthread_mutex_t sDbgLock = PTHREAD_MUTEX_INITIALIZER;
+
+/****************************************************************************/
+
+/* some simple/lame malloc replacement
+ * NOT thread-safe and leaks everything
+ */
+
+#define DBG_ALLOC_BLOCK_SIZE PAGESIZE
+static size_t sDbgAllocOffset = DBG_ALLOC_BLOCK_SIZE;
+static char* sDbgAllocPtr = NULL;
+
+static void* DbgAllocLocked(size_t size) {
+ if ((sDbgAllocOffset + size) > DBG_ALLOC_BLOCK_SIZE) {
+ sDbgAllocOffset = 0;
+ sDbgAllocPtr = mmap(NULL, DBG_ALLOC_BLOCK_SIZE, PROT_READ|PROT_WRITE,
+ MAP_ANON | MAP_PRIVATE, 0, 0);
+ if (sDbgAllocPtr == MAP_FAILED) {
+ return NULL;
+ }
+ }
+ void* addr = sDbgAllocPtr + sDbgAllocOffset;
+ sDbgAllocOffset += size;
+ return addr;
+}
+
+static void* debug_realloc(void *ptr, size_t size, size_t old_size) {
+ void* addr = mmap(NULL, size, PROT_READ|PROT_WRITE,
+ MAP_ANON | MAP_PRIVATE, 0, 0);
+ if (addr != MAP_FAILED) {
+ if (ptr) {
+ memcpy(addr, ptr, old_size);
+ munmap(ptr, old_size);
+ }
+ } else {
+ addr = NULL;
+ }
+ return addr;
+}
+
+/*****************************************************************************/
+
+struct MutexInfo;
+
+typedef struct CallStack {
+ intptr_t depth;
+ intptr_t* addrs;
+} CallStack;
+
+typedef struct MutexInfo* MutexInfoListEntry;
+typedef struct CallStack CallStackListEntry;
+
+typedef struct GrowingList {
+ int alloc;
+ int count;
+ union {
+ void* data;
+ MutexInfoListEntry* list;
+ CallStackListEntry* stack;
+ };
+} GrowingList;
+
+typedef GrowingList MutexInfoList;
+typedef GrowingList CallStackList;
+
+typedef struct MutexInfo {
+ // thread currently holding the lock or 0
+ pid_t owner;
+
+ // most-recently-locked doubly-linked list
+ struct MutexInfo* prev;
+ struct MutexInfo* next;
+
+ // for reentrant locks
+ int lockCount;
+ // when looking for loops in the graph, marks visited nodes
+ int historyMark;
+ // the actual mutex
+ pthread_mutex_t* mutex;
+ // list of locks directly acquired AFTER this one in the same thread
+ MutexInfoList children;
+ // list of locks directly acquired BEFORE this one in the same thread
+ MutexInfoList parents;
+ // list of call stacks when a new link is established to this lock form its parent
+ CallStackList stacks;
+ // call stack when this lock was acquired last
+ int stackDepth;
+ intptr_t stackTrace[STACK_TRACE_DEPTH];
+} MutexInfo;
+
+static void growingListInit(GrowingList* list) {
+ list->alloc = 0;
+ list->count = 0;
+ list->data = NULL;
+}
+
+static void growingListAdd(GrowingList* pList, size_t objSize) {
+ if (pList->count == pList->alloc) {
+ size_t oldsize = pList->alloc * objSize;
+ pList->alloc += PAGESIZE / objSize;
+ size_t size = pList->alloc * objSize;
+ pList->data = debug_realloc(pList->data, size, oldsize);
+ }
+ pList->count++;
+}
+
+static void initMutexInfo(MutexInfo* object, pthread_mutex_t* mutex) {
+ object->owner = 0;
+ object->prev = 0;
+ object->next = 0;
+ object->lockCount = 0;
+ object->historyMark = 0;
+ object->mutex = mutex;
+ growingListInit(&object->children);
+ growingListInit(&object->parents);
+ growingListInit(&object->stacks);
+ object->stackDepth = 0;
+}
+
+typedef struct ThreadInfo {
+ pid_t pid;
+ MutexInfo* mrl;
+} ThreadInfo;
+
+static void initThreadInfo(ThreadInfo* object, pid_t pid) {
+ object->pid = pid;
+ object->mrl = NULL;
+}
+
+/****************************************************************************/
+
+static MutexInfo* get_mutex_info(pthread_mutex_t *mutex);
+static void mutex_lock_checked(MutexInfo* mrl, MutexInfo* object);
+static void mutex_unlock_checked(MutexInfo* object);
+
+/****************************************************************************/
+
+extern int pthread_mutex_lock_impl(pthread_mutex_t *mutex);
+extern int pthread_mutex_unlock_impl(pthread_mutex_t *mutex);
+
+static int pthread_mutex_lock_unchecked(pthread_mutex_t *mutex) {
+ return pthread_mutex_lock_impl(mutex);
+}
+
+static int pthread_mutex_unlock_unchecked(pthread_mutex_t *mutex) {
+ return pthread_mutex_unlock_impl(mutex);
+}
+
+/****************************************************************************/
+
+static void dup_backtrace(CallStack* stack, int count, intptr_t const* addrs) {
+ stack->depth = count;
+ stack->addrs = DbgAllocLocked(count * sizeof(intptr_t));
+ memcpy(stack->addrs, addrs, count * sizeof(intptr_t));
+}
+
+/****************************************************************************/
+
+static int historyListHas(
+ const MutexInfoList* list, MutexInfo const * obj) {
+ int i;
+ for (i=0; i<list->count; i++) {
+ if (list->list[i] == obj) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static void historyListAdd(MutexInfoList* pList, MutexInfo* obj) {
+ growingListAdd(pList, sizeof(MutexInfoListEntry));
+ pList->list[pList->count - 1] = obj;
+}
+
+static int historyListRemove(MutexInfoList* pList, MutexInfo* obj) {
+ int i;
+ for (i = pList->count-1; i >= 0; i--) {
+ if (pList->list[i] == obj) {
+ break;
+ }
+ }
+ if (i < 0) {
+ // not found!
+ return 0;
+ }
+
+ if (i != pList->count-1) {
+ // copy the last entry to the new free slot
+ pList->list[i] = pList->list[pList->count-1];
+ }
+ pList->count--;
+ memset(&pList->list[pList->count], 0, sizeof(MutexInfoListEntry));
+ return 1;
+}
+
+static void linkParentToChild(MutexInfo* parent, MutexInfo* child) {
+ historyListAdd(&parent->children, child);
+ historyListAdd(&child->parents, parent);
+}
+
+static void unlinkParentFromChild(MutexInfo* parent, MutexInfo* child) {
+ historyListRemove(&parent->children, child);
+ historyListRemove(&child->parents, parent);
+}
+
+/****************************************************************************/
+
+static void callstackListAdd(CallStackList* pList,
+ int count, intptr_t const* addrs) {
+ growingListAdd(pList, sizeof(CallStackListEntry));
+ dup_backtrace(&pList->stack[pList->count - 1], count, addrs);
+}
+
+/****************************************************************************/
+
+/*
+ * Recursively traverse the object hierarchy starting at "obj". We mark
+ * ourselves on entry and clear the mark on exit. If we ever encounter
+ * a marked object, we have a cycle.
+ *
+ * Returns "true" if all is well, "false" if we found a cycle.
+ */
+
+static int traverseTree(MutexInfo* obj, MutexInfo const* objParent)
+{
+ /*
+ * Have we been here before?
+ */
+ if (obj->historyMark) {
+ int stackDepth;
+ intptr_t addrs[STACK_TRACE_DEPTH];
+
+ /* Turn off prediction temporarily in this thread while logging */
+ sPthreadDebugDisabledThread = gettid();
+
+ if (sMapInfo == NULL) {
+ // note: we're protected by sDbgLock
+ sMapInfo = init_mapinfo(getpid());
+ }
+
+ LOGW("%s\n", kStartBanner);
+ LOGW("pid: %d, tid: %d >>> %s <<<", getpid(), gettid(), __progname);
+ LOGW("Illegal lock attempt:\n");
+ LOGW("--- pthread_mutex_t at %p\n", obj->mutex);
+ stackDepth = get_backtrace(addrs, STACK_TRACE_DEPTH);
+ log_backtrace(addrs, stackDepth);
+
+ LOGW("+++ Currently held locks in this thread (in reverse order):");
+ MutexInfo* cur = obj;
+ pid_t ourtid = gettid();
+ int i;
+ for (i=0 ; i<cur->parents.count ; i++) {
+ MutexInfo* parent = cur->parents.list[i];
+ if (parent->owner == ourtid) {
+ LOGW("--- pthread_mutex_t at %p\n", parent->mutex);
+ if (sPthreadDebugLevel >= CAPTURE_CALLSTACK) {
+ log_backtrace(parent->stackTrace, parent->stackDepth);
+ }
+ cur = parent;
+ break;
+ }
+ }
+
+ LOGW("+++ Earlier, the following lock order (from last to first) was established\n");
+ return 0;
+ }
+
+ obj->historyMark = 1;
+
+ MutexInfoList* pList = &obj->children;
+ int result = 1;
+ int i;
+ for (i = pList->count-1; i >= 0; i--) {
+ MutexInfo* child = pList->list[i];
+ if (!traverseTree(child, obj)) {
+ LOGW("--- pthread_mutex_t at %p\n", obj->mutex);
+ if (sPthreadDebugLevel >= CAPTURE_CALLSTACK) {
+ int index = historyListHas(&obj->parents, objParent);
+ if ((size_t)index < (size_t)obj->stacks.count) {
+ log_backtrace(
+ obj->stacks.stack[index].addrs,
+ obj->stacks.stack[index].depth);
+ } else {
+ log_backtrace(
+ obj->stackTrace,
+ obj->stackDepth);
+ }
+ }
+ result = 0;
+ break;
+ }
+ }
+
+ obj->historyMark = 0;
+ return result;
+}
+
+/****************************************************************************/
+
+static void mutex_lock_checked(MutexInfo* mrl, MutexInfo* object)
+{
+ pid_t tid = gettid();
+ if (object->owner == tid) {
+ object->lockCount++;
+ return;
+ }
+
+ object->owner = tid;
+ object->lockCount = 0;
+
+ if (sPthreadDebugLevel >= CAPTURE_CALLSTACK) {
+ // always record the call stack when acquiring a lock.
+ // it's not efficient, but is useful during diagnostics
+ object->stackDepth = get_backtrace(object->stackTrace, STACK_TRACE_DEPTH);
+ }
+
+ // no other locks held in this thread -- no deadlock possible!
+ if (mrl == NULL)
+ return;
+
+ // check if the lock we're trying to acquire is a direct descendant of
+ // the most recently locked mutex in this thread, in which case we're
+ // in a good situation -- no deadlock possible
+ if (historyListHas(&mrl->children, object) >= 0)
+ return;
+
+ pthread_mutex_lock_unchecked(&sDbgLock);
+
+ linkParentToChild(mrl, object);
+ if (!traverseTree(object, mrl)) {
+ deinit_mapinfo(sMapInfo);
+ sMapInfo = NULL;
+ LOGW("%s\n", kEndBanner);
+ unlinkParentFromChild(mrl, object);
+ // reenable pthread debugging for this thread
+ sPthreadDebugDisabledThread = -1;
+ } else {
+ // record the call stack for this link
+ // NOTE: the call stack is added at the same index
+ // as mrl in object->parents[]
+ // ie: object->parents.count == object->stacks.count, which is
+ // also the index.
+ if (sPthreadDebugLevel >= CAPTURE_CALLSTACK) {
+ callstackListAdd(&object->stacks,
+ object->stackDepth, object->stackTrace);
+ }
+ }
+
+ pthread_mutex_unlock_unchecked(&sDbgLock);
+}
+
+static void mutex_unlock_checked(MutexInfo* object)
+{
+ pid_t tid = gettid();
+ if (object->owner == tid) {
+ if (object->lockCount == 0) {
+ object->owner = 0;
+ } else {
+ object->lockCount--;
+ }
+ }
+}
+
+
+// =============================================================================
+// Hash Table functions
+// =============================================================================
+
+/****************************************************************************/
+
+#define HASHTABLE_SIZE 256
+
+typedef struct HashEntry HashEntry;
+struct HashEntry {
+ size_t slot;
+ HashEntry* prev;
+ HashEntry* next;
+ void* data;
+};
+
+typedef struct HashTable HashTable;
+struct HashTable {
+ HashEntry* slots[HASHTABLE_SIZE];
+};
+
+static HashTable sMutexMap;
+static HashTable sThreadMap;
+
+/****************************************************************************/
+
+static uint32_t get_hashcode(void const * key, size_t keySize)
+{
+ uint32_t h = keySize;
+ char const* data = (char const*)key;
+ size_t i;
+ for (i = 0; i < keySize; i++) {
+ h = h * 31 + *data;
+ data++;
+ }
+ return (uint32_t)h;
+}
+
+static size_t get_index(uint32_t h)
+{
+ // We apply this secondary hashing discovered by Doug Lea to defend
+ // against bad hashes.
+ h += ~(h << 9);
+ h ^= (((unsigned int) h) >> 14);
+ h += (h << 4);
+ h ^= (((unsigned int) h) >> 10);
+ return (size_t)h & (HASHTABLE_SIZE - 1);
+}
+
+/****************************************************************************/
+
+static void hashmap_init(HashTable* table) {
+ memset(table, 0, sizeof(HashTable));
+}
+
+static void hashmap_removeEntry(HashTable* table, HashEntry* entry)
+{
+ HashEntry* prev = entry->prev;
+ HashEntry* next = entry->next;
+ if (prev != NULL) entry->prev->next = next;
+ if (next != NULL) entry->next->prev = prev;
+ if (prev == NULL) {
+ // we are the head of the list. set the head to be next
+ table->slots[entry->slot] = entry->next;
+ }
+}
+
+static HashEntry* hashmap_lookup(HashTable* table,
+ void const* key, size_t ksize,
+ int (*equals)(void const* data, void const* key))
+{
+ const uint32_t hash = get_hashcode(key, ksize);
+ const size_t slot = get_index(hash);
+
+ HashEntry* entry = table->slots[slot];
+ while (entry) {
+ if (equals(entry->data, key)) {
+ break;
+ }
+ entry = entry->next;
+ }
+
+ if (entry == NULL) {
+ // create a new entry
+ entry = (HashEntry*)DbgAllocLocked(sizeof(HashEntry));
+ entry->data = NULL;
+ entry->slot = slot;
+ entry->prev = NULL;
+ entry->next = table->slots[slot];
+ if (entry->next != NULL) {
+ entry->next->prev = entry;
+ }
+ table->slots[slot] = entry;
+ }
+ return entry;
+}
+
+/****************************************************************************/
+
+static int MutexInfo_equals(void const* data, void const* key) {
+ return ((MutexInfo const *)data)->mutex == *(pthread_mutex_t **)key;
+}
+
+static MutexInfo* get_mutex_info(pthread_mutex_t *mutex)
+{
+ pthread_mutex_lock_unchecked(&sDbgLock);
+
+ HashEntry* entry = hashmap_lookup(&sMutexMap,
+ &mutex, sizeof(mutex),
+ &MutexInfo_equals);
+ if (entry->data == NULL) {
+ entry->data = (MutexInfo*)DbgAllocLocked(sizeof(MutexInfo));
+ initMutexInfo(entry->data, mutex);
+ }
+
+ pthread_mutex_unlock_unchecked(&sDbgLock);
+
+ return (MutexInfo *)entry->data;
+}
+
+/****************************************************************************/
+
+static int ThreadInfo_equals(void const* data, void const* key) {
+ return ((ThreadInfo const *)data)->pid == *(pid_t *)key;
+}
+
+static ThreadInfo* get_thread_info(pid_t pid)
+{
+ pthread_mutex_lock_unchecked(&sDbgLock);
+
+ HashEntry* entry = hashmap_lookup(&sThreadMap,
+ &pid, sizeof(pid),
+ &ThreadInfo_equals);
+ if (entry->data == NULL) {
+ entry->data = (ThreadInfo*)DbgAllocLocked(sizeof(ThreadInfo));
+ initThreadInfo(entry->data, pid);
+ }
+
+ pthread_mutex_unlock_unchecked(&sDbgLock);
+
+ return (ThreadInfo *)entry->data;
+}
+
+static void push_most_recently_locked(MutexInfo* mrl) {
+ ThreadInfo* tinfo = get_thread_info(gettid());
+ mrl->next = NULL;
+ mrl->prev = tinfo->mrl;
+ tinfo->mrl = mrl;
+}
+
+static void remove_most_recently_locked(MutexInfo* mrl) {
+ ThreadInfo* tinfo = get_thread_info(gettid());
+ if (mrl->next) {
+ (mrl->next)->prev = mrl->prev;
+ }
+ if (mrl->prev) {
+ (mrl->prev)->next = mrl->next;
+ }
+ if (tinfo->mrl == mrl) {
+ tinfo->mrl = mrl->next;
+ }
+}
+
+static MutexInfo* get_most_recently_locked() {
+ ThreadInfo* tinfo = get_thread_info(gettid());
+ return tinfo->mrl;
+}
+
+/****************************************************************************/
+
+/* pthread_debug_init() is called from libc_init_dynamic() just
+ * after system properties have been initialized
+ */
+
+__LIBC_HIDDEN__
+void pthread_debug_init(void) {
+ char env[PROP_VALUE_MAX];
+ if (__system_property_get("debug.libc.pthread", env)) {
+ int level = atoi(env);
+ if (level) {
+ LOGI("pthread deadlock detection level %d enabled for pid %d (%s)",
+ level, getpid(), __progname);
+ hashmap_init(&sMutexMap);
+ sPthreadDebugLevel = level;
+ }
+ }
+}
+
+/*
+ * See if we were allowed to grab the lock at this time. We do it
+ * *after* acquiring the lock, rather than before, so that we can
+ * freely update the MutexInfo struct. This seems counter-intuitive,
+ * but our goal is deadlock *prediction* not deadlock *prevention*.
+ * (If we actually deadlock, the situation is easy to diagnose from
+ * a thread dump, so there's no point making a special effort to do
+ * the checks before the lock is held.)
+ */
+
+__LIBC_HIDDEN__
+void pthread_debug_mutex_lock_check(pthread_mutex_t *mutex)
+{
+ if (sPthreadDebugLevel == 0) return;
+ // prediction disabled for this thread
+ if (sPthreadDebugDisabledThread == gettid())
+ return;
+ MutexInfo* object = get_mutex_info(mutex);
+ MutexInfo* mrl = get_most_recently_locked();
+ mutex_lock_checked(mrl, object);
+ push_most_recently_locked(object);
+}
+
+/*
+ * pthread_debug_mutex_unlock_check() must be called with the mutex
+ * still held (ie: before calling the real unlock)
+ */
+
+__LIBC_HIDDEN__
+void pthread_debug_mutex_unlock_check(pthread_mutex_t *mutex)
+{
+ if (sPthreadDebugLevel == 0) return;
+ // prediction disabled for this thread
+ if (sPthreadDebugDisabledThread == gettid())
+ return;
+ MutexInfo* object = get_mutex_info(mutex);
+ remove_most_recently_locked(object);
+ mutex_unlock_checked(object);
+}