From: Tom Tromey Date: Tue, 4 Jul 2023 15:15:54 +0000 (-0600) Subject: Export gdb.block_signals and create gdb.Thread X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=560c121c207af3c31c83a815de2569535fcd3aa7;p=binutils-gdb.git Export gdb.block_signals and create gdb.Thread While working on an experiment, I realized that I needed the DAP block_signals function. I figured other developers may need it as well, so this patch moves it from DAP to the gdb module and exports it. I also added a new subclass of threading.Thread that ensures that signals are blocked in the new thread. Finally, this patch slightly rearranges the documentation so that gdb-side threading issues and functions are all discussed in a single node. --- diff --git a/gdb/NEWS b/gdb/NEWS index a44f90e7c1c..3f414a54fb7 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -228,6 +228,12 @@ info main ** New function gdb.execute_mi(COMMAND, [ARG]...), that invokes a GDB/MI command and returns the output as a Python dictionary. + ** New function gdb.block_signals(). This returns a context manager + that blocks any signals that GDB needs to handle itself. + + ** New class gdb.Thread. This is a subclass of threading.Thread + that calls gdb.block_signals in its "start" method. + ** gdb.parse_and_eval now has a new "global_context" parameter. This can be used to request that the parse only examine global symbols. diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index b4cdc22f626..a4ec5bf5ceb 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -190,6 +190,7 @@ optional arguments while skipping others. Example: @menu * Basic Python:: Basic Python Functions. +* Threading in GDB:: Using Python threads in GDB. * Exception Handling:: How Python exceptions are translated. * Values From Inferior:: Python representation of values. * Types In Python:: Python representation of types. @@ -436,44 +437,6 @@ will be @code{None} and 0 respectively. This is identical to historical compatibility. @end defun -@defun gdb.post_event (event) -Put @var{event}, a callable object taking no arguments, into -@value{GDBN}'s internal event queue. This callable will be invoked at -some later point, during @value{GDBN}'s event processing. Events -posted using @code{post_event} will be run in the order in which they -were posted; however, there is no way to know when they will be -processed relative to other events inside @value{GDBN}. - -@value{GDBN} is not thread-safe. If your Python program uses multiple -threads, you must be careful to only call @value{GDBN}-specific -functions in the @value{GDBN} thread. @code{post_event} ensures -this. For example: - -@smallexample -(@value{GDBP}) python ->import threading -> ->class Writer(): -> def __init__(self, message): -> self.message = message; -> def __call__(self): -> gdb.write(self.message) -> ->class MyThread1 (threading.Thread): -> def run (self): -> gdb.post_event(Writer("Hello ")) -> ->class MyThread2 (threading.Thread): -> def run (self): -> gdb.post_event(Writer("World\n")) -> ->MyThread1().start() ->MyThread2().start() ->end -(@value{GDBP}) Hello World -@end smallexample -@end defun - @defun gdb.write (string @r{[}, stream@r{]}) Print a string to @value{GDBN}'s paginated output stream. The optional @var{stream} determines the stream to print to. The default @@ -669,6 +632,71 @@ In Python}), the @code{language} method might be preferable in some cases, as that is not affected by the user's language setting. @end defun +@node Threading in GDB +@subsubsection Threading in GDB + +@value{GDBN} is not thread-safe. If your Python program uses multiple +threads, you must be careful to only call @value{GDBN}-specific +functions in the @value{GDBN} thread. @value{GDBN} provides some +functions to help with this. + +@defun gdb.block_signals () +As mentioned earlier (@pxref{Basic Python}), certain signals must be +delivered to the @value{GDBN} main thread. The @code{block_signals} +function returns a context manager that will block these signals on +entry. This can be used when starting a new thread to ensure that the +signals are blocked there, like: + +@smallexample +with gdb.block_signals(): + start_new_thread() +@end smallexample +@end defun + +@deftp {class} gdb.Thread +This is a subclass of Python's @code{threading.Thread} class. It +overrides the @code{start} method to call @code{block_signals}, making +this an easy-to-use drop-in replacement for creating threads that will +work well in @value{GDBN}. +@end deftp + +@defun gdb.post_event (event) +Put @var{event}, a callable object taking no arguments, into +@value{GDBN}'s internal event queue. This callable will be invoked at +some later point, during @value{GDBN}'s event processing. Events +posted using @code{post_event} will be run in the order in which they +were posted; however, there is no way to know when they will be +processed relative to other events inside @value{GDBN}. + +Unlike most Python APIs in @value{GDBN}, @code{post_event} is +thread-safe. For example: + +@smallexample +(@value{GDBP}) python +>import threading +> +>class Writer(): +> def __init__(self, message): +> self.message = message; +> def __call__(self): +> gdb.write(self.message) +> +>class MyThread1 (threading.Thread): +> def run (self): +> gdb.post_event(Writer("Hello ")) +> +>class MyThread2 (threading.Thread): +> def run (self): +> gdb.post_event(Writer("World\n")) +> +>MyThread1().start() +>MyThread2().start() +>end +(@value{GDBP}) Hello World +@end smallexample +@end defun + + @node Exception Handling @subsubsection Exception Handling @cindex python exceptions diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py index 6f3f1945f62..98aadb1dfea 100644 --- a/gdb/python/lib/gdb/__init__.py +++ b/gdb/python/lib/gdb/__init__.py @@ -13,6 +13,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import signal +import threading import traceback import os import sys @@ -259,3 +261,33 @@ def with_parameter(name, value): yield None finally: set_parameter(name, old_value) + + +@contextmanager +def blocked_signals(): + """A helper function that blocks and unblocks signals.""" + if not hasattr(signal, "pthread_sigmask"): + yield + return + + to_block = {signal.SIGCHLD, signal.SIGINT, signal.SIGALRM, signal.SIGWINCH} + signal.pthread_sigmask(signal.SIG_BLOCK, to_block) + try: + yield None + finally: + signal.pthread_sigmask(signal.SIG_UNBLOCK, to_block) + + +class Thread(threading.Thread): + """A GDB-specific wrapper around threading.Thread + + This wrapper ensures that the new thread blocks any signals that + must be delivered on GDB's main thread.""" + + def start(self): + # GDB requires that these be delivered to the main thread. We + # do this here to avoid any possible race with the creation of + # the new thread. The thread mask is inherited by new + # threads. + with blocked_signals(): + super().start() diff --git a/gdb/python/lib/gdb/dap/startup.py b/gdb/python/lib/gdb/dap/startup.py index aa834cdb14c..15d1fb9e9e5 100644 --- a/gdb/python/lib/gdb/dap/startup.py +++ b/gdb/python/lib/gdb/dap/startup.py @@ -18,10 +18,8 @@ import functools import gdb import queue -import signal import threading import traceback -from contextlib import contextmanager import sys @@ -33,32 +31,12 @@ _gdb_thread = threading.current_thread() _dap_thread = None -@contextmanager -def blocked_signals(): - """A helper function that blocks and unblocks signals.""" - if not hasattr(signal, "pthread_sigmask"): - yield - return - - to_block = {signal.SIGCHLD, signal.SIGINT, signal.SIGALRM, signal.SIGWINCH} - signal.pthread_sigmask(signal.SIG_BLOCK, to_block) - try: - yield None - finally: - signal.pthread_sigmask(signal.SIG_UNBLOCK, to_block) - - def start_thread(name, target, args=()): """Start a new thread, invoking TARGET with *ARGS there. This is a helper function that ensures that any GDB signals are correctly blocked.""" - # GDB requires that these be delivered to the gdb thread. We - # do this here to avoid any possible race with the creation of - # the new thread. The thread mask is inherited by new - # threads. - with blocked_signals(): - result = threading.Thread(target=target, args=args, daemon=True) - result.start() + result = gdb.Thread(target=target, args=args, daemon=True) + result.start() def start_dap(target):