Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
From: Aaron Gamble <agamble@google.com>
To: Tom Tromey <tromey@redhat.com>
Cc: gdb-patches@sourceware.org
Subject: Re: [RFC] Thread Name Printers
Date: Fri, 24 Aug 2012 22:02:00 -0000	[thread overview]
Message-ID: <CAHX8C+KctA8K9tWZZug5o6Z+5f6GB8hNJ-qaa2PyW7=nHQAp5Q@mail.gmail.com> (raw)
In-Reply-To: <CAHX8C+KPH3dBcu7sxBnPj_PHkD1uyJ=ruVNXS74_gDMD0vnHng@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 8289 bytes --]

Oops. Forgot to actually attach patch.

On Fri, Aug 24, 2012 at 3:00 PM, Aaron Gamble <agamble@google.com> wrote:
> New patch. See inline comments.
>
> (FYI today is the last day of my internship, so this e-mail address
> will be dead very soon. I can be reached at invalidfunction or
> jaarongamble both at gmail.com)
>
> gdb/ChangeLog:
>     * data-directory/Makefile.in: Add gdb/thread_printing.py.
>     * doc/gdb.texinfo: Add section about thread name printers in Python section.
>     Add reference to thread name printers in 'info threads' section.
>     * python/lib/gdb/__init__.py: Import ThreadNamePrinter.
>     * python/lib/gdb/thread_printing.py: new file.
>     (class ThreadNamePrinter): base class, contains helper functions
> and list of registered printers.
>     (class ExamplePrinter): example printer class for reference.
>     (class EnableThreadNamePrinter, DisableThreadNamePrinter,
> InfoThreadNamePrinter):
>         gdb.commands for managing and getting information about thread
> name printers.
>     (register_thread_name_printer_commands): Registers thread name
> printer commands.
>     * python/py-inferior.c (add_thread_object): Save pointer to
> thread_object in thread_info.
>     * python/py-infthread.c (prepare_thread_name_printers): Prepares
> all the registered thread name printers
>         and returns a list of the printers. Called once by
> thread.c:print_thread_info.
>     (thread_printer_enabled): Returns TRUE if printer enabled.
>     (apply_thread_name_pretty_printers): Iterates through the list of
> printers calling print_thread until one returns a string.
>         Called for each thread by thread.c:print_thread_info
>     * python/python-internal.h: Extern gdbpy_print_thread_cst PyObject
> string object.
>     * pyhon/python.c: Declare and assign gdbpy_print_thread_cst.
>     * testsuite/gdb.python/Makefile.in: add py-threadprettyprint.
>     * testsuite/gdb.python/py-threadprettyprint.c: Basic threaded
> program for testing thread name printing.
>     * testsuite/gdb.python/py-threadprettyprint.exp: Tests for thread
> name printers.
>     * thread.c (print_thread_info): Calls prepare_thread_name_printers
> to initialize printers and get list of printers.
>         Calls apply_thread_name_pretty_printers for each thread if no
> name for thread is already stored.
>     (_initialize_thread): Add aliases for 'info threads' to
> disambiguate between 'info thread-name-printer'
>
>
> On Thu, Aug 23, 2012 at 9:17 AM, Tom Tromey <tromey@redhat.com> wrote:
>>
>>>>>>> "Aaron" == Aaron Gamble <agamble@google.com> writes:
>>
>> Tom> How about just 'gdb.threads.add_printer'?
>> Tom> Or just have the base class constructor do it, and make it private?
>>
>> Aaron> How about gdb.thread_printing.add_printer? It also seems fine to me to
>> Aaron> have a method in the base class.
>> Aaron> e.g.
>> Aaron> myclass().add_printer("my printer")
>> Aaron> # instantiates class and adds it to list of printers with alias "my printer"
>>
>> It's sort of a norm in gdb to have the instantiation of the class
>> install the CLI bits as a side effect.  E.g., Command and Parameter do
>> this.  I don't insist on it, though.
> I just stuck with myclass().add_printer("my alias") for now.
>>
>> I like the name gdb.threads over gdb.thread_printing, just because I
>> assume we'll want to have more thread utility code, and this would
>> provide a spot to put it.
> Done. changed to gdb.threads.
>
>
>> Tom> However, if you do this, then you probably ought to change how thread
>> Tom> object lifetimes are handled more generally, getting rid of
>> Tom> threadlist_entry, etc.
>>
>> Aaron> Not entirely sure what you mean here, but I will look into it. My
>> Aaron> unfamiliarity with the codebase is to blame.
>>
>> No worries.
>>
>> The current code is written in a way to let us associate a gdb.Thread
>> object with a thread_info in an indirect way.  However, this probably
>> doesn't perform extremely well if there are many threads.
>>
>> So, your approach is superior.  However, I think it is best to do the
>> conversion completely.  This could be a separate refactoring patch --
>> just rip out the old threadlist_entry stuff and replace it with your
>> approach.  You can see how we handled this in breakpoint.h (search for
>> struct breakpoint_object) to make it work without including python.h
>> everywhere.
> I will leave this for dje@ to do.
>
>
>> Tom> What is the rationale for the 'prepare' step, anyway?
>>
>> Aaron> This is for the printers to do any one time setup before printing a
>> Aaron> list of threads. A common case I can see is if the printer needs to
>> Aaron> examine memory and traverse something like a linked list. Without a
>> Aaron> call like this, or an indicator in print_thread, there is no way for a
>> Aaron> printer to know the different between multiple calls to info threads.
>>
>> Ok, I see.
>> Should there also be an "unprepare" step so that these objects can
>> release resources after "info threads" exits?
> That's a good point. I've added a cleanup step.
>
>> Aaron> Ah, sorry, I'm unfamiliar with the Python C api. I will add a call to
>> Aaron> gdbpy_print_stack when printers == NULL. Are you also worried about
>> Aaron> the potential exceptions raised in PyList_Check and PyList_Size? I
>> Aaron> suppose in that case just having a call to gdbpy_print_stack at the
>> Aaron> end of this function or in each case of printers == NULL would be
>> Aaron> sufficient.
>>
>> Nearly all Python functions have to have their result checked for error.
>> This often leads to spaghetti code, unfortunately, but that's how it is.
>>
>> Exactly how to handle the error depends on the situation.
>>
>> In "Python-facing" code, the typical thing to do is propagate the error
>> -- release locally-acquired resources and return NULL (or -1 or whatever
>> it is for the current context).
>>
>> In "gdb-facing" code, usually we call gdbpy_print_stack.  This isn't
>> ideal, since in many situations I think it would be preferable to
>> convert the Python exception to a gdb exception.
>>
>> In your particular case I think it is friendliest to the user to call
>> gdbpy_print_stack, even assuming we implement real exception handling,
>> just because this approach means that some bad Python code won't make
>> "info threads" fail.
> Added stack trace printing to "handle" exceptions.
>
>> Tom> If you're making a list of printers in the .py code, you might as well
>> Tom> filter there instead of having this.  It's a lot easier to do it there.
>>
>> Aaron> The thing is that we do not create a new list. The same list is
>> Aaron> returned instead. I suppose if we add the check to
>> Aaron> prepare_thread_name_printers and just return a new list, that would be
>> Aaron> fine as well.
>>
>> I got a little lost here.
>>
>> If there is a single list, where disabled items are filtered when
>> iterating over it, then don't bother returning a list at all, just use
>> the global one from the .py code.
>>
>> Aaron> The problem with only setting names on thread-creation events is that
>> Aaron> a library managing threads and assigning internal thread names we may
>> Aaron> wish to print will potentially not have set the name at the time the
>> Aaron> thread is created.
>>
>> Ok, makes sense.
>>
>> Aaron> e.g.
>> Aaron> 1. thread created.
>> Aaron> 2. thread-created event in gdb. (no name available yet)
>> Aaron> 3. library sets internal name for thread.
>>
>> In this scenario I was picturing that the python code would set a
>> breakpoint to capture the interesting event.  But I can accept that this
>> may not always be desirable.
>>
>> Aaron> The thread name is assigned here because I think it is safe to assume
>> Aaron> that once a thread has a name, that name will not change. Also, if a
>> Aaron> user assigns a name via 'thread name foo', they would want that name
>> Aaron> to override any thread name printer.
>>
>> Ok, thanks.
>>
>> Aaron> Ok. I'll use a global static variable for the list of printers in
>> Aaron> python/py-infthread.c and use dummy functions for the #ifdef stuff.
>>
>> IIUC it could all just be in the python code.
> You are entirely right. I changed this to just keep the printer in the
> Python world, along with some other changes that cleans things up.

