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
next prev parent 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