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.
This commit is contained in:
parent
8a9da63e40
commit
560c121c20
6
gdb/NEWS
6
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.
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user