From 55d7aec85e81c4597e94ebcc8b85f20a1d439bd0 Mon Sep 17 00:00:00 2001 From: Tom Tromey Date: Mon, 13 Apr 2020 12:42:59 -0600 Subject: [PATCH] Switch gdbserver to gdbsupport event loop This changes gdbserver to use the gdbserver event loop, removing the ancient fork. gdbserver/ChangeLog 2020-04-13 Tom Tromey * server.h (handle_serial_event, handle_target_event): Update. * server.c: Don't call initialize_event_loop. (keep_processing_events): New global. (handle_serial_event): Return void. Set keep_processing_events. (handle_target_event): Return void. (start_event_loop): Move from event-loop.c. Rewrite. * remote-utils.c (handle_accept_event): Return void. (reset_readchar): Use delete_timer. (process_remaining): Return void. (reschedule): Use create_timer. * event-loop.h: Remove. * event-loop.cc: Remove. * Makefile.in (OBS): Use gdbsupport/event-loop.o, not event-loop.o. --- gdbserver/ChangeLog | 16 ++ gdbserver/Makefile.in | 1 - gdbserver/event-loop.cc | 567 -------------------------------------- gdbserver/event-loop.h | 36 --- gdbserver/remote-utils.cc | 18 +- gdbserver/server.cc | 44 ++- gdbserver/server.h | 6 +- 7 files changed, 60 insertions(+), 628 deletions(-) delete mode 100644 gdbserver/event-loop.cc delete mode 100644 gdbserver/event-loop.h diff --git a/gdbserver/ChangeLog b/gdbserver/ChangeLog index 309cae3d701..1d0fbb87c06 100644 --- a/gdbserver/ChangeLog +++ b/gdbserver/ChangeLog @@ -1,3 +1,19 @@ +2020-04-13 Tom Tromey + + * server.h (handle_serial_event, handle_target_event): Update. + * server.c: Don't call initialize_event_loop. + (keep_processing_events): New global. + (handle_serial_event): Return void. Set keep_processing_events. + (handle_target_event): Return void. + (start_event_loop): Move from event-loop.c. Rewrite. + * remote-utils.c (handle_accept_event): Return void. + (reset_readchar): Use delete_timer. + (process_remaining): Return void. + (reschedule): Use create_timer. + * event-loop.h: Remove. + * event-loop.cc: Remove. + * Makefile.in (OBS): Use gdbsupport/event-loop.o, not event-loop.o. + 2020-04-13 Tom Tromey * server.c (invoke_async_signal_handlers) diff --git a/gdbserver/Makefile.in b/gdbserver/Makefile.in index 8c35c169d62..417a530e25d 100644 --- a/gdbserver/Makefile.in +++ b/gdbserver/Makefile.in @@ -241,7 +241,6 @@ OBS = \ ax.o \ debug.o \ dll.o \ - event-loop.o \ hostio.o \ inferiors.o \ mem-break.o \ diff --git a/gdbserver/event-loop.cc b/gdbserver/event-loop.cc deleted file mode 100644 index 8827e8a32eb..00000000000 --- a/gdbserver/event-loop.cc +++ /dev/null @@ -1,567 +0,0 @@ -/* Event loop machinery for the remote server for GDB. - Copyright (C) 1999-2020 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 . */ - -/* Based on src/gdb/event-loop.c. */ - -#include "server.h" - -#include -#include "gdbsupport/gdb_sys_time.h" - -#ifdef USE_WIN32API -#include -#include -#endif - -#include -#include - -typedef int (event_handler_func) (gdb_fildes_t); - -/* Tell create_file_handler what events we are interested in. */ - -#define GDB_READABLE (1<<1) -#define GDB_WRITABLE (1<<2) -#define GDB_EXCEPTION (1<<3) - -/* Events are queued by on the event_queue and serviced later - on by do_one_event. An event can be, for instance, a file - descriptor becoming ready to be read. Servicing an event simply - means that the procedure PROC will be called. We have 2 queues, - one for file handlers that we listen to in the event loop, and one - for the file handlers+events that are ready. The procedure PROC - associated with each event is always the same (handle_file_event). - Its duty is to invoke the handler associated with the file - descriptor whose state change generated the event, plus doing other - cleanups and such. */ - -struct gdb_event - { - /* Procedure to call to service this event. */ - event_handler_func *proc; - - /* File descriptor that is ready. */ - gdb_fildes_t fd; - }; - -/* Information about each file descriptor we register with the event - loop. */ - -typedef struct file_handler - { - /* File descriptor. */ - gdb_fildes_t fd; - - /* Events we want to monitor. */ - int mask; - - /* Events that have been seen since the last time. */ - int ready_mask; - - /* Procedure to call when fd is ready. */ - handler_func *proc; - - /* Argument to pass to proc. */ - gdb_client_data client_data; - - /* Was an error detected on this fd? */ - int error; - - /* Next registered file descriptor. */ - struct file_handler *next_file; - } -file_handler; - -typedef gdb::unique_xmalloc_ptr gdb_event_up; - -static std::queue> event_queue; - -/* Gdb_notifier is just a list of file descriptors gdb is interested - in. These are the input file descriptor, and the target file - descriptor. Each of the elements in the gdb_notifier list is - basically a description of what kind of events gdb is interested - in, for each fd. */ - -static struct - { - /* Ptr to head of file handler list. */ - file_handler *first_file_handler; - - /* Masks to be used in the next call to select. Bits are set in - response to calls to create_file_handler. */ - fd_set check_masks[3]; - - /* What file descriptors were found ready by select. */ - fd_set ready_masks[3]; - - /* Number of valid bits (highest fd value + 1). (for select) */ - int num_fds; - } -gdb_notifier; - -/* Callbacks are just routines that are executed before waiting for the - next event. In GDB this is struct gdb_timer. We don't need timers - so rather than copy all that complexity in gdbserver, we provide what - we need, but we do so in a way that if/when the day comes that we need - that complexity, it'll be easier to add - replace callbacks with timers - and use a delta of zero (which is all gdb currently uses timers for anyway). - - PROC will be executed before gdbserver goes to sleep to wait for the - next event. */ - -struct callback_event - { - int id; - callback_handler_func *proc; - gdb_client_data data; - struct callback_event *next; - }; - -/* Table of registered callbacks. */ - -static struct - { - struct callback_event *first; - struct callback_event *last; - - /* Id of the last callback created. */ - int num_callbacks; - } -callback_list; - -void -initialize_event_loop (void) -{ -} - -/* Process one event. If an event was processed, 1 is returned - otherwise 0 is returned. Scan the queue from head to tail, - processing therefore the high priority events first, by invoking - the associated event handler procedure. */ - -static int -process_event (void) -{ - /* Let's get rid of the event from the event queue. We need to - do this now because while processing the event, since the - proc function could end up jumping out to the caller of this - function. In that case, we would have on the event queue an - event which has been processed, but not deleted. */ - if (!event_queue.empty ()) - { - gdb_event_up event_ptr = std::move (event_queue.front ()); - event_queue.pop (); - - event_handler_func *proc = event_ptr->proc; - gdb_fildes_t fd = event_ptr->fd; - - /* Now call the procedure associated with the event. */ - if ((*proc) (fd)) - return -1; - return 1; - } - - /* This is the case if there are no event on the event queue. */ - return 0; -} - -/* Append PROC to the callback list. - The result is the "id" of the callback that can be passed back to - delete_callback_event. */ - -int -append_callback_event (callback_handler_func *proc, gdb_client_data data) -{ - struct callback_event *event_ptr = XNEW (struct callback_event); - - event_ptr->id = callback_list.num_callbacks++; - event_ptr->proc = proc; - event_ptr->data = data; - event_ptr->next = NULL; - if (callback_list.first == NULL) - callback_list.first = event_ptr; - if (callback_list.last != NULL) - callback_list.last->next = event_ptr; - callback_list.last = event_ptr; - return event_ptr->id; -} - -/* Delete callback ID. - It is not an error callback ID doesn't exist. */ - -void -delete_callback_event (int id) -{ - struct callback_event **p; - - for (p = &callback_list.first; *p != NULL; p = &(*p)->next) - { - struct callback_event *event_ptr = *p; - - if (event_ptr->id == id) - { - *p = event_ptr->next; - if (event_ptr == callback_list.last) - callback_list.last = NULL; - free (event_ptr); - break; - } - } -} - -/* Run the next callback. - The result is 1 if a callback was called and event processing - should continue, -1 if the callback wants the event loop to exit, - and 0 if there are no more callbacks. */ - -static int -process_callback (void) -{ - struct callback_event *event_ptr; - - event_ptr = callback_list.first; - if (event_ptr != NULL) - { - callback_handler_func *proc = event_ptr->proc; - gdb_client_data data = event_ptr->data; - - /* Remove the event before calling PROC, - more events may get added by PROC. */ - callback_list.first = event_ptr->next; - if (callback_list.first == NULL) - callback_list.last = NULL; - free (event_ptr); - if ((*proc) (data)) - return -1; - return 1; - } - - return 0; -} - -/* Add a file handler/descriptor to the list of descriptors we are - interested in. FD is the file descriptor for the file/stream to be - listened to. MASK is a combination of READABLE, WRITABLE, - EXCEPTION. PROC is the procedure that will be called when an event - occurs for FD. CLIENT_DATA is the argument to pass to PROC. */ - -static void -create_file_handler (gdb_fildes_t fd, int mask, handler_func *proc, - gdb_client_data client_data) -{ - file_handler *file_ptr; - - /* Do we already have a file handler for this file? (We may be - changing its associated procedure). */ - for (file_ptr = gdb_notifier.first_file_handler; - file_ptr != NULL; - file_ptr = file_ptr->next_file) - if (file_ptr->fd == fd) - break; - - /* It is a new file descriptor. Add it to the list. Otherwise, - just change the data associated with it. */ - if (file_ptr == NULL) - { - file_ptr = XNEW (struct file_handler); - file_ptr->fd = fd; - file_ptr->ready_mask = 0; - file_ptr->next_file = gdb_notifier.first_file_handler; - gdb_notifier.first_file_handler = file_ptr; - - if (mask & GDB_READABLE) - FD_SET (fd, &gdb_notifier.check_masks[0]); - else - FD_CLR (fd, &gdb_notifier.check_masks[0]); - - if (mask & GDB_WRITABLE) - FD_SET (fd, &gdb_notifier.check_masks[1]); - else - FD_CLR (fd, &gdb_notifier.check_masks[1]); - - if (mask & GDB_EXCEPTION) - FD_SET (fd, &gdb_notifier.check_masks[2]); - else - FD_CLR (fd, &gdb_notifier.check_masks[2]); - - if (gdb_notifier.num_fds <= fd) - gdb_notifier.num_fds = fd + 1; - } - - file_ptr->proc = proc; - file_ptr->client_data = client_data; - file_ptr->mask = mask; -} - -/* Wrapper function for create_file_handler. */ - -void -add_file_handler (gdb_fildes_t fd, - handler_func *proc, gdb_client_data client_data) -{ - create_file_handler (fd, GDB_READABLE | GDB_EXCEPTION, proc, client_data); -} - -/* Remove the file descriptor FD from the list of monitored fd's: - i.e. we don't care anymore about events on the FD. */ - -void -delete_file_handler (gdb_fildes_t fd) -{ - file_handler *file_ptr, *prev_ptr = NULL; - int i; - - /* Find the entry for the given file. */ - - for (file_ptr = gdb_notifier.first_file_handler; - file_ptr != NULL; - file_ptr = file_ptr->next_file) - if (file_ptr->fd == fd) - break; - - if (file_ptr == NULL) - return; - - if (file_ptr->mask & GDB_READABLE) - FD_CLR (fd, &gdb_notifier.check_masks[0]); - if (file_ptr->mask & GDB_WRITABLE) - FD_CLR (fd, &gdb_notifier.check_masks[1]); - if (file_ptr->mask & GDB_EXCEPTION) - FD_CLR (fd, &gdb_notifier.check_masks[2]); - - /* Find current max fd. */ - - if ((fd + 1) == gdb_notifier.num_fds) - { - gdb_notifier.num_fds--; - for (i = gdb_notifier.num_fds; i; i--) - { - if (FD_ISSET (i - 1, &gdb_notifier.check_masks[0]) - || FD_ISSET (i - 1, &gdb_notifier.check_masks[1]) - || FD_ISSET (i - 1, &gdb_notifier.check_masks[2])) - break; - } - gdb_notifier.num_fds = i; - } - - /* Deactivate the file descriptor, by clearing its mask, so that it - will not fire again. */ - - file_ptr->mask = 0; - - /* Get rid of the file handler in the file handler list. */ - if (file_ptr == gdb_notifier.first_file_handler) - gdb_notifier.first_file_handler = file_ptr->next_file; - else - { - for (prev_ptr = gdb_notifier.first_file_handler; - prev_ptr->next_file != file_ptr; - prev_ptr = prev_ptr->next_file) - ; - prev_ptr->next_file = file_ptr->next_file; - } - free (file_ptr); -} - -/* Handle the given event by calling the procedure associated to the - corresponding file handler. Called by process_event indirectly, - through event_ptr->proc. EVENT_FILE_DESC is file descriptor of the - event in the front of the event queue. */ - -static int -handle_file_event (gdb_fildes_t event_file_desc) -{ - file_handler *file_ptr; - int mask; - - /* Search the file handler list to find one that matches the fd in - the event. */ - for (file_ptr = gdb_notifier.first_file_handler; file_ptr != NULL; - file_ptr = file_ptr->next_file) - { - if (file_ptr->fd == event_file_desc) - { - /* See if the desired events (mask) match the received - events (ready_mask). */ - - if (file_ptr->ready_mask & GDB_EXCEPTION) - { - warning ("Exception condition detected on fd %s", - pfildes (file_ptr->fd)); - file_ptr->error = 1; - } - else - file_ptr->error = 0; - mask = file_ptr->ready_mask & file_ptr->mask; - - /* Clear the received events for next time around. */ - file_ptr->ready_mask = 0; - - /* If there was a match, then call the handler. */ - if (mask != 0) - { - if ((*file_ptr->proc) (file_ptr->error, - file_ptr->client_data) < 0) - return -1; - } - break; - } - } - - return 0; -} - -/* Create a file event, to be enqueued in the event queue for - processing. The procedure associated to this event is always - handle_file_event, which will in turn invoke the one that was - associated to FD when it was registered with the event loop. */ - -static gdb_event * -create_file_event (gdb_fildes_t fd) -{ - gdb_event *file_event_ptr; - - file_event_ptr = XNEW (gdb_event); - file_event_ptr->proc = handle_file_event; - file_event_ptr->fd = fd; - - return file_event_ptr; -} - -/* Called by do_one_event to wait for new events on the monitored file - descriptors. Queue file events as they are detected by the poll. - If there are no events, this function will block in the call to - select. Return -1 if there are no files descriptors to monitor, - otherwise return 0. */ - -static int -wait_for_event (void) -{ - file_handler *file_ptr; - int num_found = 0; - - /* Make sure all output is done before getting another event. */ - fflush (stdout); - fflush (stderr); - - if (gdb_notifier.num_fds == 0) - return -1; - - gdb_notifier.ready_masks[0] = gdb_notifier.check_masks[0]; - gdb_notifier.ready_masks[1] = gdb_notifier.check_masks[1]; - gdb_notifier.ready_masks[2] = gdb_notifier.check_masks[2]; - num_found = select (gdb_notifier.num_fds, - &gdb_notifier.ready_masks[0], - &gdb_notifier.ready_masks[1], - &gdb_notifier.ready_masks[2], - NULL); - - /* Clear the masks after an error from select. */ - if (num_found == -1) - { - FD_ZERO (&gdb_notifier.ready_masks[0]); - FD_ZERO (&gdb_notifier.ready_masks[1]); - FD_ZERO (&gdb_notifier.ready_masks[2]); -#ifdef EINTR - /* Dont print anything if we got a signal, let gdb handle - it. */ - if (errno != EINTR) - perror_with_name ("select"); -#endif - } - - /* Enqueue all detected file events. */ - - for (file_ptr = gdb_notifier.first_file_handler; - file_ptr != NULL && num_found > 0; - file_ptr = file_ptr->next_file) - { - int mask = 0; - - if (FD_ISSET (file_ptr->fd, &gdb_notifier.ready_masks[0])) - mask |= GDB_READABLE; - if (FD_ISSET (file_ptr->fd, &gdb_notifier.ready_masks[1])) - mask |= GDB_WRITABLE; - if (FD_ISSET (file_ptr->fd, &gdb_notifier.ready_masks[2])) - mask |= GDB_EXCEPTION; - - if (!mask) - continue; - else - num_found--; - - /* Enqueue an event only if this is still a new event for this - fd. */ - - if (file_ptr->ready_mask == 0) - { - gdb_event *file_event_ptr = create_file_event (file_ptr->fd); - - event_queue.emplace (file_event_ptr); - } - file_ptr->ready_mask = mask; - } - - return 0; -} - -/* Start up the event loop. This is the entry point to the event - loop. */ - -void -start_event_loop (void) -{ - /* Loop until there is nothing to do. This is the entry point to - the event loop engine. If nothing is ready at this time, wait - for something to happen (via wait_for_event), then process it. - Return when there are no longer event sources to wait for. */ - - while (1) - { - /* Any events already waiting in the queue? */ - int res = process_event (); - - /* Did the event handler want the event loop to stop? */ - if (res == -1) - return; - - if (res) - continue; - - /* Process any queued callbacks before we go to sleep. */ - res = process_callback (); - - /* Did the callback want the event loop to stop? */ - if (res == -1) - return; - - if (res) - continue; - - /* Wait for a new event. If wait_for_event returns -1, we - should get out because this means that there are no event - sources left. This will make the event loop stop, and the - application exit. */ - - if (wait_for_event () < 0) - return; - } - - /* We are done with the event loop. There are no more event sources - to listen to. So we exit gdbserver. */ -} diff --git a/gdbserver/event-loop.h b/gdbserver/event-loop.h deleted file mode 100644 index a49173e867d..00000000000 --- a/gdbserver/event-loop.h +++ /dev/null @@ -1,36 +0,0 @@ -/* Event loop machinery for the remote server for GDB. - Copyright (C) 1993-2020 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 . */ - -#ifndef GDBSERVER_EVENT_LOOP_H -#define GDBSERVER_EVENT_LOOP_H - -typedef void *gdb_client_data; -typedef int (handler_func) (int, gdb_client_data); -typedef int (callback_handler_func) (gdb_client_data); - -extern void delete_file_handler (gdb_fildes_t fd); -extern void add_file_handler (gdb_fildes_t fd, handler_func *proc, - gdb_client_data client_data); -extern int append_callback_event (callback_handler_func *proc, - gdb_client_data client_data); -extern void delete_callback_event (int id); - -extern void start_event_loop (void); -extern void initialize_event_loop (void); - -#endif /* GDBSERVER_EVENT_LOOP_H */ diff --git a/gdbserver/remote-utils.cc b/gdbserver/remote-utils.cc index 1c211e25720..6249691954d 100644 --- a/gdbserver/remote-utils.cc +++ b/gdbserver/remote-utils.cc @@ -144,7 +144,7 @@ enable_async_notification (int fd) #endif } -static int +static void handle_accept_event (int err, gdb_client_data client_data) { struct sockaddr_storage sockaddr; @@ -213,8 +213,6 @@ handle_accept_event (int err, gdb_client_data client_data) until GDB as selected all-stop/non-stop, and has queried the threads' status ('?'). */ target_async (0); - - return 0; } /* Prepare for a later connection to a remote debugger. @@ -930,27 +928,21 @@ reset_readchar (void) readchar_bufcnt = 0; if (readchar_callback != NOT_SCHEDULED) { - delete_callback_event (readchar_callback); + delete_timer (readchar_callback); readchar_callback = NOT_SCHEDULED; } } /* Process remaining data in readchar_buf. */ -static int +static void process_remaining (void *context) { - int res; - /* This is a one-shot event. */ readchar_callback = NOT_SCHEDULED; if (readchar_bufcnt > 0) - res = handle_serial_event (0, NULL); - else - res = 0; - - return res; + handle_serial_event (0, NULL); } /* If there is still data in the buffer, queue another event to process it, @@ -960,7 +952,7 @@ static void reschedule (void) { if (readchar_bufcnt > 0 && readchar_callback == NOT_SCHEDULED) - readchar_callback = append_callback_event (process_remaining, NULL); + readchar_callback = create_timer (0, process_remaining, NULL); } /* Read a packet from the remote machine, with error checking, diff --git a/gdbserver/server.cc b/gdbserver/server.cc index ac7a7fd75aa..77175ff74cb 100644 --- a/gdbserver/server.cc +++ b/gdbserver/server.cc @@ -83,6 +83,10 @@ bool run_once; /* Whether to report TARGET_WAITKIND_NO_RESUMED events. */ static bool report_no_resumed; +/* The event loop checks this to decide whether to continue accepting + events. */ +static bool keep_processing_events = true; + bool non_stop; static struct { @@ -3463,6 +3467,32 @@ gdbserver_show_disableable (FILE *stream) " threads \tAll of the above\n"); } +/* Start up the event loop. This is the entry point to the event + loop. */ + +static void +start_event_loop () +{ + /* Loop until there is nothing to do. This is the entry point to + the event loop engine. If nothing is ready at this time, wait + for something to happen (via wait_for_event), then process it. + Return when there are no longer event sources to wait for. */ + + keep_processing_events = true; + while (keep_processing_events) + { + /* Any events already waiting in the queue? */ + int res = gdb_do_one_event (); + + /* Was there an error? */ + if (res == -1) + break; + } + + /* We are done with the event loop. There are no more event sources + to listen to. So we exit gdbserver. */ +} + static void kill_inferior_callback (process_info *process) { @@ -3762,7 +3792,6 @@ captured_main (int argc, char *argv[]) initialize_async_io (); initialize_low (); have_job_control (); - initialize_event_loop (); if (target_supports_tracepoints ()) initialize_tracepoint (); @@ -4365,7 +4394,7 @@ process_serial_event (void) /* Event-loop callback for serial events. */ -int +void handle_serial_event (int err, gdb_client_data client_data) { if (debug_threads) @@ -4373,13 +4402,14 @@ handle_serial_event (int err, gdb_client_data client_data) /* Really handle it. */ if (process_serial_event () < 0) - return -1; + { + keep_processing_events = false; + return; + } /* Be sure to not change the selected thread behind GDB's back. Important in the non-stop mode asynchronous protocol. */ set_desired_thread (); - - return 0; } /* Push a stop notification on the notification queue. */ @@ -4397,7 +4427,7 @@ push_stop_notification (ptid_t ptid, struct target_waitstatus *status) /* Event-loop callback for target events. */ -int +void handle_target_event (int err, gdb_client_data client_data) { client_state &cs = get_client_state (); @@ -4474,8 +4504,6 @@ handle_target_event (int err, gdb_client_data client_data) /* Be sure to not change the selected thread behind GDB's back. Important in the non-stop mode asynchronous protocol. */ set_desired_thread (); - - return 0; } /* See gdbsupport/event-loop.h. */ diff --git a/gdbserver/server.h b/gdbserver/server.h index 5ef48b62c62..039082e2eff 100644 --- a/gdbserver/server.h +++ b/gdbserver/server.h @@ -88,13 +88,13 @@ typedef SOCKET gdb_fildes_t; typedef int gdb_fildes_t; #endif -#include "event-loop.h" +#include "gdbsupport/event-loop.h" /* Functions from server.c. */ extern void handle_v_requests (char *own_buf, int packet_len, int *new_packet_len); -extern int handle_serial_event (int err, gdb_client_data client_data); -extern int handle_target_event (int err, gdb_client_data client_data); +extern void handle_serial_event (int err, gdb_client_data client_data); +extern void handle_target_event (int err, gdb_client_data client_data); /* Get rid of the currently pending stop replies that match PTID. */ extern void discard_queued_stop_replies (ptid_t ptid); -- 2.30.2