mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2026-08-27 00:26:02 -04:00
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
144 lines
4.1 KiB
C
144 lines
4.1 KiB
C
/* Run a function on the main thread
|
|
Copyright (C) 2019-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/>. */
|
|
|
|
#include "run-on-main-thread.h"
|
|
#include "ser-event.h"
|
|
#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. */
|
|
|
|
static struct serial_event *runnable_event;
|
|
|
|
/* Runnables that have been posted. */
|
|
|
|
static std::vector<std::function<void ()>> runnables;
|
|
|
|
/* Mutex to hold when handling RUNNABLE_EVENT or RUNNABLES. */
|
|
|
|
static gdb::mutex runnable_mutex;
|
|
|
|
/* The main thread's thread id. */
|
|
|
|
static gdb::thread::id main_thread_id;
|
|
|
|
/* Run all the queued runnables. */
|
|
|
|
static void
|
|
run_events (int error, gdb_client_data client_data)
|
|
{
|
|
std::vector<std::function<void ()>> local;
|
|
|
|
/* Hold the lock while changing the globals, but not while running
|
|
the runnables. */
|
|
{
|
|
gdb::lock_guard<gdb::mutex> lock (runnable_mutex);
|
|
|
|
/* Clear the event fd. Do this before flushing the events list,
|
|
so that any new event post afterwards is sure to re-awaken the
|
|
event loop. */
|
|
serial_event_clear (runnable_event);
|
|
|
|
/* Move the vector in case running a runnable pushes a new
|
|
runnable. */
|
|
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
|
|
{
|
|
item ();
|
|
}
|
|
catch (const gdb_exception_forced_quit &e)
|
|
{
|
|
/* GDB is terminating, so:
|
|
- make sure this is propagated, and
|
|
- no need to keep running things, so propagate immediately. */
|
|
throw;
|
|
}
|
|
catch (const gdb_exception_quit &e)
|
|
{
|
|
/* Should cancellation of a runnable event cancel the execution of
|
|
the following one? The answer is not clear, so keep doing what
|
|
we've done so far: ignore this exception. */
|
|
}
|
|
catch (const gdb_exception &)
|
|
{
|
|
/* Ignore exceptions in the callback. */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* See run-on-main-thread.h. */
|
|
|
|
void
|
|
run_on_main_thread (std::function<void ()> &&func)
|
|
{
|
|
gdb::lock_guard<gdb::mutex> lock (runnable_mutex);
|
|
runnables.emplace_back (std::move (func));
|
|
serial_event_set (runnable_event);
|
|
}
|
|
|
|
static bool main_thread_id_initialized = false;
|
|
|
|
/* See run-on-main-thread.h. */
|
|
|
|
bool
|
|
is_main_thread ()
|
|
{
|
|
/* Initialize main_thread_id on first use of is_main_thread. */
|
|
if (!main_thread_id_initialized)
|
|
{
|
|
main_thread_id_initialized = true;
|
|
|
|
main_thread_id = gdb::this_thread::get_id ();
|
|
}
|
|
|
|
return gdb::this_thread::get_id () == main_thread_id;
|
|
}
|
|
|
|
INIT_GDB_FILE (run_on_main_thread)
|
|
{
|
|
/* The variable main_thread_id should be initialized when entering main, or
|
|
at an earlier use, so it should already be initialized here. */
|
|
gdb_assert (main_thread_id_initialized);
|
|
|
|
/* Assume that we execute this in the main thread. */
|
|
gdb_assert (is_main_thread ());
|
|
|
|
runnable_event = make_serial_event ();
|
|
add_file_handler (serial_event_fd (runnable_event), run_events, nullptr,
|
|
"run-on-main-thread");
|
|
|
|
/* A runnable may refer to an extension language. So, we want to
|
|
make sure any pending ones have been deleted before the extension
|
|
languages are shut down. */
|
|
add_final_cleanup ([] ()
|
|
{
|
|
gdb::lock_guard<gdb::mutex> lock (runnable_mutex);
|
|
runnables.clear ();
|
|
});
|
|
}
|