X-Git-Url: http://git.efficios.com/?a=blobdiff_plain;f=gdb%2Finflow.c;h=339b55c0bc69e3ef52cd96636c5b5ea63b84847f;hb=739b5c9c778dee9e2f54d864f83a81ecb0639535;hp=d46d6938e27d4e555a672a713f4793a62096a169;hpb=a94799ac1e430404fda5cbc181406cf5f3630108;p=deliverable%2Fbinutils-gdb.git diff --git a/gdb/inflow.c b/gdb/inflow.c index d46d6938e2..339b55c0bc 100644 --- a/gdb/inflow.c +++ b/gdb/inflow.c @@ -1,5 +1,5 @@ /* Low level interface to ptrace, for GDB when running under Unix. - Copyright (C) 1986-2017 Free Software Foundation, Inc. + Copyright (C) 1986-2019 Free Software Foundation, Inc. This file is part of GDB. @@ -24,7 +24,7 @@ #include "terminal.h" #include "target.h" #include "gdbthread.h" -#include "observer.h" +#include "observable.h" #include #include #include "gdb_select.h" @@ -34,7 +34,7 @@ #ifdef HAVE_TERMIOS_H #include #endif -#include "job-control.h" +#include "common/job-control.h" #ifdef HAVE_SYS_IOCTL_H #include @@ -46,7 +46,7 @@ static void pass_signal (int); -static void child_terminal_ours_1 (int); +static void child_terminal_ours_1 (target_terminal_state); /* Record terminal status separately for debugger and inferior. */ @@ -54,25 +54,40 @@ static struct serial *stdin_serial; /* Terminal related info we need to keep track of. Each inferior holds an instance of this structure --- we save it whenever the - corresponding inferior stops, and restore it to the foreground - inferior when it resumes. */ + corresponding inferior stops, and restore it to the terminal when + the inferior is resumed in the foreground. */ struct terminal_info { + terminal_info () = default; + ~terminal_info (); + /* The name of the tty (from the `tty' command) that we gave to the inferior when it was started. */ - char *run_terminal; + char *run_terminal = nullptr; /* TTY state. We save it whenever the inferior stops, and restore - it when it resumes. */ - serial_ttystate ttystate; + it when it resumes in the foreground. */ + serial_ttystate ttystate {}; #ifdef HAVE_TERMIOS_H - /* Process group. Saved and restored just like ttystate. */ - pid_t process_group; + /* The terminal's foreground process group. Saved whenever the + inferior stops. This is the pgrp displayed by "info terminal". + Note that this may be not the inferior's actual process group, + since each inferior that we spawn has its own process group, and + only one can be in the foreground at a time. When the inferior + resumes, if we can determine the inferior's actual pgrp, then we + make that the foreground pgrp instead of what was saved here. + While it's a bit arbitrary which inferior's pgrp ends up in the + foreground when we resume several inferiors, this at least makes + 'resume inf1+inf2' + 'stop all' + 'resume inf2' end up with + inf2's pgrp in the foreground instead of inf1's (which would be + problematic since it would be left stopped: Ctrl-C wouldn't work, + for example). */ + pid_t process_group = 0; #endif /* fcntl flags. Saved and restored just like ttystate. */ - int tflags; + int tflags = 0; }; /* Our own tty state, which we restore every time we need to deal with @@ -91,16 +106,34 @@ static serial_ttystate initial_gdb_ttystate; static struct terminal_info *get_inflow_inferior_data (struct inferior *); -#ifdef HAVE_TERMIOS_H - -/* Return the process group of the current inferior. */ +/* RAII class used to ignore SIGTTOU in a scope. */ -pid_t -inferior_process_group (void) +class scoped_ignore_sigttou { - return get_inflow_inferior_data (current_inferior ())->process_group; -} +public: + scoped_ignore_sigttou () + { +#ifdef SIGTTOU + if (job_control) + m_osigttou = signal (SIGTTOU, SIG_IGN); +#endif + } + + ~scoped_ignore_sigttou () + { +#ifdef SIGTTOU + if (job_control) + signal (SIGTTOU, m_osigttou); +#endif + } + + DISABLE_COPY_AND_ASSIGN (scoped_ignore_sigttou); + +private: +#ifdef SIGTTOU + sighandler_t m_osigttou = NULL; #endif +}; /* While the inferior is running, we want SIGINT and SIGQUIT to go to the inferior only. If we have job control, that takes care of it. If not, @@ -108,7 +141,9 @@ inferior_process_group (void) to SIG_IGN. */ static sighandler_t sigint_ours; +#ifdef SIGQUIT static sighandler_t sigquit_ours; +#endif /* The name of the tty (from the `tty' command) that we're giving to the inferior when starting it up. This is only (and should only @@ -117,11 +152,17 @@ static sighandler_t sigquit_ours; fork_inferior, while forking a new child. */ static const char *inferior_thisrun_terminal; -/* Nonzero if our terminal settings are in effect. Zero if the - inferior's settings are in effect. Ignored if !gdb_has_a_terminal - (). */ +/* Track who owns GDB's terminal (is it GDB or some inferior?). While + target_terminal::is_ours() etc. tracks the core's intention and is + independent of the target backend, this tracks the actual state of + GDB's own tty. So for example, -int terminal_is_ours; + (target_terminal::is_inferior () && gdb_tty_state == terminal_is_ours) + + is true when the (native) inferior is not sharing a terminal with + GDB (e.g., because we attached to an inferior that is running on a + different terminal). */ +static target_terminal_state gdb_tty_state = target_terminal_state::is_ours; /* See terminal.h. */ @@ -165,29 +206,23 @@ gdb_has_a_terminal (void) before we actually run the inferior. */ void -child_terminal_init_with_pgrp (int pgrp) +child_terminal_init (struct target_ops *self) { - struct inferior *inf = current_inferior (); - struct terminal_info *tinfo = get_inflow_inferior_data (inf); + if (!gdb_has_a_terminal ()) + return; + + inferior *inf = current_inferior (); + terminal_info *tinfo = get_inflow_inferior_data (inf); #ifdef HAVE_TERMIOS_H - /* Store the process group even without a terminal as it is used not - only to reset the tty foreground process group, but also to - interrupt the inferior. */ - tinfo->process_group = pgrp; + /* A child we spawn should be a process group leader (PGID==PID) at + this point, though that may not be true if we're attaching to an + existing process. */ + tinfo->process_group = inf->pid; #endif - if (gdb_has_a_terminal ()) - { - xfree (tinfo->ttystate); - tinfo->ttystate = serial_copy_tty_state (stdin_serial, - initial_gdb_ttystate); - - /* Make sure that next time we call terminal_inferior (which will be - before the program runs, as it needs to be), we install the new - process group. */ - terminal_is_ours = 1; - } + xfree (tinfo->ttystate); + tinfo->ttystate = serial_copy_tty_state (stdin_serial, initial_gdb_ttystate); } /* Save the terminal settings again. This is necessary for the TUI @@ -204,45 +239,137 @@ gdb_save_tty_state (void) } } -void -child_terminal_init (struct target_ops *self) +/* Try to determine whether TTY is GDB's input terminal. Returns + TRIBOOL_UNKNOWN if we can't tell. */ + +static tribool +is_gdb_terminal (const char *tty) { -#ifdef HAVE_TERMIOS_H - /* This is for Lynx, and should be cleaned up by having Lynx be a - separate debugging target with a version of target_terminal::init - which passes in the process group to a generic routine which does - all the work (and the non-threaded child_terminal_init can just - pass in inferior_ptid to the same routine). */ - /* We assume INFERIOR_PID is also the child's process group. */ - child_terminal_init_with_pgrp (ptid_get_pid (inferior_ptid)); -#endif /* HAVE_TERMIOS_H */ + struct stat gdb_tty; + struct stat other_tty; + int res; + + res = stat (tty, &other_tty); + if (res == -1) + return TRIBOOL_UNKNOWN; + + res = fstat (STDIN_FILENO, &gdb_tty); + if (res == -1) + return TRIBOOL_UNKNOWN; + + return ((gdb_tty.st_dev == other_tty.st_dev + && gdb_tty.st_ino == other_tty.st_ino) + ? TRIBOOL_TRUE + : TRIBOOL_FALSE); } -/* Put the inferior's terminal settings into effect. - This is preparation for starting or resuming the inferior. +/* Helper for sharing_input_terminal. Try to determine whether + inferior INF is using the same TTY for input as GDB is. Returns + TRIBOOL_UNKNOWN if we can't tell. */ - N.B. Targets that want to use this with async support must build that - support on top of this (e.g., the caller still needs to remove stdin - from the event loop). E.g., see linux_nat_terminal_inferior. */ +static tribool +sharing_input_terminal_1 (inferior *inf) +{ + /* Using host-dependent code here is fine, because the + child_terminal_foo functions are meant to be used by child/native + targets. */ +#if defined (__linux__) || defined (__sun__) + char buf[100]; + + xsnprintf (buf, sizeof (buf), "/proc/%d/fd/0", inf->pid); + return is_gdb_terminal (buf); +#else + return TRIBOOL_UNKNOWN; +#endif +} + +/* Return true if the inferior is using the same TTY for input as GDB + is. If this is true, then we save/restore terminal flags/state. + + This is necessary because if inf->attach_flag is set, we don't + offhand know whether we are sharing a terminal with the inferior or + not. Attaching a process without a terminal is one case where we + do not; attaching a process which we ran from the same shell as GDB + via `&' is one case where we do. + + If we can't determine, we assume the TTY is being shared. This + works OK if you're only debugging one inferior. However, if you're + debugging more than one inferior, and e.g., one is spawned by GDB + with "run" (sharing terminal with GDB), and another is attached to + (and running on a different terminal, as is most common), then it + matters, because we can only restore the terminal settings of one + of the inferiors, and in that scenario, we want to restore the + settings of the "run"'ed inferior. + + Note, this is not the same as determining whether GDB and the + inferior are in the same session / connected to the same + controlling tty. An inferior (fork child) may call setsid, + disconnecting itself from the ctty, while still leaving + stdin/stdout/stderr associated with the original terminal. If + we're debugging that process, we should also save/restore terminal + settings. */ + +static bool +sharing_input_terminal (inferior *inf) +{ + terminal_info *tinfo = get_inflow_inferior_data (inf); + + tribool res = sharing_input_terminal_1 (inf); + + if (res == TRIBOOL_UNKNOWN) + { + /* As fallback, if we can't determine by stat'ing the inferior's + tty directly (because it's not supported on this host) and + the child was spawned, check whether run_terminal is our tty. + This isn't ideal, since this is checking the child's + controlling terminal, not the input terminal (which may have + been redirected), but is still better than nothing. A false + positive ("set inferior-tty" points to our terminal, but I/O + was redirected) is much more likely than a false negative + ("set inferior-tty" points to some other terminal, and then + output was redirected to our terminal), and with a false + positive we just end up trying to save/restore terminal + settings when we didn't need to or we actually can't. */ + if (tinfo->run_terminal != NULL) + res = is_gdb_terminal (tinfo->run_terminal); + + /* If we still can't determine, assume yes. */ + if (res == TRIBOOL_UNKNOWN) + return true; + } + + return res == TRIBOOL_TRUE; +} + +/* Put the inferior's terminal settings into effect. This is + preparation for starting or resuming the inferior. */ void child_terminal_inferior (struct target_ops *self) { - struct inferior *inf; - struct terminal_info *tinfo; - - if (!terminal_is_ours) + /* If we resume more than one inferior in the foreground on GDB's + terminal, then the first inferior's terminal settings "win". + Note that every child process is put in its own process group, so + the first process that ends up resumed ends up determining which + process group the kernel forwards Ctrl-C/Ctrl-Z (SIGINT/SIGTTOU) + to. */ + if (gdb_tty_state == target_terminal_state::is_inferior) return; - inf = current_inferior (); - tinfo = get_inflow_inferior_data (inf); + inferior *inf = current_inferior (); + terminal_info *tinfo = get_inflow_inferior_data (inf); if (gdb_has_a_terminal () && tinfo->ttystate != NULL - && tinfo->run_terminal == NULL) + && sharing_input_terminal (inf)) { int result; + /* Ignore SIGTTOU since it will happen when we try to set the + terminal's state (if gdb_tty_state is currently + ours_for_output). */ + scoped_ignore_sigttou ignore_sigttou; + #ifdef F_GETFL result = fcntl (0, F_SETFL, tinfo->tflags); OOPSY ("fcntl F_SETFL"); @@ -259,28 +386,40 @@ child_terminal_inferior (struct target_ops *self) #endif } - /* If attach_flag is set, we don't know whether we are sharing a - terminal with the inferior or not. (attaching a process - without a terminal is one case where we do not; attaching a - process which we ran from the same shell as GDB via `&' is - one case where we do, I think (but perhaps this is not - `sharing' in the sense that we need to save and restore tty - state)). I don't know if there is any way to tell whether we - are sharing a terminal. So what we do is to go through all - the saving and restoring of the tty state, but ignore errors - setting the process group, which will happen if we are not - sharing a terminal). */ - if (job_control) { #ifdef HAVE_TERMIOS_H + /* If we can't tell the inferior's actual process group, + then restore whatever was the foreground pgrp the last + time the inferior was running. See also comments + describing terminal_state::process_group. */ +#ifdef HAVE_GETPGID + result = tcsetpgrp (0, getpgid (inf->pid)); +#else result = tcsetpgrp (0, tinfo->process_group); - if (!inf->attach_flag) - OOPSY ("tcsetpgrp"); +#endif + if (result == -1) + { +#if 0 + /* This fails if either GDB has no controlling terminal, + e.g., running under 'setsid(1)', or if the inferior + is not attached to GDB's controlling terminal. E.g., + if it called setsid to create a new session or used + the TIOCNOTTY ioctl, or simply if we've attached to a + process running on another terminal and we couldn't + tell whether it was sharing GDB's terminal (and so + assumed yes). */ + fprintf_unfiltered + (gdb_stderr, + "[tcsetpgrp failed in child_terminal_inferior: %s]\n", + safe_strerror (errno)); +#endif + } #endif } + + gdb_tty_state = target_terminal_state::is_inferior; } - terminal_is_ours = 0; } /* Put some of our terminal settings into effect, @@ -297,7 +436,7 @@ child_terminal_inferior (struct target_ops *self) void child_terminal_ours_for_output (struct target_ops *self) { - child_terminal_ours_1 (1); + child_terminal_ours_1 (target_terminal_state::is_ours_for_output); } /* Put our terminal settings into effect. @@ -311,63 +450,66 @@ child_terminal_ours_for_output (struct target_ops *self) void child_terminal_ours (struct target_ops *self) { - child_terminal_ours_1 (0); + child_terminal_ours_1 (target_terminal_state::is_ours); } -/* output_only is not used, and should not be used unless we introduce - separate terminal_is_ours and terminal_is_ours_for_output - flags. */ +/* Save the current terminal settings in the inferior's terminal_info + cache. */ -static void -child_terminal_ours_1 (int output_only) +void +child_terminal_save_inferior (struct target_ops *self) { - struct inferior *inf; - struct terminal_info *tinfo; - - if (terminal_is_ours) + /* Avoid attempting all the ioctl's when running in batch. */ + if (!gdb_has_a_terminal ()) return; - terminal_is_ours = 1; + inferior *inf = current_inferior (); + terminal_info *tinfo = get_inflow_inferior_data (inf); - /* Checking inferior->run_terminal is necessary so that - if GDB is running in the background, it won't block trying - to do the ioctl()'s below. Checking gdb_has_a_terminal - avoids attempting all the ioctl's when running in batch. */ + /* No need to save/restore if the inferior is not sharing GDB's + tty. */ + if (!sharing_input_terminal (inf)) + return; - inf = current_inferior (); - tinfo = get_inflow_inferior_data (inf); + xfree (tinfo->ttystate); + tinfo->ttystate = serial_get_tty_state (stdin_serial); - if (tinfo->run_terminal != NULL || gdb_has_a_terminal () == 0) - return; - else - { -#ifdef SIGTTOU - /* Ignore this signal since it will happen when we try to set the - pgrp. */ - sighandler_t osigttou = NULL; +#ifdef HAVE_TERMIOS_H + tinfo->process_group = tcgetpgrp (0); #endif - int result ATTRIBUTE_UNUSED; -#ifdef SIGTTOU - if (job_control) - osigttou = signal (SIGTTOU, SIG_IGN); +#ifdef F_GETFL + tinfo->tflags = fcntl (0, F_GETFL, 0); #endif +} - xfree (tinfo->ttystate); - tinfo->ttystate = serial_get_tty_state (stdin_serial); +/* Switch terminal state to DESIRED_STATE, either is_ours, or + is_ours_for_output. */ -#ifdef HAVE_TERMIOS_H - if (!inf->attach_flag) - /* If tcsetpgrp failed in terminal_inferior, this would give us - our process group instead of the inferior's. See - terminal_inferior for details. */ - tinfo->process_group = tcgetpgrp (0); -#endif +static void +child_terminal_ours_1 (target_terminal_state desired_state) +{ + gdb_assert (desired_state != target_terminal_state::is_inferior); + + /* Avoid attempting all the ioctl's when running in batch. */ + if (!gdb_has_a_terminal ()) + return; + + if (gdb_tty_state != desired_state) + { + int result ATTRIBUTE_UNUSED; + + /* Ignore SIGTTOU since it will happen when we try to set the + terminal's pgrp. */ + scoped_ignore_sigttou ignore_sigttou; /* Set tty state to our_ttystate. */ serial_set_tty_state (stdin_serial, our_terminal_info.ttystate); - if (job_control) + /* If we only want output, then leave the inferior's pgrp in the + foreground, so that Ctrl-C/Ctrl-Z reach the inferior + directly. */ + if (job_control && desired_state == target_terminal_state::is_ours) { #ifdef HAVE_TERMIOS_H result = tcsetpgrp (0, our_terminal_info.process_group); @@ -384,12 +526,7 @@ child_terminal_ours_1 (int output_only) #endif /* termios */ } -#ifdef SIGTTOU - if (job_control) - signal (SIGTTOU, osigttou); -#endif - - if (!job_control) + if (!job_control && desired_state == target_terminal_state::is_ours) { signal (SIGINT, sigint_ours); #ifdef SIGQUIT @@ -398,23 +535,103 @@ child_terminal_ours_1 (int output_only) } #ifdef F_GETFL - tinfo->tflags = fcntl (0, F_GETFL, 0); result = fcntl (0, F_SETFL, our_terminal_info.tflags); #endif + + gdb_tty_state = desired_state; } } -/* Per-inferior data key. */ -static const struct inferior_data *inflow_inferior_data; +/* Interrupt the inferior. Implementation of target_interrupt for + child/native targets. */ -static void -inflow_inferior_data_cleanup (struct inferior *inf, void *arg) +void +child_interrupt (struct target_ops *self) { - struct terminal_info *info = (struct terminal_info *) arg; + /* Interrupt the first inferior that has a resumed thread. */ + thread_info *resumed = NULL; + for (thread_info *thr : all_non_exited_threads ()) + { + if (thr->executing) + { + resumed = thr; + break; + } + if (thr->suspend.waitstatus_pending_p) + resumed = thr; + } - xfree (info->run_terminal); - xfree (info->ttystate); - xfree (info); + if (resumed != NULL) + { + /* Note that unlike pressing Ctrl-C on the controlling terminal, + here we only interrupt one process, not the whole process + group. This is because interrupting a process group (with + either Ctrl-C or with kill(3) with negative PID) sends a + SIGINT to each process in the process group, and we may not + be debugging all processes in the process group. */ +#ifndef _WIN32 + kill (resumed->inf->pid, SIGINT); +#endif + } +} + +/* Pass a Ctrl-C to the inferior as-if a Ctrl-C was pressed while the + inferior was in the foreground. Implementation of + target_pass_ctrlc for child/native targets. */ + +void +child_pass_ctrlc (struct target_ops *self) +{ + gdb_assert (!target_terminal::is_ours ()); + +#ifdef HAVE_TERMIOS_H + if (job_control) + { + pid_t term_pgrp = tcgetpgrp (0); + + /* If there's any inferior sharing our terminal, pass the SIGINT + to the terminal's foreground process group. This acts just + like the user typed a ^C on the terminal while the inferior + was in the foreground. Note that using a negative process + number in kill() is a System V-ism. The proper BSD interface + is killpg(). However, all modern BSDs support the System V + interface too. */ + + if (term_pgrp != -1 && term_pgrp != our_terminal_info.process_group) + { + kill (-term_pgrp, SIGINT); + return; + } + } +#endif + + /* Otherwise, pass the Ctrl-C to the first inferior that was resumed + in the foreground. */ + for (inferior *inf : all_inferiors ()) + { + if (inf->terminal_state != target_terminal_state::is_ours) + { + gdb_assert (inf->pid != 0); + +#ifndef _WIN32 + kill (inf->pid, SIGINT); +#endif + return; + } + } + + /* If no inferior was resumed in the foreground, then how did the + !is_ours assert above pass? */ + gdb_assert_not_reached ("no inferior resumed in the fg found"); +} + +/* Per-inferior data key. */ +static const struct inferior_key inflow_inferior_data; + +terminal_info::~terminal_info () +{ + xfree (run_terminal); + xfree (ttystate); } /* Get the current svr4 data. If none is found yet, add it now. This @@ -425,12 +642,9 @@ get_inflow_inferior_data (struct inferior *inf) { struct terminal_info *info; - info = (struct terminal_info *) inferior_data (inf, inflow_inferior_data); + info = inflow_inferior_data.get (inf); if (info == NULL) - { - info = XCNEW (struct terminal_info); - set_inferior_data (inf, inflow_inferior_data, info); - } + info = inflow_inferior_data.emplace (inf); return info; } @@ -444,16 +658,8 @@ get_inflow_inferior_data (struct inferior *inf) static void inflow_inferior_exit (struct inferior *inf) { - struct terminal_info *info; - - info = (struct terminal_info *) inferior_data (inf, inflow_inferior_data); - if (info != NULL) - { - xfree (info->run_terminal); - xfree (info->ttystate); - xfree (info); - set_inferior_data (inf, inflow_inferior_data, NULL); - } + inf->terminal_state = target_terminal_state::is_ours; + inflow_inferior_data.clear (inf); } void @@ -476,10 +682,26 @@ copy_terminal_info (struct inferior *to, struct inferior *from) if (tinfo_from->ttystate) tinfo_to->ttystate = serial_copy_tty_state (stdin_serial, tinfo_from->ttystate); + + to->terminal_state = from->terminal_state; } +/* See terminal.h. */ + void -info_terminal_command (char *arg, int from_tty) +swap_terminal_info (inferior *a, inferior *b) +{ + terminal_info *info_a = inflow_inferior_data.get (a); + terminal_info *info_b = inflow_inferior_data.get (b); + + inflow_inferior_data.set (a, info_b); + inflow_inferior_data.set (b, info_a); + + std::swap (a->terminal_state, b->terminal_state); +} + +void +info_terminal_command (const char *arg, int from_tty) { target_terminal::info (arg, from_tty); } @@ -496,7 +718,7 @@ child_terminal_info (struct target_ops *self, const char *args, int from_tty) return; } - if (ptid_equal (inferior_ptid, null_ptid)) + if (inferior_ptid == null_ptid) return; inf = current_inferior (); @@ -603,11 +825,11 @@ check_syscall (const char *msg, int result) void new_tty (void) { - int tty; - if (inferior_thisrun_terminal == 0) return; #if !defined(__GO32__) && !defined(_WIN32) + int tty; + #ifdef TIOCNOTTY /* Disconnect the child process from our controlling terminal. On some systems (SVR4 for example), this may cause a SIGTTOU, so temporarily @@ -615,12 +837,10 @@ new_tty (void) tty = open ("/dev/tty", O_RDWR); if (tty > 0) { - sighandler_t osigttou; + scoped_ignore_sigttou ignore_sigttou; - osigttou = signal (SIGTTOU, SIG_IGN); ioctl (tty, TIOCNOTTY, 0); close (tty); - signal (SIGTTOU, osigttou); } #endif @@ -688,7 +908,7 @@ static void pass_signal (int signo) { #ifndef _WIN32 - kill (ptid_get_pid (inferior_ptid), SIGINT); + kill (inferior_ptid.pid (), SIGINT); #endif } @@ -766,13 +986,8 @@ _initialize_inflow (void) add_info ("terminal", info_terminal_command, _("Print inferior's saved terminal status.")); - terminal_is_ours = 1; - /* OK, figure out whether we have job control. */ have_job_control (); - observer_attach_inferior_exit (inflow_inferior_exit); - - inflow_inferior_data - = register_inferior_data_with_cleanup (NULL, inflow_inferior_data_cleanup); + gdb::observers::inferior_exit.attach (inflow_inferior_exit); }