[-- Attachment #2: thread_name_printers_2.patch --]
[-- Type: application/octet-stream, Size: 29915 bytes --]

From 3ebdf3e05d01f1c48ec9dee57f412ccb25a90a81 Mon Sep 17 00:00:00 2001
From: Aaron Gamble <agamble@google.com>
Date: Fri, 24 Aug 2012 14:48:07 -0700
Subject: [PATCH] thread name printers

---
 gdb/data-directory/Makefile.in                    |    2 +
 gdb/doc/gdb.texinfo                               |   89 ++++++++++++++-
 gdb/gdbthread.h                                   |    5 +
 gdb/python/lib/gdb/__init__.py                    |    8 ++
 gdb/python/lib/gdb/command/thread_printers.py     |   72 ++++++++++++
 gdb/python/lib/gdb/threads.py                     |  127 +++++++++++++++++++++
 gdb/python/py-inferior.c                          |    2 +
 gdb/python/py-infthread.c                         |   96 ++++++++++++++++
 gdb/python/python-internal.h                      |    2 +
 gdb/python/python.c                               |    6 +
 gdb/python/python.h                               |    7 +
 gdb/testsuite/gdb.python/Makefile.in              |    2 +-
 gdb/testsuite/gdb.python/py-threadprettyprint.c   |  122 ++++++++++++++++++++
 gdb/testsuite/gdb.python/py-threadprettyprint.exp |   86 ++++++++++++++
 gdb/thread.c                                      |   23 ++++-
 15 files changed, 645 insertions(+), 4 deletions(-)
 create mode 100644 gdb/python/lib/gdb/command/thread_printers.py
 create mode 100644 gdb/python/lib/gdb/threads.py
 create mode 100644 gdb/testsuite/gdb.python/py-threadprettyprint.c
 create mode 100644 gdb/testsuite/gdb.python/py-threadprettyprint.exp

diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index 41947c6..3a5bb05 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -55,9 +55,11 @@ PYTHON_FILES = \
 	gdb/__init__.py \
 	gdb/types.py \
 	gdb/printing.py \
+	gdb/threads.py \
 	gdb/prompt.py \
 	gdb/command/__init__.py \
 	gdb/command/pretty_printers.py \
+	gdb/command/thread_printers.py \
 	gdb/command/prompt.py \
 	gdb/command/explore.py \
 	gdb/function/__init__.py \
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 08ba92d..51d9072 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -2766,8 +2766,8 @@ the target system's thread identifier (@var{systag})
 
 @item
 the thread's name, if one is known.  A thread can either be named by
-the user (see @code{thread name}, below), or, in some cases, by the
-program itself.
+the user (see @code{thread name}, below), by Thread Name Printers
+(@pxref{Thread Name Printers}), or, in some cases, by the program itself.
 
 @item
 the current stack frame summary for that thread
@@ -22585,6 +22585,7 @@ situation, a Python @code{KeyboardInterrupt} exception is thrown.
 * Inferiors In Python::         Python representation of inferiors (processes)
 * Events In Python::            Listening for events from @value{GDBN}.
 * Threads In Python::           Accessing inferior threads from Python.
+* Thread Name Printers::        Custom thread name printing from Python.
 * Commands In Python::          Implementing new commands in Python.
 * Parameters In Python::        Adding new @value{GDBN} parameters.
 * Functions In Python::         Writing new convenience functions.
@@ -24175,6 +24176,90 @@ Return a Boolean indicating whether the thread is exited.
 @end defun
 @end table
 
+@node Thread Name Printers
+@subsubsection Thread Name Printers
+@cindex thread name printers
+
+Thread name printers are used to print custom thread names. They are
+implemented as Python classes that inherit from
+gdb.thread_printing.ThreadNamePrinter in thread_printing.py and define
+the following two methods: @code{prepare} and @code{print_thread}.
+
+Thread name printers are registered and may be disabled and enabled by the
+provided alias when the printer was registered.
+
+Once a printer has been defined, it can be instantiated and added to the global
+list of printers as follows:
+@code{my_printer_class().add_printer("my printer alias")}
+
+@defun thread_name_printer.prepare (@var{self})
+@value{GDBN} will call this method for each printer at the beginning of the
+'info threads' command.
+
+In this method the printer may gather any information and store it internally
+for use when printing thread names.
+@end defun
+
+@defun thread_name_printer.print_thread (@var{self}, @var{name}, @var{thread_info})
+For each thread, @value{GDBN} will iterate through the registered printers and call this
+method in each enabled printer until a string is returned. It is up to each thread printer
+to determine whether it can or should provide a thread name.
+@var{name} is the default name for the thread.
+
+@var{thread_info} is the InferiorThread as defined in @pxref{Threads In Python}.
+@end defun
+
+@smallexample
+class ExamplePrinter(ThreadNamePrinter):
+    """Reference class for thread name printers.
+
+    Thread name printers must implement the two methods below.
+    In order for a printer to be used it needs to be instantiated and
+    added to the gdb.thread_printers list.
+    Use the add_printer(alias, printer) function for this task.
+    This function will instantiate the printer, set the alias of the instance
+    and add the instance to the printer list.
+    """
+
+    def prepare(self):
+        """Called once for each 'info threads' command before the threads
+           are printed. Printers may re-define this method to initialize
+           their internal data.
+        """
+        # Usually we will have some map of threads we own.
+        # If necessary, We should discover them here.
+        # For this dummy example, it will be empty.
+        self.my_threads = {}
+
+    def print_thread(self, thread_info):
+        """Return a new thread name or None.
+
+        Printers must define this function, which takes a InveriorThread object,
+        and, if the thread matches the printer (usually a ptid check) returns
+        a new string for the threads name.
+
+        Parameters:
+            thread_info: A gdb.inferior_thread object for the thread.
+
+        Returns:
+            -A string, if the thread matches the printer.
+            -None, if the thread does not match.
+        """
+        # We only return a string name if we own the thread.
+        if thread_info.ptid[0] in self.my_thread:
+          return "some interesting name"
+        else:
+          return None
+
+    def cleanup(self):
+        """Printers can use this method to do any cleanup they may wish to do
+           after all threads have been printed.
+        """
+        pass
+
+ExamplePrinter().add_printer("Example Printer")
+@end smallexample
+
 @node Commands In Python
 @subsubsection Commands In Python
 
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 0250555..58fac60 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -28,6 +28,7 @@ struct symtab;
 #include "frame.h"
 #include "ui-out.h"
 #include "inferior.h"
+#include "python/python-internal.h"
 
 /* Frontend view of the thread state.  Possible extensions: stepping,
    finishing, until(ling),...  */
@@ -227,6 +228,10 @@ struct thread_info
   /* Function that is called to free PRIVATE.  If this is NULL, then
      xfree will be called on PRIVATE.  */
   void (*private_dtor) (struct private_thread_info *);
+
+  /* Pointer to the corresponding Python PyObject for this thread.
+     This is set in python/py-inferior.c:add_thread_object.  */
+  thread_object *thread_object;
 };
 
 /* Create an empty thread list, or empty the existing one.  */
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index a82e495..6f33bce 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -39,3 +39,11 @@ for module, location in module_dict.iteritems():
          exec('import ' + py_file)
        except:
          print >> sys.stderr, traceback.format_exc()
+
+try:
+    from gdb.threads import ThreadNamePrinter
+    gdb.prepare_thread_name_printers = ThreadNamePrinter.prepare_thread_name_printers
+    gdb.apply_thread_name_printers = ThreadNamePrinter.apply_thread_name_printers
+    gdb.cleanup_thread_name_printers = ThreadNamePrinter.cleanup_thread_name_printers
+except:
+    print >> sys.stderr, "Unable to setup thread name pretty printing"
diff --git a/gdb/python/lib/gdb/command/thread_printers.py b/gdb/python/lib/gdb/command/thread_printers.py
new file mode 100644
index 0000000..40f472f
--- /dev/null
+++ b/gdb/python/lib/gdb/command/thread_printers.py
@@ -0,0 +1,72 @@
+# Copyright (C) 2010-2012 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""GDB commands for working with thread-name-printers."""
+
+import gdb
+from gdb.threads import ThreadNamePrinter
+
+class EnableThreadNamePrinter (gdb.Command):
+    """GDB command to enable the specified thread printer.
+
+    Usage: enable thread-name-printer [alias]
+
+    ALIAS is the name given to the printer when it was registered.
+    """
+
+    def __init__(self):
+        super(EnableThreadNamePrinter, self).__init__(
+                "enable thread-name-printer",
+                gdb.COMMAND_DATA)
+
+    def invoke(self, arg, from_tty):
+        ThreadNamePrinter.enable_printer(arg)
+
+class DisableThreadNamePrinter (gdb.Command):
+    """GDB command to disable the specified thread printer.
+
+    Usage: disable thread-name-printer [alias]
+
+    ALIAS is the name given to the printer when it was registered.
+    """
+
+    def __init__(self):
+        super(DisableThreadNamePrinter, self).__init__(
+                "disable thread-name-printer",
+                gdb.COMMAND_DATA)
+
+    def invoke(self, arg, from_tty):
+        ThreadNamePrinter.disable_printer(arg)
+
+class InfoThreadNamePrinter (gdb.Command):
+    """GDB command to list all the registered thread printers.
+
+    Usage: info thread-name-printer
+    """
+
+    def __init__(self):
+        super(InfoThreadNamePrinter, self).__init__(
+                "info thread-name-printer",
+                gdb.COMMAND_DATA)
+
+    def invoke(self, arg, from_tty):
+        print "Thread name pretty-printers:"
+        for p in ThreadNamePrinter.printers:
+            print "[%s] %s" % ("x" if p.enabled else " ",
+                               p.alias)
+
+EnableThreadNamePrinter()
+DisableThreadNamePrinter()
+InfoThreadNamePrinter()
diff --git a/gdb/python/lib/gdb/threads.py b/gdb/python/lib/gdb/threads.py
new file mode 100644
index 0000000..2a7eedb
--- /dev/null
+++ b/gdb/python/lib/gdb/threads.py
@@ -0,0 +1,127 @@
+# Pretty-printer utilities.
+# Copyright (C) 2010-2012 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Utilities and class definitions for thread name printers."""
+
+import gdb
+
+class ThreadNamePrinter(object):
+    """Base class for thread name printers.
+
+    Thread name printers must inherit from this base class
+    and implement the methods
+        -prepare()
+            Called once per info threads command prior to printing
+            any threads.
+
+        -print_thread(thread_info)
+            Called per thread. Return a new string name for any
+            thread that applies.
+
+        -cleanup() (OPTIONAL)
+            Called after all threads have been printed. Allows for
+            thread prints to do any cleanup they may need.
+    """
+
+    # This is the global list of thread printers
+    printers = []
+
+    def add_printer(self, alias, replace = False):
+        if not hasattr(self, "prepare"):
+            raise TypeError("printer missing attribute: prepare")
+        if not hasattr(self, "print_thread"):
+            raise TypeError("printer missing attribute: print_thread")
+
+        if not hasattr(self, "enabled"):
+            self.enabled = True
+
+        self.alias = alias
+
+        for i, p in enumerate(ThreadNamePrinter.printers):
+            if hasattr(p, "alias") and p.alias == alias:
+                if replace:
+                    del cls.printers[i]
+                    break # assumption: there will only one in the list.
+                else:
+                    raise RuntimeError(
+                        "Thread printer with alias %s already registered" % alias)
+        ThreadNamePrinter.printers.append(self)
+
+    @classmethod
+    def prepare_thread_name_printers(cls):
+        """Run for every 'info threads' command.
+        Initialize all the printers and return the list.
+
+        This is called by GDB in the 'info threads' code.
+        """
+        for p in cls.printers:
+            p.prepare()
+        return cls.printers
+
+    @classmethod
+    def apply_thread_name_printers(cls, thread_info):
+        """Apply thread name printers to a particular thread.
+
+        If a new name is returned by any printer, update the name of the thread
+        and return.
+
+        This is called by GDB in the 'info threads' code.
+        """
+        for p in [ p for p in cls.printers if p.enabled == True ]:
+            new_name = p.print_thread(thread_info)
+            if new_name:
+                thread_info.name = new_name
+                break
+        return
+
+    @classmethod
+    def cleanup_thread_name_printers(cls):
+        """Call the cleanup method for each printer."""
+        for p in [ p for p in cls.printers if p.enabled == True ]:
+            if hasattr(p.cleanup):
+                p.cleanup()
+
+    @classmethod
+    def remove_printer(cls, alias):
+        for i, p in enumerate(cls.printers):
+            if p.alias == alias:
+                del cls.printers[i]
+                return
+        print "Printer '%s' not found" % alias
+
+    @classmethod
+    def disable_printer(cls, alias):
+        for p in cls.printers:
+            if p.alias == alias:
+              cls.disable_printer_instance(p)
+              return
+        print "Printer '%s' not found" % alias
+
+    @classmethod
+    def enable_printer(cls, alias):
+        for p in cls.printers:
+            if p.alias == alias:
+                cls.enable_printer_instance(p)
+                return
+        print "Printer '%s' not found" % alias
+
+    @staticmethod
+    def disable_printer_instance(p):
+        p.enabled = False
+
+    @staticmethod
+    def enable_printer_instance(p):
+        p.enabled = True
diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c
index 907b73e..f087d3c 100644
--- a/gdb/python/py-inferior.c
+++ b/gdb/python/py-inferior.c
@@ -239,6 +239,8 @@ add_thread_object (struct thread_info *tp)
       return;
     }
 
+  tp->thread_object = thread_obj;
+
   inf_obj = (inferior_object *) thread_obj->inf_obj;
 
   entry = xmalloc (sizeof (struct threadlist_entry));
diff --git a/gdb/python/py-infthread.c b/gdb/python/py-infthread.c
index 8821f20..747c138 100644
--- a/gdb/python/py-infthread.c
+++ b/gdb/python/py-infthread.c
@@ -21,7 +21,10 @@
 #include "exceptions.h"
 #include "gdbthread.h"
 #include "inferior.h"
+#include "python/python.h"
 #include "python-internal.h"
+#include "arch-utils.h"
+#include "language.h"
 
 static PyTypeObject thread_object_type;
 
@@ -340,3 +343,96 @@ static PyTypeObject thread_object_type =
   0,				  /* tp_init */
   0				  /* tp_alloc */
 };
+
+#ifdef HAVE_PYTHON
+
+/* Calls prepare for each printer in the the list of thread name printers.  */
+
+void
+prepare_thread_name_printers (void)
+{
+  PyObject *ret;
+  struct cleanup *cleanups;
+
+  cleanups = ensure_python_env (get_current_arch (), current_language);
+
+  /* Prepare each thread printer.  */
+  if (PyObject_HasAttrString (gdb_module, "prepare_thread_name_printers"))
+    {
+      ret = PyObject_CallMethod (gdb_module, "prepare_thread_name_printers",
+				 NULL);
+      if (ret == NULL)
+        gdbpy_print_stack();
+      else
+	Py_DECREF (ret);
+    }
+  do_cleanups (cleanups);
+}
+
+/* Called per thread by print_thread_info to check if a thread name printer
+   applies to a thread to give it a new name. If a new name is returned for
+   a thread, the name is updated and the loop terminates.  */
+
+void
+apply_thread_name_pretty_printers (struct thread_info *info)
+{
+  struct cleanup *cleanups;
+  PyObject *ret;
+
+  cleanups = ensure_python_env (get_current_arch (), current_language);
+
+  if (PyObject_HasAttr (gdb_module, gdbpy_apply_thread_printers_cst))
+    {
+      ret = PyObject_CallMethodObjArgs (gdb_module,
+					gdbpy_apply_thread_printers_cst,
+					(PyObject *) info->thread_object, NULL);
+      if (ret == NULL)
+	  gdbpy_print_stack();
+      else
+	Py_DECREF (ret);
+    }
+
+  do_cleanups (cleanups);
+}
+
+/* Called after all threads have been printed. Allows for thread name printers
+   to release any objects or do any cleanups.  */
+
+void
+cleanup_thread_name_pretty_printers (void *unused)
+{
+  struct cleanup *cleanups;
+  PyObject *ret;
+
+  cleanups = ensure_python_env (get_current_arch (), current_language);
+
+  if (PyObject_HasAttr (gdb_module, gdbpy_cleanup_thread_printers_cst))
+    {
+      ret = PyObject_CallMethodObjArgs (gdb_module,
+					gdbpy_cleanup_thread_printers_cst,
+					NULL);
+      if (ret == NULL)
+	  gdbpy_print_stack();
+      else
+	Py_DECREF (ret);
+    }
+
+  do_cleanups (cleanups);
+}
+
+#else // HAVE_PYTHON
+void
+prepare_thread_name_printers (void)
+{
+}
+
+void
+apply_thread_name_pretty_printers (struct thread_info *info)
+{
+}
+
+void
+cleanup_thread_name_pretty_printers (void *unused)
+{
+}
+#endif
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index bae61c2..c5e412e 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -328,6 +328,8 @@ extern PyObject *gdbpy_to_string_cst;
 extern PyObject *gdbpy_display_hint_cst;
 extern PyObject *gdbpy_enabled_cst;
 extern PyObject *gdbpy_value_cst;
