diff options
author | Pedro Alves <palves@redhat.com> | 2018-01-30 14:23:51 +0000 |
---|---|---|
committer | Pedro Alves <palves@redhat.com> | 2018-01-30 14:55:18 +0000 |
commit | e671cd59d74cec9f53e110ce887128d1eeadb7f2 (patch) | |
tree | c2e6a7cd82327a763f8648770e9326a02fbc71ef /gdb/testsuite/gdb.multi | |
parent | 9c3a5d9319648db16b30a91253ad02d41d242cef (diff) | |
download | gdb-e671cd59d74cec9f53e110ce887128d1eeadb7f2.zip gdb-e671cd59d74cec9f53e110ce887128d1eeadb7f2.tar.gz gdb-e671cd59d74cec9f53e110ce887128d1eeadb7f2.tar.bz2 |
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.
Diffstat (limited to 'gdb/testsuite/gdb.multi')
-rw-r--r-- | gdb/testsuite/gdb.multi/multi-term-settings.c | 52 | ||||
-rw-r--r-- | gdb/testsuite/gdb.multi/multi-term-settings.exp | 242 |
2 files changed, 294 insertions, 0 deletions
diff --git a/gdb/testsuite/gdb.multi/multi-term-settings.c b/gdb/testsuite/gdb.multi/multi-term-settings.c new file mode 100644 index 0000000..c96779e --- /dev/null +++ b/gdb/testsuite/gdb.multi/multi-term-settings.c @@ -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; +} diff --git a/gdb/testsuite/gdb.multi/multi-term-settings.exp b/gdb/testsuite/gdb.multi/multi-term-settings.exp new file mode 100644 index 0000000..7245b84 --- /dev/null +++ b/gdb/testsuite/gdb.multi/multi-term-settings.exp @@ -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 |