forked from Imagelibrary/binutils-gdb
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.
243 lines
6.8 KiB
Plaintext
243 lines
6.8 KiB
Plaintext
# 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
|