+extern PyObject *gdbpy_apply_thread_printers_cst;
+extern PyObject *gdbpy_cleanup_thread_printers_cst;
 
 /* Exception types.  */
 extern PyObject *gdbpy_gdb_error;
diff --git a/gdb/python/python.c b/gdb/python/python.c
index c66efe4..f25dab6 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -82,6 +82,8 @@ PyObject *gdbpy_display_hint_cst;
 PyObject *gdbpy_doc_cst;
 PyObject *gdbpy_enabled_cst;
 PyObject *gdbpy_value_cst;
+PyObject *gdbpy_apply_thread_printers_cst;
+PyObject *gdbpy_cleanup_thread_printers_cst;
 
 /* The GdbError exception.  */
 PyObject *gdbpy_gdberror_exc;
@@ -1305,6 +1307,10 @@ message == an error message without a stack will be printed."),
   gdbpy_doc_cst = PyString_FromString ("__doc__");
   gdbpy_enabled_cst = PyString_FromString ("enabled");
   gdbpy_value_cst = PyString_FromString ("value");
+  gdbpy_apply_thread_printers_cst
+    = PyString_FromString ("apply_thread_name_printers");
+  gdbpy_cleanup_thread_printers_cst
+    = PyString_FromString ("cleanup_thread_name_printers");
 
   /* Release the GIL while gdb runs.  */
   PyThreadState_Swap (NULL);
