import gdb

#----------------------------------------------------------------------
# http://www.cygwin.com/ml/archer/2010-q3/msg00024.html

class ThreadHolder:
    """A class that can be used with the 'with' statement to save and
restore the current thread while operating on some other thread."""
    def __init__(self, thread):
        self.thread = thread

    def __enter__(self):
        self.save = gdb.selected_thread()
        self.thread.switch()

    def __exit__ (self, exc_type, exc_value, traceback):
        try:
            self.save.switch()
        except:
            pass
        return None

def print_thread (selected_thread, thr, owner):
    "A helper function to nicely print a gdb.Thread."
    if thr == selected_thread:
        print "* ",
    else:
        print "  ",
    if owner:
        print "Owned by thread",
    else:
        print "Thread",
    (pid, lwp, tid) = thr.ptid
    print "%d  " % lwp
    # FIXME - function, file name, etc

class InfoMutex(gdb.Command):
    def __init__ (self):
        gdb.Command.__init__ (self, "info mutex", gdb.COMMAND_NONE)

    def invoke (self, arg, from_tty):
        # Map a mutex ID to the LWP owning the mutex.
        owner = {}
        # Map an LWP id to a thread object.
        threads = {}
        # Map a mutex ID to a list of thread objects that are waiting
        # for the lock.
        mutexes = {}

        for inf in gdb.inferiors():
            for thr in inf.threads():
                id = thr.ptid[1]
                threads[id] = thr
                with ThreadHolder (thr):
                    frame = gdb.selected_frame()
                    lock_name = None
                    for n in range(5):
                        if frame is None:
                            break
                        fn_sym = frame.function()
                        if fn_sym is not None and (fn_sym.name == '__pthread_mutex_lock' or fn_sym.name == '__pthread_mutex_lock_full' or fn_sym.name == 'pthread_mutex_timedlock'):
                            m = frame.read_var ('mutex')
                            lock_name = long (m)
                            if lock_name not in owner:
                                owner[lock_name] = long (m['__data']['__owner'])
                                break
                        frame = frame.older()
                    if lock_name not in mutexes:
                        mutexes[lock_name] = []
                    mutexes[lock_name] += [thr]

        selected_thread = gdb.selected_thread()

        for id in mutexes.keys():
            if id is None:
                print "Threads not waiting for a lock:"
            else:
                print "Mutex 0x%x:" % id
                print_thread (selected_thread, threads[owner[id]], True)
            for thr in mutexes[id]:
                print_thread (selected_thread, thr, False)
            print

InfoMutex()
