* [PATCH 0/6] gdb/tui: fix debuginfod related crash
@ 2026-05-01 14:22 Andrew Burgess
2026-05-01 14:22 ` [PATCH 1/6] gdb/tui: convert a window handle `if` into an `assert` Andrew Burgess
` (6 more replies)
0 siblings, 7 replies; 15+ messages in thread
From: Andrew Burgess @ 2026-05-01 14:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
This series is an alternative to the latest patches proposed here:
https://inbox.sourceware.org/gdb-patches/20260417075719.852558-1-tdevries@suse.de
Patches #1, #2, and #3 cover changes that are not part of the above
series, I suspect these will be reasonably non-contentious.
Patch #4 is a small refactor to support the later patches.
Patches #5 and #6 are my take on solving the problem covered by the
above series.
Thanks,
Andrew
---
Andrew Burgess (6):
gdb/tui: convert a window handle `if` into an `assert`
gdb/testsuite: fix tuiterm linefeed scrolling new line content
gdb/tui: prevent TUI activation from a secondary prompt
gdb/tui: make tui_win_info::rerender public
gdb/tui: fix for debuginfod prompt while enabling the TUI
gdb/tui: fix debuginfod prompt using 'C-x C-a' to enter TUI
.../gdb.tui/activate-from-secondary-prompt.c | 22 ++
.../activate-from-secondary-prompt.exp | 154 +++++++++++
.../gdb.tui/activate-with-key-combo.c | 22 ++
.../gdb.tui/activate-with-key-combo.exp | 83 ++++++
gdb/testsuite/gdb.tui/debuginfod-query.c | 22 ++
gdb/testsuite/gdb.tui/debuginfod-query.exp | 242 ++++++++++++++++++
.../gdb.tui/tui-enable-failure-lib.c | 31 +++
gdb/testsuite/gdb.tui/tui-enable-failure.c | 22 ++
gdb/testsuite/gdb.tui/tui-enable-failure.exp | 136 ++++++++++
gdb/testsuite/gdb.tui/tuiterm.exp | 2 +-
gdb/testsuite/lib/gdb.exp | 28 ++
gdb/testsuite/lib/tuiterm.exp | 1 +
gdb/tui/tui-data.h | 12 +-
gdb/tui/tui-hooks.c | 21 +-
gdb/tui/tui-layout.c | 3 +-
gdb/tui/tui-regs.h | 4 +-
gdb/tui/tui-wingeneral.c | 13 +-
gdb/tui/tui-winsource.c | 2 +
gdb/tui/tui-winsource.h | 4 +-
gdb/tui/tui.c | 202 +++++++++++----
gdb/tui/tui.h | 5 +
21 files changed, 959 insertions(+), 72 deletions(-)
create mode 100644 gdb/testsuite/gdb.tui/activate-from-secondary-prompt.c
create mode 100644 gdb/testsuite/gdb.tui/activate-from-secondary-prompt.exp
create mode 100644 gdb/testsuite/gdb.tui/activate-with-key-combo.c
create mode 100644 gdb/testsuite/gdb.tui/activate-with-key-combo.exp
create mode 100644 gdb/testsuite/gdb.tui/debuginfod-query.c
create mode 100644 gdb/testsuite/gdb.tui/debuginfod-query.exp
create mode 100644 gdb/testsuite/gdb.tui/tui-enable-failure-lib.c
create mode 100644 gdb/testsuite/gdb.tui/tui-enable-failure.c
create mode 100644 gdb/testsuite/gdb.tui/tui-enable-failure.exp
base-commit: 1a5ae95bc1fde4aeb159e4e963118b17e1e730b5
--
2.25.4
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 1/6] gdb/tui: convert a window handle `if` into an `assert`
2026-05-01 14:22 [PATCH 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
@ 2026-05-01 14:22 ` Andrew Burgess
2026-05-01 14:22 ` [PATCH 2/6] gdb/testsuite: fix tuiterm linefeed scrolling new line content Andrew Burgess
` (5 subsequent siblings)
6 siblings, 0 replies; 15+ messages in thread
From: Andrew Burgess @ 2026-05-01 14:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
It should only be possible to call tui_win_info::refresh_window on a
window with a valid tui_win_info::handle. To do otherwise would
suggest we're trying to draw to the screen a window which GDB doesn't
think is part of the current layout. Which is just wrong.
Currently tui_win_info::refresh_window guards its content with an
`if (handle != NULL)`, but this can be changed to an assert.
A similar assert can be added to
tui_source_window_base::refresh_window, there's no `if` in this
function, which only backs up the reasoning in the first paragraph.
There should be no user-visible changes after this commit.
---
gdb/tui/tui-wingeneral.c | 13 ++++++-------
gdb/tui/tui-winsource.c | 2 ++
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/gdb/tui/tui-wingeneral.c b/gdb/tui/tui-wingeneral.c
index 86f13d275b4..cd04876ad1b 100644
--- a/gdb/tui/tui-wingeneral.c
+++ b/gdb/tui/tui-wingeneral.c
@@ -55,13 +55,12 @@ tui_batch_rendering::~tui_batch_rendering ()
void
tui_win_info::refresh_window ()
{
- if (handle != NULL)
- {
- if (suppress_output)
- wnoutrefresh (handle.get ());
- else
- wrefresh (handle.get ());
- }
+ gdb_assert (handle != nullptr);
+
+ if (suppress_output)
+ wnoutrefresh (handle.get ());
+ else
+ wrefresh (handle.get ());
}
/* Draw a border around the window. */
diff --git a/gdb/tui/tui-winsource.c b/gdb/tui/tui-winsource.c
index 8609a7cd4fe..d040019563c 100644
--- a/gdb/tui/tui-winsource.c
+++ b/gdb/tui/tui-winsource.c
@@ -310,6 +310,8 @@ tui_source_window_base::refresh_window ()
{
TUI_SCOPED_DEBUG_START_END ("window `%s`", name ());
+ gdb_assert (handle != nullptr);
+
/* tui_win_info::refresh_window would draw the empty background window to
the screen, potentially creating a flicker. */
wnoutrefresh (handle.get ());
--
2.25.4
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 2/6] gdb/testsuite: fix tuiterm linefeed scrolling new line content
2026-05-01 14:22 [PATCH 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
2026-05-01 14:22 ` [PATCH 1/6] gdb/tui: convert a window handle `if` into an `assert` Andrew Burgess
@ 2026-05-01 14:22 ` Andrew Burgess
2026-05-01 14:22 ` [PATCH 3/6] gdb/tui: prevent TUI activation from a secondary prompt Andrew Burgess
` (4 subsequent siblings)
6 siblings, 0 replies; 15+ messages in thread
From: Andrew Burgess @ 2026-05-01 14:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
I came across a bug in the implementation of line feed in tuiterm.
Consider the gdb.tui/tuiterm.exp test 'test_linefeed_scroll', before
sending the line feed we have:
Screen Dump (size 8 columns x 4 rows, cursor at column 0, row 3):
0 abcdefgh
1 ijklmnop
2 qrstuvwx
3 yz01234
and after sending the line feed we have:
Screen Dump (size 8 columns x 4 rows, cursor at column 0, row 3):
0 ijklmnop
1 qrstuvwx
2 yz01234
3 yz01234
Notice that the new line #3 retains its previous contents, all lines
have scrolled up, with the old line #0 having been moved off the
terminal, but the new line is starting with these cloned contents.
I don't believe this is correct. My understanding is that new lines
should be created empty -- or really full of space characters.
After fixing this issue so that new lines are created empty, the only
test failure is the tuiterm.exp unit test mentioned above, this was
added in commit:
commit e20baea1298d2227db953862d131d9bbf91cf522
Date: Mon May 29 22:11:05 2023 +0200
[gdb/testsuite] Fix linefeed scrolling in tuiterm
This commit is fixing an issue with cursor placement after a scroll,
there is no mention of the content of the new line, which makes me
think that the test is just checking whatever behaviour used to be
there.
In this commit I think we should fix the new line content, and update
the existing unit test to match the new behaviour.
---
gdb/testsuite/gdb.tui/tuiterm.exp | 2 +-
gdb/testsuite/lib/tuiterm.exp | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/gdb/testsuite/gdb.tui/tuiterm.exp b/gdb/testsuite/gdb.tui/tuiterm.exp
index ccf26195ddc..b6330b0d6a8 100644
--- a/gdb/testsuite/gdb.tui/tuiterm.exp
+++ b/gdb/testsuite/gdb.tui/tuiterm.exp
@@ -152,7 +152,7 @@ proc test_linefeed_scroll { } {
"ijklmnop"
"qrstuvwx"
"yz01234 "
- "yz01234 "
+ " "
} 0 3
Term::dump_screen
}
diff --git a/gdb/testsuite/lib/tuiterm.exp b/gdb/testsuite/lib/tuiterm.exp
index a03c32b3f60..55b8e595604 100644
--- a/gdb/testsuite/lib/tuiterm.exp
+++ b/gdb/testsuite/lib/tuiterm.exp
@@ -165,6 +165,7 @@ proc Term::_ctl_0x0a {} {
}
incr _cur_row -1
+ _clear_lines $_cur_row $_rows
}
}
}
--
2.25.4
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 3/6] gdb/tui: prevent TUI activation from a secondary prompt
2026-05-01 14:22 [PATCH 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
2026-05-01 14:22 ` [PATCH 1/6] gdb/tui: convert a window handle `if` into an `assert` Andrew Burgess
2026-05-01 14:22 ` [PATCH 2/6] gdb/testsuite: fix tuiterm linefeed scrolling new line content Andrew Burgess
@ 2026-05-01 14:22 ` Andrew Burgess
2026-05-01 14:22 ` [PATCH 4/6] gdb/tui: make tui_win_info::rerender public Andrew Burgess
` (3 subsequent siblings)
6 siblings, 0 replies; 15+ messages in thread
From: Andrew Burgess @ 2026-05-01 14:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
The TUI can be activated with key combinations like 'C-x C-a'. This
is handled by readline calling the function tui_rl_switch_mode, or
various other functions which indirectly call that function.
These multi-key combinations can be used even at a secondary prompt,
e.g. the:
Make breakpoint pending on future shared library load? (y or [n])
The problem with this is that when the TUI activates CLI content
doesn't carry over into the `cmd` window, so the secondary prompt is
not visible to the user after the mode switch. Worse, because the
content doesn't carry over we clear the readline state, and this
involves sending a '\n' to readline. This newline will select the
default action at the secondary prompt, which might not be what the
user actually wants.
Now, we could imagine trying to "fix" this so that the CLI content is
copied over into the `cmd` window, and the secondary prompt is
represented to the user, so they can then make the choice they want,
but implementing this fix would be a big job, for very little gain.
Easier I think, to just prevent the user switching to TUI mode while
at a secondary prompt.
I created a new templated wrapper function tui_rl_keybinding, which is
then used to wrap every function that is bound to a readline multi-key
combination. The wrapper function checks if we are in a secondary
prompt, and if we are, performs an early return.
For completeness, I've added an assert that we are not in a secondary
prompt to all of the wrapped functions, this (hopefully) will help
catch cases where these functions are called directly without going
through the wrapper.
There's a new helper proc added to lib/gdb.exp, this will be used by
additional tests later in this series.
The user can still switch to TUI mode at the primary '(gdb)' prompt.
---
.../gdb.tui/activate-from-secondary-prompt.c | 22 +++
.../activate-from-secondary-prompt.exp | 154 ++++++++++++++++++
gdb/testsuite/lib/gdb.exp | 28 ++++
gdb/tui/tui.c | 73 ++++++---
4 files changed, 255 insertions(+), 22 deletions(-)
create mode 100644 gdb/testsuite/gdb.tui/activate-from-secondary-prompt.c
create mode 100644 gdb/testsuite/gdb.tui/activate-from-secondary-prompt.exp
diff --git a/gdb/testsuite/gdb.tui/activate-from-secondary-prompt.c b/gdb/testsuite/gdb.tui/activate-from-secondary-prompt.c
new file mode 100644
index 00000000000..6a0e311ef41
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/activate-from-secondary-prompt.c
@@ -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;
+}
diff --git a/gdb/testsuite/gdb.tui/activate-from-secondary-prompt.exp b/gdb/testsuite/gdb.tui/activate-from-secondary-prompt.exp
new file mode 100644
index 00000000000..8de044e7b19
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/activate-from-secondary-prompt.exp
@@ -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
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 28709004570..d777b25e45c 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -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.
diff --git a/gdb/tui/tui.c b/gdb/tui/tui.c
index ba4f6f68769..d2e9c1b4def 100644
--- a/gdb/tui/tui.c
+++ b/gdb/tui/tui.c
@@ -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 ();
--
2.25.4
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 4/6] gdb/tui: make tui_win_info::rerender public
2026-05-01 14:22 [PATCH 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
` (2 preceding siblings ...)
2026-05-01 14:22 ` [PATCH 3/6] gdb/tui: prevent TUI activation from a secondary prompt Andrew Burgess
@ 2026-05-01 14:22 ` Andrew Burgess
2026-05-01 14:22 ` [PATCH 5/6] gdb/tui: fix for debuginfod prompt while enabling the TUI Andrew Burgess
` (2 subsequent siblings)
6 siblings, 0 replies; 15+ messages in thread
From: Andrew Burgess @ 2026-05-01 14:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
In the next commit I'm going to want to call rerender from outside the
tui_win_info class, so let's make it public.
This is just a refactor, there should be no user-visible changes after
this commit.
---
gdb/tui/tui-data.h | 12 +++++++-----
gdb/tui/tui-regs.h | 4 ++--
gdb/tui/tui-winsource.h | 4 ++--
3 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/gdb/tui/tui-data.h b/gdb/tui/tui-data.h
index ec3cf63a286..155d9dd4e0c 100644
--- a/gdb/tui/tui-data.h
+++ b/gdb/tui/tui-data.h
@@ -44,10 +44,6 @@ struct tui_win_info
tui_win_info () = default;
DISABLE_COPY_AND_ASSIGN (tui_win_info);
- /* This is called after the window is resized, and should update the
- window's contents. */
- virtual void rerender ();
-
/* Create the curses window. */
void make_window ();
@@ -55,7 +51,13 @@ struct tui_win_info
tui_win_info (tui_win_info &&) = default;
virtual ~tui_win_info () = default;
- /* Call to refresh this window. */
+ /* Call to update the in-memory contents of this window. Does not
+ cause the contents to be drawn to the screen. */
+ virtual void rerender ();
+
+ /* Call to refresh this window on the screen. The in-memory contents of
+ the window are not updated by this call, whatever the current contents
+ are, they are drawn to the screen. */
virtual void refresh_window ();
/* Make this window visible or invisible. */
diff --git a/gdb/tui/tui-regs.h b/gdb/tui/tui-regs.h
index ec056e1a042..2ed4614976f 100644
--- a/gdb/tui/tui-regs.h
+++ b/gdb/tui/tui-regs.h
@@ -90,6 +90,8 @@ struct tui_data_window : public tui_win_info
return m_current_group;
}
+ void rerender () override;
+
protected:
void do_scroll_vertical (int num_to_scroll) override;
@@ -97,8 +99,6 @@ struct tui_data_window : public tui_win_info
{
}
- void rerender () override;
-
private:
/* Display the registers in the content from 'start_element_no'
diff --git a/gdb/tui/tui-winsource.h b/gdb/tui/tui-winsource.h
index d4b25cbf9cd..5becb2fd715 100644
--- a/gdb/tui/tui-winsource.h
+++ b/gdb/tui/tui-winsource.h
@@ -103,8 +103,6 @@ struct tui_source_window_base : public tui_win_info
/* Erase the content and display STRING. */
void do_erase_source_content (const char *string);
- void rerender () override;
-
virtual bool set_contents (struct gdbarch *gdbarch,
const struct symtab_and_line &sal) = 0;
@@ -138,6 +136,8 @@ struct tui_source_window_base : public tui_win_info
public:
+ void rerender () override;
+
/* Refill the source window's source cache and update it. If this
is a disassembly window, then just update it. */
void refill ();
--
2.25.4
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 5/6] gdb/tui: fix for debuginfod prompt while enabling the TUI
2026-05-01 14:22 [PATCH 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
` (3 preceding siblings ...)
2026-05-01 14:22 ` [PATCH 4/6] gdb/tui: make tui_win_info::rerender public Andrew Burgess
@ 2026-05-01 14:22 ` Andrew Burgess
2026-05-01 14:22 ` [PATCH 6/6] gdb/tui: fix debuginfod prompt using 'C-x C-a' to enter TUI Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
6 siblings, 0 replies; 15+ messages in thread
From: Andrew Burgess @ 2026-05-01 14:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess, Tom de Vries
PR tui/31449 reports a SIGFPE when the debuginfod query happens while
enabling TUI using the "tui enable" command:
Thread 1 "gdb" received signal SIGFPE, Arithmetic exception.
0x0000000001021084 in tui_inject_newline_into_command_window () at /data/vries/gdb/src/gdb/tui/tui-io.c:1096
1096 py += px / tui_cmd_win ()->width;
due to divide-by-zero because tui_cmd_win ()->width == 0.
The corresponding backtrace is:
(gdb) bt
#0 0x0000000001021084 in tui_inject_newline_into_command_window () at gdb/tui/tui-io.c:1096
#1 0x0000000000fe65fd in gdb_readline_wrapper_line (line=...) at gdb/top.c:939
#2 0x0000000000944eef in gdb_rl_callback_handler (rl=0x2cc865a0 "n") at gdb/event-top.c:288
#3 0x0000000001175779 in rl_callback_read_char () at readline/readline/callback.c:302
#4 0x0000000000944bc3 in gdb_rl_callback_read_char_wrapper_sjlj () at gdb/event-top.c:197
#5 0x0000000000944cd4 in gdb_rl_callback_read_char_wrapper_noexcept () at gdb/event-top.c:240
#6 0x0000000000944d52 in gdb_rl_callback_read_char_wrapper (...) at gdb/event-top.c:252
#7 0x0000000001062352 in stdin_event_handler (error=0, client_data=0x2c865150) at gdb/ui.c:154
#8 0x0000000001a04edf in handle_file_event (file_ptr=0x2ccf8850, ready_mask=1) at gdbsupport/event-loop.cc:551
#9 0x0000000001a05522 in gdb_wait_for_event (block=1) at gdbsupport/event-loop.cc:672
#10 0x0000000001a043ff in gdb_do_one_event (mstimeout=-1) at gdbsupport/event-loop.cc:263
#11 0x00000000006d5480 in interp::do_one_event (this=0x2cc2af20, mstimeout=-1) at gdb/interps.h:93
#12 0x0000000000fe670d in gdb_readline_wrapper (prompt=0x2ccca4e0 "Enable debuginfod for this session? (y or [n]) ") at gdb/top.c:1033
#13 0x00000000010c6853 in defaulted_query(...) (...) at gdb/utils.c:844
#14 0x00000000010c6b8a in nquery (...) at gdb/utils.c:901
#15 0x00000000007a9324 in debuginfod_is_enabled () at gdb/debuginfod-support.c:268
#16 0x00000000007a950d in debuginfod_source_query (...) at gdb/debuginfod-support.c:311
#17 0x0000000000efc2c7 in open_source_file (s=0x2cc8f4b0) at gdb/source.c:1152
#18 0x0000000000efc619 in symtab_to_fullname (...) at gdb/source.c:1214
#19 0x0000000000f5ebb3 in find_line_symtab (...) at gdb/symtab.c:3287
#20 0x0000000000f5f0e5 in find_pc_for_line (...) at gdb/symtab.c:3391
#21 0x0000000001011f54 in tui_get_begin_asm_address (...) at gdb/tui/tui-disasm.c:404
#22 0x000000000104888d in tui_source_window_base::rerender (this=0x2cbdc570) at gdb/tui/tui-winsource.c:474
#23 0x0000000001028e81 in tui_win_info::resize (this=0x2cbdc570, height_=21, width_=127, origin_x_=0, origin_y_=0) at gdb/tui/tui-layout.c:299
#24 0x00000000010297d0 in tui_layout_window::apply (this=0x2cc50350, x_=0, y_=0, width_=127, height_=21, preserve_cmd_win_size_p=false) at gdb/tui/tui-layout.c:432
#25 0x000000000102bfea in tui_layout_split::apply (this=0x2caea920, x_=0, y_=0, width_=127, height_=33, preserve_cmd_win_size_p=false) at gdb/tui/tui-layout.c:1026
#26 0x0000000001028267 in tui_apply_current_layout (...) at gdb/tui/tui-layout.c:68
#27 0x0000000001028737 in tui_set_layout (layout=0x2c9b9e90) at gdb/tui/tui-layout.c:133
#28 0x0000000001028af5 in tui_set_initial_layout () at gdb/tui/tui-layout.c:209
#29 0x000000000104b795 in tui_enable () at gdb/tui/tui.c:496
#30 0x000000000104bab3 in tui_enable_command (args=0x0, from_tty=1) at gdb/tui/tui.c:591
#31 0x00000000006c5ffe in do_simple_func (args=0x0, from_tty=1, c=0x2c9bb2f0) at gdb/cli/cli-decode.c:94
#32 0x00000000006cc94f in cmd_func (cmd=0x2c9bb2f0, args=0x0, from_tty=1) at gdb/cli/cli-decode.c:2831
#33 0x0000000000fe53ad in execute_command (p=0x2c86699a "", from_tty=1) at gdb/top.c:563
#34 0x000000000094584d in command_handler (command=0x2c866990 "tui enable") at gdb/event-top.c:611
#35 0x0000000000945dfe in command_line_handler (rl=...) at gdb/event-top.c:844
#36 0x000000000101e916 in tui_command_line_handler (rl=...) at gdb/tui/tui-interp.c:101
#37 0x0000000000944eef in gdb_rl_callback_handler (rl=0x2cc86a30 "tui enable") at gdb/event-top.c:288
#38 0x0000000001175779 in rl_callback_read_char () at readline/readline/callback.c:302
#39 0x0000000000944bc3 in gdb_rl_callback_read_char_wrapper_sjlj () at gdb/event-top.c:197
#40 0x0000000000944cd4 in gdb_rl_callback_read_char_wrapper_noexcept () at gdb/event-top.c:240
#41 0x0000000000944d52 in gdb_rl_callback_read_char_wrapper (...) at gdb/event-top.c:252
#42 0x0000000001062352 in stdin_event_handler (error=0, client_data=0x2c865150) at gdb/ui.c:154
#43 0x0000000001a04edf in handle_file_event (file_ptr=0x2ccf8850, ready_mask=1) at gdbsupport/event-loop.cc:551
#44 0x0000000001a05522 in gdb_wait_for_event (block=1) at gdbsupport/event-loop.cc:672
#45 0x0000000001a043ff in gdb_do_one_event (mstimeout=-1) at gdbsupport/event-loop.cc:263
#46 0x00000000006d5480 in interp::do_one_event (this=0x2cc2af20, mstimeout=-1) at gdb/interps.h:93
#47 0x0000000000b77f25 in start_event_loop () at gdb/main.c:403
#48 0x0000000000b78113 in captured_command_loop () at gdb/main.c:468
#49 0x0000000000b7a07c in captured_main (context=0x7fff660b9e60) at gdb/main.c:1381
#50 0x0000000000b7a178 in gdb_main (args=0x7fff660b9e60) at gdb/main.c:1400
#51 0x0000000000419705 in main (argc=5, argv=0x7fff660b9f98) at gdb/gdb.c:38
(gdb)
The problem is that while the TUI is being enabled for the first time,
none of the TUI windows yet exist. As each window is created its
contents are rendered (i.e. filled in based on GDB's state), which for
some windows can trigger an interactive prompt, in this case a missing
source file triggers a debuginfod prompt while trying to render the
`src` window.
The interactive prompt will be written to the `cmd` window, but at
this point the `cmd` window has not yet been created.
There have been several different attempts to fix this issue:
1. https://inbox.sourceware.org/gdb-patches/20240312215334.37888-1-amerey@redhat.com
2. https://inbox.sourceware.org/gdb-patches/20260114172833.1824823-1-tdevries@suse.de
3. https://inbox.sourceware.org/gdb-patches/20260116104313.2704994-1-tdevries@suse.de
4. https://inbox.sourceware.org/gdb-patches/20260221101818.2678136-1-tdevries@suse.de
5. https://inbox.sourceware.org/gdb-patches/20260314173737.1436116-1-tdevries@suse.de
6. https://inbox.sourceware.org/gdb-patches/20260417075719.852558-1-tdevries@suse.de
The patch presented here is similar to what was presented in (3)
above, but I think the implementation is maybe a little simpler.
Additionally, all but (6) of the above patches don't address issues
related to using multi-key combinations like 'C-x C-a' to enable TUI
mode, and that patch just takes the (admittedly safe) approach of
preventing the user from activating the TUI using a multi-key
combination if debuginfod is in ASK mode (and so could trigger an
interactive prompt).
This patch doesn't address the multi-key problem, that is left for the
next patch in this series.
This patch ensures that the `cmd` window always exists before
rendering the windows (i.e. filling in their content). This is done
by introducing a tui_defer_rerender global which is set in tui_enable,
and checked in tui_win_info::resize. Then, prior to the prompt being
displayed, if the flag is set, we render the contents of all visible
windows. This can trigger a secondary prompt (e.g. the debuginfod
prompt), but by this point the `cmd` window exists, and can display
the prompt.
The debuginfod-query.exp test included here is based on the test Tom
de Vries wrote for one of his patches listed above, but extended to
cover some additional cases. The activate-with-key-combo.exp test is
new for this series.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31449
Co-Authored-By: Tom de Vries <tdevries@suse.de>
---
.../gdb.tui/activate-with-key-combo.c | 22 ++
.../gdb.tui/activate-with-key-combo.exp | 83 ++++++
gdb/testsuite/gdb.tui/debuginfod-query.c | 22 ++
gdb/testsuite/gdb.tui/debuginfod-query.exp | 245 ++++++++++++++++++
gdb/tui/tui-hooks.c | 21 +-
gdb/tui/tui-layout.c | 3 +-
gdb/tui/tui.c | 11 +
gdb/tui/tui.h | 5 +
8 files changed, 410 insertions(+), 2 deletions(-)
create mode 100644 gdb/testsuite/gdb.tui/activate-with-key-combo.c
create mode 100644 gdb/testsuite/gdb.tui/activate-with-key-combo.exp
create mode 100644 gdb/testsuite/gdb.tui/debuginfod-query.c
create mode 100644 gdb/testsuite/gdb.tui/debuginfod-query.exp
diff --git a/gdb/testsuite/gdb.tui/activate-with-key-combo.c b/gdb/testsuite/gdb.tui/activate-with-key-combo.c
new file mode 100644
index 00000000000..6a0e311ef41
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/activate-with-key-combo.c
@@ -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;
+}
diff --git a/gdb/testsuite/gdb.tui/activate-with-key-combo.exp b/gdb/testsuite/gdb.tui/activate-with-key-combo.exp
new file mode 100644
index 00000000000..b411c6b6b6e
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/activate-with-key-combo.exp
@@ -0,0 +1,83 @@
+# 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/>.
+
+# Exercise the multi-key combinations (e.g. 'C-x C-a') as a means to
+# activate TUI mode. After activating TUI mode check that GDB is
+# still responsive, and output shows up in the CMD window.
+
+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
+}
+
+# Use KEY to activate TUI mode, where KEY should be a multi-key
+# combination, e.g. 'C-x C-a' expressed as an escape sequence,
+# e.g. "\030\001". Wait for the GDB prompt to be displayed then type
+# a single character 'q'. Check that the character shows up on the
+# screen then cancel the 'q' with Ctrl-C. Finally send a basic print
+# command to GDB to ensure everything is still working and
+# responsive.
+proc run_test { key } {
+ Term::clean_restart 24 80 $::testfile
+
+ if {![Term::prepare_for_tui]} {
+ return
+ }
+
+ send_gdb $key
+
+ Term::wait_for ""
+
+ gdb_assert {[Term::check_region_contents_p 0 16 5 1 \
+ [string_to_regexp "(gdb)"]]} \
+ "check prompt is where we want it"
+
+ send_gdb "q"
+
+ gdb_assert {[Term::wait_for_region_contents 0 16 80 1 q]} \
+ "the 'q' key appears in the terminal"
+
+ # Send Ctrl-c, cancelling the 'q' we typed.
+ send_gdb "\003"
+
+ gdb_assert {[Term::wait_for_region_contents 0 16 80 1 Quit]} \
+ "cancel the partial command"
+
+ Term::command "print 1"
+
+ gdb_assert {[Term::check_region_contents_p 0 18 80 1 " = 1"]} \
+ "wait for output of the print command"
+
+ Term::dump_screen
+}
+
+# Use different multi-key combinations to trigger a switch to TUI mode.
+foreach mk { \030\001 \030a \030A \0302 \0301 \030o } {
+ with_test_prefix "using key '[unprintable_to_octal $mk]'" {
+ run_test $mk
+ }
+}
diff --git a/gdb/testsuite/gdb.tui/debuginfod-query.c b/gdb/testsuite/gdb.tui/debuginfod-query.c
new file mode 100644
index 00000000000..6a0e311ef41
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/debuginfod-query.c
@@ -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;
+}
diff --git a/gdb/testsuite/gdb.tui/debuginfod-query.exp b/gdb/testsuite/gdb.tui/debuginfod-query.exp
new file mode 100644
index 00000000000..5baf3e24e1f
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/debuginfod-query.exp
@@ -0,0 +1,245 @@
+# 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 a debuginfod query during TUI initialization doesn't cause a
+# crash.
+
+require allow_tui_tests
+
+load_lib debuginfod-support.exp
+require allow_debuginfod_tests
+
+tuiterm_env
+
+standard_testfile
+
+if { [build_executable "build executable" $testfile $srcfile] == -1 } {
+ return
+}
+
+if {[section_get $binfile ".gnu_debuglink"] ne ""} {
+ unsupported "debug information has already been split out"
+ return
+}
+
+# Assume that GDB is in TUI mode, in the SRC layout, and that a
+# debuginfod prompt is currently active in the CMD window.
+#
+# Send ANSWER, either 'y' or 'n' to the debuginfod prompt, and check
+# how the SRC window updates. Answer 'y' and debuginfod should
+# download the source code, which should be displayed in the SRC
+# window. Answer 'n' and the 'No Source Available' message should
+# appear in the SRC window.
+#
+# Finally, send a basic 'print' command to GDB to check that the CMD
+# window is still responsive.
+proc src_layout_answer_prompt_check_screen { answer } {
+ set re \
+ [string_to_regexp \
+ {(y or [n]) }]
+
+ send_gdb "${answer}\n"
+ Term::wait_for ""
+ set re "${re}${answer}"
+ gdb_assert { [Term::check_region_contents_p 0 15 80 9 $re] } \
+ "wait for answer to interactive prompt to show"
+
+ if { $answer eq "n" } {
+ gdb_assert { [Term::check_region_contents_p 0 15 80 9 \
+ "Debuginfod has been disabled\\."] } \
+ "debuginfod has been disabled"
+
+ gdb_assert { [Term::check_region_contents_p 1 1 78 13 \
+ "\\\[ No Source Available \\\]"] } \
+ "source code was not downloaded"
+ } else {
+ gdb_assert { [Term::check_region_contents_p 0 15 80 9 \
+ "Debuginfod has been enabled\\."] } \
+ "debuginfod has been enabled"
+
+ gdb_assert { [Term::check_region_contents_p 1 1 78 13 \
+ "main \\(void\\)"] } \
+ "source code was downloaded"
+ }
+
+ # Check that prompt is responsive.
+ gdb_assert { [Term::command "print 1"] } "responsive prompt"
+}
+
+# Basic setup before running a test. Disable debuginfod progress
+# bars, setup the substitute path so GDB cannot find the source code,
+# and must instead, use debuginfod to get the source code.
+proc setup_for_debuginfod_test {} {
+ # Hide progress bars when downloading from debuginfod.
+ gdb_test_no_output "set progress-bars enabled off"
+
+ # Ensure GDB cannot find the source file. If available the source
+ # can be fetched from debuginfod.
+ gdb_test_no_output "set directories"
+ gdb_test_no_output "set substitute-path $::srcdir /dev/null" \
+ "set substitute-path"
+
+ # Reset the height, and enable the pager. We don't expect to see
+ # any pagination prompts from these tests, but some bugs trigger
+ # the pager at the wrong time, and we want to expose those.
+ gdb_test_no_output "set pagination on"
+ gdb_test_no_output "set height 24"
+}
+
+
+# Trigger a switch to TUI mode based on HOW, which is either 'command'
+# or 'keys'. We expect to see an interactive prompt while TUI is
+# being enabled, to which we reply ANSWER (either 'y' or 'n').
+#
+# When FILL_SCREEN is true we emit lots of lines at the CLI prompt
+# before switching to the TUI, at one point this would cause GDB to
+# incorrectly print the pagination prompt when switching to the TUI
+# via a multi-key combination (e.g. C-x C-a).
+proc_with_prefix run_switch_with_prompt_test { how answer fill_screen } {
+ Term::clean_restart 24 80 $::testfile
+
+ setup_for_debuginfod_test
+
+ if {![Term::prepare_for_tui]} {
+ return
+ }
+
+ Term::gen_prompt
+
+ # Print lots of content to the screen, but not enough to trigger
+ # the pager. The critical thing here is that we must print more
+ # lines than will fit in the TUI's CMD window. If the pager is
+ # not reset when switching to TUI mode, the first output in the
+ # TUI CMD window will trigger the pager, which is not correct.
+ if { $fill_screen } {
+ set cmd "echo "
+ for { set i 0 } { $i < 20 } { incr i } {
+ set cmd "${cmd}${i}\\n"
+ }
+ gdb_test $cmd ".*" \
+ "echo lots of lines"
+ }
+
+ # Trigger the switch to TUI mode.
+ if { $how eq "command" } {
+ send_gdb "tui enable\n"
+ } elseif { $how eq "keys" } {
+ send_gdb "\030\001"
+ } else {
+ perror "unknown test mode: $how"
+ }
+
+ set re \
+ [string_to_regexp \
+ {(y or [n]) }]
+ gdb_assert { [Term::wait_for_region_contents 0 17 80 3 $re] } \
+ "wait for initial interactive prompt"
+
+ foreach mk { \030\001 \0302 \0301 \030o \030q \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]'"
+ }
+
+ src_layout_answer_prompt_check_screen $answer
+}
+
+# Switch into TUI mode, but directly into ASM layout using "layout
+# asm". As the SRC window is not visible no debuginfod prompt should
+# be displayed.
+#
+# Now switch to SRC layout. If HOW is 'command' the use 'layout next'
+# to select the SRC layout. If HOW is 'keys' then use 'C-x 2' which
+# also selects the next layout, which is the SRC layout.
+#
+# As the SRC window is now visible, we should see a debuginfod prompt.
+# Use ANSWER (either 'y' or 'n') to answer the prompt, and check how
+# the SRC window updates as a result.
+proc_with_prefix run_switch_to_asm_test { how answer } {
+ Term::clean_restart 24 80 $::testfile
+
+ setup_for_debuginfod_test
+
+ if {![Term::prepare_for_tui]} {
+ return
+ }
+
+ Term::gen_prompt
+
+ # Switching to ASM layout doesn't require the source file, so
+ # shouldn't trigger the debuginfod prompt.
+ send_gdb "layout asm\n"
+ Term::wait_for ""
+
+ # Now switching to the next layout, which should be the SRC
+ # layout. The SRC layout needs the source code, so this should
+ # trigger the debuginfod prompt.
+ if { $how eq "command" } {
+ send_gdb "layout next\n"
+ } elseif { $how eq "keys" } {
+ send_gdb "\0302"
+ } else {
+ perror "unknown test mode: $how"
+ }
+
+ set re \
+ [string_to_regexp \
+ {(y or [n]) }]
+ gdb_assert { [Term::wait_for_region_contents 0 18 80 3 $re] } \
+ "wait for debuginfod interactive prompt"
+
+ src_layout_answer_prompt_check_screen $answer
+}
+
+# Setup directory for the debuginfod server to serve from.
+set debugdir [standard_output_file "debug"]
+file copy -force $binfile $debugdir/
+
+# Create CACHE and DB directories ready for debuginfod to use.
+prepare_for_debuginfod cache db
+
+# Can add 'keys' to MODES list, but this doesn't currently
+# work due to PR gdb/33794. This will be fixed in the next
+# commit, and this comment removed.
+set modes {command}
+
+with_debuginfod_env $cache {
+ save_vars { env(DEBUGINFOD_URLS) } {
+ foreach_with_prefix how $modes {
+ foreach_with_prefix ans { n y } {
+ file delete -force $cache
+
+ set url [start_debuginfod $db $debugdir]
+ if { $url eq "" } {
+ unresolved "failed to start debuginfod server"
+ continue
+ }
+
+ # Point the client to the server.
+ setenv DEBUGINFOD_URLS $url
+
+ foreach_with_prefix fill_screen { true false } {
+ run_switch_with_prompt_test $how $ans $fill_screen
+ }
+ run_switch_to_asm_test $how $ans
+
+ stop_debuginfod
+ }
+ }
+ }
+}
diff --git a/gdb/tui/tui-hooks.c b/gdb/tui/tui-hooks.c
index 17ae8e7a590..0701dd3b698 100644
--- a/gdb/tui/tui-hooks.c
+++ b/gdb/tui/tui-hooks.c
@@ -167,7 +167,26 @@ tui_inferior_exit (struct inferior *inf)
static void
tui_before_prompt (const char *current_gdb_prompt)
{
- tui_refresh_frame_and_register_information ();
+ /* If TUI_DEFER_RERENDER is true then the window content will not
+ yet have been filled in, do so now. This is only expected to
+ happen during the switches into TUI mode as it means the CMD
+ window will exist by the time all the other windows have their
+ content filled in. If filling in one of the other windows
+ triggers a secondary prompt (e.g. debuginfod prompt) then the CMD
+ window will be available to display the prompt. */
+ if (tui_defer_rerender)
+ {
+ target_terminal::scoped_restore_terminal_state term_state;
+ target_terminal::ours_for_output ();
+
+ tui_defer_rerender = false;
+ for (tui_win_info *window : tui_windows)
+ if (window->is_visible ())
+ window->rerender ();
+ }
+ else
+ tui_refresh_frame_and_register_information ();
+
from_stack = false;
from_source_symtab = false;
}
diff --git a/gdb/tui/tui-layout.c b/gdb/tui/tui-layout.c
index 40db5f160ca..01208e05513 100644
--- a/gdb/tui/tui-layout.c
+++ b/gdb/tui/tui-layout.c
@@ -296,7 +296,8 @@ tui_win_info::resize (int height_, int width_,
if (handle == nullptr)
make_window ();
- rerender ();
+ if (!tui_defer_rerender)
+ rerender ();
}
\f
diff --git a/gdb/tui/tui.c b/gdb/tui/tui.c
index d2e9c1b4def..5da5a58a6fb 100644
--- a/gdb/tui/tui.c
+++ b/gdb/tui/tui.c
@@ -69,6 +69,9 @@ show_tui_debug (struct ui_file *file, int from_tty,
/* Tells whether the TUI is active or not. */
bool tui_active = false;
+/* See tui.h. */
+bool tui_defer_rerender = false;
+
/* Tells whether the TUI should do deferred curses initialization.
If TRIBOOL_TRUE, then yes. If TRIBOOL_FALSE. then no (because
initialization is already done). If TRIBOOL_UNKNOWN, then no (because
@@ -449,6 +452,14 @@ tui_enable (void)
tui_batch_rendering defer;
+ /* Defer filling in window contents at this time. The window
+ content will be filled in by calling rerender later. We do this
+ so that we can be sure the CMD window will exist as other windows
+ are rendered, filling in some windows might trigger a secondary
+ prompt (e.g. debuginfod prompt) and we want to be sure that the
+ CMD window exists to display the prompt in. */
+ tui_defer_rerender = true;
+
/* To avoid to initialize curses when gdb starts, there is a deferred
curses initialization. This initialization is made only once
and the first time the curses mode is entered. */
diff --git a/gdb/tui/tui.h b/gdb/tui/tui.h
index 7daefe2c9bb..b24fed2b322 100644
--- a/gdb/tui/tui.h
+++ b/gdb/tui/tui.h
@@ -96,4 +96,9 @@ extern void tui_set_key_mode (enum tui_key_mode mode);
extern bool tui_active;
+/* When true, defer rendering the window contents during the initial switch
+ to TUI mode. Each window's rerender method will be called later before
+ the prompt is displayed. */
+extern bool tui_defer_rerender;
+
#endif /* GDB_TUI_TUI_H */
--
2.25.4
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 6/6] gdb/tui: fix debuginfod prompt using 'C-x C-a' to enter TUI
2026-05-01 14:22 [PATCH 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
` (4 preceding siblings ...)
2026-05-01 14:22 ` [PATCH 5/6] gdb/tui: fix for debuginfod prompt while enabling the TUI Andrew Burgess
@ 2026-05-01 14:22 ` Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
6 siblings, 0 replies; 15+ messages in thread
From: Andrew Burgess @ 2026-05-01 14:22 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
This commit ties closely into the previous commit. The previous
commit looks at issues that can arise when using 'tui enable' to enter
TUI mode if a debuginfod prompt is triggered. This commit looks at
the problems that can arise when a multi-key combination is used to
enter TUI mode, e.g. 'C-x C-a'. Bug PR gdb/33794 discusses this
issue.
There has been a previous attempt to address this issue here:
https://inbox.sourceware.org/gdb-patches/20260417075719.852558-5-tdevries@suse.de
The approach taken in that patch was to prevent switching to TUI mode
if debuginfod is still in ASK mode, this means the switch could
potentially trigger a secondary prompt.
While the previous commit is relatively simple, the complexity in this
case arises from how multi-key combinations are handled by readline.
Currently global readline state is used to track the multi-key press
situation, and when the multi-key is dispatched back to application
(GDB) code, the globals are still live.
If GDB then triggers reentry into readline, e.g. by triggering a
secondary prompt, the call into readline for this prompt will cause
the global state to be released. When the secondary prompt is
finished and we return back to readline the global state will be
accessed, and undefined behaviour occurs, including crashes.
The core idea of my proposed solution to this is to move handling of
the multi-key actions out of the readline callback, and into GDB's
normal event loop.
When the user presses a combination like 'C-x C-a' this will call a
templated tui_rl_keybinding function as it currently does, but instead
of immediately forwarding to another function to carry out the TUI
changes, we instead schedule a callback with the event loop and then
return.
As far as readline is concerned the multi-key action has now been
dealt with, however, no interface changes have yet occurred. As
readline has now finished handling this key press, readline returns to
the event loop to get the next user input.
At the event loop the pending callback is seen and dispatched. This
callback triggers the actual UI changes, e.g. entering TUI mode. As
we are not inside readline at this point we are free to create
secondary prompts if needed.
In tui_rl_keybinding we use run_on_main_thread to schedule a callback
with the event loop, but there are some additional changes needed:
1. If we changed the tui_active state then we need to call
reinitialize_more_filter. Previously tui_rl_switch_mode would call
rl_newline which would make readline think that a command had been
fully entered, this would trigger a call to GDB's
command_line_handler, which calls command_handler, which then calls
reinitialize_more_filter.
For reasons explained below tui_rl_switch_mode can no longer call
rl_newline, so the reinitialize_more_filter is never reached. This
means that especially when switching CLI to TUI, when the `cmd`
window is smaller than the CLI terminal, GDB might enter TUI mode
thinking that the TUI is already full. This leads to incorrect
pager prompts appearing. Resolve this by explicitly resetting the
pager.
2. After changing the tui_active state (i.e. entering or leaving TUI
mode), there will not be a GDB prompt displayed. Under the old
scheme, the rl_newline call in tui_rl_switch_mode would trick GDB
into thinking an empty command had just been completed, this would
then trigger a prompt redisplay.
Under the new scheme we need to explicitly call display_gdb_prompt
or tui_redisplay_readline to redraw the prompt. However, as we
were at a GDB prompt already when the user pressed a key like 'C-x
C-a', the current_ui will not think that a prompt is needed, if we
plan to call display_gdb_prompt then we'll need to change the
prompt_state to PROMPT_NEEDED before calling display_gdb_prompt.
When possible we prefer calling tui_redisplay_readline, as this
preserves the current readline input line buffer contents, so if
the user types something at the prompt and then does 'C-x o' to
change window focus, the partially typed text is preserved.
Both of these additional actions need to be performed for both the
normal exit path, and the exception path in order that the prompt be
correctly displayed, so this code is done in a SCOPE_EXIT block.
The other set of changes are in tui_rl_switch_mode:
1. The calls to rl_prep_terminal are no longer needed as
display_gdb_prompt will take care of calling this for us if
appropriate (e.g. we are not in TUI mode).
2. The gdb_exception_forced_quit handling can now just propagate the
exception, We are no longer within a readline callback, and so can
throw this exception further up the stack.
3. Likewise with gdb_exception, we can re-throw this. As the
run_on_main_thread mechanism silently swallows all gdb_exceptions
except the gdb_exception_forced_quit sub-class, we do need to print
the exception ourselves first though. This is why we had to
separate out the gdb_exception_forced_quit handling.
4. The rl_kill_text call is no longer needed as the following
rl_newline call is going to be removed.
5. The rl_newline call was a neat trick to force a prompt redisplay,
but this only works when we are within a readline callback, it
injects a newline so that when we return from this callback
readline will see the pending newline character, process the now
empty line (thanks to the rl_kill_text call), and the print the
prompt. This is replaced by the display_gdb_prompt call that was
added to tui_rl_keybinding.
6. The dont_repeat call was needed because the rl_kill_text and
rl_newline calls were tricking readline into thinking the user had
pressed Enter on an empty line, this was done to force a prompt
redisplay.
However, pressing Enter on an empty line repeats the previous
command unless dont_repeat has been called. Now we don't use the
rl_newline trick, the dont_repeat call is not needed.
The gdb.tui/debuginfod-query.exp test is updated to include tests that
switch using multi-key combinations.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33794
---
gdb/testsuite/gdb.tui/debuginfod-query.exp | 5 +-
.../gdb.tui/tui-enable-failure-lib.c | 31 ++++
gdb/testsuite/gdb.tui/tui-enable-failure.c | 22 +++
gdb/testsuite/gdb.tui/tui-enable-failure.exp | 136 ++++++++++++++++++
gdb/tui/tui.c | 120 +++++++++++-----
5 files changed, 278 insertions(+), 36 deletions(-)
create mode 100644 gdb/testsuite/gdb.tui/tui-enable-failure-lib.c
create mode 100644 gdb/testsuite/gdb.tui/tui-enable-failure.c
create mode 100644 gdb/testsuite/gdb.tui/tui-enable-failure.exp
diff --git a/gdb/testsuite/gdb.tui/debuginfod-query.exp b/gdb/testsuite/gdb.tui/debuginfod-query.exp
index 5baf3e24e1f..b7727090c3e 100644
--- a/gdb/testsuite/gdb.tui/debuginfod-query.exp
+++ b/gdb/testsuite/gdb.tui/debuginfod-query.exp
@@ -213,10 +213,7 @@ file copy -force $binfile $debugdir/
# Create CACHE and DB directories ready for debuginfod to use.
prepare_for_debuginfod cache db
-# Can add 'keys' to MODES list, but this doesn't currently
-# work due to PR gdb/33794. This will be fixed in the next
-# commit, and this comment removed.
-set modes {command}
+set modes {command keys}
with_debuginfod_env $cache {
save_vars { env(DEBUGINFOD_URLS) } {
diff --git a/gdb/testsuite/gdb.tui/tui-enable-failure-lib.c b/gdb/testsuite/gdb.tui/tui-enable-failure-lib.c
new file mode 100644
index 00000000000..c267743fd2e
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/tui-enable-failure-lib.c
@@ -0,0 +1,31 @@
+/* Copyright 2026 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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/>. */
+
+/* Preload library that overrides ncurses newterm to always return NULL,
+ causing tui_enable to fail. */
+
+#include <stddef.h>
+
+/* Define this type so we can override newterm. We only plan to
+ return NULL, so the details of this type are not important. */
+typedef struct screen_dummy SCREEN;
+
+SCREEN *
+newterm (const char *type, void *outfd, void *infd)
+{
+ return NULL;
+}
diff --git a/gdb/testsuite/gdb.tui/tui-enable-failure.c b/gdb/testsuite/gdb.tui/tui-enable-failure.c
new file mode 100644
index 00000000000..1dc4b7ca539
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/tui-enable-failure.c
@@ -0,0 +1,22 @@
+/* Copyright 2026 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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;
+}
diff --git a/gdb/testsuite/gdb.tui/tui-enable-failure.exp b/gdb/testsuite/gdb.tui/tui-enable-failure.exp
new file mode 100644
index 00000000000..5ad2eca3d03
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/tui-enable-failure.exp
@@ -0,0 +1,136 @@
+# 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/>.
+
+# Test that GDB handles tui_enable failure gracefully.
+#
+# Use an LD_PRELOAD library to override ncurses' newterm function,
+# making it always return NULL. This causes tui_enable to fail with
+# an error.
+#
+# Test both the "tui enable" command and the multi-key combination
+# (C-x C-a) paths. The multi-key path is particularly important as it
+# exercises the tui_rl_keybinding SCOPE_EXIT error handling.
+
+require allow_tui_tests
+require {!is_remote host}
+require {istarget *-linux*}
+
+tuiterm_env
+
+standard_testfile .c -lib.c
+
+set libfile ${testfile}-lib
+set libobj [standard_output_file ${libfile}.so]
+
+# Compile the preload library.
+if { [build_executable "build preload lib" $libobj $srcfile2 \
+ {debug shlib}] == -1 } {
+ return
+}
+
+# Compile the test executable.
+if { [build_executable "build executable" $testfile $srcfile] == -1 } {
+ return
+}
+
+# Send 'print NUM' to GDB and check the output appear on LINE.
+proc check_responsiveness { num line } {
+ gdb_assert { [Term::command "print $num"] } \
+ "send print command"
+
+ gdb_assert { [Term::check_region_contents_p 0 $line 80 1 " = $num"] } \
+ "check print output is in expected location"
+}
+
+# Try to enable TUI mode using HOW, which is either 'command' or
+# 'keys'. The preload library ensures that newterm returns NULL, so
+# tui_enable will fail. Check that an error is produced and that GDB
+# remains responsive.
+#
+# The first failure sets tui_finish_init to TRIBOOL_UNKNOWN, so a
+# second attempt produces a different (generic) error message. Try
+# the second attempt too, and check that GDB is still responsive.
+#
+# The 'keys' path (C-x C-a) is particularly important as it exercises
+# the tui_rl_keybinding SCOPE_EXIT error handling.
+proc_with_prefix run_test { how } {
+ Term::clean_restart 24 80 $::testfile
+
+ if {![Term::prepare_for_tui]} {
+ return
+ }
+
+ Term::gen_prompt
+
+ # First attempt to enable TUI.
+ if { $how eq "command" } {
+ send_gdb "tui enable\n"
+ set line_offset 1
+ } elseif { $how eq "keys" } {
+ send_gdb "\030\001"
+ set line_offset 0
+ } else {
+ perror "unknown test mode: $how"
+ }
+
+ gdb_assert { [Term::wait_for "Cannot enable the TUI: error opening terminal"] } \
+ "error from first tui enable attempt"
+
+ # Check that GDB is still responsive.
+ with_test_prefix "responsive after first failure" {
+ check_responsiveness 1 [expr {2 + $line_offset}]
+ }
+
+ # Second attempt using 'tui enable' command should also fail.
+ send_gdb "tui enable\n"
+ gdb_assert { [Term::wait_for "Cannot enable the TUI"] } \
+ "error from second tui enable attempt"
+
+ # Check that GDB is still responsive.
+ with_test_prefix "responsive after second failure" {
+ check_responsiveness 2 [expr {6 + $line_offset}]
+ }
+
+ # Third attempt using multi-key combo should also fail.
+ send_gdb "\030\001"
+ gdb_assert { [Term::wait_for "Cannot enable the TUI"] } \
+ "error from third tui enable attempt"
+
+ # Check that GDB is still responsive.
+ with_test_prefix "responsive after third failure" {
+ check_responsiveness 3 [expr {9 + $line_offset}]
+ }
+
+ verbose -log "\n\n\nAPB: Dump:\n\n"
+ Term::dump_screen
+}
+
+set modes {command keys}
+
+save_vars { env(LD_PRELOAD) env(ASAN_OPTIONS) } {
+ if { ![info exists env(LD_PRELOAD)]
+ || $env(LD_PRELOAD) == "" } {
+ set env(LD_PRELOAD) "$libobj"
+ } else {
+ append env(LD_PRELOAD) ":$libobj"
+ }
+
+ # Prevent address sanitizer error about library ordering.
+ append_environment_default ASAN_OPTIONS verify_asan_link_order 0
+
+ foreach_with_prefix how $modes {
+ run_test $how
+ }
+}
diff --git a/gdb/tui/tui.c b/gdb/tui/tui.c
index 5da5a58a6fb..bf83e9c3fdf 100644
--- a/gdb/tui/tui.c
+++ b/gdb/tui/tui.c
@@ -42,6 +42,7 @@
#include "top.h"
#include "ui.h"
#include "observable.h"
+#include "run-on-main-thread.h"
#include <fcntl.h>
@@ -119,15 +120,20 @@ 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. */
+ /* This function is called through the run_on_main_thread event loop
+ callback mechanism. That mechanism propagates
+ gdb_exception_forced_quit exceptions, but silently discards
+ gdb_exception exceptions. We catch and print gdb_exception
+ exceptions before propagating them, the run_on_main_thread
+ mechanism will then discard these and return to the event loop.
+ Any RAII cleanup between here and there will have been done,
+ which is important. For gdb_exception_forced_quit exceptions we
+ just propagate these up the stack without printing, these will be
+ handled when they are caught by the event loop. */
try
{
if (tui_active)
- {
- tui_disable ();
- rl_prep_terminal (0);
- }
+ tui_disable ();
else
{
/* If we type "foo", entering it into the readline buffer
@@ -143,43 +149,24 @@ tui_rl_switch_mode (int notused1 = 0, int notused2 = 0)
TUI. */
rl_clear_visible_line ();
- /* If tui_enable throws, we'll re-prep below. */
+ /* Disable readline state ahead of enabling TUI mode. If
+ tui_enable fails then the next display_gdb_prompt will
+ re-prep the terminal for us. */
rl_deprep_terminal ();
+
tui_enable ();
}
}
catch (const gdb_exception_forced_quit &ex)
{
- /* Ideally, we'd do a 'throw' here, but as noted above, we can't
- do that, so, instead, we'll set the necessary flags so that
- a later QUIT check will restart the forced quit. */
- set_force_quit_flag ();
+ throw;
}
catch (const gdb_exception &ex)
{
exception_print (gdb_stderr, ex);
-
- if (!tui_active)
- rl_prep_terminal (0);
+ throw;
}
- /* Clear the readline in case switching occurred in middle of
- something. */
- if (rl_end)
- rl_kill_text (0, rl_end);
-
- /* Since we left the curses mode, the terminal mode is restored to
- some previous state. That state may not be suitable for readline
- to work correctly (it may be restored in line mode). We force an
- exit of the current readline so that readline is re-entered and
- it will be able to setup the terminal for its needs. By
- re-entering in readline, we also redisplay its prompt in the
- non-curses mode. */
- rl_newline (1, '\n');
-
- /* Make sure the \n we are returning does not repeat the last
- command. */
- dont_repeat ();
return 0;
}
@@ -334,7 +321,76 @@ tui_rl_keybinding (int count, int key)
if (gdb_in_secondary_prompt_p (current_ui))
return 0;
- return FPTR (count, key);
+ run_on_main_thread ([=] () {
+ bool was_active = tui_active;
+
+ /* Cleanup required even on the exception path. */
+ SCOPE_EXIT {
+ /* If we switched from CLI to TUI (or back) then we should
+ reinitialize the pager in order to avoid spurious pagination
+ prompts. This is especially important going from CLI to TUI
+ where the command window is usually smaller than the full
+ terminal, so the pager might already think that we have more
+ lines printed than will fit in the window, despite the window
+ starting empty after a mode switch. */
+ if (was_active != tui_active)
+ reinitialize_more_filter ();
+
+ /* The user was at a prompt and pressed a multi-key combination
+ (e.g. C-x C-a). As a result this callback was invoked from
+ the event loop. We're now exiting this callback and want to
+ ensure that the prompt is drawn correctly.
+
+ We have two approaches, full display_gdb_prompt, or a light
+ weight tui_redisplay_readline. The former will clear the
+ readline input buffer and redisplay the prompt, while the
+ second will redraw the prompt along with anything in the
+ input buffer. We want the light weight option where
+ possible, but there are times when this isn't an option:
+
+ 1. A successful switch between CLI and TUI, in either
+ direction, changes how we update the prompt. We need to
+ call display_gdb_prompt the first time to ensure
+ everything is done correctly. We also need to consider
+ the case where tui_enable fails, leaving us in CLI mode,
+ this also requires a call to display_gdb_prompt as the
+ light weight tui_redisplay_readline is not appropriate for
+ CLI use.
+
+ 2. Usually the TUI will have the RL_STATE_CALLBACK state flag
+ set because of when this event callback is called. If a
+ secondary prompt has been displayed, then once the
+ secondary prompt completed, the RL_STATE_CALLBACK flag
+ will have been cleared. This is good for us, because
+ after a secondary prompt rl_prompt will still hold the
+ secondary prompt string, so we need display_gdb_prompt to
+ set the correct top-level prompt. */
+ if (!was_active || !tui_active || !RL_ISSTATE (RL_STATE_CALLBACK))
+ {
+ current_ui->prompt_state = PROMPT_NEEDED;
+ display_gdb_prompt (nullptr);
+ }
+ else
+ {
+ /* If we switched TUI to CLI then we took the above block.
+ If we're in CLI mode then these callbacks will switch us
+ back to TUI mode. Either way, if we get here then the
+ TUI must be active. */
+ gdb_assert (tui_active);
+
+ /* The only time rl_prompt will be NULL is when we switch
+ from CLI to TUI, but that will be handled by the block
+ above. */
+ gdb_assert (rl_prompt != nullptr);
+
+ tui_redisplay_readline ();
+ }
+ };
+
+ (void) FPTR (count, key);
+ });
+
+ return 0;
}
/* Initialize readline and configure the keymap for the switching
--
2.25.4
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCHv2 0/6] gdb/tui: fix debuginfod related crash
2026-05-01 14:22 [PATCH 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
` (5 preceding siblings ...)
2026-05-01 14:22 ` [PATCH 6/6] gdb/tui: fix debuginfod prompt using 'C-x C-a' to enter TUI Andrew Burgess
@ 2026-07-10 9:47 ` Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 1/6] gdb/tui: convert a window handle `if` into an `assert` Andrew Burgess
` (6 more replies)
6 siblings, 7 replies; 15+ messages in thread
From: Andrew Burgess @ 2026-07-10 9:47 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
This series is an alternative to the latest patches proposed here:
https://inbox.sourceware.org/gdb-patches/20260417075719.852558-1-tdevries@suse.de
Patches #1, #2, and #3 cover changes that are not part of the above
series, I suspect these will be reasonably non-contentious.
Patch #4 is a small refactor to support the later patches.
Patches #5 and #6 are my take on solving the problem covered by the
above series.
In v2:
- Rebase to current HEAD.
- Minor testsuite tweaks to avoid possible race condition where GDB
might not spot if the test failed.
- Some improvements to comments and commits messages to make things
clearer.
- Retested.
Thanks,
Andrew
---
Andrew Burgess (6):
gdb/tui: convert a window handle `if` into an `assert`
gdb/testsuite: fix tuiterm linefeed scrolling new line content
gdb/tui: prevent TUI activation from a secondary prompt
gdb/tui: make tui_win_info::rerender public
gdb/tui: fix for debuginfod prompt while enabling the TUI
gdb/tui: fix debuginfod prompt using 'C-x C-a' to enter TUI
.../gdb.tui/activate-from-secondary-prompt.c | 22 ++
.../activate-from-secondary-prompt.exp | 179 ++++++++++++
.../gdb.tui/activate-with-key-combo.c | 22 ++
.../gdb.tui/activate-with-key-combo.exp | 83 ++++++
gdb/testsuite/gdb.tui/debuginfod-query.c | 22 ++
gdb/testsuite/gdb.tui/debuginfod-query.exp | 267 ++++++++++++++++++
.../gdb.tui/tui-enable-failure-lib.c | 32 +++
gdb/testsuite/gdb.tui/tui-enable-failure.c | 22 ++
gdb/testsuite/gdb.tui/tui-enable-failure.exp | 133 +++++++++
gdb/testsuite/gdb.tui/tuiterm.exp | 2 +-
gdb/testsuite/lib/gdb.exp | 27 ++
gdb/testsuite/lib/tuiterm.exp | 1 +
gdb/tui/tui-data.h | 12 +-
gdb/tui/tui-hooks.c | 21 +-
gdb/tui/tui-layout.c | 3 +-
gdb/tui/tui-regs.h | 4 +-
gdb/tui/tui-wingeneral.c | 13 +-
gdb/tui/tui-winsource.c | 2 +
gdb/tui/tui-winsource.h | 4 +-
gdb/tui/tui.c | 203 +++++++++----
gdb/tui/tui.h | 5 +
21 files changed, 1007 insertions(+), 72 deletions(-)
create mode 100644 gdb/testsuite/gdb.tui/activate-from-secondary-prompt.c
create mode 100644 gdb/testsuite/gdb.tui/activate-from-secondary-prompt.exp
create mode 100644 gdb/testsuite/gdb.tui/activate-with-key-combo.c
create mode 100644 gdb/testsuite/gdb.tui/activate-with-key-combo.exp
create mode 100644 gdb/testsuite/gdb.tui/debuginfod-query.c
create mode 100644 gdb/testsuite/gdb.tui/debuginfod-query.exp
create mode 100644 gdb/testsuite/gdb.tui/tui-enable-failure-lib.c
create mode 100644 gdb/testsuite/gdb.tui/tui-enable-failure.c
create mode 100644 gdb/testsuite/gdb.tui/tui-enable-failure.exp
base-commit: 32a7e94f84a3df0bcd08a2835e2c316f5ed07f77
--
2.25.4
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCHv2 1/6] gdb/tui: convert a window handle `if` into an `assert`
2026-07-10 9:47 ` [PATCHv2 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
@ 2026-07-10 9:47 ` Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 2/6] gdb/testsuite: fix tuiterm linefeed scrolling new line content Andrew Burgess
` (5 subsequent siblings)
6 siblings, 0 replies; 15+ messages in thread
From: Andrew Burgess @ 2026-07-10 9:47 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
It should only be possible to call tui_win_info::refresh_window on a
window with a valid handle member. To do otherwise would suggest
we're trying to draw to the screen a window which GDB doesn't think is
part of the current layout, which is just wrong.
Currently tui_win_info::refresh_window guards its content with an
`if (handle != NULL)`, but this can be changed to an assert.
A similar assert can be added to
tui_source_window_base::refresh_window, there's no `if` in this
function, which only backs up the reasoning in the first paragraph.
There should be no user-visible changes after this commit.
---
gdb/tui/tui-wingeneral.c | 13 ++++++-------
gdb/tui/tui-winsource.c | 2 ++
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/gdb/tui/tui-wingeneral.c b/gdb/tui/tui-wingeneral.c
index 149ca4efc6e..281c56d475e 100644
--- a/gdb/tui/tui-wingeneral.c
+++ b/gdb/tui/tui-wingeneral.c
@@ -55,13 +55,12 @@ tui_batch_rendering::~tui_batch_rendering ()
void
tui_win_info::refresh_window ()
{
- if (handle != NULL)
- {
- if (suppress_output)
- wnoutrefresh (handle.get ());
- else
- wrefresh (handle.get ());
- }
+ gdb_assert (handle != nullptr);
+
+ if (suppress_output)
+ wnoutrefresh (handle.get ());
+ else
+ wrefresh (handle.get ());
}
/* Draw a border around the window. */
diff --git a/gdb/tui/tui-winsource.c b/gdb/tui/tui-winsource.c
index e3f64892e27..c7149ff7c80 100644
--- a/gdb/tui/tui-winsource.c
+++ b/gdb/tui/tui-winsource.c
@@ -310,6 +310,8 @@ tui_source_window_base::refresh_window ()
{
TUI_SCOPED_DEBUG_START_END ("window `%s`", name ());
+ gdb_assert (handle != nullptr);
+
/* tui_win_info::refresh_window would draw the empty background window to
the screen, potentially creating a flicker. */
wnoutrefresh (handle.get ());
--
2.25.4
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCHv2 2/6] gdb/testsuite: fix tuiterm linefeed scrolling new line content
2026-07-10 9:47 ` [PATCHv2 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 1/6] gdb/tui: convert a window handle `if` into an `assert` Andrew Burgess
@ 2026-07-10 9:47 ` Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 3/6] gdb/tui: prevent TUI activation from a secondary prompt Andrew Burgess
` (4 subsequent siblings)
6 siblings, 0 replies; 15+ messages in thread
From: Andrew Burgess @ 2026-07-10 9:47 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
I came across a bug in the implementation of line feed in tuiterm.
Consider the gdb.tui/tuiterm.exp test 'test_linefeed_scroll', before
sending the line feed we have:
Screen Dump (size 8 columns x 4 rows, cursor at column 0, row 3):
0 abcdefgh
1 ijklmnop
2 qrstuvwx
3 yz01234
and after sending the line feed we have:
Screen Dump (size 8 columns x 4 rows, cursor at column 0, row 3):
0 ijklmnop
1 qrstuvwx
2 yz01234
3 yz01234
Notice that the new line #3 retains its previous contents, all lines
have scrolled up, with the old line #0 having been moved off the
terminal, but the new line is starting with these cloned contents.
I don't believe this is correct. My understanding is that new lines
should be created empty -- or really full of space characters.
After fixing this issue so that new lines are created empty, the only
test failure is the tuiterm.exp unit test mentioned above, this was
added in commit:
commit e20baea1298d2227db953862d131d9bbf91cf522
Date: Mon May 29 22:11:05 2023 +0200
[gdb/testsuite] Fix linefeed scrolling in tuiterm
This commit is fixing an issue with cursor placement after a scroll,
there is no mention of the content of the new line, which makes me
think that the test is just checking whatever behaviour used to be
there.
In this commit I think we should fix the new line content, and update
the existing unit test to match the new behaviour.
---
gdb/testsuite/gdb.tui/tuiterm.exp | 2 +-
gdb/testsuite/lib/tuiterm.exp | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/gdb/testsuite/gdb.tui/tuiterm.exp b/gdb/testsuite/gdb.tui/tuiterm.exp
index ccf26195ddc..b6330b0d6a8 100644
--- a/gdb/testsuite/gdb.tui/tuiterm.exp
+++ b/gdb/testsuite/gdb.tui/tuiterm.exp
@@ -152,7 +152,7 @@ proc test_linefeed_scroll { } {
"ijklmnop"
"qrstuvwx"
"yz01234 "
- "yz01234 "
+ " "
} 0 3
Term::dump_screen
}
diff --git a/gdb/testsuite/lib/tuiterm.exp b/gdb/testsuite/lib/tuiterm.exp
index e889e3b931b..d23e29d1a6d 100644
--- a/gdb/testsuite/lib/tuiterm.exp
+++ b/gdb/testsuite/lib/tuiterm.exp
@@ -165,6 +165,7 @@ proc Term::_ctl_0x0a {} {
}
incr _cur_row -1
+ _clear_lines $_cur_row $_rows
}
}
}
--
2.25.4
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCHv2 3/6] gdb/tui: prevent TUI activation from a secondary prompt
2026-07-10 9:47 ` [PATCHv2 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 1/6] gdb/tui: convert a window handle `if` into an `assert` Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 2/6] gdb/testsuite: fix tuiterm linefeed scrolling new line content Andrew Burgess
@ 2026-07-10 9:47 ` Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 4/6] gdb/tui: make tui_win_info::rerender public Andrew Burgess
` (3 subsequent siblings)
6 siblings, 0 replies; 15+ messages in thread
From: Andrew Burgess @ 2026-07-10 9:47 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
The TUI can be activated with key combinations like 'C-x C-a'. This
is handled by readline calling the function tui_rl_switch_mode, or
various other functions which indirectly call that function.
These multi-key combinations can be used even at a secondary prompt,
e.g. the:
Make breakpoint pending on future shared library load? (y or [n])
The problem with this is that when the TUI activates CLI content
doesn't carry over into the `cmd` window, so the secondary prompt is
not visible to the user after the mode switch. Worse, because the
content doesn't carry over we clear the readline state, and this
involves sending a '\n' to readline. This newline will select the
default action at the secondary prompt, which might not be what the
user actually wants.
Now, we could imagine trying to "fix" this so that the CLI content is
copied over into the `cmd` window, and the secondary prompt is
represented to the user, so they can then make the choice they want,
but implementing this fix would be a big job, for very little gain.
I think it is easier to just prevent the user switching to TUI mode
while at a secondary prompt.
I created a new templated wrapper function tui_rl_keybinding, which is
then used to wrap every function that is bound to a readline multi-key
combination. The wrapper function checks if we are in a secondary
prompt, and if we are, performs an early return.
For completeness, I've added an assert that we are not in a secondary
prompt to all of the wrapped functions, this (hopefully) will help
catch cases where these functions are called directly without going
through the wrapper. I also added the same assert to
tui_rl_command_key and tui_rl_command_mode which are not themselves
wrapped functions, but are only used when in single key mode, and it
is not possible to enter single key mode when at a separate prompt,
see tui_rl_startup_hook (which checks for being at a secondary prompt)
and tui_rl_next_keymap (which is wrapped).
There's a new helper proc added to lib/gdb.exp, this will be used by
additional tests later in this series.
The user can still switch to TUI mode at the primary '(gdb)' prompt.
---
.../gdb.tui/activate-from-secondary-prompt.c | 22 +++
.../activate-from-secondary-prompt.exp | 179 ++++++++++++++++++
gdb/testsuite/lib/gdb.exp | 27 +++
gdb/tui/tui.c | 74 +++++---
4 files changed, 280 insertions(+), 22 deletions(-)
create mode 100644 gdb/testsuite/gdb.tui/activate-from-secondary-prompt.c
create mode 100644 gdb/testsuite/gdb.tui/activate-from-secondary-prompt.exp
diff --git a/gdb/testsuite/gdb.tui/activate-from-secondary-prompt.c b/gdb/testsuite/gdb.tui/activate-from-secondary-prompt.c
new file mode 100644
index 00000000000..6a0e311ef41
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/activate-from-secondary-prompt.c
@@ -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;
+}
diff --git a/gdb/testsuite/gdb.tui/activate-from-secondary-prompt.exp b/gdb/testsuite/gdb.tui/activate-from-secondary-prompt.exp
new file mode 100644
index 00000000000..3bfc7b1156d
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/activate-from-secondary-prompt.exp
@@ -0,0 +1,179 @@
+# 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
+
+ # It's not easy to check that pressing a key doesn't cause the
+ # display to change. If we read the output then either the
+ # display didn't change, or we read the display to quickly,
+ # and the changes haven't been drawn yet.
+ #
+ # We could just throw a 'sleep 1' in here, which will mostly
+ # work, but still depends on GDB responding fast enough.
+ #
+ # To avoid this race we send the string '123' to GDB. As
+ # GDB's UI is single threaded, by the time '123' is drawn then
+ # any effects from the multi-key combination should also have
+ # been drawn.
+ #
+ # We can then delete the '123' by sending 'C-u' to clear the
+ # line, and after this we can check that the display is
+ # exactly as it was before we started, this means that the
+ # multi-key combination didn't result in a UI change.
+
+ set a_string 123
+ send_gdb $a_string
+ Term::wait_for_region_contents 0 0 80 24 $a_string
+
+ send_gdb "\025"
+ Term::wait_for_region_contents 0 0 80 24 [string_to_regexp $before]
+
+ 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
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index a19e8ba6729..a2972e767be 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -12154,6 +12154,33 @@ 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.
+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.
diff --git a/gdb/tui/tui.c b/gdb/tui/tui.c
index 958fc47b7b6..4c61e4b879c 100644
--- a/gdb/tui/tui.c
+++ b/gdb/tui/tui.c
@@ -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,8 @@ 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 +280,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 +321,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 +349,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 +391,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 ();
--
2.25.4
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCHv2 4/6] gdb/tui: make tui_win_info::rerender public
2026-07-10 9:47 ` [PATCHv2 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
` (2 preceding siblings ...)
2026-07-10 9:47 ` [PATCHv2 3/6] gdb/tui: prevent TUI activation from a secondary prompt Andrew Burgess
@ 2026-07-10 9:47 ` Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 5/6] gdb/tui: fix for debuginfod prompt while enabling the TUI Andrew Burgess
` (2 subsequent siblings)
6 siblings, 0 replies; 15+ messages in thread
From: Andrew Burgess @ 2026-07-10 9:47 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
In the next commit I'm going to want to call rerender from outside the
tui_win_info class, so let's make it public.
This is just a refactor, there should be no user-visible changes after
this commit.
---
gdb/tui/tui-data.h | 12 +++++++-----
gdb/tui/tui-regs.h | 4 ++--
gdb/tui/tui-winsource.h | 4 ++--
3 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/gdb/tui/tui-data.h b/gdb/tui/tui-data.h
index ec3cf63a286..cbe451ae86c 100644
--- a/gdb/tui/tui-data.h
+++ b/gdb/tui/tui-data.h
@@ -44,10 +44,6 @@ struct tui_win_info
tui_win_info () = default;
DISABLE_COPY_AND_ASSIGN (tui_win_info);
- /* This is called after the window is resized, and should update the
- window's contents. */
- virtual void rerender ();
-
/* Create the curses window. */
void make_window ();
@@ -55,7 +51,13 @@ struct tui_win_info
tui_win_info (tui_win_info &&) = default;
virtual ~tui_win_info () = default;
- /* Call to refresh this window. */
+ /* Call to update the in-memory contents of this window. Does not
+ cause the contents to be drawn to the screen. */
+ virtual void rerender ();
+
+ /* Call to refresh this window on the screen. The in-memory contents of
+ the window are not updated by this call; whatever the current contents
+ are, they are drawn to the screen. */
virtual void refresh_window ();
/* Make this window visible or invisible. */
diff --git a/gdb/tui/tui-regs.h b/gdb/tui/tui-regs.h
index ec056e1a042..2ed4614976f 100644
--- a/gdb/tui/tui-regs.h
+++ b/gdb/tui/tui-regs.h
@@ -90,6 +90,8 @@ struct tui_data_window : public tui_win_info
return m_current_group;
}
+ void rerender () override;
+
protected:
void do_scroll_vertical (int num_to_scroll) override;
@@ -97,8 +99,6 @@ struct tui_data_window : public tui_win_info
{
}
- void rerender () override;
-
private:
/* Display the registers in the content from 'start_element_no'
diff --git a/gdb/tui/tui-winsource.h b/gdb/tui/tui-winsource.h
index 9f94a453749..2353f04915a 100644
--- a/gdb/tui/tui-winsource.h
+++ b/gdb/tui/tui-winsource.h
@@ -103,8 +103,6 @@ struct tui_source_window_base : public tui_win_info
/* Erase the content and display STRING. */
void do_erase_source_content (const char *string);
- void rerender () override;
-
virtual bool set_contents (struct gdbarch *gdbarch,
const struct symtab_and_line &sal) = 0;
@@ -138,6 +136,8 @@ struct tui_source_window_base : public tui_win_info
public:
+ void rerender () override;
+
/* Refill the source window's source cache and update it. If this
is a disassembly window, then just update it. */
void refill ();
--
2.25.4
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCHv2 5/6] gdb/tui: fix for debuginfod prompt while enabling the TUI
2026-07-10 9:47 ` [PATCHv2 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
` (3 preceding siblings ...)
2026-07-10 9:47 ` [PATCHv2 4/6] gdb/tui: make tui_win_info::rerender public Andrew Burgess
@ 2026-07-10 9:47 ` Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 6/6] gdb/tui: fix debuginfod prompt using 'C-x C-a' to enter TUI Andrew Burgess
2026-07-27 15:50 ` [PATCHv2 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
6 siblings, 0 replies; 15+ messages in thread
From: Andrew Burgess @ 2026-07-10 9:47 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess, Tom de Vries
PR tui/31449 reports a SIGFPE when the debuginfod query happens while
enabling TUI using the "tui enable" command:
Thread 1 "gdb" received signal SIGFPE, Arithmetic exception.
0x0000000001021084 in tui_inject_newline_into_command_window () at /data/vries/gdb/src/gdb/tui/tui-io.c:1096
1096 py += px / tui_cmd_win ()->width;
due to divide-by-zero because tui_cmd_win ()->width == 0.
The corresponding backtrace is:
(gdb) bt
#0 0x0000000001021084 in tui_inject_newline_into_command_window () at gdb/tui/tui-io.c:1096
#1 0x0000000000fe65fd in gdb_readline_wrapper_line (line=...) at gdb/top.c:939
#2 0x0000000000944eef in gdb_rl_callback_handler (rl=0x2cc865a0 "n") at gdb/event-top.c:288
#3 0x0000000001175779 in rl_callback_read_char () at readline/readline/callback.c:302
#4 0x0000000000944bc3 in gdb_rl_callback_read_char_wrapper_sjlj () at gdb/event-top.c:197
#5 0x0000000000944cd4 in gdb_rl_callback_read_char_wrapper_noexcept () at gdb/event-top.c:240
#6 0x0000000000944d52 in gdb_rl_callback_read_char_wrapper (...) at gdb/event-top.c:252
#7 0x0000000001062352 in stdin_event_handler (error=0, client_data=0x2c865150) at gdb/ui.c:154
#8 0x0000000001a04edf in handle_file_event (file_ptr=0x2ccf8850, ready_mask=1) at gdbsupport/event-loop.cc:551
#9 0x0000000001a05522 in gdb_wait_for_event (block=1) at gdbsupport/event-loop.cc:672
#10 0x0000000001a043ff in gdb_do_one_event (mstimeout=-1) at gdbsupport/event-loop.cc:263
#11 0x00000000006d5480 in interp::do_one_event (this=0x2cc2af20, mstimeout=-1) at gdb/interps.h:93
#12 0x0000000000fe670d in gdb_readline_wrapper (prompt=0x2ccca4e0 "Enable debuginfod for this session? (y or [n]) ") at gdb/top.c:1033
#13 0x00000000010c6853 in defaulted_query(...) (...) at gdb/utils.c:844
#14 0x00000000010c6b8a in nquery (...) at gdb/utils.c:901
#15 0x00000000007a9324 in debuginfod_is_enabled () at gdb/debuginfod-support.c:268
#16 0x00000000007a950d in debuginfod_source_query (...) at gdb/debuginfod-support.c:311
#17 0x0000000000efc2c7 in open_source_file (s=0x2cc8f4b0) at gdb/source.c:1152
#18 0x0000000000efc619 in symtab_to_fullname (...) at gdb/source.c:1214
#19 0x0000000000f5ebb3 in find_line_symtab (...) at gdb/symtab.c:3287
#20 0x0000000000f5f0e5 in find_pc_for_line (...) at gdb/symtab.c:3391
#21 0x0000000001011f54 in tui_get_begin_asm_address (...) at gdb/tui/tui-disasm.c:404
#22 0x000000000104888d in tui_source_window_base::rerender (this=0x2cbdc570) at gdb/tui/tui-winsource.c:474
#23 0x0000000001028e81 in tui_win_info::resize (this=0x2cbdc570, height_=21, width_=127, origin_x_=0, origin_y_=0) at gdb/tui/tui-layout.c:299
#24 0x00000000010297d0 in tui_layout_window::apply (this=0x2cc50350, x_=0, y_=0, width_=127, height_=21, preserve_cmd_win_size_p=false) at gdb/tui/tui-layout.c:432
#25 0x000000000102bfea in tui_layout_split::apply (this=0x2caea920, x_=0, y_=0, width_=127, height_=33, preserve_cmd_win_size_p=false) at gdb/tui/tui-layout.c:1026
#26 0x0000000001028267 in tui_apply_current_layout (...) at gdb/tui/tui-layout.c:68
#27 0x0000000001028737 in tui_set_layout (layout=0x2c9b9e90) at gdb/tui/tui-layout.c:133
#28 0x0000000001028af5 in tui_set_initial_layout () at gdb/tui/tui-layout.c:209
#29 0x000000000104b795 in tui_enable () at gdb/tui/tui.c:496
#30 0x000000000104bab3 in tui_enable_command (args=0x0, from_tty=1) at gdb/tui/tui.c:591
#31 0x00000000006c5ffe in do_simple_func (args=0x0, from_tty=1, c=0x2c9bb2f0) at gdb/cli/cli-decode.c:94
#32 0x00000000006cc94f in cmd_func (cmd=0x2c9bb2f0, args=0x0, from_tty=1) at gdb/cli/cli-decode.c:2831
#33 0x0000000000fe53ad in execute_command (p=0x2c86699a "", from_tty=1) at gdb/top.c:563
#34 0x000000000094584d in command_handler (command=0x2c866990 "tui enable") at gdb/event-top.c:611
#35 0x0000000000945dfe in command_line_handler (rl=...) at gdb/event-top.c:844
#36 0x000000000101e916 in tui_command_line_handler (rl=...) at gdb/tui/tui-interp.c:101
#37 0x0000000000944eef in gdb_rl_callback_handler (rl=0x2cc86a30 "tui enable") at gdb/event-top.c:288
#38 0x0000000001175779 in rl_callback_read_char () at readline/readline/callback.c:302
#39 0x0000000000944bc3 in gdb_rl_callback_read_char_wrapper_sjlj () at gdb/event-top.c:197
#40 0x0000000000944cd4 in gdb_rl_callback_read_char_wrapper_noexcept () at gdb/event-top.c:240
#41 0x0000000000944d52 in gdb_rl_callback_read_char_wrapper (...) at gdb/event-top.c:252
#42 0x0000000001062352 in stdin_event_handler (error=0, client_data=0x2c865150) at gdb/ui.c:154
#43 0x0000000001a04edf in handle_file_event (file_ptr=0x2ccf8850, ready_mask=1) at gdbsupport/event-loop.cc:551
#44 0x0000000001a05522 in gdb_wait_for_event (block=1) at gdbsupport/event-loop.cc:672
#45 0x0000000001a043ff in gdb_do_one_event (mstimeout=-1) at gdbsupport/event-loop.cc:263
#46 0x00000000006d5480 in interp::do_one_event (this=0x2cc2af20, mstimeout=-1) at gdb/interps.h:93
#47 0x0000000000b77f25 in start_event_loop () at gdb/main.c:403
#48 0x0000000000b78113 in captured_command_loop () at gdb/main.c:468
#49 0x0000000000b7a07c in captured_main (context=0x7fff660b9e60) at gdb/main.c:1381
#50 0x0000000000b7a178 in gdb_main (args=0x7fff660b9e60) at gdb/main.c:1400
#51 0x0000000000419705 in main (argc=5, argv=0x7fff660b9f98) at gdb/gdb.c:38
(gdb)
The problem is that while the TUI is being enabled for the first time,
none of the TUI windows yet exist. As each window is created its
contents are rendered (i.e. filled in based on GDB's state), which for
some windows can trigger an interactive prompt, in this case a missing
source file triggers a debuginfod prompt while trying to render the
`src` window.
The interactive prompt will be written to the `cmd` window, but at
this point the `cmd` window has not yet been created.
There have been several different attempts to fix this issue:
1. https://inbox.sourceware.org/gdb-patches/20240312215334.37888-1-amerey@redhat.com
2. https://inbox.sourceware.org/gdb-patches/20260114172833.1824823-1-tdevries@suse.de
3. https://inbox.sourceware.org/gdb-patches/20260116104313.2704994-1-tdevries@suse.de
4. https://inbox.sourceware.org/gdb-patches/20260221101818.2678136-1-tdevries@suse.de
5. https://inbox.sourceware.org/gdb-patches/20260314173737.1436116-1-tdevries@suse.de
6. https://inbox.sourceware.org/gdb-patches/20260417075719.852558-1-tdevries@suse.de
The patch presented here is similar to what was presented in (3)
above, but I think the implementation is maybe a little simpler.
Additionally, all but (6) of the above patches don't address issues
related to using multi-key combinations like 'C-x C-a' to enable TUI
mode, and that patch just takes the (admittedly safe) approach of
preventing the user from activating the TUI using a multi-key
combination if debuginfod is in ASK mode (and so could trigger an
interactive prompt).
This patch doesn't address the multi-key problem, that is left for the
next patch in this series.
This patch ensures that the `cmd` window always exists before
rendering the windows (i.e. filling in their content). This is done
by introducing a tui_defer_rerender global which is set in tui_enable,
and checked in tui_win_info::resize. Then, prior to the prompt being
displayed, if the flag is set, we render the contents of all visible
windows. This can trigger a secondary prompt (e.g. the debuginfod
prompt), but by this point the `cmd` window exists, and can display
the prompt.
The debuginfod-query.exp test included here is based on the test Tom
de Vries wrote for one of his patches listed above, but extended to
cover some additional cases. The activate-with-key-combo.exp test is
new for this series.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31449
Co-Authored-By: Tom de Vries <tdevries@suse.de>
---
.../gdb.tui/activate-with-key-combo.c | 22 ++
.../gdb.tui/activate-with-key-combo.exp | 83 ++++++
gdb/testsuite/gdb.tui/debuginfod-query.c | 22 ++
gdb/testsuite/gdb.tui/debuginfod-query.exp | 270 ++++++++++++++++++
gdb/tui/tui-hooks.c | 21 +-
gdb/tui/tui-layout.c | 3 +-
gdb/tui/tui.c | 11 +
gdb/tui/tui.h | 5 +
8 files changed, 435 insertions(+), 2 deletions(-)
create mode 100644 gdb/testsuite/gdb.tui/activate-with-key-combo.c
create mode 100644 gdb/testsuite/gdb.tui/activate-with-key-combo.exp
create mode 100644 gdb/testsuite/gdb.tui/debuginfod-query.c
create mode 100644 gdb/testsuite/gdb.tui/debuginfod-query.exp
diff --git a/gdb/testsuite/gdb.tui/activate-with-key-combo.c b/gdb/testsuite/gdb.tui/activate-with-key-combo.c
new file mode 100644
index 00000000000..6a0e311ef41
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/activate-with-key-combo.c
@@ -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;
+}
diff --git a/gdb/testsuite/gdb.tui/activate-with-key-combo.exp b/gdb/testsuite/gdb.tui/activate-with-key-combo.exp
new file mode 100644
index 00000000000..b411c6b6b6e
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/activate-with-key-combo.exp
@@ -0,0 +1,83 @@
+# 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/>.
+
+# Exercise the multi-key combinations (e.g. 'C-x C-a') as a means to
+# activate TUI mode. After activating TUI mode check that GDB is
+# still responsive, and output shows up in the CMD window.
+
+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
+}
+
+# Use KEY to activate TUI mode, where KEY should be a multi-key
+# combination, e.g. 'C-x C-a' expressed as an escape sequence,
+# e.g. "\030\001". Wait for the GDB prompt to be displayed then type
+# a single character 'q'. Check that the character shows up on the
+# screen then cancel the 'q' with Ctrl-C. Finally send a basic print
+# command to GDB to ensure everything is still working and
+# responsive.
+proc run_test { key } {
+ Term::clean_restart 24 80 $::testfile
+
+ if {![Term::prepare_for_tui]} {
+ return
+ }
+
+ send_gdb $key
+
+ Term::wait_for ""
+
+ gdb_assert {[Term::check_region_contents_p 0 16 5 1 \
+ [string_to_regexp "(gdb)"]]} \
+ "check prompt is where we want it"
+
+ send_gdb "q"
+
+ gdb_assert {[Term::wait_for_region_contents 0 16 80 1 q]} \
+ "the 'q' key appears in the terminal"
+
+ # Send Ctrl-c, cancelling the 'q' we typed.
+ send_gdb "\003"
+
+ gdb_assert {[Term::wait_for_region_contents 0 16 80 1 Quit]} \
+ "cancel the partial command"
+
+ Term::command "print 1"
+
+ gdb_assert {[Term::check_region_contents_p 0 18 80 1 " = 1"]} \
+ "wait for output of the print command"
+
+ Term::dump_screen
+}
+
+# Use different multi-key combinations to trigger a switch to TUI mode.
+foreach mk { \030\001 \030a \030A \0302 \0301 \030o } {
+ with_test_prefix "using key '[unprintable_to_octal $mk]'" {
+ run_test $mk
+ }
+}
diff --git a/gdb/testsuite/gdb.tui/debuginfod-query.c b/gdb/testsuite/gdb.tui/debuginfod-query.c
new file mode 100644
index 00000000000..6a0e311ef41
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/debuginfod-query.c
@@ -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;
+}
diff --git a/gdb/testsuite/gdb.tui/debuginfod-query.exp b/gdb/testsuite/gdb.tui/debuginfod-query.exp
new file mode 100644
index 00000000000..89cd9d38f28
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/debuginfod-query.exp
@@ -0,0 +1,270 @@
+# 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 a debuginfod query during TUI initialization doesn't cause a
+# crash.
+
+require allow_tui_tests
+
+load_lib debuginfod-support.exp
+require allow_debuginfod_tests
+
+tuiterm_env
+
+standard_testfile
+
+if { [build_executable "build executable" $testfile $srcfile] == -1 } {
+ return
+}
+
+if {[section_get $binfile ".gnu_debuglink"] ne ""} {
+ unsupported "debug information has already been split out"
+ return
+}
+
+# Assume that GDB is in TUI mode, in the SRC layout, and that a
+# debuginfod prompt is currently active in the CMD window.
+#
+# Send ANSWER, either 'y' or 'n' to the debuginfod prompt, and check
+# how the SRC window updates. Answer 'y' and debuginfod should
+# download the source code, which should be displayed in the SRC
+# window. Answer 'n' and the 'No Source Available' message should
+# appear in the SRC window.
+#
+# Finally, send a basic 'print' command to GDB to check that the CMD
+# window is still responsive.
+proc src_layout_answer_prompt_check_screen { answer } {
+ set re \
+ [string_to_regexp \
+ {(y or [n]) }]
+
+ send_gdb "${answer}\n"
+ Term::wait_for ""
+ set re "${re}${answer}"
+ gdb_assert { [Term::check_region_contents_p 0 15 80 9 $re] } \
+ "wait for answer to interactive prompt to show"
+
+ if { $answer eq "n" } {
+ gdb_assert { [Term::check_region_contents_p 0 15 80 9 \
+ "Debuginfod has been disabled\\."] } \
+ "debuginfod has been disabled"
+
+ gdb_assert { [Term::check_region_contents_p 1 1 78 13 \
+ "\\\[ No Source Available \\\]"] } \
+ "source code was not downloaded"
+ } else {
+ gdb_assert { [Term::check_region_contents_p 0 15 80 9 \
+ "Debuginfod has been enabled\\."] } \
+ "debuginfod has been enabled"
+
+ gdb_assert { [Term::check_region_contents_p 1 1 78 13 \
+ "main \\(void\\)"] } \
+ "source code was downloaded"
+ }
+
+ # Check that prompt is responsive.
+ gdb_assert { [Term::command "print 1"] } "responsive prompt"
+}
+
+# Basic setup before running a test. Disable debuginfod progress
+# bars, setup the substitute path so GDB cannot find the source code,
+# and must instead, use debuginfod to get the source code.
+proc setup_for_debuginfod_test {} {
+ # Hide progress bars when downloading from debuginfod.
+ gdb_test_no_output "set progress-bars enabled off"
+
+ # Ensure GDB cannot find the source file. If available the source
+ # can be fetched from debuginfod.
+ gdb_test_no_output "set directories"
+ gdb_test_no_output "set substitute-path $::srcdir /dev/null" \
+ "set substitute-path"
+
+ # Reset the height, and enable the pager. We don't expect to see
+ # any pagination prompts from these tests, but some bugs trigger
+ # the pager at the wrong time, and we want to expose those.
+ gdb_test_no_output "set pagination on"
+ gdb_test_no_output "set height 24"
+}
+
+
+# Trigger a switch to TUI mode based on HOW, which is either 'command'
+# or 'keys'. We expect to see an interactive prompt while TUI is
+# being enabled, to which we reply ANSWER (either 'y' or 'n').
+#
+# When FILL_SCREEN is true we emit lots of lines at the CLI prompt
+# before switching to the TUI, at one point this would cause GDB to
+# incorrectly print the pagination prompt when switching to the TUI
+# via a multi-key combination (e.g. C-x C-a).
+proc_with_prefix run_switch_with_prompt_test { how answer fill_screen } {
+ Term::clean_restart 24 80 $::testfile
+
+ setup_for_debuginfod_test
+
+ if {![Term::prepare_for_tui]} {
+ return
+ }
+
+ Term::gen_prompt
+
+ # Print lots of content to the screen, but not enough to trigger
+ # the pager. The critical thing here is that we must print more
+ # lines than will fit in the TUI's CMD window. If the pager is
+ # not reset when switching to TUI mode, the first output in the
+ # TUI CMD window will trigger the pager, which is not correct.
+ if { $fill_screen } {
+ set cmd "echo "
+ for { set i 0 } { $i < 20 } { incr i } {
+ set cmd "${cmd}${i}\\n"
+ }
+ gdb_test $cmd ".*" \
+ "echo lots of lines"
+ }
+
+ # Trigger the switch to TUI mode.
+ if { $how eq "command" } {
+ send_gdb "tui enable\n"
+ } elseif { $how eq "keys" } {
+ send_gdb "\030\001"
+ } else {
+ perror "unknown test mode: $how"
+ }
+
+ set re \
+ [string_to_regexp \
+ {(y or [n]) }]
+ gdb_assert { [Term::wait_for_region_contents 0 17 80 3 $re] } \
+ "wait for initial interactive prompt"
+
+ foreach mk { \030\001 \0302 \0301 \030o \030q \030s } {
+ set before [Term::get_region 0 0 80 24 "\r\n"]
+ send_gdb $mk
+
+ # It's not easy to check that pressing a key doesn't cause the
+ # display to change. If we read the output then either the
+ # display didn't change, or we read the display to quickly,
+ # and the changes haven't been drawn yet.
+ #
+ # We could just throw a 'sleep 1' in here, which will mostly
+ # work, but still depends on GDB responding fast enough.
+ #
+ # To avoid this race we send the string '123' to GDB. As
+ # GDB's UI is single threaded, by the time '123' is drawn then
+ # any effects from the multi-key combination should also have
+ # been drawn.
+ #
+ # We can then delete the '123' by sending 'C-u' to clear the
+ # line, and after this we can check that the display is
+ # exactly as it was before we started, this means that the
+ # multi-key combination didn't result in a UI change.
+
+ set a_string 123
+ send_gdb $a_string
+ Term::wait_for_region_contents 0 0 80 24 $a_string
+
+ send_gdb "\025"
+ Term::wait_for_region_contents 0 0 80 24 [string_to_regexp $before]
+
+ set after [Term::get_region 0 0 80 24 "\r\n"]
+ gdb_assert { $before eq $after } \
+ "no changes after sending '[unprintable_to_octal $mk]'"
+ }
+
+ src_layout_answer_prompt_check_screen $answer
+}
+
+# Switch into TUI mode, but directly into ASM layout using "layout
+# asm". As the SRC window is not visible no debuginfod prompt should
+# be displayed.
+#
+# Now switch to SRC layout. If HOW is 'command' then use 'layout
+# next' to select the SRC layout. If HOW is 'keys' then use 'C-x 2'
+# which also selects the next layout, which is the SRC layout.
+#
+# As the SRC window is now visible, we should see a debuginfod prompt.
+# Use ANSWER (either 'y' or 'n') to answer the prompt, and check how
+# the SRC window updates as a result.
+proc_with_prefix run_switch_to_asm_test { how answer } {
+ Term::clean_restart 24 80 $::testfile
+
+ setup_for_debuginfod_test
+
+ if {![Term::prepare_for_tui]} {
+ return
+ }
+
+ Term::gen_prompt
+
+ # Switching to ASM layout doesn't require the source file, so
+ # shouldn't trigger the debuginfod prompt.
+ send_gdb "layout asm\n"
+ Term::wait_for ""
+
+ # Now switching to the next layout, which should be the SRC
+ # layout. The SRC layout needs the source code, so this should
+ # trigger the debuginfod prompt.
+ if { $how eq "command" } {
+ send_gdb "layout next\n"
+ } elseif { $how eq "keys" } {
+ send_gdb "\0302"
+ } else {
+ perror "unknown test mode: $how"
+ }
+
+ set re \
+ [string_to_regexp \
+ {(y or [n]) }]
+ gdb_assert { [Term::wait_for_region_contents 0 18 80 3 $re] } \
+ "wait for debuginfod interactive prompt"
+
+ src_layout_answer_prompt_check_screen $answer
+}
+
+# Setup directory for the debuginfod server to serve from.
+set debugdir [standard_output_file "debug"]
+file copy -force $binfile $debugdir/
+
+# Create CACHE and DB directories ready for debuginfod to use.
+prepare_for_debuginfod cache db
+
+# Can add 'keys' to MODES list, but this doesn't currently
+# work due to PR gdb/33794. This will be fixed in the next
+# commit, and this comment removed.
+set modes {command}
+
+with_debuginfod_env $cache {
+ save_vars { env(DEBUGINFOD_URLS) } {
+ foreach_with_prefix how $modes {
+ foreach_with_prefix ans { n y } {
+ file delete -force $cache
+
+ set url [start_debuginfod $db $debugdir]
+ if { $url eq "" } {
+ unresolved "failed to start debuginfod server"
+ continue
+ }
+
+ # Point the client to the server.
+ setenv DEBUGINFOD_URLS $url
+
+ foreach_with_prefix fill_screen { true false } {
+ run_switch_with_prompt_test $how $ans $fill_screen
+ }
+ run_switch_to_asm_test $how $ans
+
+ stop_debuginfod
+ }
+ }
+ }
+}
diff --git a/gdb/tui/tui-hooks.c b/gdb/tui/tui-hooks.c
index f3d3cbe87a6..f8578c1f732 100644
--- a/gdb/tui/tui-hooks.c
+++ b/gdb/tui/tui-hooks.c
@@ -167,7 +167,26 @@ tui_inferior_exit (struct inferior *inf)
static void
tui_before_prompt (const char *current_gdb_prompt)
{
- tui_refresh_frame_and_register_information ();
+ /* If TUI_DEFER_RERENDER is true then the window content will not
+ yet have been filled in, do so now. This is only expected to
+ happen during the switches into TUI mode as it means the CMD
+ window will exist by the time all the other windows have their
+ content filled in. If filling in one of the other windows
+ triggers a secondary prompt (e.g. debuginfod prompt) then the CMD
+ window will be available to display the prompt. */
+ if (tui_defer_rerender)
+ {
+ target_terminal::scoped_restore_terminal_state term_state;
+ target_terminal::ours_for_output ();
+
+ tui_defer_rerender = false;
+ for (tui_win_info *window : tui_windows)
+ if (window->is_visible ())
+ window->rerender ();
+ }
+ else
+ tui_refresh_frame_and_register_information ();
+
from_stack = false;
from_source_symtab = false;
}
diff --git a/gdb/tui/tui-layout.c b/gdb/tui/tui-layout.c
index 40db5f160ca..01208e05513 100644
--- a/gdb/tui/tui-layout.c
+++ b/gdb/tui/tui-layout.c
@@ -296,7 +296,8 @@ tui_win_info::resize (int height_, int width_,
if (handle == nullptr)
make_window ();
- rerender ();
+ if (!tui_defer_rerender)
+ rerender ();
}
\f
diff --git a/gdb/tui/tui.c b/gdb/tui/tui.c
index 4c61e4b879c..275d5c528d7 100644
--- a/gdb/tui/tui.c
+++ b/gdb/tui/tui.c
@@ -69,6 +69,9 @@ show_tui_debug (struct ui_file *file, int from_tty,
/* Tells whether the TUI is active or not. */
bool tui_active = false;
+/* See tui.h. */
+bool tui_defer_rerender = false;
+
/* Tells whether the TUI should do deferred curses initialization.
If TRIBOOL_TRUE, then yes. If TRIBOOL_FALSE. then no (because
initialization is already done). If TRIBOOL_UNKNOWN, then no (because
@@ -500,6 +503,14 @@ tui_enable (void)
tui_batch_rendering defer;
+ /* Defer filling in window contents at this time. The window
+ content will be filled in by calling rerender later. We do this
+ so that we can be sure the CMD window will exist as other windows
+ are rendered, filling in some windows might trigger a secondary
+ prompt (e.g. debuginfod prompt) and we want to be sure that the
+ CMD window exists to display the prompt in. */
+ tui_defer_rerender = true;
+
/* To avoid to initialize curses when gdb starts, there is a deferred
curses initialization. This initialization is made only once
and the first time the curses mode is entered. */
diff --git a/gdb/tui/tui.h b/gdb/tui/tui.h
index 7daefe2c9bb..b24fed2b322 100644
--- a/gdb/tui/tui.h
+++ b/gdb/tui/tui.h
@@ -96,4 +96,9 @@ extern void tui_set_key_mode (enum tui_key_mode mode);
extern bool tui_active;
+/* When true, defer rendering the window contents during the initial switch
+ to TUI mode. Each window's rerender method will be called later before
+ the prompt is displayed. */
+extern bool tui_defer_rerender;
+
#endif /* GDB_TUI_TUI_H */
--
2.25.4
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCHv2 6/6] gdb/tui: fix debuginfod prompt using 'C-x C-a' to enter TUI
2026-07-10 9:47 ` [PATCHv2 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
` (4 preceding siblings ...)
2026-07-10 9:47 ` [PATCHv2 5/6] gdb/tui: fix for debuginfod prompt while enabling the TUI Andrew Burgess
@ 2026-07-10 9:47 ` Andrew Burgess
2026-07-27 15:50 ` [PATCHv2 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
6 siblings, 0 replies; 15+ messages in thread
From: Andrew Burgess @ 2026-07-10 9:47 UTC (permalink / raw)
To: gdb-patches; +Cc: Andrew Burgess
This commit ties closely into the previous commit. The previous
commit looks at issues that can arise when using 'tui enable' to enter
TUI mode if a debuginfod prompt is triggered. This commit looks at
the problems that can arise when a multi-key combination is used to
enter TUI mode, e.g. 'C-x C-a'. Bug PR gdb/33794 discusses this
issue.
There has been a previous attempt to address this issue here:
https://inbox.sourceware.org/gdb-patches/20260417075719.852558-5-tdevries@suse.de
The approach taken in that patch was to prevent switching to TUI mode
if debuginfod is still in ASK mode, this means the switch could
potentially trigger a secondary prompt.
While the previous commit is relatively simple, the complexity in this
case arises from how multi-key combinations are handled by readline.
Currently global readline state is used to track the multi-key press
situation, and when the multi-key is dispatched back to application
(GDB) code, the globals are still live.
If GDB then triggers reentry into readline, e.g. by triggering a
secondary prompt, the call into readline for this prompt will cause
the global state to be released. When the secondary prompt is
finished and we return back to readline the global state will be
accessed, and undefined behaviour occurs, including crashes.
The core idea of my proposed solution to this is to move handling of
the multi-key actions out of the readline callback, and into GDB's
normal event loop.
When the user presses a combination like 'C-x C-a' this will call a
templated tui_rl_keybinding function as it currently does, but instead
of immediately forwarding to another function to carry out the TUI
changes, we instead schedule a callback with the event loop and then
return.
As far as readline is concerned the multi-key action has now been
dealt with, however, no interface changes have yet occurred. As
readline has now finished handling this key press, readline returns to
the event loop to get the next user input.
At the event loop the pending callback is seen and dispatched. This
callback triggers the actual UI changes, e.g. entering TUI mode. As
we are not inside readline at this point we are free to create
secondary prompts if needed.
In tui_rl_keybinding we use run_on_main_thread to schedule a callback
with the event loop, but there are some additional changes needed:
1. If we changed the tui_active state then we need to call
reinitialize_more_filter. Previously tui_rl_switch_mode would call
rl_newline which would make readline think that a command had been
fully entered, this would trigger a call to GDB's
command_line_handler, which calls command_handler, which then calls
reinitialize_more_filter.
For reasons explained below tui_rl_switch_mode can no longer call
rl_newline, so the reinitialize_more_filter is never reached. This
means that especially when switching CLI to TUI, when the `cmd`
window is smaller than the CLI terminal, GDB might enter TUI mode
thinking that the TUI is already full. This leads to incorrect
pager prompts appearing. Resolve this by explicitly resetting the
pager.
2. After changing the tui_active state (i.e. entering or leaving TUI
mode), there will not be a GDB prompt displayed. Under the old
scheme, the rl_newline call in tui_rl_switch_mode would trick GDB
into thinking an empty command had just been completed, this would
then trigger a prompt redisplay.
Under the new scheme we need to explicitly call display_gdb_prompt
or tui_redisplay_readline to redraw the prompt. However, as we
were at a GDB prompt already when the user pressed a key like 'C-x
C-a', the current_ui will not think that a prompt is needed, if we
plan to call display_gdb_prompt then we'll need to change the
prompt_state to PROMPT_NEEDED before calling display_gdb_prompt.
When possible we prefer calling tui_redisplay_readline, as this
preserves the current readline input line buffer contents, so if
the user types something at the prompt and then does 'C-x o' to
change window focus, the partially typed text is preserved.
Both of these additional actions need to be performed for both the
normal exit path, and the exception path in order that the prompt be
correctly displayed, so this code is done in a SCOPE_EXIT block.
The other set of changes are in tui_rl_switch_mode:
1. The calls to rl_prep_terminal are no longer needed as
display_gdb_prompt will take care of calling this for us if
appropriate (e.g. we are not in TUI mode).
2. The gdb_exception_forced_quit handling can now just propagate the
exception, We are no longer within a readline callback, and so can
throw this exception further up the stack.
3. Likewise with gdb_exception, we can re-throw this. As the
run_on_main_thread mechanism silently swallows all gdb_exceptions
except the gdb_exception_forced_quit sub-class, we do need to print
the exception ourselves first though. This is why we had to
separate out the gdb_exception_forced_quit handling.
4. The rl_kill_text call is no longer needed as the following
rl_newline call is going to be removed.
5. The rl_newline call was a neat trick to force a prompt redisplay,
but this only works when we are within a readline callback, it
injects a newline so that when we return from this callback
readline will see the pending newline character, process the now
empty line (thanks to the rl_kill_text call), and the print the
prompt. This is replaced by the display_gdb_prompt call that was
added to tui_rl_keybinding.
6. The dont_repeat call was needed because the rl_kill_text and
rl_newline calls were tricking readline into thinking the user had
pressed Enter on an empty line, this was done to force a prompt
redisplay.
However, pressing Enter on an empty line repeats the previous
command unless dont_repeat has been called. Now we don't use the
rl_newline trick, the dont_repeat call is not needed.
The gdb.tui/debuginfod-query.exp test is updated to include tests that
switch using multi-key combinations.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33794
---
gdb/testsuite/gdb.tui/debuginfod-query.exp | 5 +-
.../gdb.tui/tui-enable-failure-lib.c | 32 +++++
gdb/testsuite/gdb.tui/tui-enable-failure.c | 22 +++
gdb/testsuite/gdb.tui/tui-enable-failure.exp | 133 ++++++++++++++++++
gdb/tui/tui.c | 120 +++++++++++-----
5 files changed, 276 insertions(+), 36 deletions(-)
create mode 100644 gdb/testsuite/gdb.tui/tui-enable-failure-lib.c
create mode 100644 gdb/testsuite/gdb.tui/tui-enable-failure.c
create mode 100644 gdb/testsuite/gdb.tui/tui-enable-failure.exp
diff --git a/gdb/testsuite/gdb.tui/debuginfod-query.exp b/gdb/testsuite/gdb.tui/debuginfod-query.exp
index 89cd9d38f28..0f7a19dfc51 100644
--- a/gdb/testsuite/gdb.tui/debuginfod-query.exp
+++ b/gdb/testsuite/gdb.tui/debuginfod-query.exp
@@ -238,10 +238,7 @@ file copy -force $binfile $debugdir/
# Create CACHE and DB directories ready for debuginfod to use.
prepare_for_debuginfod cache db
-# Can add 'keys' to MODES list, but this doesn't currently
-# work due to PR gdb/33794. This will be fixed in the next
-# commit, and this comment removed.
-set modes {command}
+set modes {command keys}
with_debuginfod_env $cache {
save_vars { env(DEBUGINFOD_URLS) } {
diff --git a/gdb/testsuite/gdb.tui/tui-enable-failure-lib.c b/gdb/testsuite/gdb.tui/tui-enable-failure-lib.c
new file mode 100644
index 00000000000..d2229bba9f5
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/tui-enable-failure-lib.c
@@ -0,0 +1,32 @@
+/* Copyright 2026 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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/>. */
+
+/* Preload library that overrides ncurses newterm to always return NULL,
+ causing tui_enable to fail. */
+
+#include <stddef.h>
+#include <stdio.h>
+
+/* Define this type so we can override newterm. We only plan to
+ return NULL, so the details of this type are not important. */
+typedef struct screen_dummy SCREEN;
+
+SCREEN *
+newterm (const char *type, FILE *outfd, FILE *infd)
+{
+ return NULL;
+}
diff --git a/gdb/testsuite/gdb.tui/tui-enable-failure.c b/gdb/testsuite/gdb.tui/tui-enable-failure.c
new file mode 100644
index 00000000000..1dc4b7ca539
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/tui-enable-failure.c
@@ -0,0 +1,22 @@
+/* Copyright 2026 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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;
+}
diff --git a/gdb/testsuite/gdb.tui/tui-enable-failure.exp b/gdb/testsuite/gdb.tui/tui-enable-failure.exp
new file mode 100644
index 00000000000..65ca10aefe1
--- /dev/null
+++ b/gdb/testsuite/gdb.tui/tui-enable-failure.exp
@@ -0,0 +1,133 @@
+# 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/>.
+
+# Test that GDB handles tui_enable failure gracefully.
+#
+# Use an LD_PRELOAD library to override ncurses' newterm function,
+# making it always return NULL. This causes tui_enable to fail with
+# an error.
+#
+# Test both the "tui enable" command and the multi-key combination
+# (C-x C-a) paths. The multi-key path is particularly important as it
+# exercises the tui_rl_keybinding SCOPE_EXIT error handling.
+
+require allow_tui_tests
+require {!is_remote host}
+require {istarget *-linux*}
+
+tuiterm_env
+
+standard_testfile .c -lib.c
+
+set libfile ${testfile}-lib
+set libobj [standard_output_file ${libfile}.so]
+
+# Compile the preload library.
+if { [build_executable "build preload lib" $libobj $srcfile2 \
+ {debug shlib}] == -1 } {
+ return
+}
+
+# Compile the test executable.
+if { [build_executable "build executable" $testfile $srcfile] == -1 } {
+ return
+}
+
+# Send 'print NUM' to GDB and check the output appear on LINE.
+proc check_responsiveness { num line } {
+ gdb_assert { [Term::command "print $num"] } \
+ "send print command"
+
+ gdb_assert { [Term::check_region_contents_p 0 $line 80 1 " = $num"] } \
+ "check print output is in expected location"
+}
+
+# Try to enable TUI mode using HOW, which is either 'command' or
+# 'keys'. The preload library ensures that newterm returns NULL, so
+# tui_enable will fail. Check that an error is produced and that GDB
+# remains responsive.
+#
+# The first failure sets tui_finish_init to TRIBOOL_UNKNOWN, so a
+# second attempt produces a different (generic) error message. Try
+# the second attempt too, and check that GDB is still responsive.
+#
+# The 'keys' path (C-x C-a) is particularly important as it exercises
+# the tui_rl_keybinding SCOPE_EXIT error handling.
+proc_with_prefix run_test { how } {
+ Term::clean_restart 24 80 $::testfile
+
+ if {![Term::prepare_for_tui]} {
+ return
+ }
+
+ Term::gen_prompt
+
+ # First attempt to enable TUI.
+ if { $how eq "command" } {
+ send_gdb "tui enable\n"
+ set line_offset 1
+ } elseif { $how eq "keys" } {
+ send_gdb "\030\001"
+ set line_offset 0
+ } else {
+ perror "unknown test mode: $how"
+ }
+
+ gdb_assert { [Term::wait_for "Cannot enable the TUI: error opening terminal"] } \
+ "error from first tui enable attempt"
+
+ # Check that GDB is still responsive.
+ with_test_prefix "responsive after first failure" {
+ check_responsiveness 1 [expr {2 + $line_offset}]
+ }
+
+ # Second attempt using 'tui enable' command should also fail.
+ send_gdb "tui enable\n"
+ gdb_assert { [Term::wait_for "Cannot enable the TUI"] } \
+ "error from second tui enable attempt"
+
+ # Check that GDB is still responsive.
+ with_test_prefix "responsive after second failure" {
+ check_responsiveness 2 [expr {6 + $line_offset}]
+ }
+
+ # Third attempt using multi-key combo should also fail.
+ send_gdb "\030\001"
+ gdb_assert { [Term::wait_for "Cannot enable the TUI"] } \
+ "error from third tui enable attempt"
+
+ # Check that GDB is still responsive.
+ with_test_prefix "responsive after third failure" {
+ check_responsiveness 3 [expr {9 + $line_offset}]
+ }
+}
+
+set modes {command keys}
+
+save_vars { env(LD_PRELOAD) env(ASAN_OPTIONS) } {
+ if { ![info exists env(LD_PRELOAD)]
+ || $env(LD_PRELOAD) == "" } {
+ set env(LD_PRELOAD) "$libobj"
+ } else {
+ append env(LD_PRELOAD) ":$libobj"
+ }
+
+ # Prevent address sanitizer error about library ordering.
+ append_environment_default ASAN_OPTIONS verify_asan_link_order 0
+
+ foreach_with_prefix how $modes {
+ run_test $how
+ }
+}
diff --git a/gdb/tui/tui.c b/gdb/tui/tui.c
index 275d5c528d7..a9a23f29b9a 100644
--- a/gdb/tui/tui.c
+++ b/gdb/tui/tui.c
@@ -42,6 +42,7 @@
#include "top.h"
#include "ui.h"
#include "observable.h"
+#include "run-on-main-thread.h"
#include <fcntl.h>
@@ -119,15 +120,20 @@ 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. */
+ /* This function is called through the run_on_main_thread event loop
+ callback mechanism. That mechanism propagates
+ gdb_exception_forced_quit exceptions, but silently discards
+ gdb_exception exceptions. We catch and print gdb_exception
+ exceptions before propagating them, the run_on_main_thread
+ mechanism will then discard these and return to the event loop.
+ Any RAII cleanup between here and there will have been done,
+ which is important. For gdb_exception_forced_quit exceptions we
+ just propagate these up the stack without printing, these will be
+ handled when they are caught by the event loop. */
try
{
if (tui_active)
- {
- tui_disable ();
- rl_prep_terminal (0);
- }
+ tui_disable ();
else
{
/* If we type "foo", entering it into the readline buffer
@@ -143,43 +149,24 @@ tui_rl_switch_mode (int notused1 = 0, int notused2 = 0)
TUI. */
rl_clear_visible_line ();
- /* If tui_enable throws, we'll re-prep below. */
+ /* Disable readline state ahead of enabling TUI mode. If
+ tui_enable fails then the next display_gdb_prompt will
+ re-prep the terminal for us. */
rl_deprep_terminal ();
+
tui_enable ();
}
}
catch (const gdb_exception_forced_quit &ex)
{
- /* Ideally, we'd do a 'throw' here, but as noted above, we can't
- do that, so, instead, we'll set the necessary flags so that
- a later QUIT check will restart the forced quit. */
- set_force_quit_flag ();
+ throw;
}
catch (const gdb_exception &ex)
{
exception_print (gdb_stderr, ex);
-
- if (!tui_active)
- rl_prep_terminal (0);
+ throw;
}
- /* Clear the readline in case switching occurred in middle of
- something. */
- if (rl_end)
- rl_kill_text (0, rl_end);
-
- /* Since we left the curses mode, the terminal mode is restored to
- some previous state. That state may not be suitable for readline
- to work correctly (it may be restored in line mode). We force an
- exit of the current readline so that readline is re-entered and
- it will be able to setup the terminal for its needs. By
- re-entering in readline, we also redisplay its prompt in the
- non-curses mode. */
- rl_newline (1, '\n');
-
- /* Make sure the \n we are returning does not repeat the last
- command. */
- dont_repeat ();
return 0;
}
@@ -335,7 +322,76 @@ tui_rl_keybinding (int count, int key)
if (gdb_in_secondary_prompt_p (current_ui))
return 0;
- return FPTR (count, key);
+ run_on_main_thread ([=] () {
+ bool was_active = tui_active;
+
+ /* Cleanup required even on the exception path. */
+ SCOPE_EXIT {
+ /* If we switched from CLI to TUI (or back) then we should
+ reinitialize the pager in order to avoid spurious pagination
+ prompts. This is especially important going from CLI to TUI
+ where the command window is usually smaller than the full
+ terminal, so the pager might already think that we have more
+ lines printed than will fit in the window, despite the window
+ starting empty after a mode switch. */
+ if (was_active != tui_active)
+ reinitialize_more_filter ();
+
+ /* The user was at a prompt and pressed a multi-key combination
+ (e.g. C-x C-a). As a result this callback was invoked from
+ the event loop. We're now exiting this callback and want to
+ ensure that the prompt is drawn correctly.
+
+ We have two approaches, full display_gdb_prompt, or a light
+ weight tui_redisplay_readline. The former will clear the
+ readline input buffer and redisplay the prompt, while the
+ second will redraw the prompt along with anything in the
+ input buffer. We want the light weight option where
+ possible, but there are times when this isn't an option:
+
+ 1. A successful switch between CLI and TUI, in either
+ direction, changes how we update the prompt. We need to
+ call display_gdb_prompt the first time to ensure
+ everything is done correctly. We also need to consider
+ the case where tui_enable fails, leaving us in CLI mode,
+ this also requires a call to display_gdb_prompt as the
+ light weight tui_redisplay_readline is not appropriate for
+ CLI use.
+
+ 2. Usually the TUI will have the RL_STATE_CALLBACK state flag
+ set because of when this event callback is called. If a
+ secondary prompt has been displayed, then once the
+ secondary prompt completed, the RL_STATE_CALLBACK flag
+ will have been cleared. This is good for us, because
+ after a secondary prompt rl_prompt will still hold the
+ secondary prompt string, so we need display_gdb_prompt to
+ set the correct top-level prompt. */
+ if (!was_active || !tui_active || !RL_ISSTATE (RL_STATE_CALLBACK))
+ {
+ current_ui->prompt_state = PROMPT_NEEDED;
+ display_gdb_prompt (nullptr);
+ }
+ else
+ {
+ /* We can only reach here when was_active and tui_active are
+ both true: starting in CLI mode (!was_active) or ending
+ in CLI mode (!tui_active) both take the if block
+ above. */
+ gdb_assert (tui_active);
+
+ /* The only time rl_prompt will be NULL is when we switch
+ from CLI to TUI, but that will be handled by the block
+ above. */
+ gdb_assert (rl_prompt != nullptr);
+
+ tui_redisplay_readline ();
+ }
+ };
+
+ (void) FPTR (count, key);
+ });
+
+ return 0;
}
/* Initialize readline and configure the keymap for the switching
--
2.25.4
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCHv2 0/6] gdb/tui: fix debuginfod related crash
2026-07-10 9:47 ` [PATCHv2 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
` (5 preceding siblings ...)
2026-07-10 9:47 ` [PATCHv2 6/6] gdb/tui: fix debuginfod prompt using 'C-x C-a' to enter TUI Andrew Burgess
@ 2026-07-27 15:50 ` Andrew Burgess
6 siblings, 0 replies; 15+ messages in thread
From: Andrew Burgess @ 2026-07-27 15:50 UTC (permalink / raw)
To: gdb-patches
Andrew Burgess <aburgess@redhat.com> writes:
> This series is an alternative to the latest patches proposed here:
>
> https://inbox.sourceware.org/gdb-patches/20260417075719.852558-1-tdevries@suse.de
>
> Patches #1, #2, and #3 cover changes that are not part of the above
> series, I suspect these will be reasonably non-contentious.
>
> Patch #4 is a small refactor to support the later patches.
>
> Patches #5 and #6 are my take on solving the problem covered by the
> above series.
>
> In v2:
>
> - Rebase to current HEAD.
>
> - Minor testsuite tweaks to avoid possible race condition where GDB
> might not spot if the test failed.
>
> - Some improvements to comments and commits messages to make things
> clearer.
>
> - Retested.
I've checked this in now.
Thanks,
Andrew
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-07-27 15:50 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-05-01 14:22 [PATCH 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
2026-05-01 14:22 ` [PATCH 1/6] gdb/tui: convert a window handle `if` into an `assert` Andrew Burgess
2026-05-01 14:22 ` [PATCH 2/6] gdb/testsuite: fix tuiterm linefeed scrolling new line content Andrew Burgess
2026-05-01 14:22 ` [PATCH 3/6] gdb/tui: prevent TUI activation from a secondary prompt Andrew Burgess
2026-05-01 14:22 ` [PATCH 4/6] gdb/tui: make tui_win_info::rerender public Andrew Burgess
2026-05-01 14:22 ` [PATCH 5/6] gdb/tui: fix for debuginfod prompt while enabling the TUI Andrew Burgess
2026-05-01 14:22 ` [PATCH 6/6] gdb/tui: fix debuginfod prompt using 'C-x C-a' to enter TUI Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 1/6] gdb/tui: convert a window handle `if` into an `assert` Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 2/6] gdb/testsuite: fix tuiterm linefeed scrolling new line content Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 3/6] gdb/tui: prevent TUI activation from a secondary prompt Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 4/6] gdb/tui: make tui_win_info::rerender public Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 5/6] gdb/tui: fix for debuginfod prompt while enabling the TUI Andrew Burgess
2026-07-10 9:47 ` [PATCHv2 6/6] gdb/tui: fix debuginfod prompt using 'C-x C-a' to enter TUI Andrew Burgess
2026-07-27 15:50 ` [PATCHv2 0/6] gdb/tui: fix debuginfod related crash Andrew Burgess
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox