aboutsummaryrefslogtreecommitdiff
path: root/gdb
diff options
context:
space:
mode:
authorTom Tromey <tom@tromey.com>2023-07-04 09:15:54 -0600
committerTom Tromey <tom@tromey.com>2023-07-23 14:33:44 -0600
commit560c121c207af3c31c83a815de2569535fcd3aa7 (patch)
tree950e734a9586020f9b2d3f70293eda7957330bb0 /gdb
parent8a9da63e407c511df32841abcbe20effe2f3e398 (diff)
downloadbinutils-560c121c207af3c31c83a815de2569535fcd3aa7.zip
binutils-560c121c207af3c31c83a815de2569535fcd3aa7.tar.gz
binutils-560c121c207af3c31c83a815de2569535fcd3aa7.tar.bz2
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.
Diffstat (limited to 'gdb')
-rw-r--r--gdb/NEWS6
-rw-r--r--gdb/doc/python.texi104
-rw-r--r--gdb/python/lib/gdb/__init__.py32
-rw-r--r--gdb/python/lib/gdb/dap/startup.py26
4 files changed, 106 insertions, 62 deletions
diff --git a/gdb/NEWS b/gdb/NEWS
index a44f90e..3f414a5 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 b4cdc22..a4ec5bf 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 6f3f194..98aadb1 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 <http://www.gnu.org/licenses/>.
+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 aa834cd..15d1fb9 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):