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>
|
2018-01-30 Pedro Alves <palves@redhat.com>
|
||||||
|
|
||||||
* linux-nat.c (wait_for_signal): New function.
|
* linux-nat.c (wait_for_signal): New function.
|
||||||
|
@ -192,6 +192,9 @@
|
|||||||
/* Define to 1 if you have the `getpagesize' function. */
|
/* Define to 1 if you have the `getpagesize' function. */
|
||||||
#undef HAVE_GETPAGESIZE
|
#undef HAVE_GETPAGESIZE
|
||||||
|
|
||||||
|
/* Define to 1 if you have the `getpgid' function. */
|
||||||
|
#undef HAVE_GETPGID
|
||||||
|
|
||||||
/* Define to 1 if you have the `getrlimit' function. */
|
/* Define to 1 if you have the `getrlimit' function. */
|
||||||
#undef HAVE_GETRLIMIT
|
#undef HAVE_GETRLIMIT
|
||||||
|
|
||||||
|
2
gdb/configure
vendored
2
gdb/configure
vendored
@ -13212,7 +13212,7 @@ fi
|
|||||||
|
|
||||||
for ac_func in getauxval getrusage getuid getgid \
|
for ac_func in getauxval getrusage getuid getgid \
|
||||||
pipe poll pread pread64 pwrite resize_term \
|
pipe poll pread pread64 pwrite resize_term \
|
||||||
sbrk setpgid setpgrp setsid \
|
sbrk getpgid setpgid setpgrp setsid \
|
||||||
sigaction sigprocmask sigsetmask socketpair \
|
sigaction sigprocmask sigsetmask socketpair \
|
||||||
ttrace wborder wresize setlocale iconvlist libiconvlist btowc \
|
ttrace wborder wresize setlocale iconvlist libiconvlist btowc \
|
||||||
setrlimit getrlimit posix_madvise waitpid \
|
setrlimit getrlimit posix_madvise waitpid \
|
||||||
|
@ -1368,7 +1368,7 @@ AC_FUNC_MMAP
|
|||||||
AC_FUNC_VFORK
|
AC_FUNC_VFORK
|
||||||
AC_CHECK_FUNCS([getauxval getrusage getuid getgid \
|
AC_CHECK_FUNCS([getauxval getrusage getuid getgid \
|
||||||
pipe poll pread pread64 pwrite resize_term \
|
pipe poll pread pread64 pwrite resize_term \
|
||||||
sbrk setpgid setpgrp setsid \
|
sbrk getpgid setpgid setpgrp setsid \
|
||||||
sigaction sigprocmask sigsetmask socketpair \
|
sigaction sigprocmask sigsetmask socketpair \
|
||||||
ttrace wborder wresize setlocale iconvlist libiconvlist btowc \
|
ttrace wborder wresize setlocale iconvlist libiconvlist btowc \
|
||||||
setrlimit getrlimit posix_madvise waitpid \
|
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>
|
2018-01-19 James Clarke <jrtc27@jrtc27.com>
|
||||||
|
|
||||||
* linux-low.c (handle_extended_wait): Surround call to
|
* linux-low.c (handle_extended_wait): Surround call to
|
||||||
|
@ -360,8 +360,8 @@ default_breakpoint_kind_from_pc (CORE_ADDR *pcptr)
|
|||||||
|
|
||||||
/* Define it. */
|
/* Define it. */
|
||||||
|
|
||||||
enum target_terminal::terminal_state target_terminal::terminal_state
|
target_terminal_state target_terminal::m_terminal_state
|
||||||
= target_terminal::terminal_is_ours;
|
= target_terminal_state::is_ours;
|
||||||
|
|
||||||
/* See target/target.h. */
|
/* 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
|
static int
|
||||||
go32_thread_alive (struct target_ops *ops, ptid_t ptid)
|
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_for_output = go32_terminal_ours;
|
||||||
t->to_terminal_ours = go32_terminal_ours;
|
t->to_terminal_ours = go32_terminal_ours;
|
||||||
t->to_terminal_info = go32_terminal_info;
|
t->to_terminal_info = go32_terminal_info;
|
||||||
|
t->to_pass_ctrlc = go32_pass_ctrlc;
|
||||||
t->to_kill = go32_kill_inferior;
|
t->to_kill = go32_kill_inferior;
|
||||||
t->to_create_inferior = go32_create_inferior;
|
t->to_create_inferior = go32_create_inferior;
|
||||||
t->to_mourn_inferior = go32_mourn_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_remove_breakpoint = memory_remove_breakpoint;
|
||||||
t->to_terminal_init = child_terminal_init;
|
t->to_terminal_init = child_terminal_init;
|
||||||
t->to_terminal_inferior = child_terminal_inferior;
|
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_for_output = child_terminal_ours_for_output;
|
||||||
t->to_terminal_ours = child_terminal_ours;
|
t->to_terminal_ours = child_terminal_ours;
|
||||||
t->to_terminal_info = child_terminal_info;
|
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_post_startup_inferior = inf_child_post_startup_inferior;
|
||||||
t->to_follow_fork = inf_child_follow_fork;
|
t->to_follow_fork = inf_child_follow_fork;
|
||||||
t->to_can_run = inf_child_can_run;
|
t->to_can_run = inf_child_can_run;
|
||||||
|
@ -294,19 +294,6 @@ inf_ptrace_kill (struct target_ops *ops)
|
|||||||
target_mourn_inferior (inferior_ptid);
|
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
|
/* Return which PID to pass to ptrace in order to observe/control the
|
||||||
tracee identified by PTID. */
|
tracee identified by PTID. */
|
||||||
|
|
||||||
@ -688,7 +675,6 @@ inf_ptrace_target (void)
|
|||||||
t->to_mourn_inferior = inf_ptrace_mourn_inferior;
|
t->to_mourn_inferior = inf_ptrace_mourn_inferior;
|
||||||
t->to_thread_alive = inf_ptrace_thread_alive;
|
t->to_thread_alive = inf_ptrace_thread_alive;
|
||||||
t->to_pid_to_str = inf_ptrace_pid_to_str;
|
t->to_pid_to_str = inf_ptrace_pid_to_str;
|
||||||
t->to_interrupt = inf_ptrace_interrupt;
|
|
||||||
t->to_xfer_partial = inf_ptrace_xfer_partial;
|
t->to_xfer_partial = inf_ptrace_xfer_partial;
|
||||||
#if defined (PT_IO) && defined (PIOD_READ_AUXV)
|
#if defined (PT_IO) && defined (PIOD_READ_AUXV)
|
||||||
t->to_auxv_parse = inf_ptrace_auxv_parse;
|
t->to_auxv_parse = inf_ptrace_auxv_parse;
|
||||||
|
@ -3033,7 +3033,7 @@ interrupt_target_1 (int all_threads)
|
|||||||
if (non_stop)
|
if (non_stop)
|
||||||
target_stop (ptid);
|
target_stop (ptid);
|
||||||
else
|
else
|
||||||
target_interrupt (ptid);
|
target_interrupt ();
|
||||||
|
|
||||||
/* Tag the thread as having been explicitly requested to stop, so
|
/* Tag the thread as having been explicitly requested to stop, so
|
||||||
other parts of gdb know not to resume this thread automatically,
|
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_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 (struct target_ops *self);
|
||||||
|
|
||||||
extern void child_terminal_init_with_pgrp (int pgrp);
|
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 */
|
/* From fork-child.c */
|
||||||
|
|
||||||
/* Helper function to call STARTUP_INFERIOR with PID and NUM_TRAPS.
|
/* 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. */
|
/* The name of terminal device to use for I/O. */
|
||||||
char *terminal = NULL;
|
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,
|
/* Environment to use for running inferior,
|
||||||
in format described in environ.h. */
|
in format described in environ.h. */
|
||||||
gdb_environ environment;
|
gdb_environ environment;
|
||||||
|
414
gdb/inflow.c
414
gdb/inflow.c
@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
static void pass_signal (int);
|
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. */
|
/* 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
|
/* Terminal related info we need to keep track of. Each inferior
|
||||||
holds an instance of this structure --- we save it whenever the
|
holds an instance of this structure --- we save it whenever the
|
||||||
corresponding inferior stops, and restore it to the foreground
|
corresponding inferior stops, and restore it to the terminal when
|
||||||
inferior when it resumes. */
|
the inferior is resumed in the foreground. */
|
||||||
struct terminal_info
|
struct terminal_info
|
||||||
{
|
{
|
||||||
/* The name of the tty (from the `tty' command) that we gave to the
|
/* The name of the tty (from the `tty' command) that we gave to the
|
||||||
@ -63,11 +63,23 @@ struct terminal_info
|
|||||||
char *run_terminal;
|
char *run_terminal;
|
||||||
|
|
||||||
/* TTY state. We save it whenever the inferior stops, and restore
|
/* 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;
|
serial_ttystate ttystate;
|
||||||
|
|
||||||
#ifdef HAVE_TERMIOS_H
|
#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;
|
pid_t process_group;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -120,17 +132,6 @@ private:
|
|||||||
#endif
|
#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
|
/* 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,
|
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
|
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. */
|
fork_inferior, while forking a new child. */
|
||||||
static const char *inferior_thisrun_terminal;
|
static const char *inferior_thisrun_terminal;
|
||||||
|
|
||||||
/* Nonzero if our terminal settings are in effect. Zero if the
|
/* Track who owns GDB's terminal (is it GDB or some inferior?). While
|
||||||
inferior's settings are in effect. Ignored if !gdb_has_a_terminal
|
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. */
|
/* See terminal.h. */
|
||||||
|
|
||||||
@ -196,29 +203,21 @@ gdb_has_a_terminal (void)
|
|||||||
void
|
void
|
||||||
child_terminal_init (struct target_ops *self)
|
child_terminal_init (struct target_ops *self)
|
||||||
{
|
{
|
||||||
struct inferior *inf = current_inferior ();
|
if (!gdb_has_a_terminal ())
|
||||||
struct terminal_info *tinfo = get_inflow_inferior_data (inf);
|
return;
|
||||||
|
|
||||||
|
inferior *inf = current_inferior ();
|
||||||
|
terminal_info *tinfo = get_inflow_inferior_data (inf);
|
||||||
|
|
||||||
#ifdef HAVE_TERMIOS_H
|
#ifdef HAVE_TERMIOS_H
|
||||||
/* Store the process group even without a terminal as it is used not
|
/* A child we spawn should be a process group leader (PGID==PID) at
|
||||||
only to reset the tty foreground process group, but also to
|
this point, though that may not be true if we're attaching to an
|
||||||
interrupt the inferior. A child we spawn should be a process
|
existing 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;
|
tinfo->process_group = inf->pid;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (gdb_has_a_terminal ())
|
xfree (tinfo->ttystate);
|
||||||
{
|
tinfo->ttystate = serial_copy_tty_state (stdin_serial, initial_gdb_ttystate);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Save the terminal settings again. This is necessary for the TUI
|
/* 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.
|
/* Try to determine whether TTY is GDB's input terminal. Returns
|
||||||
This is preparation for starting or resuming the inferior.
|
TRIBOOL_UNKNOWN if we can't tell. */
|
||||||
|
|
||||||
N.B. Targets that want to use this with async support must build that
|
static tribool
|
||||||
support on top of this (e.g., the caller still needs to remove stdin
|
is_gdb_terminal (const char *tty)
|
||||||
from the event loop). E.g., see linux_nat_terminal_inferior. */
|
{
|
||||||
|
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
|
void
|
||||||
child_terminal_inferior (struct target_ops *self)
|
child_terminal_inferior (struct target_ops *self)
|
||||||
{
|
{
|
||||||
struct inferior *inf;
|
/* If we resume more than one inferior in the foreground on GDB's
|
||||||
struct terminal_info *tinfo;
|
terminal, then the first inferior's terminal settings "win".
|
||||||
|
Note that every child process is put in its own process group, so
|
||||||
if (!terminal_is_ours)
|
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;
|
return;
|
||||||
|
|
||||||
inf = current_inferior ();
|
inferior *inf = current_inferior ();
|
||||||
tinfo = get_inflow_inferior_data (inf);
|
terminal_info *tinfo = get_inflow_inferior_data (inf);
|
||||||
|
|
||||||
if (gdb_has_a_terminal ()
|
if (gdb_has_a_terminal ()
|
||||||
&& tinfo->ttystate != NULL
|
&& tinfo->ttystate != NULL
|
||||||
&& tinfo->run_terminal == NULL)
|
&& sharing_input_terminal (inf))
|
||||||
{
|
{
|
||||||
int result;
|
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
|
#ifdef F_GETFL
|
||||||
result = fcntl (0, F_SETFL, tinfo->tflags);
|
result = fcntl (0, F_SETFL, tinfo->tflags);
|
||||||
OOPSY ("fcntl F_SETFL");
|
OOPSY ("fcntl F_SETFL");
|
||||||
@ -276,28 +381,40 @@ child_terminal_inferior (struct target_ops *self)
|
|||||||
#endif
|
#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)
|
if (job_control)
|
||||||
{
|
{
|
||||||
#ifdef HAVE_TERMIOS_H
|
#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);
|
result = tcsetpgrp (0, tinfo->process_group);
|
||||||
if (!inf->attach_flag)
|
#endif
|
||||||
OOPSY ("tcsetpgrp");
|
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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gdb_tty_state = target_terminal_state::is_inferior;
|
||||||
}
|
}
|
||||||
terminal_is_ours = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Put some of our terminal settings into effect,
|
/* Put some of our terminal settings into effect,
|
||||||
@ -314,7 +431,7 @@ child_terminal_inferior (struct target_ops *self)
|
|||||||
void
|
void
|
||||||
child_terminal_ours_for_output (struct target_ops *self)
|
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.
|
/* Put our terminal settings into effect.
|
||||||
@ -328,35 +445,50 @@ child_terminal_ours_for_output (struct target_ops *self)
|
|||||||
void
|
void
|
||||||
child_terminal_ours (struct target_ops *self)
|
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
|
/* Save the current terminal settings in the inferior's terminal_info
|
||||||
separate terminal_is_ours and terminal_is_ours_for_output
|
cache. */
|
||||||
flags. */
|
|
||||||
|
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
|
static void
|
||||||
child_terminal_ours_1 (int output_only)
|
child_terminal_ours_1 (target_terminal_state desired_state)
|
||||||
{
|
{
|
||||||
struct inferior *inf;
|
gdb_assert (desired_state != target_terminal_state::is_inferior);
|
||||||
struct terminal_info *tinfo;
|
|
||||||
|
|
||||||
if (terminal_is_ours)
|
/* Avoid attempting all the ioctl's when running in batch. */
|
||||||
|
if (!gdb_has_a_terminal ())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
terminal_is_ours = 1;
|
if (gdb_tty_state != desired_state)
|
||||||
|
|
||||||
/* 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
|
|
||||||
{
|
{
|
||||||
int result ATTRIBUTE_UNUSED;
|
int result ATTRIBUTE_UNUSED;
|
||||||
|
|
||||||
@ -364,21 +496,13 @@ child_terminal_ours_1 (int output_only)
|
|||||||
terminal's pgrp. */
|
terminal's pgrp. */
|
||||||
scoped_ignore_sigttou ignore_sigttou;
|
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. */
|
/* Set tty state to our_ttystate. */
|
||||||
serial_set_tty_state (stdin_serial, our_terminal_info.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
|
#ifdef HAVE_TERMIOS_H
|
||||||
result = tcsetpgrp (0, our_terminal_info.process_group);
|
result = tcsetpgrp (0, our_terminal_info.process_group);
|
||||||
@ -395,7 +519,7 @@ child_terminal_ours_1 (int output_only)
|
|||||||
#endif /* termios */
|
#endif /* termios */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!job_control)
|
if (!job_control && desired_state == target_terminal_state::is_ours)
|
||||||
{
|
{
|
||||||
signal (SIGINT, sigint_ours);
|
signal (SIGINT, sigint_ours);
|
||||||
#ifdef SIGQUIT
|
#ifdef SIGQUIT
|
||||||
@ -404,12 +528,94 @@ child_terminal_ours_1 (int output_only)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef F_GETFL
|
#ifdef F_GETFL
|
||||||
tinfo->tflags = fcntl (0, F_GETFL, 0);
|
|
||||||
result = fcntl (0, F_SETFL, our_terminal_info.tflags);
|
result = fcntl (0, F_SETFL, our_terminal_info.tflags);
|
||||||
#endif
|
#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. */
|
/* Per-inferior data key. */
|
||||||
static const struct inferior_data *inflow_inferior_data;
|
static const struct inferior_data *inflow_inferior_data;
|
||||||
|
|
||||||
@ -452,6 +658,8 @@ inflow_inferior_exit (struct inferior *inf)
|
|||||||
{
|
{
|
||||||
struct terminal_info *info;
|
struct terminal_info *info;
|
||||||
|
|
||||||
|
inf->terminal_state = target_terminal_state::is_ours;
|
||||||
|
|
||||||
info = (struct terminal_info *) inferior_data (inf, inflow_inferior_data);
|
info = (struct terminal_info *) inferior_data (inf, inflow_inferior_data);
|
||||||
if (info != NULL)
|
if (info != NULL)
|
||||||
{
|
{
|
||||||
@ -482,6 +690,8 @@ copy_terminal_info (struct inferior *to, struct inferior *from)
|
|||||||
if (tinfo_from->ttystate)
|
if (tinfo_from->ttystate)
|
||||||
tinfo_to->ttystate
|
tinfo_to->ttystate
|
||||||
= serial_copy_tty_state (stdin_serial, tinfo_from->ttystate);
|
= serial_copy_tty_state (stdin_serial, tinfo_from->ttystate);
|
||||||
|
|
||||||
|
to->terminal_state = from->terminal_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -770,8 +980,6 @@ _initialize_inflow (void)
|
|||||||
add_info ("terminal", info_terminal_command,
|
add_info ("terminal", info_terminal_command,
|
||||||
_("Print inferior's saved terminal status."));
|
_("Print inferior's saved terminal status."));
|
||||||
|
|
||||||
terminal_is_ours = 1;
|
|
||||||
|
|
||||||
/* OK, figure out whether we have job control. */
|
/* OK, figure out whether we have job control. */
|
||||||
have_job_control ();
|
have_job_control ();
|
||||||
|
|
||||||
|
@ -22,9 +22,4 @@
|
|||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
/* Process group of the current inferior. */
|
|
||||||
#ifdef HAVE_TERMIOS_H
|
|
||||||
extern pid_t inferior_process_group (void);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* inflow.h */
|
#endif /* inflow.h */
|
||||||
|
@ -732,7 +732,7 @@ nto_handle_sigint (int signo)
|
|||||||
/* If this doesn't work, try more severe steps. */
|
/* If this doesn't work, try more severe steps. */
|
||||||
signal (signo, nto_handle_sigint_twice);
|
signal (signo, nto_handle_sigint_twice);
|
||||||
|
|
||||||
target_interrupt (inferior_ptid);
|
target_interrupt ();
|
||||||
}
|
}
|
||||||
|
|
||||||
static ptid_t
|
static ptid_t
|
||||||
@ -1275,7 +1275,7 @@ procfs_create_inferior (struct target_ops *ops, const char *exec_file,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
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);
|
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_detach (struct target_ops *, const char *, int);
|
||||||
static void procfs_resume (struct target_ops *,
|
static void procfs_resume (struct target_ops *,
|
||||||
ptid_t, int, enum gdb_signal);
|
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_files_info (struct target_ops *);
|
||||||
static void procfs_fetch_registers (struct target_ops *,
|
static void procfs_fetch_registers (struct target_ops *,
|
||||||
struct regcache *, int);
|
struct regcache *, int);
|
||||||
@ -172,7 +171,6 @@ procfs_target (void)
|
|||||||
t->to_xfer_partial = procfs_xfer_partial;
|
t->to_xfer_partial = procfs_xfer_partial;
|
||||||
t->to_pass_signals = procfs_pass_signals;
|
t->to_pass_signals = procfs_pass_signals;
|
||||||
t->to_files_info = procfs_files_info;
|
t->to_files_info = procfs_files_info;
|
||||||
t->to_interrupt = procfs_interrupt;
|
|
||||||
|
|
||||||
t->to_update_thread_list = procfs_update_thread_list;
|
t->to_update_thread_list = procfs_update_thread_list;
|
||||||
t->to_thread_alive = procfs_thread_alive;
|
t->to_thread_alive = procfs_thread_alive;
|
||||||
@ -2836,16 +2834,6 @@ procfs_files_info (struct target_ops *ignore)
|
|||||||
target_pid_to_str (inferior_ptid));
|
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
|
/* 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
|
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
|
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
|
/* All-stop protocol, and blocked waiting for stop reply. Send
|
||||||
an interrupt request. */
|
an interrupt request. */
|
||||||
else if (!target_terminal::is_ours () && rs->waiting_for_stop_reply)
|
else if (!target_terminal::is_ours () && rs->waiting_for_stop_reply)
|
||||||
target_interrupt (inferior_ptid);
|
target_interrupt ();
|
||||||
else
|
else
|
||||||
rs->got_ctrlc_during_io = 1;
|
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. */
|
/* Implement the to_interrupt function for the remote targets. */
|
||||||
|
|
||||||
static void
|
static void
|
||||||
remote_interrupt (struct target_ops *self, ptid_t ptid)
|
remote_interrupt (struct target_ops *self)
|
||||||
{
|
{
|
||||||
if (remote_debug)
|
if (remote_debug)
|
||||||
fprintf_unfiltered (gdb_stdlog, "remote_interrupt called\n");
|
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)
|
else if (rs->ctrlc_pending_p)
|
||||||
interrupt_query ();
|
interrupt_query ();
|
||||||
else
|
else
|
||||||
target_interrupt (inferior_ptid);
|
target_interrupt ();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ask the user what to do when an interrupt is received. */
|
/* 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);
|
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
|
static void
|
||||||
delegate_terminal_ours_for_output (struct target_ops *self)
|
delegate_terminal_ours_for_output (struct target_ops *self)
|
||||||
{
|
{
|
||||||
@ -1639,26 +1661,24 @@ debug_stop (struct target_ops *self, ptid_t arg1)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
delegate_interrupt (struct target_ops *self, ptid_t arg1)
|
delegate_interrupt (struct target_ops *self)
|
||||||
{
|
{
|
||||||
self = self->beneath;
|
self = self->beneath;
|
||||||
self->to_interrupt (self, arg1);
|
self->to_interrupt (self);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
tdefault_interrupt (struct target_ops *self, ptid_t arg1)
|
tdefault_interrupt (struct target_ops *self)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
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);
|
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);
|
fprintf_unfiltered (gdb_stdlog, "<- %s->to_interrupt (", debug_target.to_shortname);
|
||||||
target_debug_print_struct_target_ops_p (&debug_target);
|
target_debug_print_struct_target_ops_p (&debug_target);
|
||||||
fputs_unfiltered (", ", gdb_stdlog);
|
|
||||||
target_debug_print_ptid_t (arg1);
|
|
||||||
fputs_unfiltered (")\n", gdb_stdlog);
|
fputs_unfiltered (")\n", gdb_stdlog);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4248,6 +4268,8 @@ install_delegators (struct target_ops *ops)
|
|||||||
ops->to_terminal_init = delegate_terminal_init;
|
ops->to_terminal_init = delegate_terminal_init;
|
||||||
if (ops->to_terminal_inferior == NULL)
|
if (ops->to_terminal_inferior == NULL)
|
||||||
ops->to_terminal_inferior = delegate_terminal_inferior;
|
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)
|
if (ops->to_terminal_ours_for_output == NULL)
|
||||||
ops->to_terminal_ours_for_output = delegate_terminal_ours_for_output;
|
ops->to_terminal_ours_for_output = delegate_terminal_ours_for_output;
|
||||||
if (ops->to_terminal_ours == NULL)
|
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_can_do_single_step = tdefault_can_do_single_step;
|
||||||
ops->to_terminal_init = tdefault_terminal_init;
|
ops->to_terminal_init = tdefault_terminal_init;
|
||||||
ops->to_terminal_inferior = tdefault_terminal_inferior;
|
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_for_output = tdefault_terminal_ours_for_output;
|
||||||
ops->to_terminal_ours = tdefault_terminal_ours;
|
ops->to_terminal_ours = tdefault_terminal_ours;
|
||||||
ops->to_terminal_info = default_terminal_info;
|
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_can_do_single_step = debug_can_do_single_step;
|
||||||
ops->to_terminal_init = debug_terminal_init;
|
ops->to_terminal_init = debug_terminal_init;
|
||||||
ops->to_terminal_inferior = debug_terminal_inferior;
|
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_for_output = debug_terminal_ours_for_output;
|
||||||
ops->to_terminal_ours = debug_terminal_ours;
|
ops->to_terminal_ours = debug_terminal_ours;
|
||||||
ops->to_terminal_info = debug_terminal_info;
|
ops->to_terminal_info = debug_terminal_info;
|
||||||
|
123
gdb/target.c
123
gdb/target.c
@ -47,6 +47,7 @@
|
|||||||
#include "event-top.h"
|
#include "event-top.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include "byte-vector.h"
|
#include "byte-vector.h"
|
||||||
|
#include "terminal.h"
|
||||||
|
|
||||||
static void generic_tls_error (void) ATTRIBUTE_NORETURN;
|
static void generic_tls_error (void) ATTRIBUTE_NORETURN;
|
||||||
|
|
||||||
@ -431,8 +432,8 @@ target_load (const char *arg, int from_tty)
|
|||||||
|
|
||||||
/* Define it. */
|
/* Define it. */
|
||||||
|
|
||||||
enum target_terminal::terminal_state target_terminal::terminal_state
|
target_terminal_state target_terminal::m_terminal_state
|
||||||
= target_terminal::terminal_is_ours;
|
= target_terminal_state::is_ours;
|
||||||
|
|
||||||
/* See target/target.h. */
|
/* See target/target.h. */
|
||||||
|
|
||||||
@ -441,7 +442,7 @@ target_terminal::init (void)
|
|||||||
{
|
{
|
||||||
(*current_target.to_terminal_init) (¤t_target);
|
(*current_target.to_terminal_init) (¤t_target);
|
||||||
|
|
||||||
terminal_state = terminal_is_ours;
|
m_terminal_state = target_terminal_state::is_ours;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* See target/target.h. */
|
/* See target/target.h. */
|
||||||
@ -463,13 +464,18 @@ target_terminal::inferior (void)
|
|||||||
if (ui != main_ui)
|
if (ui != main_ui)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (terminal_state == terminal_is_inferior)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* If GDB is resuming the inferior in the foreground, install
|
/* If GDB is resuming the inferior in the foreground, install
|
||||||
inferior's terminal modes. */
|
inferior's terminal modes. */
|
||||||
(*current_target.to_terminal_inferior) (¤t_target);
|
|
||||||
terminal_state = terminal_is_inferior;
|
struct inferior *inf = current_inferior ();
|
||||||
|
|
||||||
|
if (inf->terminal_state != target_terminal_state::is_inferior)
|
||||||
|
{
|
||||||
|
(*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
|
/* If the user hit C-c before, pretend that it was hit right
|
||||||
here. */
|
here. */
|
||||||
@ -479,6 +485,88 @@ target_terminal::inferior (void)
|
|||||||
|
|
||||||
/* See target/target.h. */
|
/* 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
|
void
|
||||||
target_terminal::ours ()
|
target_terminal::ours ()
|
||||||
{
|
{
|
||||||
@ -488,11 +576,11 @@ target_terminal::ours ()
|
|||||||
if (ui != main_ui)
|
if (ui != main_ui)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (terminal_state == terminal_is_ours)
|
if (m_terminal_state == target_terminal_state::is_ours)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
(*current_target.to_terminal_ours) (¤t_target);
|
target_terminal_is_ours_kind (target_terminal_state::is_ours);
|
||||||
terminal_state = terminal_is_ours;
|
m_terminal_state = target_terminal_state::is_ours;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* See target/target.h. */
|
/* See target/target.h. */
|
||||||
@ -506,10 +594,11 @@ target_terminal::ours_for_output ()
|
|||||||
if (ui != main_ui)
|
if (ui != main_ui)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (terminal_state != terminal_is_inferior)
|
if (!target_terminal::is_inferior ())
|
||||||
return;
|
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. */
|
/* See target/target.h. */
|
||||||
@ -3332,7 +3421,7 @@ target_stop (ptid_t ptid)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
target_interrupt (ptid_t ptid)
|
target_interrupt ()
|
||||||
{
|
{
|
||||||
if (!may_stop)
|
if (!may_stop)
|
||||||
{
|
{
|
||||||
@ -3340,7 +3429,7 @@ target_interrupt (ptid_t ptid)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(*current_target.to_interrupt) (¤t_target, ptid);
|
(*current_target.to_interrupt) (¤t_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* See target.h. */
|
/* See target.h. */
|
||||||
@ -3356,7 +3445,7 @@ target_pass_ctrlc (void)
|
|||||||
void
|
void
|
||||||
default_target_pass_ctrlc (struct target_ops *ops)
|
default_target_pass_ctrlc (struct target_ops *ops)
|
||||||
{
|
{
|
||||||
target_interrupt (inferior_ptid);
|
target_interrupt ();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* See target/target.h. */
|
/* See target/target.h. */
|
||||||
|
20
gdb/target.h
20
gdb/target.h
@ -561,6 +561,8 @@ struct target_ops
|
|||||||
TARGET_DEFAULT_IGNORE ();
|
TARGET_DEFAULT_IGNORE ();
|
||||||
void (*to_terminal_inferior) (struct target_ops *)
|
void (*to_terminal_inferior) (struct target_ops *)
|
||||||
TARGET_DEFAULT_IGNORE ();
|
TARGET_DEFAULT_IGNORE ();
|
||||||
|
void (*to_terminal_save_inferior) (struct target_ops *)
|
||||||
|
TARGET_DEFAULT_IGNORE ();
|
||||||
void (*to_terminal_ours_for_output) (struct target_ops *)
|
void (*to_terminal_ours_for_output) (struct target_ops *)
|
||||||
TARGET_DEFAULT_IGNORE ();
|
TARGET_DEFAULT_IGNORE ();
|
||||||
void (*to_terminal_ours) (struct target_ops *)
|
void (*to_terminal_ours) (struct target_ops *)
|
||||||
@ -640,7 +642,7 @@ struct target_ops
|
|||||||
TARGET_DEFAULT_RETURN (NULL);
|
TARGET_DEFAULT_RETURN (NULL);
|
||||||
void (*to_stop) (struct target_ops *, ptid_t)
|
void (*to_stop) (struct target_ops *, ptid_t)
|
||||||
TARGET_DEFAULT_IGNORE ();
|
TARGET_DEFAULT_IGNORE ();
|
||||||
void (*to_interrupt) (struct target_ops *, ptid_t)
|
void (*to_interrupt) (struct target_ops *)
|
||||||
TARGET_DEFAULT_IGNORE ();
|
TARGET_DEFAULT_IGNORE ();
|
||||||
void (*to_pass_ctrlc) (struct target_ops *)
|
void (*to_pass_ctrlc) (struct target_ops *)
|
||||||
TARGET_DEFAULT_FUNC (default_target_pass_ctrlc);
|
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);
|
extern void target_stop (ptid_t ptid);
|
||||||
|
|
||||||
/* Interrupt the target just like the user typed a ^C on the
|
/* Interrupt the target. Unlike target_stop, this does not specify
|
||||||
inferior's controlling terminal. (For instance, under Unix, this
|
which thread/process reports the stop. For most target this acts
|
||||||
should act like SIGINT). This function is asynchronous. */
|
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
|
/* Pass a ^C, as determined to have been pressed by checking the quit
|
||||||
flag, to the target. Normally calls target_interrupt, but remote
|
flag, to the target, as if the user had typed the ^C on the
|
||||||
targets may take the opportunity to detect the remote side is not
|
inferior's controlling terminal while the inferior was in the
|
||||||
responding and offer to disconnect. */
|
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);
|
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);
|
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. */
|
/* Represents the state of the target terminal. */
|
||||||
class target_terminal
|
class target_terminal
|
||||||
{
|
{
|
||||||
@ -108,9 +123,9 @@ public:
|
|||||||
before we actually run the inferior. */
|
before we actually run the inferior. */
|
||||||
static void init ();
|
static void init ();
|
||||||
|
|
||||||
/* Put the inferior's terminal settings into effect. This is
|
/* Put the current inferior's terminal settings into effect. This
|
||||||
preparation for starting or resuming the inferior. This is a no-op
|
is preparation for starting or resuming the inferior. This is a
|
||||||
unless called with the main UI as current UI. */
|
no-op unless called with the main UI as current UI. */
|
||||||
static void inferior ();
|
static void inferior ();
|
||||||
|
|
||||||
/* Put our terminal settings into effect. First record the inferior's
|
/* Put our terminal settings into effect. First record the inferior's
|
||||||
@ -125,40 +140,34 @@ public:
|
|||||||
UI as current UI. */
|
UI as current UI. */
|
||||||
static void ours_for_output ();
|
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
|
/* Returns true if the terminal settings of the inferior are in
|
||||||
effect. */
|
effect. */
|
||||||
static bool is_inferior ()
|
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. */
|
/* Returns true if our terminal settings are in effect. */
|
||||||
static bool is_ours ()
|
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
|
/* Print useful information about our terminal status, if such a thing
|
||||||
exists. */
|
exists. */
|
||||||
static void info (const char *arg, int from_tty);
|
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:
|
public:
|
||||||
|
|
||||||
/* A class that restores the state of the terminal to the current
|
/* A class that restores the state of the terminal to the current
|
||||||
@ -168,7 +177,7 @@ public:
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
scoped_restore_terminal_state ()
|
scoped_restore_terminal_state ()
|
||||||
: m_state (terminal_state)
|
: m_state (m_terminal_state)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,14 +185,14 @@ public:
|
|||||||
{
|
{
|
||||||
switch (m_state)
|
switch (m_state)
|
||||||
{
|
{
|
||||||
case terminal_is_ours:
|
case target_terminal_state::is_ours:
|
||||||
ours ();
|
ours ();
|
||||||
break;
|
break;
|
||||||
case terminal_is_ours_for_output:
|
case target_terminal_state::is_ours_for_output:
|
||||||
ours_for_output ();
|
ours_for_output ();
|
||||||
break;
|
break;
|
||||||
case terminal_is_inferior:
|
case target_terminal_state::is_inferior:
|
||||||
inferior ();
|
restore_inferior ();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -192,12 +201,12 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
target_terminal::terminal_state m_state;
|
target_terminal_state m_state;
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
static terminal_state terminal_state;
|
static target_terminal_state m_terminal_state;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* TARGET_COMMON_H */
|
#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>
|
2018-01-30 Joel Brobecker <brobecker@adacore.com>
|
||||||
|
|
||||||
* gdb.base/break.exp: Save the location where the breakpoint
|
* 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