Per-inferior target_terminal state, fix PR gdb/13211, more
In my multi-target branch I ran into problems with GDB's terminal handling that exist in master as well, with multi-inferior debugging. This patch adds a testcase for said problems (gdb.multi/multi-term-settings.exp), fixes the problems, fixes PR gdb/13211 as well (and adds a testcase for that too, gdb.base/interrupt-daemon.exp). The basis of the problem I ran into is the following. Consider a scenario where you have: - inferior 1 - started with "attach", process is running on some other terminal. - inferior 2 - started with "run", process is sharing gdb's terminal. In this scenario, when you stop/resume both inferiors, you want GDB to save/restore the terminal settings of inferior 2, the one that is sharing GDB's terminal. I.e., you want inferior 2 to "own" the terminal (in target_terminal::is_ours/target_terminal::is_inferior sense). Unfortunately, that's not what you get currently. Because GDB doesn't know whether an attached inferior is actually sharing GDB's terminal, it tries to save/restore its settings anyway, ignoring errors. In this case, this is pointless, because inferior 1 is running on a different terminal, but GDB doesn't know better. And then, because it is only possible to have the terminal settings of a single inferior be in effect at a time, or make one inferior/pgrp be the terminal's foreground pgrp (aka, only one inferior can "own" the terminal, ignoring fork children here), if GDB happens to try to restore the terminal settings of inferior 1 first, then GDB never restores the terminal settings of inferior 2. This patch fixes that and a few things more along the way: - Moves enum target_terminal::terminal_state out of the target_terminal class (it's currently private) and makes it a scoped enum so that it can be easily used elsewhere. - Replaces the inflow.c:terminal_is_ours boolean with a target_terminal_state variable. This allows distinguishing is_ours and is_ours_for_output states. This allows finally making child_terminal_ours_1 do something with its "output_only" parameter. - Makes each inferior have its own copy of the is_ours/is_ours_for_output/is_inferior state. - Adds a way for GDB to tell whether the inferior is sharing GDB's terminal. Works best on Linux and Solaris; the fallback works just as well as currently. - With that, we can remove the inf->attach_flag tests from child_terminal_inferior/child_terminal_ours. - Currently target_ops.to_ours is responsible for both saving the current inferior's terminal state, and restoring gdb's state. Because each inferior has its own terminal state (possibly handled by different targets in a multi-target world, even), we need to split the inferior-saving part from the gdb-restoring part. The patch adds a new target_ops.to_save_inferior target method for that. - Adds a new target_terminal::save_inferior() function, so that sequences like: scoped_restore_terminal_state save_state; target_terminal::ours_for_output (); ... restore back inferiors that were target_terminal_state::is_inferior before back to is_inferior, and leaves inferiors that were is_ours alone. - Along the way, this adds a default implementation of target_pass_ctrlc to inflow.c (for inf-child.c), that handles passing the Ctrl-C to a process running on GDB's terminal or to some other process otherwise. - Similarly, adds a new target default implementation of target_interrupt, for the "interrupt" command. The current implementation of this hook in inf-ptrace.c kills the whole process group, but that's incorrect/undesirable because we may not be attached to all processes in the process group. And also, it's incorrect because inferior_process_group() doesn't really return the inferior's real process group id if the inferior is not a process group leader... This is the cause of PR gdb/13211 [1], which this patch fixes. While at it, that target method's "ptid" parameter is eliminated, because it's not really used. - A new test is included that exercises and fixes PR gdb/13211, and also fixes a GDB issue reported on stackoverflow that I ran into while working on this [2]. The problem is similar to PR gdb/13211, except that it also triggers with Ctrl-C. When debugging a daemon (i.e., a process that disconnects from the controlling terminal and is not a process group leader, then Ctrl-C doesn't work, you just can't interrupt the inferior at all, resulting in a hung debug session. The problem is that since the inferior is no longer associated with gdb's session / controlling terminal, then trying to put the inferior in the foreground fails. And so Ctrl-C never reaches the inferior directly. pass_signal is only used when the inferior is attached, but that is not the case here. This is fixed by the new child_pass_ctrlc. Without the fix, the new interrupt-daemon.exp testcase fails with timeout waiting for a SIGINT that never arrives. [1] PR gdb/13211 - Async / Process group and interrupt not working https://sourceware.org/bugzilla/show_bug.cgi?id=13211 [2] GDB not reacting Ctrl-C when after fork() and setsid() https://stackoverflow.com/questions/46101292/gdb-not-reacting-ctrl-c-when-after-fork-and-setsid Note this patch does _not_ fix: - PR gdb/14559 - The 'interrupt' command does not work if sigwait is in use https://sourceware.org/bugzilla/show_bug.cgi?id=14559 - PR gdb/9425 - When using "sigwait" GDB doesn't trap SIGINT. Ctrl+C terminates program when should break gdb. https://sourceware.org/bugzilla/show_bug.cgi?id=9425 The only way to fix that that I know of (without changing the kernel) is to make GDB put inferiors in a separate session (create a pseudo-tty master/slave pair, make the inferior run with the slave as its terminal, and have gdb pump output/input on the master end). gdb/ChangeLog: 2018-01-30 Pedro Alves <palves@redhat.com> PR gdb/13211 * config.in, configure: Regenerate. * configure.ac: Check for getpgid. * go32-nat.c (go32_pass_ctrlc): New. (go32_target): Install it. * inf-child.c (inf_child_target): Install child_terminal_save_inferior, child_pass_ctrlc and child_interrupt. * inf-ptrace.c (inf_ptrace_interrupt): Delete. (inf_ptrace_target): No longer install it. * infcmd.c (interrupt_target_1): Adjust. * inferior.h (child_terminal_save_inferior, child_pass_ctrlc) (child_interrupt): Declare. (inferior::terminal_state): New. * inflow.c (struct terminal_info): Update comments. (inferior_process_group): Delete. (terminal_is_ours): Delete. (gdb_tty_state): New. (child_terminal_init): Adjust. (is_gdb_terminal, sharing_input_terminal_1) (sharing_input_terminal): New functions. (child_terminal_inferior): Adjust. Use sharing_input_terminal. Set the process's actual process group in the foreground if possible. Handle is_ours_for_output/is_ours distinction. Don't mark terminal as the inferior's if not sharing GDB's terminal. Don't check attach_flag. (child_terminal_ours_for_output, child_terminal_ours): Adjust to pass down a target_terminal_state. (child_terminal_save_inferior): New, factored out from ... (child_terminal_ours_1): ... this. Handle target_terminal_state::is_ours_for_output. (child_interrupt, child_pass_ctrlc): New. (inflow_inferior_exit): Clear the inferior's terminal_state. (copy_terminal_info): Copy the inferior's terminal state. (_initialize_inflow): Remove reference to terminal_is_ours. * inflow.h (inferior_process_group): Delete. * nto-procfs.c (nto_handle_sigint, procfs_interrupt): Adjust. * procfs.c (procfs_target): Don't install procfs_interrupt. (procfs_interrupt): Delete. * remote.c (remote_serial_quit_handler): Adjust. (remote_interrupt): Remove ptid parameter. Adjust. * target-delegates.c: Regenerate. * target.c: Include "terminal.h". (target_terminal::terminal_state): Rename to ... (target_terminal::m_terminal_state): ... this. (target_terminal::init): Adjust. (target_terminal::inferior): Adjust to per-inferior terminal_state. (target_terminal::restore_inferior, target_terminal_is_ours_kind): New. (target_terminal::ours, target_terminal::ours_for_output): Use target_terminal_is_ours_kind. (target_interrupt): Remove ptid parameter. Adjust. (default_target_pass_ctrlc): Adjust. * target.h (target_ops::to_terminal_save_inferior): New field. (target_ops::to_interrupt): Remove ptid_t parameter. (target_interrupt): Remove ptid_t parameter. Update comment. (target_pass_ctrlc): Update comment. * target/target.h (target_terminal_state): New scoped enum, factored out of ... (target_terminal::terminal_state): ... here. (target_terminal::inferior): Update comments. (target_terminal::restore_inferior): New. (target_terminal::is_inferior, target_terminal::is_ours) (target_terminal::is_ours_for_output): Adjust. (target_terminal::scoped_restore_terminal_state): Adjust to rename, and call restore_inferior() instead of inferior(). (target_terminal::scoped_restore_terminal_state::m_state): Change type. (target_terminal::terminal_state): Rename to ... (target_terminal::m_terminal_state): ... this and change type. gdb/gdbserver/ChangeLog: 2018-01-30 Pedro Alves <palves@redhat.com> PR gdb/13211 * target.c (target_terminal::terminal_state): Rename to ... (target_terminal::m_terminal_state): ... this. gdb/testsuite/ChangeLog: 2018-01-30 Pedro Alves <palves@redhat.com> PR gdb/13211 * gdb.base/interrupt-daemon.c: New. * gdb.base/interrupt-daemon.exp: New. * gdb.multi/multi-term-settings.c: New. * gdb.multi/multi-term-settings.exp: New.
This commit is contained in:
parent
9c3a5d9319
commit
e671cd59d7
@ -1,3 +1,76 @@
|
||||
2018-01-30 Pedro Alves <palves@redhat.com>
|
||||
|
||||
PR gdb/13211
|
||||
* config.in, configure: Regenerate.
|
||||
* configure.ac: Check for getpgid.
|
||||
* go32-nat.c (go32_pass_ctrlc): New.
|
||||
(go32_target): Install it.
|
||||
* inf-child.c (inf_child_target): Install
|
||||
child_terminal_save_inferior, child_pass_ctrlc and
|
||||
child_interrupt.
|
||||
* inf-ptrace.c (inf_ptrace_interrupt): Delete.
|
||||
(inf_ptrace_target): No longer install it.
|
||||
* infcmd.c (interrupt_target_1): Adjust.
|
||||
* inferior.h (child_terminal_save_inferior, child_pass_ctrlc)
|
||||
(child_interrupt): Declare.
|
||||
(inferior::terminal_state): New.
|
||||
* inflow.c (struct terminal_info): Update comments.
|
||||
(inferior_process_group): Delete.
|
||||
(terminal_is_ours): Delete.
|
||||
(gdb_tty_state): New.
|
||||
(child_terminal_init): Adjust.
|
||||
(is_gdb_terminal, sharing_input_terminal_1)
|
||||
(sharing_input_terminal): New functions.
|
||||
(child_terminal_inferior): Adjust. Use sharing_input_terminal.
|
||||
Set the process's actual process group in the foreground if
|
||||
possible. Handle is_ours_for_output/is_ours distinction. Don't
|
||||
mark terminal as the inferior's if not sharing GDB's terminal.
|
||||
Don't check attach_flag.
|
||||
(child_terminal_ours_for_output, child_terminal_ours): Adjust to
|
||||
pass down a target_terminal_state.
|
||||
(child_terminal_save_inferior): New, factored out from ...
|
||||
(child_terminal_ours_1): ... this. Handle
|
||||
target_terminal_state::is_ours_for_output.
|
||||
(child_interrupt, child_pass_ctrlc): New.
|
||||
(inflow_inferior_exit): Clear the inferior's terminal_state.
|
||||
(copy_terminal_info): Copy the inferior's terminal state.
|
||||
(_initialize_inflow): Remove reference to terminal_is_ours.
|
||||
* inflow.h (inferior_process_group): Delete.
|
||||
* nto-procfs.c (nto_handle_sigint, procfs_interrupt): Adjust.
|
||||
* procfs.c (procfs_target): Don't install procfs_interrupt.
|
||||
(procfs_interrupt): Delete.
|
||||
* remote.c (remote_serial_quit_handler): Adjust.
|
||||
(remote_interrupt): Remove ptid parameter. Adjust.
|
||||
* target-delegates.c: Regenerate.
|
||||
* target.c: Include "terminal.h".
|
||||
(target_terminal::terminal_state): Rename to ...
|
||||
(target_terminal::m_terminal_state): ... this.
|
||||
(target_terminal::init): Adjust.
|
||||
(target_terminal::inferior): Adjust to per-inferior
|
||||
terminal_state.
|
||||
(target_terminal::restore_inferior, target_terminal_is_ours_kind): New.
|
||||
(target_terminal::ours, target_terminal::ours_for_output): Use
|
||||
target_terminal_is_ours_kind.
|
||||
(target_interrupt): Remove ptid parameter. Adjust.
|
||||
(default_target_pass_ctrlc): Adjust.
|
||||
* target.h (target_ops::to_terminal_save_inferior): New field.
|
||||
(target_ops::to_interrupt): Remove ptid_t parameter.
|
||||
(target_interrupt): Remove ptid_t parameter. Update comment.
|
||||
(target_pass_ctrlc): Update comment.
|
||||
* target/target.h (target_terminal_state): New scoped enum,
|
||||
factored out of ...
|
||||
(target_terminal::terminal_state): ... here.
|
||||
(target_terminal::inferior): Update comments.
|
||||
(target_terminal::restore_inferior): New.
|
||||
(target_terminal::is_inferior, target_terminal::is_ours)
|
||||
(target_terminal::is_ours_for_output): Adjust.
|
||||
(target_terminal::scoped_restore_terminal_state): Adjust to
|
||||
rename, and call restore_inferior() instead of inferior().
|
||||
(target_terminal::scoped_restore_terminal_state::m_state): Change
|
||||
type.
|
||||
(target_terminal::terminal_state): Rename to ...
|
||||
(target_terminal::m_terminal_state): ... this and change type.
|
||||
|
||||
2018-01-30 Pedro Alves <palves@redhat.com>
|
||||
|
||||
* linux-nat.c (wait_for_signal): New function.
|
||||
|
@ -192,6 +192,9 @@
|
||||
/* Define to 1 if you have the `getpagesize' function. */
|
||||
#undef HAVE_GETPAGESIZE
|
||||
|
||||
/* Define to 1 if you have the `getpgid' function. */
|
||||
#undef HAVE_GETPGID
|
||||
|
||||
/* Define to 1 if you have the `getrlimit' function. */
|
||||
#undef HAVE_GETRLIMIT
|
||||
|
||||
|
2
gdb/configure
vendored
2
gdb/configure
vendored
@ -13212,7 +13212,7 @@ fi
|
||||
|
||||
for ac_func in getauxval getrusage getuid getgid \
|
||||
pipe poll pread pread64 pwrite resize_term \
|
||||
sbrk setpgid setpgrp setsid \
|
||||
sbrk getpgid setpgid setpgrp setsid \
|
||||
sigaction sigprocmask sigsetmask socketpair \
|
||||
ttrace wborder wresize setlocale iconvlist libiconvlist btowc \
|
||||
setrlimit getrlimit posix_madvise waitpid \
|
||||
|
@ -1368,7 +1368,7 @@ AC_FUNC_MMAP
|
||||
AC_FUNC_VFORK
|
||||
AC_CHECK_FUNCS([getauxval getrusage getuid getgid \
|
||||
pipe poll pread pread64 pwrite resize_term \
|
||||
sbrk setpgid setpgrp setsid \
|
||||
sbrk getpgid setpgid setpgrp setsid \
|
||||
sigaction sigprocmask sigsetmask socketpair \
|
||||
ttrace wborder wresize setlocale iconvlist libiconvlist btowc \
|
||||
setrlimit getrlimit posix_madvise waitpid \
|
||||
|
@ -1,3 +1,9 @@
|
||||
2018-01-30 Pedro Alves <palves@redhat.com>
|
||||
|
||||
PR gdb/13211
|
||||
* target.c (target_terminal::terminal_state): Rename to ...
|
||||
(target_terminal::m_terminal_state): ... this.
|
||||
|
||||
2018-01-19 James Clarke <jrtc27@jrtc27.com>
|
||||
|
||||
* linux-low.c (handle_extended_wait): Surround call to
|
||||
|
@ -360,8 +360,8 @@ default_breakpoint_kind_from_pc (CORE_ADDR *pcptr)
|
||||
|
||||
/* Define it. */
|
||||
|
||||
enum target_terminal::terminal_state target_terminal::terminal_state
|
||||
= target_terminal::terminal_is_ours;
|
||||
target_terminal_state target_terminal::m_terminal_state
|
||||
= target_terminal_state::is_ours;
|
||||
|
||||
/* See target/target.h. */
|
||||
|
||||
|
@ -937,6 +937,11 @@ go32_terminal_ours (struct target_ops *self)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
go32_pass_ctrlc (struct target_ops *self)
|
||||
{
|
||||
}
|
||||
|
||||
static int
|
||||
go32_thread_alive (struct target_ops *ops, ptid_t ptid)
|
||||
{
|
||||
@ -968,6 +973,7 @@ go32_target (void)
|
||||
t->to_terminal_ours_for_output = go32_terminal_ours;
|
||||
t->to_terminal_ours = go32_terminal_ours;
|
||||
t->to_terminal_info = go32_terminal_info;
|
||||
t->to_pass_ctrlc = go32_pass_ctrlc;
|
||||
t->to_kill = go32_kill_inferior;
|
||||
t->to_create_inferior = go32_create_inferior;
|
||||
t->to_mourn_inferior = go32_mourn_inferior;
|
||||
|
@ -408,9 +408,12 @@ inf_child_target (void)
|
||||
t->to_remove_breakpoint = memory_remove_breakpoint;
|
||||
t->to_terminal_init = child_terminal_init;
|
||||
t->to_terminal_inferior = child_terminal_inferior;
|
||||
t->to_terminal_save_inferior = child_terminal_save_inferior;
|
||||
t->to_terminal_ours_for_output = child_terminal_ours_for_output;
|
||||
t->to_terminal_ours = child_terminal_ours;
|
||||
t->to_terminal_info = child_terminal_info;
|
||||
t->to_pass_ctrlc = child_pass_ctrlc;
|
||||
t->to_interrupt = child_interrupt;
|
||||
t->to_post_startup_inferior = inf_child_post_startup_inferior;
|
||||
t->to_follow_fork = inf_child_follow_fork;
|
||||
t->to_can_run = inf_child_can_run;
|
||||
|
@ -294,19 +294,6 @@ inf_ptrace_kill (struct target_ops *ops)
|
||||
target_mourn_inferior (inferior_ptid);
|
||||
}
|
||||
|
||||
/* Interrupt the inferior. */
|
||||
|
||||
static void
|
||||
inf_ptrace_interrupt (struct target_ops *self, ptid_t ptid)
|
||||
{
|
||||
/* Send a SIGINT to the process group. This acts just like the user
|
||||
typed a ^C on the controlling terminal. 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. */
|
||||
kill (-inferior_process_group (), SIGINT);
|
||||
}
|
||||
|
||||
/* Return which PID to pass to ptrace in order to observe/control the
|
||||
tracee identified by PTID. */
|
||||
|
||||
@ -688,7 +675,6 @@ inf_ptrace_target (void)
|
||||
t->to_mourn_inferior = inf_ptrace_mourn_inferior;
|
||||
t->to_thread_alive = inf_ptrace_thread_alive;
|
||||
t->to_pid_to_str = inf_ptrace_pid_to_str;
|
||||
t->to_interrupt = inf_ptrace_interrupt;
|
||||
t->to_xfer_partial = inf_ptrace_xfer_partial;
|
||||
#if defined (PT_IO) && defined (PIOD_READ_AUXV)
|
||||
t->to_auxv_parse = inf_ptrace_auxv_parse;
|
||||
|
@ -3033,7 +3033,7 @@ interrupt_target_1 (int all_threads)
|
||||
if (non_stop)
|
||||
target_stop (ptid);
|
||||
else
|
||||
target_interrupt (ptid);
|
||||
target_interrupt ();
|
||||
|
||||
/* Tag the thread as having been explicitly requested to stop, so
|
||||
other parts of gdb know not to resume this thread automatically,
|
||||
|
@ -129,10 +129,16 @@ extern void child_terminal_ours_for_output (struct target_ops *self);
|
||||
|
||||
extern void child_terminal_inferior (struct target_ops *self);
|
||||
|
||||
extern void child_terminal_save_inferior (struct target_ops *self);
|
||||
|
||||
extern void child_terminal_init (struct target_ops *self);
|
||||
|
||||
extern void child_terminal_init_with_pgrp (int pgrp);
|
||||
|
||||
extern void child_pass_ctrlc (struct target_ops *self);
|
||||
|
||||
extern void child_interrupt (struct target_ops *self);
|
||||
|
||||
/* From fork-child.c */
|
||||
|
||||
/* Helper function to call STARTUP_INFERIOR with PID and NUM_TRAPS.
|
||||
@ -367,6 +373,10 @@ public:
|
||||
/* The name of terminal device to use for I/O. */
|
||||
char *terminal = NULL;
|
||||
|
||||
/* The terminal state as set by the last target_terminal::terminal_*
|
||||
call. */
|
||||
target_terminal_state terminal_state = target_terminal_state::is_ours;
|
||||
|
||||
/* Environment to use for running inferior,
|
||||
in format described in environ.h. */
|
||||
gdb_environ environment;
|
||||
|
412
gdb/inflow.c
412
gdb/inflow.c
@ -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,8 +54,8 @@ 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
|
||||
{
|
||||
/* The name of the tty (from the `tty' command) that we gave to the
|
||||
@ -63,11 +63,23 @@ struct terminal_info
|
||||
char *run_terminal;
|
||||
|
||||
/* TTY state. We save it whenever the inferior stops, and restore
|
||||
it when it resumes. */
|
||||
it when it resumes in the foreground. */
|
||||
serial_ttystate ttystate;
|
||||
|
||||
#ifdef HAVE_TERMIOS_H
|
||||
/* Process group. Saved and restored just like ttystate. */
|
||||
/* 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;
|
||||
#endif
|
||||
|
||||
@ -120,17 +132,6 @@ private:
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef HAVE_TERMIOS_H
|
||||
|
||||
/* Return the process group of the current inferior. */
|
||||
|
||||
pid_t
|
||||
inferior_process_group (void)
|
||||
{
|
||||
return get_inflow_inferior_data (current_inferior ())->process_group;
|
||||
}
|
||||
#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,
|
||||
we save our handlers in these two variables and set SIGINT and SIGQUIT
|
||||
@ -146,11 +147,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. */
|
||||
|
||||
@ -196,29 +203,21 @@ gdb_has_a_terminal (void)
|
||||
void
|
||||
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. 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. */
|
||||
/* 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;
|
||||
}
|
||||
tinfo->ttystate = serial_copy_tty_state (stdin_serial, initial_gdb_ttystate);
|
||||
}
|
||||
|
||||
/* Save the terminal settings again. This is necessary for the TUI
|
||||
@ -235,31 +234,137 @@ gdb_save_tty_state (void)
|
||||
}
|
||||
}
|
||||
|
||||
/* Put the inferior's terminal settings into effect.
|
||||
This is preparation for starting or resuming the inferior.
|
||||
/* Try to determine whether TTY is GDB's input terminal. 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
|
||||
is_gdb_terminal (const char *tty)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
|
||||
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");
|
||||
@ -276,28 +381,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,
|
||||
@ -314,7 +431,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.
|
||||
@ -328,35 +445,50 @@ 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. */
|
||||
|
||||
void
|
||||
child_terminal_save_inferior (struct target_ops *self)
|
||||
{
|
||||
/* Avoid attempting all the ioctl's when running in batch. */
|
||||
if (!gdb_has_a_terminal ())
|
||||
return;
|
||||
|
||||
inferior *inf = current_inferior ();
|
||||
terminal_info *tinfo = get_inflow_inferior_data (inf);
|
||||
|
||||
/* No need to save/restore if the inferior is not sharing GDB's
|
||||
tty. */
|
||||
if (!sharing_input_terminal (inf))
|
||||
return;
|
||||
|
||||
xfree (tinfo->ttystate);
|
||||
tinfo->ttystate = serial_get_tty_state (stdin_serial);
|
||||
|
||||
tinfo->process_group = tcgetpgrp (0);
|
||||
|
||||
#ifdef F_GETFL
|
||||
tinfo->tflags = fcntl (0, F_GETFL, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Switch terminal state to DESIRED_STATE, either is_ours, or
|
||||
is_ours_for_output. */
|
||||
|
||||
static void
|
||||
child_terminal_ours_1 (int output_only)
|
||||
child_terminal_ours_1 (target_terminal_state desired_state)
|
||||
{
|
||||
struct inferior *inf;
|
||||
struct terminal_info *tinfo;
|
||||
gdb_assert (desired_state != target_terminal_state::is_inferior);
|
||||
|
||||
if (terminal_is_ours)
|
||||
/* Avoid attempting all the ioctl's when running in batch. */
|
||||
if (!gdb_has_a_terminal ())
|
||||
return;
|
||||
|
||||
terminal_is_ours = 1;
|
||||
|
||||
/* 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. */
|
||||
|
||||
inf = current_inferior ();
|
||||
tinfo = get_inflow_inferior_data (inf);
|
||||
|
||||
if (tinfo->run_terminal != NULL || gdb_has_a_terminal () == 0)
|
||||
return;
|
||||
else
|
||||
if (gdb_tty_state != desired_state)
|
||||
{
|
||||
int result ATTRIBUTE_UNUSED;
|
||||
|
||||
@ -364,21 +496,13 @@ child_terminal_ours_1 (int output_only)
|
||||
terminal's pgrp. */
|
||||
scoped_ignore_sigttou ignore_sigttou;
|
||||
|
||||
xfree (tinfo->ttystate);
|
||||
tinfo->ttystate = serial_get_tty_state (stdin_serial);
|
||||
|
||||
#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
|
||||
|
||||
/* 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);
|
||||
@ -395,7 +519,7 @@ child_terminal_ours_1 (int output_only)
|
||||
#endif /* termios */
|
||||
}
|
||||
|
||||
if (!job_control)
|
||||
if (!job_control && desired_state == target_terminal_state::is_ours)
|
||||
{
|
||||
signal (SIGINT, sigint_ours);
|
||||
#ifdef SIGQUIT
|
||||
@ -404,12 +528,94 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
/* Interrupt the inferior. Implementation of target_interrupt for
|
||||
child/native targets. */
|
||||
|
||||
void
|
||||
child_interrupt (struct target_ops *self)
|
||||
{
|
||||
/* Interrupt the first inferior that has a resumed thread. */
|
||||
thread_info *thr;
|
||||
thread_info *resumed = NULL;
|
||||
ALL_NON_EXITED_THREADS (thr)
|
||||
{
|
||||
if (thr->executing)
|
||||
{
|
||||
resumed = thr;
|
||||
break;
|
||||
}
|
||||
if (thr->suspend.waitstatus_pending_p)
|
||||
resumed = thr;
|
||||
}
|
||||
|
||||
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. */
|
||||
kill (resumed->inf->pid, SIGINT);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
inferior *inf;
|
||||
ALL_INFERIORS (inf)
|
||||
{
|
||||
if (inf->terminal_state != target_terminal_state::is_ours)
|
||||
{
|
||||
gdb_assert (inf->pid != 0);
|
||||
|
||||
kill (inf->pid, SIGINT);
|
||||
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_data *inflow_inferior_data;
|
||||
|
||||
@ -452,6 +658,8 @@ inflow_inferior_exit (struct inferior *inf)
|
||||
{
|
||||
struct terminal_info *info;
|
||||
|
||||
inf->terminal_state = target_terminal_state::is_ours;
|
||||
|
||||
info = (struct terminal_info *) inferior_data (inf, inflow_inferior_data);
|
||||
if (info != NULL)
|
||||
{
|
||||
@ -482,6 +690,8 @@ 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;
|
||||
}
|
||||
|
||||
void
|
||||
@ -770,8 +980,6 @@ _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 ();
|
||||
|
||||
|
@ -22,9 +22,4 @@
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
/* Process group of the current inferior. */
|
||||
#ifdef HAVE_TERMIOS_H
|
||||
extern pid_t inferior_process_group (void);
|
||||
#endif
|
||||
|
||||
#endif /* inflow.h */
|
||||
|
@ -732,7 +732,7 @@ nto_handle_sigint (int signo)
|
||||
/* If this doesn't work, try more severe steps. */
|
||||
signal (signo, nto_handle_sigint_twice);
|
||||
|
||||
target_interrupt (inferior_ptid);
|
||||
target_interrupt ();
|
||||
}
|
||||
|
||||
static ptid_t
|
||||
@ -1275,7 +1275,7 @@ procfs_create_inferior (struct target_ops *ops, const char *exec_file,
|
||||
}
|
||||
|
||||
static void
|
||||
procfs_interrupt (struct target_ops *self, ptid_t ptid)
|
||||
procfs_interrupt (struct target_ops *self)
|
||||
{
|
||||
devctl (ctl_fd, DCMD_PROC_STOP, NULL, 0, 0);
|
||||
}
|
||||
|
12
gdb/procfs.c
12
gdb/procfs.c
@ -89,7 +89,6 @@ static void procfs_attach (struct target_ops *, const char *, int);
|
||||
static void procfs_detach (struct target_ops *, const char *, int);
|
||||
static void procfs_resume (struct target_ops *,
|
||||
ptid_t, int, enum gdb_signal);
|
||||
static void procfs_interrupt (struct target_ops *self, ptid_t);
|
||||
static void procfs_files_info (struct target_ops *);
|
||||
static void procfs_fetch_registers (struct target_ops *,
|
||||
struct regcache *, int);
|
||||
@ -172,7 +171,6 @@ procfs_target (void)
|
||||
t->to_xfer_partial = procfs_xfer_partial;
|
||||
t->to_pass_signals = procfs_pass_signals;
|
||||
t->to_files_info = procfs_files_info;
|
||||
t->to_interrupt = procfs_interrupt;
|
||||
|
||||
t->to_update_thread_list = procfs_update_thread_list;
|
||||
t->to_thread_alive = procfs_thread_alive;
|
||||
@ -2836,16 +2834,6 @@ procfs_files_info (struct target_ops *ignore)
|
||||
target_pid_to_str (inferior_ptid));
|
||||
}
|
||||
|
||||
/* Stop the child process asynchronously, as when the gdb user types
|
||||
control-c or presses a "stop" button. Works by sending
|
||||
kill(SIGINT) to the child's process group. */
|
||||
|
||||
static void
|
||||
procfs_interrupt (struct target_ops *self, ptid_t ptid)
|
||||
{
|
||||
kill (-inferior_process_group (), SIGINT);
|
||||
}
|
||||
|
||||
/* Make it die. Wait for it to die. Clean up after it. Note: this
|
||||
should only be applied to the real process, not to an LWP, because
|
||||
of the check for parent-process. If we need this to work for an
|
||||
|
@ -4913,7 +4913,7 @@ remote_serial_quit_handler (void)
|
||||
/* All-stop protocol, and blocked waiting for stop reply. Send
|
||||
an interrupt request. */
|
||||
else if (!target_terminal::is_ours () && rs->waiting_for_stop_reply)
|
||||
target_interrupt (inferior_ptid);
|
||||
target_interrupt ();
|
||||
else
|
||||
rs->got_ctrlc_during_io = 1;
|
||||
}
|
||||
@ -6164,7 +6164,7 @@ remote_stop (struct target_ops *self, ptid_t ptid)
|
||||
/* Implement the to_interrupt function for the remote targets. */
|
||||
|
||||
static void
|
||||
remote_interrupt (struct target_ops *self, ptid_t ptid)
|
||||
remote_interrupt (struct target_ops *self)
|
||||
{
|
||||
if (remote_debug)
|
||||
fprintf_unfiltered (gdb_stdlog, "remote_interrupt called\n");
|
||||
@ -6193,7 +6193,7 @@ remote_pass_ctrlc (struct target_ops *self)
|
||||
else if (rs->ctrlc_pending_p)
|
||||
interrupt_query ();
|
||||
else
|
||||
target_interrupt (inferior_ptid);
|
||||
target_interrupt ();
|
||||
}
|
||||
|
||||
/* Ask the user what to do when an interrupt is received. */
|
||||
|
@ -921,6 +921,28 @@ debug_terminal_inferior (struct target_ops *self)
|
||||
fputs_unfiltered (")\n", gdb_stdlog);
|
||||
}
|
||||
|
||||
static void
|
||||
delegate_terminal_save_inferior (struct target_ops *self)
|
||||
{
|
||||
self = self->beneath;
|
||||
self->to_terminal_save_inferior (self);
|
||||
}
|
||||
|
||||
static void
|
||||
tdefault_terminal_save_inferior (struct target_ops *self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
debug_terminal_save_inferior (struct target_ops *self)
|
||||
{
|
||||
fprintf_unfiltered (gdb_stdlog, "-> %s->to_terminal_save_inferior (...)\n", debug_target.to_shortname);
|
||||
debug_target.to_terminal_save_inferior (&debug_target);
|
||||
fprintf_unfiltered (gdb_stdlog, "<- %s->to_terminal_save_inferior (", debug_target.to_shortname);
|
||||
target_debug_print_struct_target_ops_p (&debug_target);
|
||||
fputs_unfiltered (")\n", gdb_stdlog);
|
||||
}
|
||||
|
||||
static void
|
||||
delegate_terminal_ours_for_output (struct target_ops *self)
|
||||
{
|
||||
@ -1639,26 +1661,24 @@ debug_stop (struct target_ops *self, ptid_t arg1)
|
||||
}
|
||||
|
||||
static void
|
||||
delegate_interrupt (struct target_ops *self, ptid_t arg1)
|
||||
delegate_interrupt (struct target_ops *self)
|
||||
{
|
||||
self = self->beneath;
|
||||
self->to_interrupt (self, arg1);
|
||||
self->to_interrupt (self);
|
||||
}
|
||||
|
||||
static void
|
||||
tdefault_interrupt (struct target_ops *self, ptid_t arg1)
|
||||
tdefault_interrupt (struct target_ops *self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
debug_interrupt (struct target_ops *self, ptid_t arg1)
|
||||
debug_interrupt (struct target_ops *self)
|
||||
{
|
||||
fprintf_unfiltered (gdb_stdlog, "-> %s->to_interrupt (...)\n", debug_target.to_shortname);
|
||||
debug_target.to_interrupt (&debug_target, arg1);
|
||||
debug_target.to_interrupt (&debug_target);
|
||||
fprintf_unfiltered (gdb_stdlog, "<- %s->to_interrupt (", debug_target.to_shortname);
|
||||
target_debug_print_struct_target_ops_p (&debug_target);
|
||||
fputs_unfiltered (", ", gdb_stdlog);
|
||||
target_debug_print_ptid_t (arg1);
|
||||
fputs_unfiltered (")\n", gdb_stdlog);
|
||||
}
|
||||
|
||||
@ -4248,6 +4268,8 @@ install_delegators (struct target_ops *ops)
|
||||
ops->to_terminal_init = delegate_terminal_init;
|
||||
if (ops->to_terminal_inferior == NULL)
|
||||
ops->to_terminal_inferior = delegate_terminal_inferior;
|
||||
if (ops->to_terminal_save_inferior == NULL)
|
||||
ops->to_terminal_save_inferior = delegate_terminal_save_inferior;
|
||||
if (ops->to_terminal_ours_for_output == NULL)
|
||||
ops->to_terminal_ours_for_output = delegate_terminal_ours_for_output;
|
||||
if (ops->to_terminal_ours == NULL)
|
||||
@ -4530,6 +4552,7 @@ install_dummy_methods (struct target_ops *ops)
|
||||
ops->to_can_do_single_step = tdefault_can_do_single_step;
|
||||
ops->to_terminal_init = tdefault_terminal_init;
|
||||
ops->to_terminal_inferior = tdefault_terminal_inferior;
|
||||
ops->to_terminal_save_inferior = tdefault_terminal_save_inferior;
|
||||
ops->to_terminal_ours_for_output = tdefault_terminal_ours_for_output;
|
||||
ops->to_terminal_ours = tdefault_terminal_ours;
|
||||
ops->to_terminal_info = default_terminal_info;
|
||||
@ -4690,6 +4713,7 @@ init_debug_target (struct target_ops *ops)
|
||||
ops->to_can_do_single_step = debug_can_do_single_step;
|
||||
ops->to_terminal_init = debug_terminal_init;
|
||||
ops->to_terminal_inferior = debug_terminal_inferior;
|
||||
ops->to_terminal_save_inferior = debug_terminal_save_inferior;
|
||||
ops->to_terminal_ours_for_output = debug_terminal_ours_for_output;
|
||||
ops->to_terminal_ours = debug_terminal_ours;
|
||||
ops->to_terminal_info = debug_terminal_info;
|
||||
|
121
gdb/target.c
121
gdb/target.c
@ -47,6 +47,7 @@
|
||||
#include "event-top.h"
|
||||
#include <algorithm>
|
||||
#include "byte-vector.h"
|
||||
#include "terminal.h"
|
||||
|
||||
static void generic_tls_error (void) ATTRIBUTE_NORETURN;
|
||||
|
||||
@ -431,8 +432,8 @@ target_load (const char *arg, int from_tty)
|
||||
|
||||
/* Define it. */
|
||||
|
||||
enum target_terminal::terminal_state target_terminal::terminal_state
|
||||
= target_terminal::terminal_is_ours;
|
||||
target_terminal_state target_terminal::m_terminal_state
|
||||
= target_terminal_state::is_ours;
|
||||
|
||||
/* See target/target.h. */
|
||||
|
||||
@ -441,7 +442,7 @@ target_terminal::init (void)
|
||||
{
|
||||
(*current_target.to_terminal_init) (¤t_target);
|
||||
|
||||
terminal_state = terminal_is_ours;
|
||||
m_terminal_state = target_terminal_state::is_ours;
|
||||
}
|
||||
|
||||
/* See target/target.h. */
|
||||
@ -463,13 +464,18 @@ target_terminal::inferior (void)
|
||||
if (ui != main_ui)
|
||||
return;
|
||||
|
||||
if (terminal_state == terminal_is_inferior)
|
||||
return;
|
||||
|
||||
/* If GDB is resuming the inferior in the foreground, install
|
||||
inferior's terminal modes. */
|
||||
|
||||
struct inferior *inf = current_inferior ();
|
||||
|
||||
if (inf->terminal_state != target_terminal_state::is_inferior)
|
||||
{
|
||||
(*current_target.to_terminal_inferior) (¤t_target);
|
||||
terminal_state = terminal_is_inferior;
|
||||
inf->terminal_state = target_terminal_state::is_inferior;
|
||||
}
|
||||
|
||||
m_terminal_state = target_terminal_state::is_inferior;
|
||||
|
||||
/* If the user hit C-c before, pretend that it was hit right
|
||||
here. */
|
||||
@ -479,6 +485,88 @@ target_terminal::inferior (void)
|
||||
|
||||
/* See target/target.h. */
|
||||
|
||||
void
|
||||
target_terminal::restore_inferior (void)
|
||||
{
|
||||
struct ui *ui = current_ui;
|
||||
|
||||
/* See target_terminal::inferior(). */
|
||||
if (ui->prompt_state != PROMPT_BLOCKED || ui != main_ui)
|
||||
return;
|
||||
|
||||
/* Restore the terminal settings of inferiors that were in the
|
||||
foreground but are now ours_for_output due to a temporary
|
||||
target_target::ours_for_output() call. */
|
||||
|
||||
{
|
||||
scoped_restore_current_inferior restore_inferior;
|
||||
struct inferior *inf;
|
||||
|
||||
ALL_INFERIORS (inf)
|
||||
{
|
||||
if (inf->terminal_state == target_terminal_state::is_ours_for_output)
|
||||
{
|
||||
set_current_inferior (inf);
|
||||
(*current_target.to_terminal_inferior) (¤t_target);
|
||||
inf->terminal_state = target_terminal_state::is_inferior;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_terminal_state = target_terminal_state::is_inferior;
|
||||
|
||||
/* If the user hit C-c before, pretend that it was hit right
|
||||
here. */
|
||||
if (check_quit_flag ())
|
||||
target_pass_ctrlc ();
|
||||
}
|
||||
|
||||
/* Switch terminal state to DESIRED_STATE, either is_ours, or
|
||||
is_ours_for_output. */
|
||||
|
||||
static void
|
||||
target_terminal_is_ours_kind (target_terminal_state desired_state)
|
||||
{
|
||||
scoped_restore_current_inferior restore_inferior;
|
||||
struct inferior *inf;
|
||||
|
||||
/* Must do this in two passes. First, have all inferiors save the
|
||||
current terminal settings. Then, after all inferiors have add a
|
||||
chance to safely save the terminal settings, restore GDB's
|
||||
terminal settings. */
|
||||
|
||||
ALL_INFERIORS (inf)
|
||||
{
|
||||
if (inf->terminal_state == target_terminal_state::is_inferior)
|
||||
{
|
||||
set_current_inferior (inf);
|
||||
(*current_target.to_terminal_save_inferior) (¤t_target);
|
||||
}
|
||||
}
|
||||
|
||||
ALL_INFERIORS (inf)
|
||||
{
|
||||
/* Note we don't check is_inferior here like above because we
|
||||
need to handle 'is_ours_for_output -> is_ours' too. Careful
|
||||
to never transition from 'is_ours' to 'is_ours_for_output',
|
||||
though. */
|
||||
if (inf->terminal_state != target_terminal_state::is_ours
|
||||
&& inf->terminal_state != desired_state)
|
||||
{
|
||||
set_current_inferior (inf);
|
||||
if (desired_state == target_terminal_state::is_ours)
|
||||
(*current_target.to_terminal_ours) (¤t_target);
|
||||
else if (desired_state == target_terminal_state::is_ours_for_output)
|
||||
(*current_target.to_terminal_ours_for_output) (¤t_target);
|
||||
else
|
||||
gdb_assert_not_reached ("unhandled desired state");
|
||||
inf->terminal_state = desired_state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* See target/target.h. */
|
||||
|
||||
void
|
||||
target_terminal::ours ()
|
||||
{
|
||||
@ -488,11 +576,11 @@ target_terminal::ours ()
|
||||
if (ui != main_ui)
|
||||
return;
|
||||
|
||||
if (terminal_state == terminal_is_ours)
|
||||
if (m_terminal_state == target_terminal_state::is_ours)
|
||||
return;
|
||||
|
||||
(*current_target.to_terminal_ours) (¤t_target);
|
||||
terminal_state = terminal_is_ours;
|
||||
target_terminal_is_ours_kind (target_terminal_state::is_ours);
|
||||
m_terminal_state = target_terminal_state::is_ours;
|
||||
}
|
||||
|
||||
/* See target/target.h. */
|
||||
@ -506,10 +594,11 @@ target_terminal::ours_for_output ()
|
||||
if (ui != main_ui)
|
||||
return;
|
||||
|
||||
if (terminal_state != terminal_is_inferior)
|
||||
if (!target_terminal::is_inferior ())
|
||||
return;
|
||||
(*current_target.to_terminal_ours_for_output) (¤t_target);
|
||||
terminal_state = terminal_is_ours_for_output;
|
||||
|
||||
target_terminal_is_ours_kind (target_terminal_state::is_ours_for_output);
|
||||
target_terminal::m_terminal_state = target_terminal_state::is_ours_for_output;
|
||||
}
|
||||
|
||||
/* See target/target.h. */
|
||||
@ -3332,7 +3421,7 @@ target_stop (ptid_t ptid)
|
||||
}
|
||||
|
||||
void
|
||||
target_interrupt (ptid_t ptid)
|
||||
target_interrupt ()
|
||||
{
|
||||
if (!may_stop)
|
||||
{
|
||||
@ -3340,7 +3429,7 @@ target_interrupt (ptid_t ptid)
|
||||
return;
|
||||
}
|
||||
|
||||
(*current_target.to_interrupt) (¤t_target, ptid);
|
||||
(*current_target.to_interrupt) (¤t_target);
|
||||
}
|
||||
|
||||
/* See target.h. */
|
||||
@ -3356,7 +3445,7 @@ target_pass_ctrlc (void)
|
||||
void
|
||||
default_target_pass_ctrlc (struct target_ops *ops)
|
||||
{
|
||||
target_interrupt (inferior_ptid);
|
||||
target_interrupt ();
|
||||
}
|
||||
|
||||
/* See target/target.h. */
|
||||
|
20
gdb/target.h
20
gdb/target.h
@ -561,6 +561,8 @@ struct target_ops
|
||||
TARGET_DEFAULT_IGNORE ();
|
||||
void (*to_terminal_inferior) (struct target_ops *)
|
||||
TARGET_DEFAULT_IGNORE ();
|
||||
void (*to_terminal_save_inferior) (struct target_ops *)
|
||||
TARGET_DEFAULT_IGNORE ();
|
||||
void (*to_terminal_ours_for_output) (struct target_ops *)
|
||||
TARGET_DEFAULT_IGNORE ();
|
||||
void (*to_terminal_ours) (struct target_ops *)
|
||||
@ -640,7 +642,7 @@ struct target_ops
|
||||
TARGET_DEFAULT_RETURN (NULL);
|
||||
void (*to_stop) (struct target_ops *, ptid_t)
|
||||
TARGET_DEFAULT_IGNORE ();
|
||||
void (*to_interrupt) (struct target_ops *, ptid_t)
|
||||
void (*to_interrupt) (struct target_ops *)
|
||||
TARGET_DEFAULT_IGNORE ();
|
||||
void (*to_pass_ctrlc) (struct target_ops *)
|
||||
TARGET_DEFAULT_FUNC (default_target_pass_ctrlc);
|
||||
@ -1690,16 +1692,18 @@ extern void target_update_thread_list (void);
|
||||
|
||||
extern void target_stop (ptid_t ptid);
|
||||
|
||||
/* Interrupt the target just like the user typed a ^C on the
|
||||
inferior's controlling terminal. (For instance, under Unix, this
|
||||
should act like SIGINT). This function is asynchronous. */
|
||||
/* Interrupt the target. Unlike target_stop, this does not specify
|
||||
which thread/process reports the stop. For most target this acts
|
||||
like raising a SIGINT, though that's not absolutely required. This
|
||||
function is asynchronous. */
|
||||
|
||||
extern void target_interrupt (ptid_t ptid);
|
||||
extern void target_interrupt ();
|
||||
|
||||
/* Pass a ^C, as determined to have been pressed by checking the quit
|
||||
flag, to the target. Normally calls target_interrupt, but remote
|
||||
targets may take the opportunity to detect the remote side is not
|
||||
responding and offer to disconnect. */
|
||||
flag, to the target, as if the user had typed the ^C on the
|
||||
inferior's controlling terminal while the inferior was in the
|
||||
foreground. Remote targets may take the opportunity to detect the
|
||||
remote side is not responding and offer to disconnect. */
|
||||
|
||||
extern void target_pass_ctrlc (void);
|
||||
|
||||
|
@ -95,6 +95,21 @@ extern void target_mourn_inferior (ptid_t ptid);
|
||||
|
||||
extern int target_supports_multi_process (void);
|
||||
|
||||
/* Possible terminal states. */
|
||||
|
||||
enum class target_terminal_state
|
||||
{
|
||||
/* The inferior's terminal settings are in effect. */
|
||||
is_inferior = 0,
|
||||
|
||||
/* Some of our terminal settings are in effect, enough to get
|
||||
proper output. */
|
||||
is_ours_for_output = 1,
|
||||
|
||||
/* Our terminal settings are in effect, for output and input. */
|
||||
is_ours = 2
|
||||
};
|
||||
|
||||
/* Represents the state of the target terminal. */
|
||||
class target_terminal
|
||||
{
|
||||
@ -108,9 +123,9 @@ public:
|
||||
before we actually run the inferior. */
|
||||
static void init ();
|
||||
|
||||
/* Put the inferior's terminal settings into effect. This is
|
||||
preparation for starting or resuming the inferior. This is a no-op
|
||||
unless called with the main UI as current UI. */
|
||||
/* Put the current inferior's terminal settings into effect. This
|
||||
is preparation for starting or resuming the inferior. This is a
|
||||
no-op unless called with the main UI as current UI. */
|
||||
static void inferior ();
|
||||
|
||||
/* Put our terminal settings into effect. First record the inferior's
|
||||
@ -125,40 +140,34 @@ public:
|
||||
UI as current UI. */
|
||||
static void ours_for_output ();
|
||||
|
||||
/* Restore terminal settings of inferiors that are in
|
||||
is_ours_for_output state back to "inferior". Used when we need
|
||||
to temporarily switch to is_ours_for_output state. */
|
||||
static void restore_inferior ();
|
||||
|
||||
/* Returns true if the terminal settings of the inferior are in
|
||||
effect. */
|
||||
static bool is_inferior ()
|
||||
{
|
||||
return terminal_state == terminal_is_inferior;
|
||||
return m_terminal_state == target_terminal_state::is_inferior;
|
||||
}
|
||||
|
||||
/* Returns true if our terminal settings are in effect. */
|
||||
static bool is_ours ()
|
||||
{
|
||||
return terminal_state == terminal_is_ours;
|
||||
return m_terminal_state == target_terminal_state::is_ours;
|
||||
}
|
||||
|
||||
/* Returns true if our terminal settings are in effect. */
|
||||
static bool is_ours_for_output ()
|
||||
{
|
||||
return m_terminal_state == target_terminal_state::is_ours_for_output;
|
||||
}
|
||||
|
||||
/* Print useful information about our terminal status, if such a thing
|
||||
exists. */
|
||||
static void info (const char *arg, int from_tty);
|
||||
|
||||
private:
|
||||
|
||||
/* Possible terminal states. */
|
||||
|
||||
enum terminal_state
|
||||
{
|
||||
/* The inferior's terminal settings are in effect. */
|
||||
terminal_is_inferior = 0,
|
||||
|
||||
/* Some of our terminal settings are in effect, enough to get
|
||||
proper output. */
|
||||
terminal_is_ours_for_output = 1,
|
||||
|
||||
/* Our terminal settings are in effect, for output and input. */
|
||||
terminal_is_ours = 2
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
/* A class that restores the state of the terminal to the current
|
||||
@ -168,7 +177,7 @@ public:
|
||||
public:
|
||||
|
||||
scoped_restore_terminal_state ()
|
||||
: m_state (terminal_state)
|
||||
: m_state (m_terminal_state)
|
||||
{
|
||||
}
|
||||
|
||||
@ -176,14 +185,14 @@ public:
|
||||
{
|
||||
switch (m_state)
|
||||
{
|
||||
case terminal_is_ours:
|
||||
case target_terminal_state::is_ours:
|
||||
ours ();
|
||||
break;
|
||||
case terminal_is_ours_for_output:
|
||||
case target_terminal_state::is_ours_for_output:
|
||||
ours_for_output ();
|
||||
break;
|
||||
case terminal_is_inferior:
|
||||
inferior ();
|
||||
case target_terminal_state::is_inferior:
|
||||
restore_inferior ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -192,12 +201,12 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
target_terminal::terminal_state m_state;
|
||||
target_terminal_state m_state;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
static terminal_state terminal_state;
|
||||
static target_terminal_state m_terminal_state;
|
||||
};
|
||||
|
||||
#endif /* TARGET_COMMON_H */
|
||||
|
@ -1,3 +1,11 @@
|
||||
2018-01-30 Pedro Alves <palves@redhat.com>
|
||||
|
||||
PR gdb/13211
|
||||
* gdb.base/interrupt-daemon.c: New.
|
||||
* gdb.base/interrupt-daemon.exp: New.
|
||||
* gdb.multi/multi-term-settings.c: New.
|
||||
* gdb.multi/multi-term-settings.exp: New.
|
||||
|
||||
2018-01-30 Joel Brobecker <brobecker@adacore.com>
|
||||
|
||||
* gdb.base/break.exp: Save the location where the breakpoint
|
||||
|
67
gdb/testsuite/gdb.base/interrupt-daemon.c
Normal file
67
gdb/testsuite/gdb.base/interrupt-daemon.c
Normal file
@ -0,0 +1,67 @@
|
||||
/* This testcase is part of GDB, the GNU debugger.
|
||||
|
||||
Copyright 2017 Free Software Foundation, Inc.
|
||||
|
||||
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 <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
static void
|
||||
daemon_main (void)
|
||||
{
|
||||
}
|
||||
|
||||
int
|
||||
main ()
|
||||
{
|
||||
pid_t child;
|
||||
|
||||
alarm (60);
|
||||
|
||||
/* Normally we're a progress group leader at this point, so can't
|
||||
create a session. Fork so the child can create a new
|
||||
session. */
|
||||
child = fork ();
|
||||
if (child == -1)
|
||||
return 1;
|
||||
else if (child != 0)
|
||||
return 0;
|
||||
else
|
||||
{
|
||||
/* In child. Switch to a new session. */
|
||||
pid_t session = setsid ();
|
||||
if (session == -1)
|
||||
return 1;
|
||||
|
||||
/* Fork again, so that the grand child (what we want to debug)
|
||||
can't accidentally acquire a controlling terminal, because
|
||||
it's not a session leader. We're not opening any file here,
|
||||
but this is representative of what daemons do. */
|
||||
child = fork ();
|
||||
if (child == -1)
|
||||
return 1;
|
||||
else if (child != 0)
|
||||
return 0;
|
||||
|
||||
/* In grandchild. */
|
||||
daemon_main ();
|
||||
|
||||
while (1)
|
||||
sleep (1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
89
gdb/testsuite/gdb.base/interrupt-daemon.exp
Normal file
89
gdb/testsuite/gdb.base/interrupt-daemon.exp
Normal file
@ -0,0 +1,89 @@
|
||||
# Copyright 2017 Free Software Foundation, Inc.
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
# Make sure that we can interrupt an inferior that forks and moves to
|
||||
# its own session.
|
||||
|
||||
standard_testfile
|
||||
|
||||
if {[build_executable "failed to build" $testfile $srcfile {debug}]} {
|
||||
return -1
|
||||
}
|
||||
|
||||
# The test proper.
|
||||
|
||||
proc do_test {} {
|
||||
global binfile
|
||||
global gdb_prompt
|
||||
|
||||
clean_restart $binfile
|
||||
|
||||
gdb_test "set follow-fork-mode child" ".*"
|
||||
|
||||
if ![runto "daemon_main"] {
|
||||
fail "can't run to daemon_main function"
|
||||
return
|
||||
}
|
||||
|
||||
with_test_prefix "fg" {
|
||||
global gdb_prompt
|
||||
|
||||
set test "continue"
|
||||
gdb_test_multiple $test $test {
|
||||
-re "Continuing" {
|
||||
pass $test
|
||||
}
|
||||
}
|
||||
|
||||
after 200
|
||||
|
||||
send_gdb "\003"
|
||||
|
||||
set test "ctrl-c stops process"
|
||||
gdb_test_multiple "" $test {
|
||||
-re "received signal SIGINT.*\r\n$gdb_prompt $" {
|
||||
pass $test
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
with_test_prefix "bg" {
|
||||
|
||||
set test "continue&"
|
||||
gdb_test_multiple "continue&" $test {
|
||||
-re "Continuing\\.\r\n$gdb_prompt " {
|
||||
pass $test
|
||||
}
|
||||
}
|
||||
|
||||
after 200
|
||||
|
||||
set test "interrupt"
|
||||
gdb_test_multiple $test $test {
|
||||
-re "$gdb_prompt " {
|
||||
pass $test
|
||||
}
|
||||
}
|
||||
|
||||
set test "interrupt cmd stops process"
|
||||
gdb_test_multiple "" $test {
|
||||
-re "received signal SIGINT" {
|
||||
pass $test
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do_test
|
52
gdb/testsuite/gdb.multi/multi-term-settings.c
Normal file
52
gdb/testsuite/gdb.multi/multi-term-settings.c
Normal file
@ -0,0 +1,52 @@
|
||||
/* This testcase is part of GDB, the GNU debugger.
|
||||
|
||||
Copyright 2017 Free Software Foundation, Inc.
|
||||
|
||||
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/>. */
|
||||
|
||||
/* This program is intended to be started outside of gdb, and then
|
||||
attached to by gdb. It loops for a while, but not forever. */
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
|
||||
int
|
||||
main ()
|
||||
{
|
||||
/* In case we inherit SIG_IGN. */
|
||||
signal (SIGTTOU, SIG_DFL);
|
||||
|
||||
alarm (30);
|
||||
|
||||
int count = 0;
|
||||
while (1)
|
||||
{
|
||||
struct termios termios;
|
||||
|
||||
printf ("pid=%ld, count=%d\n", (long) getpid (), count++);
|
||||
|
||||
/* This generates a SIGTTOU if our progress group is not in the
|
||||
foreground. */
|
||||
tcgetattr (0, &termios);
|
||||
tcsetattr (0, TCSANOW, &termios);
|
||||
|
||||
usleep (100000);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
242
gdb/testsuite/gdb.multi/multi-term-settings.exp
Normal file
242
gdb/testsuite/gdb.multi/multi-term-settings.exp
Normal file
@ -0,0 +1,242 @@
|
||||
# This testcase is part of GDB, the GNU debugger.
|
||||
|
||||
# Copyright 2017 Free Software Foundation, Inc.
|
||||
|
||||
# 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/>.
|
||||
|
||||
# This testcase exercises GDB's terminal ownership management
|
||||
# (terminal_ours/terminal_inferior, etc.) when debugging multiple
|
||||
# inferiors. It tests debugging multiple inferiors started with
|
||||
# different combinations of 'run'/'run+tty'/'attach', and ensures that
|
||||
# the process that is sharing GDB's terminal/session is properly made
|
||||
# the GDB terminal's foregound process group (i.e., "given the
|
||||
# terminal").
|
||||
|
||||
standard_testfile
|
||||
|
||||
if ![can_spawn_for_attach] {
|
||||
return 0
|
||||
}
|
||||
|
||||
if [build_executable "failed to prepare" $testfile $srcfile {debug}] {
|
||||
return -1
|
||||
}
|
||||
|
||||
# Start the programs running and then wait for a bit, to be sure that
|
||||
# they can be attached to. We start these processes upfront in order
|
||||
# to reuse them for the different iterations. This makes the testcase
|
||||
# faster, compared to calling spawn_wait_for_attach for each iteration
|
||||
# in the testing matrix.
|
||||
|
||||
set spawn_id_list [spawn_wait_for_attach [list $binfile $binfile]]
|
||||
set attach_spawn_id1 [lindex $spawn_id_list 0]
|
||||
set attach_spawn_id2 [lindex $spawn_id_list 1]
|
||||
set attach_pid1 [spawn_id_get_pid $attach_spawn_id1]
|
||||
set attach_pid2 [spawn_id_get_pid $attach_spawn_id2]
|
||||
|
||||
# Create inferior WHICH_INF. INF_HOW is how to create it. This can
|
||||
# be one of:
|
||||
#
|
||||
# - "run" - for "run" command.
|
||||
# - "tty" - for "run" + "tty TTY".
|
||||
# - "attach" - for attaching to an existing process.
|
||||
proc create_inferior {which_inf inf_how} {
|
||||
global binfile
|
||||
|
||||
# Run to main and delete breakpoints.
|
||||
proc my_runto_main {} {
|
||||
if ![runto_main] {
|
||||
fail "run to main"
|
||||
return 0
|
||||
} else {
|
||||
# Delete breakpoints otherwise GDB would try to step over
|
||||
# the breakpoint at 'main' without resuming the other
|
||||
# inferior, possibly masking the issue we're trying to
|
||||
# test.
|
||||
delete_breakpoints
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
if {$inf_how == "run"} {
|
||||
if [my_runto_main] {
|
||||
global inferior_spawn_id
|
||||
return $inferior_spawn_id
|
||||
}
|
||||
} elseif {$inf_how == "tty"} {
|
||||
# Create the new PTY for the inferior.
|
||||
spawn -pty
|
||||
set inf_spawn_id $spawn_id
|
||||
set inf_tty_name $spawn_out(slave,name)
|
||||
gdb_test_no_output "tty $inf_tty_name" "tty TTY"
|
||||
|
||||
if [my_runto_main] {
|
||||
return $inf_spawn_id
|
||||
}
|
||||
} elseif {$inf_how == "attach"} {
|
||||
|
||||
# Reuse the inferiors spawned at the start of the testcase (to
|
||||
# avoid having to wait the delay imposed by
|
||||
# spawn_wait_for_attach).
|
||||
global attach_spawn_id$which_inf
|
||||
set test_spawn_id [set attach_spawn_id$which_inf]
|
||||
set testpid [spawn_id_get_pid $test_spawn_id]
|
||||
if {[gdb_test "attach $testpid" \
|
||||
"Attaching to program: .*, process $testpid.*(in|at).*" \
|
||||
"attach"] == 0} {
|
||||
return $test_spawn_id
|
||||
}
|
||||
} else {
|
||||
error "unhandled inf_how: $inf_how"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
# Print debug output.
|
||||
|
||||
proc send_debug {str} {
|
||||
# Uncomment for debugging.
|
||||
#send_user $str
|
||||
}
|
||||
|
||||
# The core of the testcase. See intro. Creates two inferiors that
|
||||
# both loop changing their terminal's settings and printing output,
|
||||
# and checks whether they receive SIGTTOU. INF1_HOW and INF2_HOW
|
||||
# indicate how inferiors 1 and 2 should be created, respectively. See
|
||||
# 'create_inferior' above for what arguments these parameters accept.
|
||||
|
||||
proc coretest {inf1_how inf2_how} {
|
||||
global binfile
|
||||
global inferior_spawn_id
|
||||
global gdb_spawn_id
|
||||
global decimal
|
||||
|
||||
clean_restart $binfile
|
||||
|
||||
set inf1_spawn_id [create_inferior 1 $inf1_how]
|
||||
|
||||
set num 2
|
||||
gdb_test "add-inferior" "Added inferior $num.*" \
|
||||
"add empty inferior $num"
|
||||
gdb_test "inferior $num" "Switching to inferior $num.*" \
|
||||
"switch to inferior $num"
|
||||
gdb_file_cmd ${binfile}
|
||||
|
||||
set inf2_spawn_id [create_inferior 2 $inf2_how]
|
||||
|
||||
gdb_test_no_output "set schedule-multiple on"
|
||||
|
||||
# "run" makes each inferior be a process group leader. When we
|
||||
# run both inferiors in GDB's terminal/session, only one can end
|
||||
# up as the terminal's foreground process group, so it's expected
|
||||
# that the other receives a SIGTTOU.
|
||||
set expect_ttou [expr {$inf1_how == "run" && $inf2_how == "run"}]
|
||||
|
||||
global gdb_prompt
|
||||
|
||||
set any "\[^\r\n\]*"
|
||||
|
||||
set pid1 -1
|
||||
set pid2 -1
|
||||
|
||||
set test "info inferiors"
|
||||
gdb_test_multiple $test $test {
|
||||
-re "1${any}process ($decimal)${any}\r\n" {
|
||||
set pid1 $expect_out(1,string)
|
||||
send_debug "pid1=$pid1\n"
|
||||
exp_continue
|
||||
}
|
||||
-re "2${any}process ($decimal)${any}\r\n" {
|
||||
set pid2 $expect_out(1,string)
|
||||
send_debug "pid2=$pid2\n"
|
||||
exp_continue
|
||||
}
|
||||
-re "$gdb_prompt $" {
|
||||
pass $test
|
||||
}
|
||||
}
|
||||
|
||||
# Helper for the gdb_test_multiple call below. Issues a PASS if
|
||||
# we've seen each inferior print at least 3 times, otherwise
|
||||
# continues waiting for inferior output.
|
||||
proc pass_or_exp_continue {} {
|
||||
uplevel 1 {
|
||||
if {$count1 >= 3 && $count2 >= 3} {
|
||||
if $expect_ttou {
|
||||
fail "$test (expected SIGTTOU)"
|
||||
} else {
|
||||
pass $test
|
||||
}
|
||||
} else {
|
||||
exp_continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set infs_spawn_ids [list $inf1_spawn_id $inf2_spawn_id]
|
||||
send_debug "infs_spawn_ids=$infs_spawn_ids\n"
|
||||
|
||||
set count1 0
|
||||
set count2 0
|
||||
|
||||
set test "continue"
|
||||
gdb_test_multiple $test $test {
|
||||
-i $infs_spawn_ids -re "pid=$pid1, count=" {
|
||||
incr count1
|
||||
pass_or_exp_continue
|
||||
}
|
||||
-i $infs_spawn_ids -re "pid=$pid2, count=" {
|
||||
incr count2
|
||||
pass_or_exp_continue
|
||||
}
|
||||
-i $gdb_spawn_id -re "received signal SIGTTOU.*$gdb_prompt " {
|
||||
if $expect_ttou {
|
||||
pass "$test (expected SIGTTOU)"
|
||||
} else {
|
||||
fail "$test (SIGTTOU)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
send_gdb "\003"
|
||||
if {$expect_ttou} {
|
||||
gdb_test "" "Quit" "stop with control-c"
|
||||
} else {
|
||||
gdb_test "" "received signal SIGINT.*" "stop with control-c"
|
||||
}
|
||||
|
||||
# Useful for debugging in case the Ctrl-C above fails.
|
||||
gdb_test "info inferiors"
|
||||
gdb_test "info threads"
|
||||
}
|
||||
|
||||
set how_modes {"run" "attach" "tty"}
|
||||
|
||||
foreach_with_prefix inf1_how $how_modes {
|
||||
foreach_with_prefix inf2_how $how_modes {
|
||||
if {($inf1_how == "tty" || $inf2_how == "tty")
|
||||
&& [target_info gdb_protocol] == "extended-remote"} {
|
||||
# No way to specify the inferior's tty in the remote
|
||||
# protocol.
|
||||
unsupported "no support for \"tty\" in the RSP"
|
||||
continue
|
||||
}
|
||||
|
||||
coretest $inf1_how $inf2_how
|
||||
}
|
||||
}
|
||||
|
||||
kill_wait_spawned_process $attach_spawn_id1
|
||||
kill_wait_spawned_process $attach_spawn_id2
|
Loading…
Reference in New Issue
Block a user