From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id SR/WKyjAUGqRkAEAWB0awg (envelope-from ) for ; Fri, 10 Jul 2026 05:49:28 -0400 Authentication-Results: simark.ca; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Q/w9/HH8; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id AC7EF1E0A3; Fri, 10 Jul 2026 05:49:28 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-6.3 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,GAPPY_SUBJECT, MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [38.145.34.32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id E7D6D1E070 for ; Fri, 10 Jul 2026 05:49:25 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 7FF114BA2E09 for ; Fri, 10 Jul 2026 09:49:25 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 7FF114BA2E09 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Q/w9/HH8 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id AF0EE4BA2E3D for ; Fri, 10 Jul 2026 09:47:35 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org AF0EE4BA2E3D Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org AF0EE4BA2E3D Authentication-Results: sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783676855; cv=none; b=GW7nt7TA+rV5SoRfQs9zAuu5J/RgfNRlbMWlJeCvjlZ6HY6ZyHu6hUQj2awXqMWVCsqZaxr0i635i5K99oCrdWrK8WcGP+QX167nkVENLdgUZv3+86TTVuFPzDCtL0Bo/RrGx7/Kx77l6uqNbXwqb5G0kpYrbg3/lY3LjIrLd7M= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783676855; c=relaxed/simple; bh=oJK1rBO0IUnDL6u6klt1G9Nzxf2++KuBGgTJ8QRiHXA=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=QGzA33Da4WK6SWW8o6lVTxRlQim6/+9crPPZQvDw19AoWnO15l2oIlJ2jvSs7MeSHwBii4QfuNxnt1oJZ/hMvbmRJzB0N8oJlxKAE/njVZC/TyBE3HVDq2uCwssEj+zbWoKgMAGrQ7i0v29GNVrKR/tEFcV35LjlqVZA0waFz+k= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Q/w9/HH8 DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org AF0EE4BA2E3D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1783676855; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=cFS2Rb5s+1LPYxQwmQxKhBQNu7TmaV4JVcWGxWKTNlM=; b=Q/w9/HH8rZ1BpD/0M3egvqgLvKVPzbetcTKKCquMbr6+lfRwRWJoH/ft0kHiXfIoWrb0BM oieJqLSbYmz6mNKFH20/LP+dUC+7EqOENBp+AwkQCCcCgiNa9jsXtyC+r1BRXr8n4Ker7w dNJT75VTu9LgBRXvCYcVzTfchc9IfXk= Received: from mail-wm1-f71.google.com (mail-wm1-f71.google.com [209.85.128.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-516-XOu5vUEDOnuAFumdLm6hhw-1; Fri, 10 Jul 2026 05:47:33 -0400 X-MC-Unique: XOu5vUEDOnuAFumdLm6hhw-1 X-Mimecast-MFC-AGG-ID: XOu5vUEDOnuAFumdLm6hhw_1783676852 Received: by mail-wm1-f71.google.com with SMTP id 5b1f17b1804b1-493bdf90adaso6524305e9.2 for ; Fri, 10 Jul 2026 02:47:33 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783676852; x=1784281652; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=cFS2Rb5s+1LPYxQwmQxKhBQNu7TmaV4JVcWGxWKTNlM=; b=VGag9IeHyzy9/oa2s7jK4UQ/nEe24bmr6X4cQhewUT1un9oggDt/248HfUoIAWQKqP Y31pp4E5hkrRdqBi+Ukuz5gXCqYb6PuYQAcV7xkQ/4LfKXqhGRho6HHVlESdrPM9YkJD /VFuFiPe15wl1YQXe3jdGWnQzKZ9e3pCMU35TbvNhweyjCVX7ro/7krUBMGBjfP5IdP9 /1CocU/EDd3X/1kO3swmPIVj00zWPUVtDwiXu/RoOpN5V9xaoudWhyqEzm0uOY6fS6iw UYKSW2BbmF6crWCnO85CwhZUBpSugXCpw5bWQ0SVHrQCDfKMnB/cZAEWALwT2Df5Vfvu Mpmw== X-Gm-Message-State: AOJu0YxhMUMMpn5xRU2ytDbdl9T375JZuttAFKiXkG8N7xA5zIHrIRXr l9kaCCMlKeE6STLpgYcvw3FKOkiAb9DmoTtjHKHNmvwNx542piiciFV+rpwvAV3wJ+bqwdU0+Ks NA4suKIvYUb60+b8g09mmlOkPjl3CLmP+Dyx3LkKL/IS6tXFRgpiGeP//gfx7t2/Qo2BAf5+Lox d+OmOfqJub7oxuVkb0N7yXSZUV7+kqFvChGjZSo/Ug9ST870A= X-Gm-Gg: AfdE7cnZXMnBYWSVZIKCFGjbEF+wUxjXDSa4CSU8lfytER5Z7eZrEMPYvTL9sULKYPX dxSy4ZdBw2AP6UcVyL7pLEhLL7DjjtE4qUJc7+YgZfdg89Odl3QV90VV5eoJC/cNV8oAJzqAXb8 06GRadksj6MPIFe2KnjNJS9lGZvrgBQOBS3Bbw7V7f1RrTy1B0N4PtWy3Io7k+fttEVbqc5KeHx Kfvp+tHb3ZYc3sJbzthT+qvHquZrkRTJRSi+XoxqLdSsd9zOiFqhRdEpOcZG2GVFVo8eSH9jT2d 4OAbXswW+0tI14G6W4xydwlWIYLKG2AE+LvuFuE4KqvYbljj1PGbkgbq6LD318paO+kqph2vmtG d4SBEfI0= X-Received: by 2002:a05:600c:4ece:b0:493:c337:db1a with SMTP id 5b1f17b1804b1-493ec874dd9mr64094125e9.38.1783676852071; Fri, 10 Jul 2026 02:47:32 -0700 (PDT) X-Received: by 2002:a05:600c:4ece:b0:493:c337:db1a with SMTP id 5b1f17b1804b1-493ec874dd9mr64093845e9.38.1783676851022; Fri, 10 Jul 2026 02:47:31 -0700 (PDT) Received: from localhost ([31.111.209.233]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-493f2d88698sm47158855e9.1.2026.07.10.02.47.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 10 Jul 2026 02:47:29 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess Subject: [PATCHv2 6/6] gdb/tui: fix debuginfod prompt using 'C-x C-a' to enter TUI Date: Fri, 10 Jul 2026 10:47:18 +0100 Message-Id: <23d7eeffb176d58b4769314acfcea251d2e3817a.1783676703.git.aburgess@redhat.com> X-Mailer: git-send-email 2.25.4 In-Reply-To: References: MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: qEaw8fTDLMm33bkHZ-Yp9iIdNgz6ZT7-Nuptzai_d4M_1783676852 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org 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 . */ + +/* Preload library that overrides ncurses newterm to always return NULL, + causing tui_enable to fail. */ + +#include +#include + +/* 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 . */ + +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 . + +# 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 @@ -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