[gdb] Handle pagination prompt during posted event

PR python/24796 reports the following problem.

We have a python file test.py:
...
import gdb
def Event():
  for x in range(0, 10):
    print("Line")
gdb.post_event(Event)
...

Without pagination, we simple have:
...
(gdb) source test.py
(gdb) Line
Line
  ...
Line
Line
print 1
$1 = 1
(gdb)
...

But with pagination (and height set to 8), we get instead:
...
(gdb) source test.py
(gdb) Line
Line
  ...
Line
--Type <RET> for more, q to quit, c to continue without paging--c
Line
Line
Line
print 1
readline: readline_callback_read_char() called with no handler!

Fatal signal: Aborted
----- Backtrace -----
0x651099 gdb_internal_backtrace_1
	/data/vries/gdb/src/gdb/bt-utils.c:122
0x651137 _Z22gdb_internal_backtracev
	/data/vries/gdb/src/gdb/bt-utils.c:175
0x9462aa handle_fatal_signal
	/data/vries/gdb/src/gdb/event-top.c:1008
0x7f4efaf5f08f ???
	/usr/src/debug/glibc-2.40/signal/../sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c:0
0x7f4efafb245c __pthread_kill_implementation
	/usr/src/debug/glibc-2.40/nptl/pthread_kill.c:44
0x7f4efaf5efc5 __GI_raise
	../sysdeps/posix/raise.c:26
0x7f4efaf46916 __GI_abort
	/usr/src/debug/glibc-2.40/stdlib/abort.c:79
0x117502b rl_callback_read_char
	/data/vries/gdb/src/readline/readline/callback.c:139
0x944bc2 gdb_rl_callback_read_char_wrapper_sjlj
	/data/vries/gdb/src/gdb/event-top.c:197
0x944cd3 gdb_rl_callback_read_char_wrapper_noexcept
	/data/vries/gdb/src/gdb/event-top.c:240
0x944d51 gdb_rl_callback_read_char_wrapper
	/data/vries/gdb/src/gdb/event-top.c:252
0x10623e3 stdin_event_handler
	/data/vries/gdb/src/gdb/ui.c:154
0x1a06e9c handle_file_event
	/data/vries/gdb/src/gdbsupport/event-loop.cc:551
0x1a074df gdb_wait_for_event
	/data/vries/gdb/src/gdbsupport/event-loop.cc:672
0x1a063bc _Z16gdb_do_one_eventi
	/data/vries/gdb/src/gdbsupport/event-loop.cc:263
0x6d547f _ZN6interp12do_one_eventEi
	/data/vries/gdb/src/gdb/interps.h:93
0xb77fb6 start_event_loop
	/data/vries/gdb/src/gdb/main.c:403
0xb781a4 captured_command_loop
	/data/vries/gdb/src/gdb/main.c:468
0xb7a10d captured_main
	/data/vries/gdb/src/gdb/main.c:1381
0xb7a209 _Z8gdb_mainP18captured_main_args
	/data/vries/gdb/src/gdb/main.c:1400
0x419704 main
	/data/vries/gdb/src/gdb/gdb.c:38
...

What leads to the crash, is that this code in gdb_readline_wrapper_line:
...
  /* Prevent parts of the prompt from being redisplayed if annotations
     are enabled, and readline's state getting out of sync.  We'll
     reinstall the callback handler, which puts the terminal in raw
     mode (or in readline lingo, in prepped state), when we're next
     ready to process user input, either in display_gdb_prompt, or if
     we're handling an asynchronous target event and running in the
     background, just before returning to the event loop to process
     further input (or more target events).  */
  if (current_ui->command_editing)
    gdb_rl_callback_handler_remove ();
...
when activated by the pagination prompt removes the callback handler (which
sets rl_linefunc to NULL).

The comment mentions re-installing the callback handler, but that doesn't
happen, so when calling rl_callback_read_char we run into:
...
  if (rl_linefunc == NULL)
    {
      _rl_errmsg ("readline_callback_read_char() called with no handler!");
      abort ();
    }
...

An example of re-installation is found here in infrun.c:
...
/* Cleanup that reinstalls the readline callback handler, if the
   target is running in the background.  If while handling the target
   event something triggered a secondary prompt, like e.g., a
   pagination prompt, we'll have removed the callback handler (see
   gdb_readline_wrapper_line).  Need to do this as we go back to the
   event loop, ready to process further input.  Note this has no
   effect if the handler hasn't actually been removed, because calling
   rl_callback_handler_install resets the line buffer, thus losing
   input.  */

static void
void
reinstall_readline_callback_handler_cleanup ()
...

Fix the crash by adding the same cleanup in run_events.

Tested on x86_64-linux.

Approved-By: Andrew Burgess <aburgess@redhat.com>

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=24796
This commit is contained in:
Tom de Vries 2026-02-20 12:21:05 +01:00
parent 6121ea1e5d
commit 4314167cec
5 changed files with 76 additions and 11 deletions

View file

@ -4429,17 +4429,7 @@ wait_for_inferior (inferior *inf)
finish_state.release ();
}
/* Cleanup that reinstalls the readline callback handler, if the
target is running in the background. If while handling the target
event something triggered a secondary prompt, like e.g., a
pagination prompt, we'll have removed the callback handler (see
gdb_readline_wrapper_line). Need to do this as we go back to the
event loop, ready to process further input. Note this has no
effect if the handler hasn't actually been removed, because calling
rl_callback_handler_install resets the line buffer, thus losing
input. */
static void
void
reinstall_readline_callback_handler_cleanup ()
{
struct ui *ui = current_ui;

View file

@ -418,5 +418,14 @@ private:
bool m_prev_enable_commit_resumed;
};
/* Cleanup that reinstalls the readline callback handler, if the
target is running in the background. If something triggered a secondary
prompt, like e.g., a pagination prompt, we'll have removed the callback
handler (see gdb_readline_wrapper_line). Need to do this when going back
to the event loop, ready to process further input. Note this has no
effect if the handler hasn't actually been removed, because calling
rl_callback_handler_install resets the line buffer, thus losing
input. */
extern void reinstall_readline_callback_handler_cleanup ();
#endif /* GDB_INFRUN_H */

View file

@ -21,6 +21,8 @@
#include "gdbsupport/cleanups.h"
#include "gdbsupport/cxx-thread.h"
#include "gdbsupport/event-loop.h"
#include "infrun.h"
#include "gdbsupport/scope-exit.h"
/* The serial event used when posting runnables. */
@ -60,6 +62,10 @@ run_events (int error, gdb_client_data client_data)
local = std::move (runnables);
}
/* Schedule cleanup in case secondary prompts (for instance, the pagination
prompt) happened while running events. */
SCOPE_EXIT { reinstall_readline_callback_handler_cleanup (); };
for (auto &item : local)
{
try

View file

@ -0,0 +1,36 @@
# 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/>.
# Regression test for PR24796. Check that a pagination prompt while handling
# a posted event doesn't crash GDB.
foreach_with_prefix pagination { off on } {
clean_restart
gdb_test_no_output "set pagination $pagination"
gdb_test_no_output "set height 8"
gdb_test -no-prompt-anchor "source py-post-event.py"
if { $pagination == "on" } {
gdb_test_multiple "" "" {
-re $pagination_prompt {
send_gdb "c\n"
}
}
}
# Check that prompt is reactive.
gdb_test "print 1" " = 1"
}

View file

@ -0,0 +1,24 @@
# 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/>.
import gdb
def Event():
for x in range(0, 10):
print("Line")
gdb.post_event(Event)