2020-04-14 02:42:59 +08:00
|
|
|
|
/* Async events for the GDB event loop.
|
2023-01-01 20:49:04 +08:00
|
|
|
|
Copyright (C) 1999-2023 Free Software Foundation, Inc.
|
2020-04-14 02:42:59 +08:00
|
|
|
|
|
|
|
|
|
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"
|
2023-04-29 02:27:11 +08:00
|
|
|
|
#include "ui.h"
|
2020-04-14 02:42:59 +08:00
|
|
|
|
|
|
|
|
|
/* 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. */
|
|
|
|
|
|
2020-05-07 23:41:45 +08:00
|
|
|
|
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;
|
2020-10-03 02:44:38 +08:00
|
|
|
|
|
|
|
|
|
/* User-friendly name of this handler. */
|
|
|
|
|
const char *name;
|
2020-05-07 23:41:45 +08:00
|
|
|
|
};
|
2020-04-14 02:42:59 +08:00
|
|
|
|
|
|
|
|
|
/* 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. */
|
2020-05-07 23:41:45 +08:00
|
|
|
|
struct async_event_handler
|
|
|
|
|
{
|
|
|
|
|
/* If ready, call this handler from the main event loop, using
|
|
|
|
|
invoke_event_handler. */
|
|
|
|
|
int ready;
|
2020-04-14 02:42:59 +08:00
|
|
|
|
|
2020-05-07 23:41:45 +08:00
|
|
|
|
/* Pointer to next handler. */
|
|
|
|
|
struct async_event_handler *next_handler;
|
2020-04-14 02:42:59 +08:00
|
|
|
|
|
2020-05-07 23:41:45 +08:00
|
|
|
|
/* Function to call to do the work. */
|
|
|
|
|
async_event_handler_func *proc;
|
2020-04-14 02:42:59 +08:00
|
|
|
|
|
2020-05-07 23:41:45 +08:00
|
|
|
|
/* Argument to PROC. */
|
|
|
|
|
gdb_client_data client_data;
|
2020-10-03 02:44:38 +08:00
|
|
|
|
|
|
|
|
|
/* User-friendly name of this handler. */
|
|
|
|
|
const char *name;
|
2020-05-07 23:41:45 +08:00
|
|
|
|
};
|
2020-04-14 02:42:59 +08:00
|
|
|
|
|
|
|
|
|
/* All the async_signal_handlers gdb is interested in are kept onto
|
|
|
|
|
this list. */
|
|
|
|
|
static struct
|
2020-05-07 23:41:45 +08:00
|
|
|
|
{
|
|
|
|
|
/* Pointer to first in handler list. */
|
|
|
|
|
async_signal_handler *first_handler;
|
2020-04-14 02:42:59 +08:00
|
|
|
|
|
2020-05-07 23:41:45 +08:00
|
|
|
|
/* Pointer to last in handler list. */
|
|
|
|
|
async_signal_handler *last_handler;
|
|
|
|
|
}
|
2020-04-14 02:42:59 +08:00
|
|
|
|
sighandler_list;
|
|
|
|
|
|
|
|
|
|
/* All the async_event_handlers gdb is interested in are kept onto
|
|
|
|
|
this list. */
|
|
|
|
|
static struct
|
2020-05-07 23:41:45 +08:00
|
|
|
|
{
|
|
|
|
|
/* Pointer to first in handler list. */
|
|
|
|
|
async_event_handler *first_handler;
|
2020-04-14 02:42:59 +08:00
|
|
|
|
|
2020-05-07 23:41:45 +08:00
|
|
|
|
/* Pointer to last in handler list. */
|
|
|
|
|
async_event_handler *last_handler;
|
|
|
|
|
}
|
2020-04-14 02:42:59 +08:00
|
|
|
|
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),
|
2020-10-03 02:45:52 +08:00
|
|
|
|
async_signals_handler, NULL, "async-signals");
|
2020-04-14 02:42:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 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,
|
2020-10-03 02:44:38 +08:00
|
|
|
|
gdb_client_data client_data,
|
|
|
|
|
const char *name)
|
2020-04-14 02:42:59 +08:00
|
|
|
|
{
|
|
|
|
|
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;
|
2020-10-03 02:44:38 +08:00
|
|
|
|
async_handler_ptr->name = name;
|
2020-04-14 02:42:59 +08:00
|
|
|
|
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
|
2020-10-03 02:44:40 +08:00
|
|
|
|
mark_async_signal_handler (async_signal_handler *async_handler_ptr)
|
2020-04-14 02:42:59 +08:00
|
|
|
|
{
|
2020-10-03 02:44:40 +08:00
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-14 02:42:59 +08:00
|
|
|
|
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)
|
|
|
|
|
{
|
2020-10-03 02:44:40 +08:00
|
|
|
|
event_loop_debug_printf ("clearing async signal handler `%s`",
|
|
|
|
|
async_handler_ptr->name);
|
2020-04-14 02:42:59 +08:00
|
|
|
|
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;
|
2020-10-03 02:44:40 +08:00
|
|
|
|
event_loop_debug_printf ("invoking async signal handler `%s`",
|
|
|
|
|
async_handler_ptr->name);
|
2020-04-14 02:42:59 +08:00
|
|
|
|
(*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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-03 02:44:38 +08:00
|
|
|
|
/* See async-event.h. */
|
|
|
|
|
|
2020-04-14 02:42:59 +08:00
|
|
|
|
async_event_handler *
|
|
|
|
|
create_async_event_handler (async_event_handler_func *proc,
|
2020-10-03 02:44:38 +08:00
|
|
|
|
gdb_client_data client_data,
|
|
|
|
|
const char *name)
|
2020-04-14 02:42:59 +08:00
|
|
|
|
{
|
|
|
|
|
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;
|
2020-10-03 02:44:38 +08:00
|
|
|
|
h->name = name;
|
2020-04-14 02:42:59 +08:00
|
|
|
|
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)
|
|
|
|
|
{
|
2022-10-04 18:59:26 +08:00
|
|
|
|
event_loop_debug_printf ("marking async event handler `%s` "
|
|
|
|
|
"(previous state was %d)",
|
|
|
|
|
async_handler_ptr->name,
|
|
|
|
|
async_handler_ptr->ready);
|
2020-04-14 02:42:59 +08:00
|
|
|
|
async_handler_ptr->ready = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* See event-loop.h. */
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
clear_async_event_handler (async_event_handler *async_handler_ptr)
|
|
|
|
|
{
|
2020-10-03 02:44:40 +08:00
|
|
|
|
event_loop_debug_printf ("clearing async event handler `%s`",
|
|
|
|
|
async_handler_ptr->name);
|
2020-04-14 02:42:59 +08:00
|
|
|
|
async_handler_ptr->ready = 0;
|
|
|
|
|
}
|
|
|
|
|
|
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
2020-07-07 03:53:28 +08:00
|
|
|
|
/* See event-loop.h. */
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
async_event_handler_marked (async_event_handler *handler)
|
|
|
|
|
{
|
|
|
|
|
return handler->ready;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-14 02:42:59 +08:00
|
|
|
|
/* 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)
|
|
|
|
|
{
|
2020-10-03 02:44:40 +08:00
|
|
|
|
event_loop_debug_printf ("invoking async event handler `%s`",
|
|
|
|
|
async_handler_ptr->name);
|
2020-04-14 02:42:59 +08:00
|
|
|
|
(*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;
|
|
|
|
|
}
|