binutils-gdb/gdb/async-event.c
Simon Marchi b4b1a226df gdb: defer commit resume until all available events are consumed
Rationale
---------

Let's say you have multiple threads hitting a conditional breakpoint
at the same time, and all of these are going to evaluate to false.
All these threads will need to be resumed.

Currently, GDB fetches one target event (one SIGTRAP representing the
breakpoint hit) and decides that the thread should be resumed.  It
calls resume and commit_resume immediately.  It then fetches the
second target event, and does the same, until it went through all
threads.

The result is therefore something like:

  - consume event for thread A
  - resume thread A
  - commit resume (affects thread A)
  - consume event for thread B
  - resume thread B
  - commit resume (affects thread B)
  - consume event for thread C
  - resume thread C
  - commit resume (affects thread C)

For targets where it's beneficial to group resumptions requests (most
likely those that implement target_ops::commit_resume), it would be
much better to have:

  - consume event for thread A
  - resume thread A
  - consume event for thread B
  - resume thread B
  - consume event for thread C
  - resume thread C
  - commit resume (affects threads A, B and C)

Implementation details
----------------------

To achieve this, this patch adds another check in
maybe_set_commit_resumed_all_targets to avoid setting the
commit-resumed flag of targets that readily have events to provide to
infrun.

To determine if a target has events readily available to report, this
patch adds an `has_pending_events` target_ops method.  The method
returns a simple bool to say whether or not it has pending events to
report.

Testing
=======

To test this, I start GDBserver with a program that spawns multiple
threads:

 $ ../gdbserver/gdbserver --once :1234 ~/src/many-threads-stepping-over-breakpoints/many-threads-stepping-over-breakpoints

I then connect with GDB and install a conditional breakpoint that always
evaluates to false (and force the evaluation to be done by GDB):

 $ ./gdb -nx --data-directory=data-directory \
     /home/simark/src/many-threads-stepping-over-breakpoints/many-threads-stepping-over-breakpoints \
     -ex "set breakpoint condition-evaluation host" \
     -ex "set pag off" \
     -ex "set confirm off" \
     -ex "maint set target-non-stop on" \
     -ex "tar rem :1234" \
     -ex "tb main" \
     -ex "b 13 if 0" \
     -ex c \
     -ex "set debug infrun" \
     -ex "set debug remote 1" \
     -ex "set debug displaced"

I then do "continue" and look at the log.

The remote target receives a bunch of stop notifications for all
threads that have hit the breakpoint.  infrun consumes and processes
one event, decides it should not cause a stop, prepares a displaced
step, after which we should see:

 [infrun] maybe_set_commit_resumed_all_process_targets: not requesting commit-resumed for target remote, target has pending events

Same for a second thread (since we have 2 displaced step buffers).
For the following threads, their displaced step is deferred since
there are no more buffers available.

After consuming the last event the remote target has to offer, we get:

 [infrun] maybe_set_commit_resumed_all_process_targets: enabling commit-resumed for target remote
 [infrun] maybe_call_commit_resumed_all_process_targets: calling commit_resumed for target remote
 [remote] Sending packet: $vCont;s:p14d16b.14d1b1;s:p14d16b.14d1b2#55
 [remote] Packet received: OK

Without the patch, there would have been one vCont;s just after each
prepared displaced step.

gdb/ChangeLog:
yyyy-mm-dd  Simon Marchi  <simon.marchi@efficios.com>
	    Pedro Alves  <pedro@palves.net>

	* async-event.c (async_event_handler_marked): New.
	* async-event.h (async_event_handler_marked): Declare.
	* infrun.c (maybe_set_commit_resumed_all_targets): Switch to
	inferior before calling target method.  Don't commit-resumed if
	target_has_pending_events is true.
	* remote.c (remote_target::has_pending_events): New.
	* target-delegates.c: Regenerate.
	* target.c (target_has_pending_events): New.
	* target.h (target_ops::has_pending_events): New target method.
	(target_has_pending_events): New.

Change-Id: I18112ba19a1ff4986530c660f530d847bb4a1f1d
2021-03-26 16:02:11 +00:00

