new file mode 100644
@@ -0,0 +1,22 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2026 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/>. */
+
+int
+main (void)
+{
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,154 @@
+# Copyright 2026 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/>.
+
+# Check that the TUI can NOT be activated using 'C-x C-a', and similar
+# key combinations, from a secondary prompt. Activating from the
+# secondary prompt has the same effect as pressing 'Return' at the
+# prompt, which might not be what the user wants.
+#
+# We could imagine a world where, when activating the TUI from a
+# secondary prompt works as we might expect, the prompt is not
+# cancelled, but reprinted within the TUI, and the user can then make
+# their choice and continue with their debug session. If we ever
+# implement this, then this test will need to change.
+#
+# For now though we take the easy way and just ban the user from
+# activating the TUI from a secondary prompt.
+
+load_lib debuginfod-support.exp
+
+tuiterm_env
+
+require allow_tui_tests
+
+standard_testfile
+
+if {[build_executable "build executable" ${testfile} ${srcfile}] == -1} {
+ return
+}
+
+# Check that readline is in use.
+clean_restart
+if { ![readline_is_used] } {
+ unsupported "readline is required"
+ return
+}
+
+# Assuming that GDB is at a secondary prompt, try various multi-key
+# combinations and check that the screen doesn't change, these should
+# all be ignored.
+proc test_multi_key_combos {} {
+ foreach mk { \030\001 \030a \030A \0302 \0301 \030o \030s } {
+ set before [Term::get_region 0 0 80 24 "\r\n"]
+ send_gdb $mk
+ set after [Term::get_region 0 0 80 24 "\r\n"]
+
+ gdb_assert { $before eq $after } \
+ "no changes after sending '[unprintable_to_octal $mk]'"
+ }
+}
+
+# Send CHAR followed by a newline to GDB, this should quit the
+# currently active secondary prompt. Wait for the GDB prompt to
+# present, then send a basic print command to GDB to ensure that GDB
+# is still interactive.
+proc quit_prompt { char } {
+ send_gdb "${char}\n"
+ Term::wait_for ""
+
+ gdb_test "print 1" " = 1"
+}
+
+# Trigger the pagination prompt, then check that the multi-key
+# combinations don't result in a change to TUI mode. Finally, check
+# that GDB is still responsive.
+proc_with_prefix test_from_pager_prompt {} {
+ Term::clean_restart 24 80 $::testfile
+
+ set cmd "echo "
+ for { set i 0 } { $i < 100 } { incr i } {
+ set cmd "${cmd}${i}\\n"
+ }
+
+ gdb_test_no_output "set pagination on"
+ gdb_test_no_output "set height 24"
+ send_gdb "$cmd\n"
+
+ if {![Term::wait_for_region_contents 0 0 80 24 \
+ [string_to_regexp $::pagination_prompt_str]]} {
+ fail "spot secondary prompt"
+ return
+ }
+
+ test_multi_key_combos
+ quit_prompt q
+}
+
+# Trigger the debuginfod prompt, then check that the multi-key
+# combinations don't result in a change to TUI mode. Finally, check
+# GDB is still responsive.
+proc_with_prefix test_from_debuginfod_prompt {} {
+ if { ![allow_debuginfod_tests] } {
+ return
+ }
+
+ set stripped_binfile [standard_output_file ${::testfile}-stripped]
+ file copy -force $::binfile $stripped_binfile
+ if {[gdb_gnu_strip_debug $stripped_binfile no-debuglink]} {
+ unsupported "produce separate debug info for [file tail $stripped_binfile]"
+ return
+ }
+
+ if {[section_get $stripped_binfile ".gnu_debuglink"] ne ""} {
+ unsupported "debug information has already been split out"
+ return
+ }
+
+ save_vars { env(DEBUGINFOD_URLS) } {
+ setenv DEBUGINFOD_URLS "foo"
+ Term::clean_restart 24 80
+ }
+
+ send_gdb "file \"$stripped_binfile\"\n"
+ if {![Term::wait_for_region_contents 0 0 80 24 \
+ [string_to_regexp {(y or [n]) }]]} {
+ fail "spot secondary prompt"
+ return
+ }
+
+ test_multi_key_combos
+ quit_prompt n
+}
+
+# Trigger the pending breakpoint prompt, then check that the multi-key
+# combinations don't result in a change to TUI mode. Finally, check
+# that GDB is still responsive.
+proc_with_prefix test_from_breakpoint_prompt {} {
+ Term::clean_restart 24 80 $::testfile
+
+ send_gdb "break _a_function_that_hopefully_doesnt_exist_\n"
+ if {![Term::wait_for_region_contents 0 0 80 24 \
+ [string_to_regexp {(y or [n]) }]]} {
+ fail "spot secondary prompt"
+ return
+ }
+
+ test_multi_key_combos
+ quit_prompt n
+}
+
+test_from_pager_prompt
+test_from_debuginfod_prompt
+test_from_breakpoint_prompt
@@ -11808,6 +11808,34 @@ proc dwz_version_at_least { ver } {
return [version_compare [split $ver .] <= [dwz_version]]
}
+# Take a string containing unprintable characters and return a string
+# with the unprintable characters represented as 3 octal bytes,
+# e.g. "\030".
+#
+# To avoid having to escape backslashes, backslashes themselves are
+# also represented as octal. Probably best not to use this for
+# strings containing backslashes.
+proc unprintable_to_octal { input_string } {
+ set result ""
+
+ foreach char [split $input_string ""] {
+ # Convert character to its integer ASCII value.
+ scan $char %c ascii_val
+
+ # Include printable characters as literals. Exclude backslash
+ # to avoid needing to escape it, we don't actually expect to
+ # see that in INPUT_STRING though.
+ if {[string is print $char] && $char ne "\\"} {
+ append result $char
+ } else {
+ # Include non-printable characters as a 3-digit octal.
+ append result [format "\\%03o" $ascii_val]
+ }
+ }
+
+ return $result
+}
+
require {tcl_version_at_least 8 6 2}
# Always load compatibility stuff.
@@ -114,6 +114,7 @@ static Keymap tui_readline_standard_keymap;
static int
tui_rl_switch_mode (int notused1 = 0, int notused2 = 0)
{
+ gdb_assert (!gdb_in_secondary_prompt_p (current_ui));
/* Don't let exceptions escape. We're in the middle of a readline
callback that isn't prepared for that. */
@@ -198,6 +199,8 @@ tui_try_activate ()
static int
tui_rl_change_windows (int notused1, int notused2)
{
+ gdb_assert (!gdb_in_secondary_prompt_p (current_ui));
+
if (tui_try_activate ())
tui_next_layout ();
@@ -209,6 +212,8 @@ tui_rl_change_windows (int notused1, int notused2)
static int
tui_rl_delete_other_windows (int notused1, int notused2)
{
+ gdb_assert (!gdb_in_secondary_prompt_p (current_ui));
+
if (tui_try_activate ())
tui_remove_some_windows ();
@@ -220,6 +225,8 @@ tui_rl_delete_other_windows (int notused1, int notused2)
static int
tui_rl_other_window (int count, int key)
{
+ gdb_assert (!gdb_in_secondary_prompt_p (current_ui));
+
if (tui_try_activate ())
tui_set_win_focus_to (tui_next_win (tui_win_with_focus ()));
@@ -231,10 +238,10 @@ tui_rl_other_window (int count, int key)
static int
tui_rl_command_key (int count, int key)
{
- int i;
+ gdb_assert (!gdb_in_secondary_prompt_p (current_ui));
reinitialize_more_filter ();
- for (i = 0; tui_commands[i].cmd; i++)
+ for (int i = 0; tui_commands[i].cmd; i++)
{
if (tui_commands[i].key == key)
{
@@ -262,6 +269,7 @@ tui_rl_command_key (int count, int key)
static int
tui_rl_command_mode (int count, int key)
{
+ gdb_assert (!gdb_in_secondary_prompt_p (current_ui));
tui_set_key_mode (TUI_ONE_COMMAND_MODE);
return rl_insert (count, key);
}
@@ -271,6 +279,8 @@ tui_rl_command_mode (int count, int key)
static int
tui_rl_next_keymap (int notused1, int notused2)
{
+ gdb_assert (!gdb_in_secondary_prompt_p (current_ui));
+
if (!tui_try_activate ())
return 0;
@@ -310,6 +320,20 @@ tui_set_key_mode (enum tui_key_mode mode)
tui_show_status_content ();
}
+/* Wrapper around function FPTR, used to add common checks before
+ functions that are bound to readline multi-key combinations. */
+
+template<int (*FPTR) (int, int)>
+int
+tui_rl_keybinding (int count, int key)
+{
+ /* Don't allow TUI changes while we're at an interactive prompt. */
+ if (gdb_in_secondary_prompt_p (current_ui))
+ return 0;
+
+ return FPTR (count, key);
+}
+
/* Initialize readline and configure the keymap for the switching
key shortcut. */
void
@@ -324,11 +348,16 @@ tui_ensure_readline_initialized ()
int i;
Keymap tui_ctlx_keymap;
- rl_add_defun ("tui-switch-mode", tui_rl_switch_mode, -1);
- rl_add_defun ("next-keymap", tui_rl_next_keymap, -1);
- rl_add_defun ("tui-delete-other-windows", tui_rl_delete_other_windows, -1);
- rl_add_defun ("tui-change-windows", tui_rl_change_windows, -1);
- rl_add_defun ("tui-other-window", tui_rl_other_window, -1);
+ rl_add_defun ("tui-switch-mode",
+ tui_rl_keybinding<tui_rl_switch_mode>, -1);
+ rl_add_defun ("next-keymap",
+ tui_rl_keybinding<tui_rl_next_keymap>, -1);
+ rl_add_defun ("tui-delete-other-windows",
+ tui_rl_keybinding<tui_rl_delete_other_windows>, -1);
+ rl_add_defun ("tui-change-windows",
+ tui_rl_keybinding<tui_rl_change_windows>, -1);
+ rl_add_defun ("tui-other-window",
+ tui_rl_keybinding<tui_rl_other_window>, -1);
tui_keymap = rl_make_bare_keymap ();
@@ -361,21 +390,21 @@ tui_ensure_readline_initialized ()
rl_bind_key_in_map (i, tui_rl_command_mode, tui_keymap);
}
- rl_bind_key_in_map ('a', tui_rl_switch_mode, emacs_ctlx_keymap);
- rl_bind_key_in_map ('a', tui_rl_switch_mode, tui_ctlx_keymap);
- rl_bind_key_in_map ('A', tui_rl_switch_mode, emacs_ctlx_keymap);
- rl_bind_key_in_map ('A', tui_rl_switch_mode, tui_ctlx_keymap);
- rl_bind_key_in_map (c_ctrl ('A'), tui_rl_switch_mode, emacs_ctlx_keymap);
- rl_bind_key_in_map (c_ctrl ('A'), tui_rl_switch_mode, tui_ctlx_keymap);
- rl_bind_key_in_map ('1', tui_rl_delete_other_windows, emacs_ctlx_keymap);
- rl_bind_key_in_map ('1', tui_rl_delete_other_windows, tui_ctlx_keymap);
- rl_bind_key_in_map ('2', tui_rl_change_windows, emacs_ctlx_keymap);
- rl_bind_key_in_map ('2', tui_rl_change_windows, tui_ctlx_keymap);
- rl_bind_key_in_map ('o', tui_rl_other_window, emacs_ctlx_keymap);
- rl_bind_key_in_map ('o', tui_rl_other_window, tui_ctlx_keymap);
- rl_bind_key_in_map ('q', tui_rl_next_keymap, tui_keymap);
- rl_bind_key_in_map ('s', tui_rl_next_keymap, emacs_ctlx_keymap);
- rl_bind_key_in_map ('s', tui_rl_next_keymap, tui_ctlx_keymap);
+ rl_bind_key_in_map ('a', tui_rl_keybinding<tui_rl_switch_mode>, emacs_ctlx_keymap);
+ rl_bind_key_in_map ('a', tui_rl_keybinding<tui_rl_switch_mode>, tui_ctlx_keymap);
+ rl_bind_key_in_map ('A', tui_rl_keybinding<tui_rl_switch_mode>, emacs_ctlx_keymap);
+ rl_bind_key_in_map ('A', tui_rl_keybinding<tui_rl_switch_mode>, tui_ctlx_keymap);
+ rl_bind_key_in_map (c_ctrl ('A'), tui_rl_keybinding<tui_rl_switch_mode>, emacs_ctlx_keymap);
+ rl_bind_key_in_map (c_ctrl ('A'), tui_rl_keybinding<tui_rl_switch_mode>, tui_ctlx_keymap);
+ rl_bind_key_in_map ('1', tui_rl_keybinding<tui_rl_delete_other_windows>, emacs_ctlx_keymap);
+ rl_bind_key_in_map ('1', tui_rl_keybinding<tui_rl_delete_other_windows>, tui_ctlx_keymap);
+ rl_bind_key_in_map ('2', tui_rl_keybinding<tui_rl_change_windows>, emacs_ctlx_keymap);
+ rl_bind_key_in_map ('2', tui_rl_keybinding<tui_rl_change_windows>, tui_ctlx_keymap);
+ rl_bind_key_in_map ('o', tui_rl_keybinding<tui_rl_other_window>, emacs_ctlx_keymap);
+ rl_bind_key_in_map ('o', tui_rl_keybinding<tui_rl_other_window>, tui_ctlx_keymap);
+ rl_bind_key_in_map ('q', tui_rl_keybinding<tui_rl_next_keymap>, tui_keymap);
+ rl_bind_key_in_map ('s', tui_rl_keybinding<tui_rl_next_keymap>, emacs_ctlx_keymap);
+ rl_bind_key_in_map ('s', tui_rl_keybinding<tui_rl_next_keymap>, tui_ctlx_keymap);
/* Initialize readline after the above. */
rl_initialize ();