From patchwork Tue Nov 28 15:01:21 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom de Vries X-Patchwork-Id: 80924 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 40146388204C for ; Tue, 28 Nov 2023 15:01:40 +0000 (GMT) X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from smtp-out2.suse.de (smtp-out2.suse.de [IPv6:2a07:de40:b251:101:10:150:64:2]) by sourceware.org (Postfix) with ESMTPS id 20CFA3858C54 for ; Tue, 28 Nov 2023 15:01:25 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 20CFA3858C54 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=suse.de Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=suse.de ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 20CFA3858C54 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=2a07:de40:b251:101:10:150:64:2 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1701183686; cv=none; b=gi2jZXam/OSKlqolVFaKcXSbCC27L/caKxI+JD5YDjnetd0BXhalHDdXhew9DZfBDl5Z5NXNEXrPoday1PgrelEanbFBcjTfbj25nBPldpaDkuv/XcGST3GlCwP2wyOoMsxJdyHMivbeKwYO3Cy27nH23+HIea+cGeniSCsPBF0= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1701183686; c=relaxed/simple; bh=SlBcCdFMUgEXcCDpuj5nFBNYdpS4CyvDIb6yVc0KgFo=; h=From:To:Subject:Date:Message-Id:MIME-Version; b=pD3Y+3iT1sXjzMXiykhSaMs1s0F0G+ihknRIBXvycHoTJRRNI0l/u2ptPot2MoVbIpPZp98fmQfFDPgP9nMlltiPvtbxCf7ENJmVBwL/NVoqKT2wEk8/A4fzybcHc5Sn+S1iC3QJcvq9Z1NVCpLURNFOyzF9xaAaFoHsR2IqDsI= ARC-Authentication-Results: i=1; server2.sourceware.org Received: from imap1.dmz-prg2.suse.org (imap1.dmz-prg2.suse.org [IPv6:2a07:de40:b281:104:10:150:64:97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp-out2.suse.de (Postfix) with ESMTPS id 299B61F7AB; Tue, 28 Nov 2023 15:01:24 +0000 (UTC) Received: from imap1.dmz-prg2.suse.org (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by imap1.dmz-prg2.suse.org (Postfix) with ESMTPS id 0BDF613763; Tue, 28 Nov 2023 15:01:24 +0000 (UTC) Received: from dovecot-director2.suse.de ([2a07:de40:b281:106:10:150:64:167]) by imap1.dmz-prg2.suse.org with ESMTPSA id 6StrAcQAZmVLSQAAD6G6ig (envelope-from ); Tue, 28 Nov 2023 15:01:24 +0000 From: Tom de Vries To: gdb-patches@sourceware.org Cc: Simon Marchi Subject: [PATCH v2] [gdb] Fix assert in delete_breakpoint Date: Tue, 28 Nov 2023 16:01:21 +0100 Message-Id: <20231128150121.23760-1-tdevries@suse.de> X-Mailer: git-send-email 2.35.3 MIME-Version: 1.0 X-Spamd-Bar: ++++++++++++++ X-Spam-Score: 14.01 X-Rspamd-Server: rspamd1 Authentication-Results: smtp-out2.suse.de; dkim=none; spf=softfail (smtp-out2.suse.de: 2a07:de40:b281:104:10:150:64:97 is neither permitted nor denied by domain of tdevries@suse.de) smtp.mailfrom=tdevries@suse.de; dmarc=fail reason="No valid SPF, No valid DKIM" header.from=suse.de (policy=none) X-Rspamd-Queue-Id: 299B61F7AB X-Spamd-Result: default: False [14.01 / 50.00]; ARC_NA(0.00)[]; RCVD_VIA_SMTP_AUTH(0.00)[]; SPAMHAUS_XBL(0.00)[2a07:de40:b281:104:10:150:64:97:from]; FROM_HAS_DN(0.00)[]; TO_DN_SOME(0.00)[]; R_MISSING_CHARSET(2.50)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; MIME_GOOD(-0.10)[text/plain]; NEURAL_SPAM_SHORT(1.72)[0.573]; BROKEN_CONTENT_TYPE(1.50)[]; R_SPF_SOFTFAIL(4.60)[~all:c]; RCVD_COUNT_THREE(0.00)[3]; MX_GOOD(-0.01)[]; RCPT_COUNT_TWO(0.00)[2]; MID_CONTAINS_FROM(1.00)[]; NEURAL_SPAM_LONG(3.50)[1.000]; FUZZY_BLOCKED(0.00)[rspamd.com]; FROM_EQ_ENVFROM(0.00)[]; R_DKIM_NA(2.20)[]; MIME_TRACE(0.00)[0:+]; RCVD_TLS_ALL(0.00)[]; BAYES_HAM(-3.00)[100.00%]; DMARC_POLICY_SOFTFAIL(0.10)[suse.de : No valid SPF, No valid DKIM,none] X-Spam-Status: No, score=-12.5 required=5.0 tests=BAYES_00, GIT_PATCH_0, KAM_DMARC_STATUS, SPF_HELO_NONE, SPF_PASS, TXREP, T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org 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+patchwork=sourceware.org@sourceware.org On a pinebook (aarch64 with 64-bit kernel and 32-bit userland) with test-case gdb.base/gdb-sigterm.exp I run into: ... intrusive_list.h:458: internal-error: erase_element: \ Assertion `elem_node->prev != INTRUSIVE_LIST_UNLINKED_VALUE' failed.^M ... which happens while executing this statement in delete_breakpoint: ... breakpoint_chain.erase (breakpoint_chain.iterator_to (*bpt)); ... The following events lead to the assertion failure: - a single-step breakpoint is hit, - delete_just_stopped_threads_single_step_breakpoints is called, - during delete_just_stopped_threads_single_step_breakpoints, delete_breakpoint is called, - breakpoint_chain.erase is called, - gdb is interrupted by SIGTERM before finishing delete_breakpoint. The interrupt happens due to a QUIT during a target_write_with_progress call, which is called during default_memory_remove_breakpoint, - the SIGTERM triggers a SCOPE_EXIT cleanup, calling delete_just_stopped_threads_infrun_breakpoints which ends up calling delete_breakpoint again for the same breakpoint, and - breakpoint_chain.erase is called the second time, and the assert triggers. There is an ad-hoc reentrancy control mechanism in delete_breakpoint: ... void delete_breakpoint (struct breakpoint *bpt) { .... if (bpt->type == bp_none) return; ... bpt->type = bp_none; ... } ... which partitions up the function into three parts: - an initial part that is always executed, - a second part that can be executed more than once per breakpoint, and - a third part that can be executed only once per breakpoint. One way of looking at the assertion failure is that commit e2a1578868a ("gdb: link breakpoints with intrusive_list") introduced code in the second part that cannot be executed more than once, which can be fixed by guarding the breakpoint_chain.erase call with is_linked. Another way of looking at it is to ask the question: if we're so eager to delete the breakpoint that we call it as a SCOPE_EXIT cleanup, why do we allow it to be interrupted in the first place? Fix this by suppressing QUIT during delete_breakpoint. Tested on the pinebook and x86_64-linux. PR gdb/31061 Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31061 --- gdb/breakpoint.c | 3 +++ gdb/utils.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ gdb/utils.h | 21 +++++++++++++++++++++ 3 files changed, 69 insertions(+) base-commit: 14414227bfac8ef1803715b3b642f8ba0ab6fff8 diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c index 699919e32b3..768b92676f8 100644 --- a/gdb/breakpoint.c +++ b/gdb/breakpoint.c @@ -12582,6 +12582,9 @@ notify_breakpoint_deleted (breakpoint *b) void delete_breakpoint (struct breakpoint *bpt) { + /* Make sure that the function cannot be interrupted by QUIT. */ + scoped_suppress_quit do_scoped_suppress_quit; + gdb_assert (bpt != NULL); /* Has this bp already been deleted? This can happen because diff --git a/gdb/utils.c b/gdb/utils.c index 7a1841ba21e..405f5de29c4 100644 --- a/gdb/utils.c +++ b/gdb/utils.c @@ -654,6 +654,48 @@ quit (void) #endif } +/* See utils.h. */ + +int scoped_suppress_quit::suppress_quit_enabled_cnt; +bool scoped_suppress_quit::suppress_quit_seen; + +/* See utils.h. */ + +scoped_suppress_quit::scoped_suppress_quit () +{ + gdb_assert (is_main_thread ()); + suppress_quit_enabled_cnt++; +} + +/* See utils.h. */ + +scoped_suppress_quit::~scoped_suppress_quit () noexcept(false) +{ + suppress_quit_enabled_cnt--; + gdb_assert (suppress_quit_enabled_cnt >= 0); + + if (suppress_quit_enabled_cnt != 0) + return; + + bool tmp_suppress_quit_seen = suppress_quit_seen; + suppress_quit_seen = false; + + if (!tmp_suppress_quit_seen) + return; + + /* A QUIT was suppressed, unsuppress it here. This may throw. */ + QUIT; +} + +/* See utils.h. */ + +bool +scoped_suppress_quit::suppress_quit_enabled () +{ + suppress_quit_seen = true; + return suppress_quit_enabled_cnt > 0; +} + /* See defs.h. */ void @@ -662,6 +704,9 @@ maybe_quit (void) if (!is_main_thread ()) return; + if (scoped_suppress_quit::suppress_quit_enabled ()) + return; + if (sync_quit_force_run) quit (); diff --git a/gdb/utils.h b/gdb/utils.h index f646b300530..03a76a035b4 100644 --- a/gdb/utils.h +++ b/gdb/utils.h @@ -425,4 +425,25 @@ struct deferred_warnings std::vector m_warnings; }; +/* RAII-style class to suppress the effect of calling QUIT until scope exit. */ + +class scoped_suppress_quit +{ +public: + + scoped_suppress_quit (); + ~scoped_suppress_quit () noexcept(false); + + /* Whether suppression is currently active. */ + static bool suppress_quit_enabled (); + +private: + + /* Nesting level. */ + static int suppress_quit_enabled_cnt; + + /* Whether QUIT was called during suppression. */ + static bool suppress_quit_seen; +}; + #endif /* UTILS_H */