370 lines
11 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Async events for the GDB event loop.
Copyright (C) 1999-2021 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 "defs.h"
#include "async-event.h"
#include "ser-event.h"
#include "top.h"
/* PROC is a function to be invoked when the READY flag is set. This
happens when there has been a signal and the corresponding signal
handler has 'triggered' this async_signal_handler for execution.
The actual work to be done in response to a signal will be carried
out by PROC at a later time, within process_event. This provides a
deferred execution of signal handlers.
Async_init_signals takes care of setting up such an
async_signal_handler for each interesting signal. */
struct async_signal_handler
{
/* If ready, call this handler from the main event loop, using
invoke_async_handler. */
int ready;
/* Pointer to next handler. */
struct async_signal_handler *next_handler;
/* Function to call to do the work. */
sig_handler_func *proc;
/* Argument to PROC. */
gdb_client_data client_data;
/* User-friendly name of this handler. */
const char *name;
};
/* PROC is a function to be invoked when the READY flag is set. This
happens when the event has been marked with
MARK_ASYNC_EVENT_HANDLER. The actual work to be done in response
to an event will be carried out by PROC at a later time, within
process_event. This provides a deferred execution of event
handlers. */
struct async_event_handler
{
/* If ready, call this handler from the main event loop, using
invoke_event_handler. */
int ready;
/* Pointer to next handler. */
struct async_event_handler *next_handler;
/* Function to call to do the work. */
async_event_handler_func *proc;
/* Argument to PROC. */
gdb_client_data client_data;
/* User-friendly name of this handler. */
const char *name;
};
/* All the async_signal_handlers gdb is interested in are kept onto
this list. */
static struct
{
/* Pointer to first in handler list. */
async_signal_handler *first_handler;
/* Pointer to last in handler list. */
async_signal_handler *last_handler;
}
sighandler_list;
/* All the async_event_handlers gdb is interested in are kept onto
this list. */
static struct
{
/* Pointer to first in handler list. */
async_event_handler *first_handler;
/* Pointer to last in handler list. */
async_event_handler *last_handler;
}
async_event_handler_list;
/* This event is signalled whenever an asynchronous handler needs to
defer an action to the event loop. */
static struct serial_event *async_signal_handlers_serial_event;
/* Callback registered with ASYNC_SIGNAL_HANDLERS_SERIAL_EVENT. */
static void
async_signals_handler (int error, gdb_client_data client_data)
{
/* Do nothing. Handlers are run by invoke_async_signal_handlers
from instead. */
}
void
initialize_async_signal_handlers (void)
{
async_signal_handlers_serial_event = make_serial_event ();
add_file_handler (serial_event_fd (async_signal_handlers_serial_event),
async_signals_handler, NULL, "async-signals");
}
/* Create an asynchronous handler, allocating memory for it.
Return a pointer to the newly created handler.
This pointer will be used to invoke the handler by
invoke_async_signal_handler.
PROC is the function to call with CLIENT_DATA argument
whenever the handler is invoked. */
async_signal_handler *
create_async_signal_handler (sig_handler_func * proc,
gdb_client_data client_data,
const char *name)
{
async_signal_handler *async_handler_ptr;
async_handler_ptr = XNEW (async_signal_handler);
async_handler_ptr->ready = 0;
async_handler_ptr->next_handler = NULL;
async_handler_ptr->proc = proc;
async_handler_ptr->client_data = client_data;
async_handler_ptr->name = name;
if (sighandler_list.first_handler == NULL)
sighandler_list.first_handler = async_handler_ptr;
else
sighandler_list.last_handler->next_handler = async_handler_ptr;
sighandler_list.last_handler = async_handler_ptr;
return async_handler_ptr;
}
/* Mark the handler (ASYNC_HANDLER_PTR) as ready. This information
will be used when the handlers are invoked, after we have waited
for some event. The caller of this function is the interrupt
handler associated with a signal. */
void
mark_async_signal_handler (async_signal_handler *async_handler_ptr)
{
if (debug_event_loop != debug_event_loop_kind::OFF)
{
/* This is called by signal handlers, so we print it "by hand" using
the async-signal-safe methods. */
const char head[] = ("[event-loop] mark_async_signal_handler: marking"
"async signal handler `");
gdb_stdlog->write_async_safe (head, strlen (head));
gdb_stdlog->write_async_safe (async_handler_ptr->name,
strlen (async_handler_ptr->name));
const char tail[] = "`\n";
gdb_stdlog->write_async_safe (tail, strlen (tail));
}
async_handler_ptr->ready = 1;
serial_event_set (async_signal_handlers_serial_event);
}
/* See event-loop.h. */
void
clear_async_signal_handler (async_signal_handler *async_handler_ptr)
{
event_loop_debug_printf ("clearing async signal handler `%s`",
async_handler_ptr->name);
async_handler_ptr->ready = 0;
}
/* See event-loop.h. */
int
async_signal_handler_is_marked (async_signal_handler *async_handler_ptr)
{
return async_handler_ptr->ready;
}
/* Call all the handlers that are ready. Returns true if any was
indeed ready. */
int
invoke_async_signal_handlers (void)
{
async_signal_handler *async_handler_ptr;
int any_ready = 0;
/* We're going to handle all pending signals, so no need to wake up
the event loop again the next time around. Note this must be
cleared _before_ calling the callbacks, to avoid races. */
serial_event_clear (async_signal_handlers_serial_event);
/* Invoke all ready handlers. */
while (1)
{
for (async_handler_ptr = sighandler_list.first_handler;
async_handler_ptr != NULL;
async_handler_ptr = async_handler_ptr->next_handler)
{
if (async_handler_ptr->ready)
break;
}
if (async_handler_ptr == NULL)
break;
any_ready = 1;
async_handler_ptr->ready = 0;
/* Async signal handlers have no connection to whichever was the
current UI, and thus always run on the main one. */
current_ui = main_ui;
event_loop_debug_printf ("invoking async signal handler `%s`",
async_handler_ptr->name);
(*async_handler_ptr->proc) (async_handler_ptr->client_data);
}
return any_ready;
}
/* Delete an asynchronous handler (ASYNC_HANDLER_PTR).
Free the space allocated for it. */
void
delete_async_signal_handler (async_signal_handler ** async_handler_ptr)
{
async_signal_handler *prev_ptr;
if (sighandler_list.first_handler == (*async_handler_ptr))
{
sighandler_list.first_handler = (*async_handler_ptr)->next_handler;
if (sighandler_list.first_handler == NULL)
sighandler_list.last_handler = NULL;
}
else
{
prev_ptr = sighandler_list.first_handler;
while (prev_ptr && prev_ptr->next_handler != (*async_handler_ptr))
prev_ptr = prev_ptr->next_handler;
gdb_assert (prev_ptr);
prev_ptr->next_handler = (*async_handler_ptr)->next_handler;
if (sighandler_list.last_handler == (*async_handler_ptr))
sighandler_list.last_handler = prev_ptr;
}
xfree ((*async_handler_ptr));
(*async_handler_ptr) = NULL;
}
/* See async-event.h. */
async_event_handler *
create_async_event_handler (async_event_handler_func *proc,
gdb_client_data client_data,
const char *name)
{
async_event_handler *h;
h = XNEW (struct async_event_handler);
h->ready = 0;
h->next_handler = NULL;
h->proc = proc;
h->client_data = client_data;
h->name = name;
if (async_event_handler_list.first_handler == NULL)
async_event_handler_list.first_handler = h;
else
async_event_handler_list.last_handler->next_handler = h;
async_event_handler_list.last_handler = h;
return h;
}
/* Mark the handler (ASYNC_HANDLER_PTR) as ready. This information
will be used by gdb_do_one_event. The caller will be whoever
created the event source, and wants to signal that the event is
ready to be handled. */
void
mark_async_event_handler (async_event_handler *async_handler_ptr)
{
event_loop_debug_printf ("marking async event handler `%s`",
async_handler_ptr->name);
async_handler_ptr->ready = 1;
}
/* See event-loop.h. */
void
clear_async_event_handler (async_event_handler *async_handler_ptr)
{
event_loop_debug_printf ("clearing async event handler `%s`",
async_handler_ptr->name);
async_handler_ptr->ready = 0;
}
/* See event-loop.h. */
bool
async_event_handler_marked (async_event_handler *handler)
{
return handler->ready;
}
/* Check if asynchronous event handlers are ready, and call the
handler function for one that is. */
int
check_async_event_handlers ()
{
async_event_handler *async_handler_ptr;
for (async_handler_ptr = async_event_handler_list.first_handler;
async_handler_ptr != NULL;
async_handler_ptr = async_handler_ptr->next_handler)
{
if (async_handler_ptr->ready)
{
event_loop_debug_printf ("invoking async event handler `%s`",
async_handler_ptr->name);
(*async_handler_ptr->proc) (async_handler_ptr->client_data);
return 1;
}
}
return 0;
}
/* Delete an asynchronous handler (ASYNC_HANDLER_PTR).
Free the space allocated for it. */
void
delete_async_event_handler (async_event_handler **async_handler_ptr)
{
async_event_handler *prev_ptr;
if (async_event_handler_list.first_handler == *async_handler_ptr)
{
async_event_handler_list.first_handler
= (*async_handler_ptr)->next_handler;
if (async_event_handler_list.first_handler == NULL)
async_event_handler_list.last_handler = NULL;
}
else
{
prev_ptr = async_event_handler_list.first_handler;
while (prev_ptr && prev_ptr->next_handler != *async_handler_ptr)
prev_ptr = prev_ptr->next_handler;
gdb_assert (prev_ptr);
prev_ptr->next_handler = (*async_handler_ptr)->next_handler;
if (async_event_handler_list.last_handler == (*async_handler_ptr))
async_event_handler_list.last_handler = prev_ptr;
}
xfree (*async_handler_ptr);
*async_handler_ptr = NULL;
}