diff --git a/gdb/python/python.h b/gdb/python/python.h
index dd7066f..9f6beed 100644
--- a/gdb/python/python.h
+++ b/gdb/python/python.h
@@ -21,6 +21,7 @@
 #define GDB_PYTHON_H
 
 #include "value.h"
+#include "gdbthread.h"
 
 struct breakpoint_object;
 
@@ -37,6 +38,12 @@ int apply_val_pretty_printer (struct type *type, const gdb_byte *valaddr,
 			      const struct value_print_options *options,
 			      const struct language_defn *language);
 
+void prepare_thread_name_printers (void);
+
+void apply_thread_name_pretty_printers (struct thread_info *info);
+
+void cleanup_thread_name_pretty_printers (void *unused);
+
 void preserve_python_values (struct objfile *objfile, htab_t copied_types);
 
 void gdbpy_load_auto_scripts_for_objfile (struct objfile *objfile);
diff --git a/gdb/testsuite/gdb.python/Makefile.in b/gdb/testsuite/gdb.python/Makefile.in
index 4e286b5..9155eac 100644
--- a/gdb/testsuite/gdb.python/Makefile.in
+++ b/gdb/testsuite/gdb.python/Makefile.in
@@ -6,7 +6,7 @@ EXECUTABLES = py-type py-value py-prettyprint py-template py-block \
 	py-shared python lib-types py-events py-evthreads py-frame \
 	py-mi py-pp-maint py-progspace py-section-script py-objfile \
 	py-finish-breakpoint py-finish-breakpoint2 py-value-cc py-explore \
