/* Serial interface for a selectable event. Copyright (C) 2016-2019 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 . */ #include "defs.h" #include "ser-event.h" #include "serial.h" #include "gdbsupport/filestuff.h" #if CXX_STD_THREAD #include #endif #include "event-loop.h" /* On POSIX hosts, a serial_event is basically an abstraction for the classical self-pipe trick. On Windows, a serial_event is a wrapper around a native Windows event object. Because we want to interface with gdb_select, which takes file descriptors, we need to wrap that Windows event object in a file descriptor. As _open_osfhandle can not be used with event objects, we instead create a dummy file wrap that in a file descriptor with _open_osfhandle, and pass that as selectable descriptor to callers. As Windows' gdb_select converts file descriptors back to Windows handles by calling serial->wait_handle, nothing ever actually waits on that file descriptor. */ struct serial_event_state { #ifdef USE_WIN32API /* The Windows event object, created with CreateEvent. */ HANDLE event; #else /* The write side of the pipe. The read side is in serial->fd. */ int write_fd; #endif }; /* Open a new serial event. */ static int serial_event_open (struct serial *scb, const char *name) { struct serial_event_state *state; state = XNEW (struct serial_event_state); scb->state = state; #ifndef USE_WIN32API { int fds[2]; if (gdb_pipe_cloexec (fds) == -1) internal_error (__FILE__, __LINE__, "creating serial event pipe failed."); fcntl (fds[0], F_SETFL, O_NONBLOCK); fcntl (fds[1], F_SETFL, O_NONBLOCK); scb->fd = fds[0]; state->write_fd = fds[1]; } #else { /* A dummy file object that can be wrapped in a file descriptor. We don't need to store this handle because closing the file descriptor automatically closes this. */ HANDLE dummy_file; /* A manual-reset event. */ state->event = CreateEvent (0, TRUE, FALSE, 0); /* The dummy file handle. Created just so we have something wrappable in a file descriptor. */ dummy_file = CreateFile ("nul", 0, 0, NULL, OPEN_EXISTING, 0, NULL); scb->fd = _open_osfhandle ((intptr_t) dummy_file, 0); } #endif return 0; } static void serial_event_close (struct serial *scb) { struct serial_event_state *state = (struct serial_event_state *) scb->state; close (scb->fd); #ifndef USE_WIN32API close (state->write_fd); #else CloseHandle (state->event); #endif scb->fd = -1; xfree (state); scb->state = NULL; } #ifdef USE_WIN32API /* Implementation of the wait_handle method. Returns the native Windows event object handle. */ static void serial_event_wait_handle (struct serial *scb, HANDLE *read, HANDLE *except) { struct serial_event_state *state = (struct serial_event_state *) scb->state; *read = state->event; } #endif /* The serial_ops for struct serial_event objects. Note we never register this serial type with serial_add_interface, because this is internal implementation detail never to be used by remote targets for protocol transport. */ static const struct serial_ops serial_event_ops = { "event", serial_event_open, serial_event_close, NULL, /* fdopen */ NULL, /* readchar */ NULL, /* write */ NULL, /* flush_output */ NULL, /* flush_input */ NULL, /* send_break */ NULL, /* go_raw */ NULL, /* get_tty_state */ NULL, /* copy_tty_state */ NULL, /* set_tty_state */ NULL, /* print_tty_state */ NULL, /* setbaudrate */ NULL, /* setstopbits */ NULL, /* setparity */ NULL, /* drain_output */ NULL, /* async */ NULL, /* read_prim */ NULL, /* write_prim */ NULL, /* avail */ #ifdef USE_WIN32API serial_event_wait_handle, #endif }; /* See ser-event.h. */ struct serial_event * make_serial_event (void) { return (struct serial_event *) serial_open_ops (&serial_event_ops); } /* See ser-event.h. */ int serial_event_fd (struct serial_event *event) { struct serial *ser = (struct serial *) event; return ser->fd; } /* See ser-event.h. */ void serial_event_set (struct serial_event *event) { struct serial *ser = (struct serial *) event; struct serial_event_state *state = (struct serial_event_state *) ser->state; #ifndef USE_WIN32API int r; char c = '+'; /* Anything. */ do { r = write (state->write_fd, &c, 1); } while (r < 0 && errno == EINTR); #else SetEvent (state->event); #endif } /* See ser-event.h. */ void serial_event_clear (struct serial_event *event) { struct serial *ser = (struct serial *) event; #ifndef USE_WIN32API int r; do { char c; r = read (ser->fd, &c, 1); } while (r > 0 || (r < 0 && errno == EINTR)); #else struct serial_event_state *state = (struct serial_event_state *) ser->state; ResetEvent (state->event); #endif } /* The serial event used when posting runnables. */ static struct serial_event *runnable_event; /* Runnables that have been posted. */ static std::vector> runnables; #if CXX_STD_THREAD /* Mutex to hold when handling runnable_event or runnables. */ static std::mutex runnable_mutex; #endif /* Run all the queued runnables. */ static void run_events (int error, gdb_client_data client_data) { std::vector> local; /* Hold the lock while changing the globals, but not while running the runnables. */ { #if CXX_STD_THREAD std::lock_guard lock (runnable_mutex); #endif /* Clear the event fd. Do this before flushing the events list, so that any new event post afterwards is sure to re-awaken the event loop. */ serial_event_clear (runnable_event); /* Move the vector in case running a runnable pushes a new runnable. */ local = std::move (runnables); } for (auto &item : local) { try { item (); } catch (...) { /* Ignore exceptions in the callback. */ } } } /* See ser-event.h. */ void run_on_main_thread (std::function &&func) { #if CXX_STD_THREAD std::lock_guard lock (runnable_mutex); #endif runnables.emplace_back (std::move (func)); serial_event_set (runnable_event); } void _initialize_ser_event () { runnable_event = make_serial_event (); add_file_handler (serial_event_fd (runnable_event), run_events, nullptr); }