From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id sYZcJuhvYGrBHSQAWB0awg (envelope-from ) for ; Wed, 22 Jul 2026 03:23:20 -0400 Authentication-Results: simark.ca; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=mail.ru header.i=@mail.ru header.a=rsa-sha256 header.s=mail4 header.b=ampEtUba; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 9A0841E09E; Wed, 22 Jul 2026 03:23:20 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-5.1 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIM_INVALID,DKIM_SIGNED,FREEMAIL_FROM,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 8591C1E099 for ; Wed, 22 Jul 2026 03:23:19 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id EA22E4BA2E2B for ; Wed, 22 Jul 2026 07:23:18 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org EA22E4BA2E2B Authentication-Results: sourceware.org; dkim=fail reason="signature verification failed" (2048-bit key, unprotected) header.d=mail.ru header.i=@mail.ru header.a=rsa-sha256 header.s=mail4 header.b=ampEtUba Received: from send81.i.mail.ru (send81.i.mail.ru [89.221.237.176]) by sourceware.org (Postfix) with ESMTPS id 330964BA2E27 for ; Wed, 22 Jul 2026 07:21:58 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 330964BA2E27 Authentication-Results: sourceware.org; dmarc=pass (p=reject dis=none) header.from=mail.ru Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=mail.ru ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 330964BA2E27 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=89.221.237.176 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1784704918; cv=none; b=NlmlRwEQ5N9UnjFcQuCLD45KShDDCuYnPD5JGRljitm8UxBOHsLg01reT16N9dqZ7IYlcjYwFO12eTwFGYuL7JK6TFUXZgb7ROEteuS7PxTxynJIJPLIgeeG71emDmcVtwQYYwnPeBdXvtq9mJvlkOdrmNuzvV3W5f3v1ium8TA= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1784704918; c=relaxed/simple; bh=wSdSqcUWE1LAQ1VxsdzNsuOVyRfZVqRvDLq/yYtPC8o=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=xOMHabil/do7lZCsysOX4UOq0WcchiF6X3X7Uou/JGIme0sollF1wnYnOxyyFeFpiylqxXJcSlEGFZ3UbywQUulIVpnOdP5AHqlrAdABMG8VuWO8a86jVBm0mGFukAqEnJoru0BAJXsRjI1SQz7XwMa+DxvnGwKA87Nch9g7ORk= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=mail.ru header.i=@mail.ru header.a=rsa-sha256 header.s=mail4 header.b=ampEtUba DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 330964BA2E27 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=mail.ru; s=mail4; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:Cc:To:From:From:Sender:Reply-To:To:Cc:Content-Type: Content-Transfer-Encoding:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Id: List-Help:List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive: X-Cloud-Ids:Disposition-Notification-To; bh=vozAUFFNFNCGJB2CWau2IdyGIL300omWCA5f/jYeZYw=; t=1784704918; x=1784794918; b=ampEtUbaKAXHpqCQNtUEc0gxIwh5xwuSjemJ/wzEa7XIr871KrxHdAxTS+NZVyN0wgyHrFnVp7e n4knycpYb8zpV1IB0LxdcHT5UU4n9K4lHMrOb94eUqq485sB/t8nrTNwQW3s5htVKNRK6nq2Cw4/M jW96SbOac5axtPSPm5Mrkqz3lm/RNjdDy4SzdNY5QeYf4uaYGAIpqYUr+RRBg7i6tC9xJEtKe0jaf MjYdvxnhm64SUDH2yDp7yiYYzQcIaqiunniCSbtPQlZB3Eq+EMCDJJ63pPUYX3nSMdXUwWUyW8YDn /Xx1L/3nib9kTApIawszkIC4aoRkw+QXa7xQ==; Received: by exim-smtp-86556fd747-z2kwn with esmtpa (envelope-from ) id 1wmRH9-00000000J5c-2SsT; Wed, 22 Jul 2026 10:21:56 +0300 From: kurku To: gdb-patches@sourceware.org Cc: kurku Subject: [RFC][PATCH 2/2][PR cli/34409] gdb: Fix readline macros hazards. Date: Wed, 22 Jul 2026 10:21:37 +0300 Message-ID: <20260722072137.1397888-3-sandovin@mail.ru> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260722072137.1397888-1-sandovin@mail.ru> References: <20260722072137.1397888-1-sandovin@mail.ru> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Authentication-Results: exim-smtp-86556fd747-z2kwn; auth=pass smtp.auth=sandovin@mail.ru smtp.mailfrom=sandovin@mail.ru X-Mailru-Src: smtp X-7564579A: B8F34718100C35BD X-77F55803: 4F1203BC0FB41BD93ED3C6BF6FFA701FAADE0B55168149C2E86D56D94E759C39182A05F538085040F9762E73B7D594253DE06ABAFEAF6705D801F910F8CA0FFDE766CFF18C03850C64CEF2ECF6600738 X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE76D24A1449B9F25A2EA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F7900637AC83A81C8FD4AD23D82A6BABE6F325AC2E85FA5F3EDFCBAA7353EFBB5533756628E6EC474EC88527F202BF62F256C7314863CA96B33FCBE5C5C73070F13BE3878EEF46B7454FC60B9742502CCDD46D0DC6602A96AF88C695F6B57BC7E64490618DEB871D839B73339E8FC8737B5C2249F4758F8EC3E027EDCC7F00164DA146DAFE8445B8C89999729449624AB7ADAF37F6B57BC7E64490611E7FA7ABCAF51C92176DF2183F8FC7C0F04B652EEC242312D2E47CDBA5A96583BA9C0B312567BB2376E601842F6C81A19E625A9149C048EE140C956E756FBB7A4DB3626BA78294CCD8FC6C240DEA76429C9F4D5AE37F343AA9539A8B242431040A6AB1C7CE11FEE313BDA61BF53F5E1DC0837EA9F3D19764C4224003CC836476E2F48590F00D11D6E2021AF6380DFAD1A18204E546F3947CB11811A4A51E3B096D1867E19FE1407959CC434672EE6371089D37D7C0E48F6C8AA50765F79006379EA3FD4BFC112DD9EFF80C71ABB335746BA297DBC24807EABDAD6C7F3747799A X-C1DE0DAB: 0D63561A33F958A559687430C61414E15002B1117B3ED6967E40177BE8CB2B3B484B8D70797403F6823CB91A9FED034534781492E4B8EEAD16CD1653C963BB7BC79554A2A72441328621D336A7BC284946AD531847A6065A535571D14F44ED41 X-C8649E89: 1C3962B70DF3F0AD73CAD6646DEDE1918E10F71CB4DF9F9677DD89D51EBB774225B6776AC983F447FC0B9F89525902EE6F57B2FD27647F25E66C117BDB76D659CD71BF514BD2AC241EBA3AF817C0CF4DA40C62A72B9D788963496E09760E0CE52263B1D17EB4290DB8341EE9D5BE9A0AB9BE5125A9CE89BA2B754F01ED50E44F3B02949C54CE6DFD8CD93680B12512CF4C41F94D744909CEAE0DAB166F7E912C0B4C1C554206E9ACF0A6D2C91ED28CB6 X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu53w8ahmwBjZKM/YPHZyZHvz5uv+WouB9+ObcCpyrx6l7KImUglyhkEat/+ysWwi0gdhEs0JGjl6ggRWTy1haxBpVdbIX1nthFXMZebaIdHP2ghjoIc/363UZI6Kf1ptIMVWmxowtcrDwUTg0oed6elD8= X-Mailru-Sender: DA7A906D9E8450B159FDEA386FB503F7096B88C2D769F7288E7FD94675F33565553FD74E76300C1BFD08481A78328DC6B4EC7484A32A15E91269F1ADE14071E628A0B4807B87142D548AED299D6609AA574ADF34F9FF3B2B5FEEDEB644C299C0ED14614B50AE0675 X-Mras: Ok 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 I was trying to fix the ux bug in a tui and started reading GNU Readline manual. Because gdb tui and cli, uses well, GNU Readline. This led me to discovery of recordable keyboard macros in Readline. Which I tried to use in gdb and found it completely broken. The not so short explanation of the fix is the following: [1] The api model of GNU Readline callback mode with respect to macroinput is for the user to register the callback and keep it until readline hands back the control. The library would skim through rl_executing_macro string replace it with a new string from macros stack. And call the user program back. Readline thus controls the tty until it hands control back. [2] To the contrary gdb has its own event loop. It reacts to async events when the debuggee hits a breakpoint. It receives input from polled file descriptors. And it would like to call readline for a very short time. So that it just processes the newly streamed in data. And hands control back and notify gdb back when exactly one command for gdb is ready e.g. `continue`. GDB would then hand the tty down to the debuggee: one would like to debug very interactive program like python[*]. Receive and react to further events and at some point call readline in a stream like fashion again. As you can suspect in case of macroinput this two worlds clash. To see that suppose the user issues macros like "n\rn\r" via map in his `~/.inputrc`. # What would happen then? Well gdb wait for events from file descriptors and would wake up when stdin will receive some chars. It would then call a readline via `ui->call_readline`. After that readline would discover that the sequence that the user issued is actually a macro and would enter `RL_STATE_MACROINPUT`. Readline `rl_callback_read_char` would then loop until the string 'n' is formed. This would be sent to gdb. GDB would receive this 'n' and remove its own callback handler in `gdb_readline_wrapper_line`. Because it needs to hand the tty to the debuggee. It does that and goes into background. [*] To address such usecases as debugging python gdb uses several hacks to remove and later reinstall its own readline callback. So that readline is tricked to deprep the terminal, so that gdb can hand it down to the debuggee. Readline is still in `RL_STATE_MACROINPUT`, it keeps spinning through the macros. And throws the parts away because there is no one to call back. It then, due to the weird implementation details attempts to read from stdin. But tty is long time not controlled by the gdb and thus readline. So the kernel sends us SIGTTIN. We stop here. # My attempt to fix this [1] We need to trick somehow readline to behave more like a proper streaming api. Then this weird callback pasta. To do that: when we remove our handler in `gdb_rl_callback_handler_remove` If we also detect that we are in a `RL_STATE_MACROINPUT`. We should unset this bit as well so that readline loop terminates right after issuing a line for us. When we reinstall our handler in `gdb_rl_callback_handler_install` we should recover this bit back so that readline would recognize it has macros to handle. This immediately removes SIGTTIN problems, but the ux is pretty bad, because what happens next is the following: We tricked the readline to believe it has no macroinput. It hands the control back. But gdb doesn't know anything about pending macros. After gdb takes the foreground it attempts to listen the file descriptor events. And there is nothing. So it just waits until the user hits any key. And only after that all this merry-go-round spins. [2] Thus when we need to trick gdb now to process this bogus input. We do this by extending the `file_handler` struct with additional field `has_synthetic_input`. The idea here is that file descriptor may have some additional synthetic input associated with it. And so even though no actual POLLIN event fired. GDB should let the `file_ptr->proc` handle this synthetic input. We need to discover how many such synthetic inputs we have. Because we don't wanna waste cycles looping through a linked list. If there is nothing to read actually. That's why I added a new aggregate field to the `gdb_notifier` struct. Finally when we reinstall the callback in `gdb_rl_callback_handler_install` we also notify gdb that we, indeed, have pending readline macros to process. Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34409 --- gdb/event-top.c | 35 ++++++++++++++++++++++++ gdb/ui.h | 3 ++ gdbsupport/event-loop.cc | 59 +++++++++++++++++++++++++++++++++++++++- gdbsupport/event-loop.h | 4 +++ 4 files changed, 100 insertions(+), 1 deletion(-) diff --git a/gdb/event-top.c b/gdb/event-top.c index 0fb6eafec57..46a9623b988 100644 --- a/gdb/event-top.c +++ b/gdb/event-top.c @@ -369,6 +369,25 @@ gdb_rl_callback_handler_remove (void) { gdb_assert (current_ui == main_ui); + /* Readline assumes we never remove handler mid macro execution. + And assumes it would call us back line after line while it processes macro. + + This clashes with gdb's own event loop. + Because readline never properly returns back the control. + + And even uglier: it would make an attempt to read. + While tty might be handled to the debuggee (e.g. python) at the moment. + Which would lead to SIGTTIN immediately. + + We fix it by tricking readline to assume + that it is not in the MACROINPUT state anymore. */ +#ifdef RL_STATE_MACROINPUT + + current_ui->rl_macro_is_pending = RL_ISSTATE (RL_STATE_MACROINPUT); + if (current_ui->rl_macro_is_pending) + RL_UNSETSTATE (RL_STATE_MACROINPUT); +#endif /* RL_STATE_MACROINPUT */ + rl_callback_handler_remove (); callback_handler_installed = false; } @@ -387,6 +406,22 @@ gdb_rl_callback_handler_install (const char *prompt) therefore loses input. */ gdb_assert (!callback_handler_installed); + /* Paused macroinput (see gdb_rl_callback_handler_remove) + must be restored when gdb is ready to process more input from the promt. + + And be processed before gdb goes into real poll/select which might be empty. + And would thus block and require a user to issue another char to spin up the macro. + + Thus, after readline is reinstalled, we need to also notify gdb + that we have more macro via update_synthetic_input_fd. */ +#ifdef RL_STATE_MACROINPUT + if (current_ui->rl_macro_is_pending) + { + RL_SETSTATE (RL_STATE_MACROINPUT); + notify_has_synthetic_input(current_ui->input_fd); + } +#endif /* RL_STATE_MACROINPUT */ + #ifdef RL_STATE_EOF /* Some versions of readline contain a bug where the rl_eof_found flag would not be reset back to 0 in rl_initialize, despite the diff --git a/gdb/ui.h b/gdb/ui.h index 891660896ef..be3a0e5fe14 100644 --- a/gdb/ui.h +++ b/gdb/ui.h @@ -154,6 +154,9 @@ struct ui execution. */ bool keep_prompt_blocked = false; + /* Whether there is more readline macro to proceed */ + bool rl_macro_is_pending; + /* A "smart pointer" that references a particular member of the current UI. */ template diff --git a/gdbsupport/event-loop.cc b/gdbsupport/event-loop.cc index deca3d86e5d..57f80fae01d 100644 --- a/gdbsupport/event-loop.cc +++ b/gdbsupport/event-loop.cc @@ -72,6 +72,10 @@ struct file_handler /* If set, this file descriptor is used for a user interface. */ bool is_ui; + /* If set, this indicates this file has an associated synthetic input + to be processed */ + bool has_synthetic_input; + /* Was an error detected on this fd? */ int error; @@ -140,6 +144,9 @@ static struct /* Flag to tell whether the timeout should be used. */ int timeout_valid; + + /* Flag to tell how many files have pending synthetic input to be processed */ + int synthetic_input_events; } gdb_notifier; @@ -327,6 +334,7 @@ create_file_handler (int fd, int mask, handler_func * proc, file_ptr = new file_handler; file_ptr->fd = fd; file_ptr->ready_mask = 0; + file_ptr->has_synthetic_input = false; file_ptr->next_file = gdb_notifier.first_file_handler; gdb_notifier.first_file_handler = file_ptr; @@ -396,6 +404,35 @@ get_next_file_handler_to_handle_and_advance (void) return curr_next; } +static void +update_synthetic_input (file_handler *file_ptr, bool has_synthetic_input) +{ + if (file_ptr->has_synthetic_input < has_synthetic_input) + gdb_notifier.synthetic_input_events++; + else if (file_ptr->has_synthetic_input > has_synthetic_input) + gdb_notifier.synthetic_input_events--; + + file_ptr->has_synthetic_input = has_synthetic_input; +} + +void notify_has_synthetic_input (int fd) +{ + file_handler *file_ptr; + + for (file_ptr = gdb_notifier.first_file_handler; + file_ptr != NULL; + file_ptr = file_ptr->next_file) + { + if (file_ptr->fd == fd) + break; + } + + if (file_ptr == NULL) + return; + update_synthetic_input (file_ptr, true); +} + + /* Remove the file descriptor FD from the list of monitored fd's: i.e. we don't care anymore about events on the FD. */ void @@ -416,6 +453,10 @@ delete_file_handler (int fd) if (file_ptr == NULL) return; + /* Reset synthetic input events number agregate */ + if (file_ptr->has_synthetic_input) + gdb_notifier.synthetic_input_events -= 1; + #ifdef HAVE_POLL if (use_poll) { @@ -564,6 +605,12 @@ handle_file_event (file_handler *file_ptr, int ready_mask) event_loop_ui_debug_printf (file_ptr->is_ui, "invoking fd file handler `%s`", file_ptr->name.c_str ()); + + /* Clear synthetic input flag now + because the proc would handle it + and set it back if there is more. */ + if (file_ptr->has_synthetic_input) + update_synthetic_input(file_ptr, false); file_ptr->proc (file_ptr->error, file_ptr->client_data); } } @@ -587,6 +634,10 @@ gdb_wait_for_event (int block) if (gdb_notifier.num_fds == 0) return -1; + /* If we have synthetic input available we should not block */ + if (gdb_notifier.synthetic_input_events > 0) + block = 0; + if (block) update_wait_timeout (); @@ -647,7 +698,9 @@ gdb_wait_for_event (int block) } /* Avoid looking at poll_fds[i]->revents if no event fired. */ - if (num_found <= 0) + if (num_found < 0) + return 0; + if (num_found + gdb_notifier.synthetic_input_events == 0) return 0; /* Run event handlers. We always run just one handler and go back @@ -670,6 +723,8 @@ gdb_wait_for_event (int block) int i = file_ptr->poll_fds_index; short revents = gdb_notifier.poll_fds[i].revents; + if (file_ptr->has_synthetic_input) + mask |= POLLIN; if (revents) mask |= revents; } @@ -688,6 +743,8 @@ gdb_wait_for_event (int block) { file_ptr = get_next_file_handler_to_handle_and_advance (); + if (file_ptr->has_synthetic_input) + mask |= GDB_READABLE; if (FD_ISSET (file_ptr->fd, &gdb_notifier.ready_masks[0])) mask |= GDB_READABLE; if (FD_ISSET (file_ptr->fd, &gdb_notifier.ready_masks[1])) diff --git a/gdbsupport/event-loop.h b/gdbsupport/event-loop.h index 85cae592965..8b0fdb19915 100644 --- a/gdbsupport/event-loop.h +++ b/gdbsupport/event-loop.h @@ -97,6 +97,10 @@ extern int create_timer (int milliseconds, gdb_client_data client_data); extern void delete_timer (int id); +/* Notifies gdb that there is more synthetic input associated with fd */ + +extern void notify_has_synthetic_input (int fd); + /* Must be defined by client. */ extern void handle_event_loop_exception (const gdb_exception &); -- 2.43.0