-	py-explore-cc
+	py-explore-cc py-threadprettyprint
 
 MISCELLANEOUS = py-shared-sl.sl py-events-shlib.so py-events-shlib-nodebug.so 
 
diff --git a/gdb/testsuite/gdb.python/py-threadprettyprint.c b/gdb/testsuite/gdb.python/py-threadprettyprint.c
new file mode 100644
index 0000000..1cba2d6
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-threadprettyprint.c
@@ -0,0 +1,122 @@
+/* Test cases for thread name printers.
+
+   Copyright 2008, 2010-2012 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef NR_THREADS
+#define NR_THREADS 4
+#endif
+
+int thread_count;
+
+pthread_mutex_t thread_count_mutex;
+
+pthread_cond_t thread_count_condvar;
+
+void
+incr_thread_count (void)
+{
+  pthread_mutex_lock (&thread_count_mutex);
+  ++thread_count;
+  if (thread_count == NR_THREADS)
+    pthread_cond_signal (&thread_count_condvar);
+  pthread_mutex_unlock (&thread_count_mutex);
+}
+
+void
+cond_wait (pthread_cond_t *cond, pthread_mutex_t *mut)
+{
+  pthread_mutex_lock (mut);
+  pthread_cond_wait (cond, mut);
+  pthread_mutex_unlock (mut);
+}
+
+void
+noreturn (void)
+{
+  pthread_mutex_t mut;
+  pthread_cond_t cond;
+
+  pthread_mutex_init (&mut, NULL);
+  pthread_cond_init (&cond, NULL);
+
+  /* Wait for a condition that will never be signaled, so we effectively
+     block the thread here.  */
+  cond_wait (&cond, &mut);
+}
+
+void *
+forever_pthread (void *unused)
+{
+  incr_thread_count ();
+  noreturn ();
+}
+
+/* Wait until all threads are running.  */
+
+void
+wait_all_threads_running (void)
+{
+  pthread_mutex_lock (&thread_count_mutex);
+  if (thread_count == NR_THREADS)
+    {
+      pthread_mutex_unlock (&thread_count_mutex);
+      return;
+    }
+  pthread_cond_wait (&thread_count_condvar, &thread_count_mutex);
+  if (thread_count == NR_THREADS)
+    {
+      pthread_mutex_unlock (&thread_count_mutex);
+      return;
+    }
+  pthread_mutex_unlock (&thread_count_mutex);
+  printf ("failed waiting for all threads to start\n");
+  abort ();
+}
+
+/* Called when all threads are running.
+   Easy place for a breakpoint.  */
+
+void
+all_threads_running (void)
+{
+}
+
+int
+main (void)
+{
+  pthread_t forever[NR_THREADS];
+  int i;
+
+  pthread_mutex_init (&thread_count_mutex, NULL);
+  pthread_cond_init (&thread_count_condvar, NULL);
+
+  for (i = 0; i < NR_THREADS; ++i)
+    pthread_create (&forever[i], NULL, forever_pthread, NULL);
+
+  wait_all_threads_running ();
+  all_threads_running ();
+
+  return 0;
+}
+
diff --git a/gdb/testsuite/gdb.python/py-threadprettyprint.exp b/gdb/testsuite/gdb.python/py-threadprettyprint.exp
new file mode 100644
index 0000000..09e2756
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-threadprettyprint.exp
@@ -0,0 +1,86 @@
+# Copyright (C) 2004, 2007-2008, 2010-2012 Free Software Foundation,
+# Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Test thread name pretty printer functionality.
+
+set NR_THREADS 4
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+# Start with a fresh gdb.
+clean_restart ${binfile}
+
+if { [skip_python_tests] } { continue }
+
+if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable [list debug "additional_flags=-DNR_THREADS=$NR_THREADS"]] != "" } {
+    return -1
+}
+
+if { ![runto_main] } {
+    fail "Can't run to main"
+    return 0
+}
+
+gdb_test "break all_threads_running" \
+         "Breakpoint 2 at .*: file .*${srcfile}, line .*" \
+         "breakpoint on all_threads_running"
+
+# Run the program and make sure GDB reports that we stopped after
+# hitting breakpoint 2 in all_threads_running().
+
+gdb_test "continue" \
+         ".*Breakpoint 2, all_threads_running ().*" \
+         "run to all_threads_running"
+
+gdb_py_test_multiple "define some thread printers" \
+  "python" "" \
+  "from gdb.threads import ThreadNamePrinter" "" \
+  "class test_thread_printer (ThreadNamePrinter):" "" \
+  "  def prepare(self):" "" \
+  "    pass" "" \
+  "  def print_thread(self, thread):" "" \
+  "    return \"test_name\" + str(thread.ptid\[1\])" "" \
+  "test_thread_printer().add_printer('test_printer')" "" \
+  "class bad_no_methods(ThreadNamePrinter):" "" \
+  "  pass" "" \
+  "class bad_no_print_thread(ThreadNamePrinter):" "" \
+  "  def prepare(self): " "" \
+  "    pass" "" \
+  "end" ""
+
+gdb_test "info thread-name-printer" \
+         "Thread name pretty-printers:\r\n\\\[x\\\] test_printer" \
+         "print registered thread name printers"
+
+# Check we are correctly rejecting bad printers.
+# These "printers" were definied in the "define some thread printers" test above.
+gdb_test "python bad_no_methods().add_printer('bad printer')" \
+         ".*TypeError: printer missing attribute: prepare.*" \
+         "Catch bad printers - missing attribute prepare"
+
+gdb_test "python bad_no_print_thread().add_printer('bad printer')" \
+         ".*TypeError: printer missing attribute: print_thread.*" \
+         "Catch bad printers - missing attribute print_thread"
+
+
+# We want to make sure the thread list has printed all the thread names to include the ptid as our
+# test printer was defined.
+# Note: the \\2 backreference is 2 because gdb_test contains the first matching group
+gdb_test "info threads" \
+         " +Id +Target +Id +Frame(?:.*Thread 0x\[A-Fa-f0-9\]+ \\\(LWP (\\\d+)\\\) \"test_name\\2.*)+" \
+         "thread name printer test"
diff --git a/gdb/thread.c b/gdb/thread.c
index 7e8eec5..fae901a 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -34,6 +34,7 @@
 #include "regcache.h"
 #include "gdb.h"
 #include "gdb_string.h"
