mirror of
https://github.com/espressif/binutils-gdb.git
synced 2025-10-20 14:22:35 +08:00
Introduce interruptible_select
We have places where we call a blocking gdb_select expecting that a Ctrl-C will unblock it. However, if the Ctrl-C is pressed just before gdb_select, the SIGINT handler runs before gdb_select, and thus gdb_select won't return. For example gdb_readline_no_editing: QUIT; /* Wait until at least one byte of data is available. Control-C can interrupt gdb_select, but not fgetc. */ FD_ZERO (&readfds); FD_SET (fd, &readfds); if (gdb_select (fd + 1, &readfds, NULL, NULL, NULL) == -1) and stdio_file_read: /* For the benefit of Windows, call gdb_select before reading from the file. Wait until at least one byte of data is available. Control-C can interrupt gdb_select, but not read. */ { fd_set readfds; FD_ZERO (&readfds); FD_SET (stdio->fd, &readfds); if (gdb_select (stdio->fd + 1, &readfds, NULL, NULL, NULL) == -1) return -1; } return read (stdio->fd, buf, length_buf); This is a race classically fixed with either the self-pipe trick, or by blocking SIGINT and then using pselect instead of select. Blocking SIGINT most of the time would mean that check_quit_flag (and thus QUIT) would need to do a syscall every time it is called, which sounds best avoided, since QUIT is called in many loops. Thus we take the self-pipe trick route (wrapped in a serial event). Instead of having all places that need this manually add an extra file descriptor to the set of gdb_select's watched file descriptors, we introduce a wrapper, interruptible_select, that does that. The Windows version of gdb_select actually does not suffer from this, because mingw-hdep.c:gdb_call_async_signal_handler sets a Windows event that gdb_select always waits on. So this patch can be seen as generalization of that technique. We can't remove that extra event from mingw-hdep.c until we get rid of immediate_quit though. gdb/ChangeLog: 2016-04-12 Pedro Alves <palves@redhat.com> * defs.h: Extend QUIT-related comments to mention interruptible_select. (quit_serial_event_set, quit_serial_event_clear): Declare. * event-top.c: Include "ser-event.h" and "gdb_select.h". (quit_serial_event): New global. (async_init_signals): Make quit_serial_event. (quit_serial_event_set, quit_serial_event_clear) (quit_serial_event_fd, interruptible_select): New functions. * extension.c (set_quit_flag): Set the quit serial event. (check_quit_flag): Clear the quit serial event. * gdb_select.h (interruptible_select): New declaration. * guile/scm-ports.c (ioscm_input_waiting): Use interruptible_select instead of gdb_select. * top.c (gdb_readline_no_editing): Likewise. * ui-file.c (stdio_file_read): Likewise.
This commit is contained in:
@ -1,3 +1,21 @@
|
|||||||
|
2016-04-12 Pedro Alves <palves@redhat.com>
|
||||||
|
|
||||||
|
* defs.h: Extend QUIT-related comments to mention
|
||||||
|
interruptible_select.
|
||||||
|
(quit_serial_event_set, quit_serial_event_clear): Declare.
|
||||||
|
* event-top.c: Include "ser-event.h" and "gdb_select.h".
|
||||||
|
(quit_serial_event): New global.
|
||||||
|
(async_init_signals): Make quit_serial_event.
|
||||||
|
(quit_serial_event_set, quit_serial_event_clear)
|
||||||
|
(quit_serial_event_fd, interruptible_select): New functions.
|
||||||
|
* extension.c (set_quit_flag): Set the quit serial event.
|
||||||
|
(check_quit_flag): Clear the quit serial event.
|
||||||
|
* gdb_select.h (interruptible_select): New declaration.
|
||||||
|
* guile/scm-ports.c (ioscm_input_waiting): Use
|
||||||
|
interruptible_select instead of gdb_select.
|
||||||
|
* top.c (gdb_readline_no_editing): Likewise.
|
||||||
|
* ui-file.c (stdio_file_read): Likewise.
|
||||||
|
|
||||||
2016-04-12 Pedro Alves <palves@redhat.com>
|
2016-04-12 Pedro Alves <palves@redhat.com>
|
||||||
|
|
||||||
* event-loop.c: Include "ser-event.h".
|
* event-loop.c: Include "ser-event.h".
|
||||||
|
11
gdb/defs.h
11
gdb/defs.h
@ -131,6 +131,11 @@ extern char *debug_file_directory;
|
|||||||
take a long time, and which ought to be interruptible, checks this
|
take a long time, and which ought to be interruptible, checks this
|
||||||
flag using the QUIT macro.
|
flag using the QUIT macro.
|
||||||
|
|
||||||
|
In addition to setting a flag, the SIGINT handler also marks a
|
||||||
|
select/poll-able file descriptor as read-ready. That is used by
|
||||||
|
interruptible_select in order to support interrupting blocking I/O
|
||||||
|
in a race-free manner.
|
||||||
|
|
||||||
These functions use the extension_language_ops API to allow extension
|
These functions use the extension_language_ops API to allow extension
|
||||||
language(s) and GDB SIGINT handling to coexist seamlessly. */
|
language(s) and GDB SIGINT handling to coexist seamlessly. */
|
||||||
|
|
||||||
@ -159,6 +164,12 @@ extern void maybe_quit (void);
|
|||||||
connection. */
|
connection. */
|
||||||
#define QUIT maybe_quit ()
|
#define QUIT maybe_quit ()
|
||||||
|
|
||||||
|
/* Set the serial event associated with the quit flag. */
|
||||||
|
extern void quit_serial_event_set (void);
|
||||||
|
|
||||||
|
/* Clear the serial event associated with the quit flag. */
|
||||||
|
extern void quit_serial_event_clear (void);
|
||||||
|
|
||||||
/* * Languages represented in the symbol table and elsewhere.
|
/* * Languages represented in the symbol table and elsewhere.
|
||||||
This should probably be in language.h, but since enum's can't
|
This should probably be in language.h, but since enum's can't
|
||||||
be forward declared to satisfy opaque references before their
|
be forward declared to satisfy opaque references before their
|
||||||
|
@ -38,6 +38,8 @@
|
|||||||
#include "annotate.h"
|
#include "annotate.h"
|
||||||
#include "maint.h"
|
#include "maint.h"
|
||||||
#include "buffer.h"
|
#include "buffer.h"
|
||||||
|
#include "ser-event.h"
|
||||||
|
#include "gdb_select.h"
|
||||||
|
|
||||||
/* readline include files. */
|
/* readline include files. */
|
||||||
#include "readline/readline.h"
|
#include "readline/readline.h"
|
||||||
@ -733,6 +735,12 @@ gdb_readline_no_editing_callback (gdb_client_data client_data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* The serial event associated with the QUIT flag. set_quit_flag sets
|
||||||
|
this, and check_quit_flag clears it. Used by interruptible_select
|
||||||
|
to be able to do interruptible I/O with no race with the SIGINT
|
||||||
|
handler. */
|
||||||
|
static struct serial_event *quit_serial_event;
|
||||||
|
|
||||||
/* Initialization of signal handlers and tokens. There is a function
|
/* Initialization of signal handlers and tokens. There is a function
|
||||||
handle_sig* for each of the signals GDB cares about. Specifically:
|
handle_sig* for each of the signals GDB cares about. Specifically:
|
||||||
SIGINT, SIGFPE, SIGQUIT, SIGTSTP, SIGHUP, SIGWINCH. These
|
SIGINT, SIGFPE, SIGQUIT, SIGTSTP, SIGHUP, SIGWINCH. These
|
||||||
@ -750,6 +758,8 @@ async_init_signals (void)
|
|||||||
{
|
{
|
||||||
initialize_async_signal_handlers ();
|
initialize_async_signal_handlers ();
|
||||||
|
|
||||||
|
quit_serial_event = make_serial_event ();
|
||||||
|
|
||||||
signal (SIGINT, handle_sigint);
|
signal (SIGINT, handle_sigint);
|
||||||
sigint_token =
|
sigint_token =
|
||||||
create_async_signal_handler (async_request_quit, NULL);
|
create_async_signal_handler (async_request_quit, NULL);
|
||||||
@ -794,8 +804,33 @@ async_init_signals (void)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tell the event loop what to do if SIGINT is received.
|
/* See defs.h. */
|
||||||
See event-signal.c. */
|
|
||||||
|
void
|
||||||
|
quit_serial_event_set (void)
|
||||||
|
{
|
||||||
|
serial_event_set (quit_serial_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See defs.h. */
|
||||||
|
|
||||||
|
void
|
||||||
|
quit_serial_event_clear (void)
|
||||||
|
{
|
||||||
|
serial_event_clear (quit_serial_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return the selectable file descriptor of the serial event
|
||||||
|
associated with the quit flag. */
|
||||||
|
|
||||||
|
static int
|
||||||
|
quit_serial_event_fd (void)
|
||||||
|
{
|
||||||
|
return serial_event_fd (quit_serial_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle a SIGINT. */
|
||||||
|
|
||||||
void
|
void
|
||||||
handle_sigint (int sig)
|
handle_sigint (int sig)
|
||||||
{
|
{
|
||||||
@ -819,6 +854,42 @@ handle_sigint (int sig)
|
|||||||
gdb_call_async_signal_handler (sigint_token, immediate_quit);
|
gdb_call_async_signal_handler (sigint_token, immediate_quit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* See gdb_select.h. */
|
||||||
|
|
||||||
|
int
|
||||||
|
interruptible_select (int n,
|
||||||
|
fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
||||||
|
struct timeval *timeout)
|
||||||
|
{
|
||||||
|
fd_set my_readfds;
|
||||||
|
int fd;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if (readfds == NULL)
|
||||||
|
{
|
||||||
|
readfds = &my_readfds;
|
||||||
|
FD_ZERO (&my_readfds);
|
||||||
|
}
|
||||||
|
|
||||||
|
fd = quit_serial_event_fd ();
|
||||||
|
FD_SET (fd, readfds);
|
||||||
|
if (n <= fd)
|
||||||
|
n = fd + 1;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
res = gdb_select (n, readfds, writefds, exceptfds, timeout);
|
||||||
|
}
|
||||||
|
while (res == -1 && errno == EINTR);
|
||||||
|
|
||||||
|
if (res == 1 && FD_ISSET (fd, readfds))
|
||||||
|
{
|
||||||
|
errno = EINTR;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
/* Handle GDB exit upon receiving SIGTERM if target_can_async_p (). */
|
/* Handle GDB exit upon receiving SIGTERM if target_can_async_p (). */
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -828,7 +828,16 @@ set_quit_flag (void)
|
|||||||
&& active_ext_lang->ops->set_quit_flag != NULL)
|
&& active_ext_lang->ops->set_quit_flag != NULL)
|
||||||
active_ext_lang->ops->set_quit_flag (active_ext_lang);
|
active_ext_lang->ops->set_quit_flag (active_ext_lang);
|
||||||
else
|
else
|
||||||
quit_flag = 1;
|
{
|
||||||
|
quit_flag = 1;
|
||||||
|
|
||||||
|
/* Now wake up the event loop, or any interruptible_select. Do
|
||||||
|
this after setting the flag, because signals on Windows
|
||||||
|
actually run on a separate thread, and thus otherwise the
|
||||||
|
main code could be woken up and find quit_flag still
|
||||||
|
clear. */
|
||||||
|
quit_serial_event_set ();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return true if the quit flag has been set, false otherwise.
|
/* Return true if the quit flag has been set, false otherwise.
|
||||||
@ -852,6 +861,10 @@ check_quit_flag (void)
|
|||||||
/* This is written in a particular way to avoid races. */
|
/* This is written in a particular way to avoid races. */
|
||||||
if (quit_flag)
|
if (quit_flag)
|
||||||
{
|
{
|
||||||
|
/* No longer need to wake up the event loop or any
|
||||||
|
interruptible_select. The caller handles the quit
|
||||||
|
request. */
|
||||||
|
quit_serial_event_clear ();
|
||||||
quit_flag = 0;
|
quit_flag = 0;
|
||||||
result = 1;
|
result = 1;
|
||||||
}
|
}
|
||||||
|
@ -33,4 +33,19 @@
|
|||||||
extern int gdb_select (int n, fd_set *readfds, fd_set *writefds,
|
extern int gdb_select (int n, fd_set *readfds, fd_set *writefds,
|
||||||
fd_set *exceptfds, struct timeval *timeout);
|
fd_set *exceptfds, struct timeval *timeout);
|
||||||
|
|
||||||
|
/* Convenience wrapper around gdb_select that returns -1/EINTR if
|
||||||
|
set_quit_flag is set, either on entry or from a signal handler or
|
||||||
|
from a different thread while select is blocked. The quit flag is
|
||||||
|
not cleared on exit -- the caller is responsible to check it with
|
||||||
|
check_quit_flag or QUIT.
|
||||||
|
|
||||||
|
Note this does NOT return -1/EINTR if any signal handler other than
|
||||||
|
SIGINT runs, nor if the current SIGINT handler does not call
|
||||||
|
set_quit_flag. */
|
||||||
|
extern int interruptible_select (int n,
|
||||||
|
fd_set *readfds,
|
||||||
|
fd_set *writefds,
|
||||||
|
fd_set *exceptfds,
|
||||||
|
struct timeval *timeout);
|
||||||
|
|
||||||
#endif /* !defined(GDB_SELECT_H) */
|
#endif /* !defined(GDB_SELECT_H) */
|
||||||
|
@ -201,7 +201,9 @@ ioscm_input_waiting (SCM port)
|
|||||||
FD_ZERO (&input_fds);
|
FD_ZERO (&input_fds);
|
||||||
FD_SET (fdes, &input_fds);
|
FD_SET (fdes, &input_fds);
|
||||||
|
|
||||||
num_found = gdb_select (num_fds, &input_fds, NULL, NULL, &timeout);
|
num_found = interruptible_select (num_fds,
|
||||||
|
&input_fds, NULL, NULL,
|
||||||
|
&timeout);
|
||||||
if (num_found < 0)
|
if (num_found < 0)
|
||||||
{
|
{
|
||||||
/* Guile doesn't export SIGINT hooks like Python does.
|
/* Guile doesn't export SIGINT hooks like Python does.
|
||||||
|
@ -613,10 +613,10 @@ gdb_readline_no_editing (const char *prompt)
|
|||||||
QUIT;
|
QUIT;
|
||||||
|
|
||||||
/* Wait until at least one byte of data is available. Control-C
|
/* Wait until at least one byte of data is available. Control-C
|
||||||
can interrupt gdb_select, but not fgetc. */
|
can interrupt interruptible_select, but not fgetc. */
|
||||||
FD_ZERO (&readfds);
|
FD_ZERO (&readfds);
|
||||||
FD_SET (fd, &readfds);
|
FD_SET (fd, &readfds);
|
||||||
if (gdb_select (fd + 1, &readfds, NULL, NULL, NULL) == -1)
|
if (interruptible_select (fd + 1, &readfds, NULL, NULL, NULL) == -1)
|
||||||
{
|
{
|
||||||
if (errno == EINTR)
|
if (errno == EINTR)
|
||||||
{
|
{
|
||||||
|
@ -567,14 +567,14 @@ stdio_file_read (struct ui_file *file, char *buf, long length_buf)
|
|||||||
internal_error (__FILE__, __LINE__,
|
internal_error (__FILE__, __LINE__,
|
||||||
_("stdio_file_read: bad magic number"));
|
_("stdio_file_read: bad magic number"));
|
||||||
|
|
||||||
/* For the benefit of Windows, call gdb_select before reading from
|
/* Wait until at least one byte of data is available, or we get
|
||||||
the file. Wait until at least one byte of data is available.
|
interrupted with Control-C. */
|
||||||
Control-C can interrupt gdb_select, but not read. */
|
|
||||||
{
|
{
|
||||||
fd_set readfds;
|
fd_set readfds;
|
||||||
|
|
||||||
FD_ZERO (&readfds);
|
FD_ZERO (&readfds);
|
||||||
FD_SET (stdio->fd, &readfds);
|
FD_SET (stdio->fd, &readfds);
|
||||||
if (gdb_select (stdio->fd + 1, &readfds, NULL, NULL, NULL) == -1)
|
if (interruptible_select (stdio->fd + 1, &readfds, NULL, NULL, NULL) == -1)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user