+#include "python/python.h"
 
 #include <ctype.h>
 #include <sys/types.h>
@@ -774,6 +775,10 @@ print_thread_info (struct ui_out *uiout, char *requested_threads, int pid)
   /* We'll be switching threads temporarily.  */
   old_chain = make_cleanup_restore_current_thread ();
 
+  /* Prepare the list of thread printers.  */
+  prepare_thread_name_printers ();
+  make_cleanup (cleanup_thread_name_pretty_printers, NULL);
+
   /* For backward compatibility, we make a list for MI.  A table is
      preferable for the CLI, though, because it shows table
      headers.  */
@@ -867,7 +872,16 @@ print_thread_info (struct ui_out *uiout, char *requested_threads, int pid)
 
       target_id = target_pid_to_str (tp->ptid);
       extra_info = target_extra_thread_info (tp);
-      name = tp->name ? tp->name : target_thread_name (tp);
+
+      if (tp->name != NULL)
+	name = tp->name;
+      else
+	{
+	  /* Potentially update tp->name with a pretty printer.  */
+	  apply_thread_name_pretty_printers (tp);
+	  if (tp->name != NULL)
+	    name = tp->name;
+	}
 
       if (ui_out_is_mi_like_p (uiout))
 	{
@@ -1469,6 +1483,13 @@ _initialize_thread (void)
 Usage: info threads [ID]...\n\
 Optional arguments are thread IDs with spaces between.\n\
 If no arguments, all threads are displayed."));
+  /* Keep these abbreviations of "info threads", "info thread-name-printer"
+     makes them ambigous otherwise.  */
+  add_info_alias ("th", "threads" , 1);
+  add_info_alias ("thr", "threads" , 1);
+  add_info_alias ("thre", "threads" , 1);
+  add_info_alias ("threa", "threads" , 1);
+  add_info_alias ("thread", "threads" , 1);
 
   add_prefix_cmd ("thread", class_run, thread_command, _("\
 Use this command to switch between threads.\n\
-- 
1.7.7.3


  reply	other threads:[~2012-08-24 22:02 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-08-21 20:59 Aaron Gamble
2012-08-22 19:39 ` Tom Tromey
2012-08-23  0:29   ` Aaron Gamble
2012-08-23 16:18     ` Tom Tromey
2012-08-24 22:00       ` Aaron Gamble
2012-08-24 22:02         ` Aaron Gamble [this message]
2012-08-25  5:28           ` Eli Zaretskii

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='CAHX8C+KctA8K9tWZZug5o6Z+5f6GB8hNJ-qaa2PyW7=nHQAp5Q@mail.gmail.com' \
    --to=agamble@google.com \
    --cc=gdb-patches@sourceware.org \
    --cc=tromey@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox