| Message ID | 76728c018feaf3df1dd2703300f965f5666a036c.1766508488.git.aburgess@redhat.com |
|---|---|
| State | New |
| Headers |
Return-Path: <gdb-patches-bounces~patchwork=sourceware.org@sourceware.org> X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id A18A04BA2E25 for <patchwork@sourceware.org>; Tue, 23 Dec 2025 16:49:15 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org A18A04BA2E25 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=dDEkQ99L X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org 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 6E3B44BA2E05 for <gdb-patches@sourceware.org>; Tue, 23 Dec 2025 16:48:36 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 6E3B44BA2E05 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 6E3B44BA2E05 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1766508516; cv=none; b=SPcd1SBNuWiXda9DIufBzzBDgfteAlvHs6cO8Csr8B2CwIIB4+I9CeACSEV7sMkL0CqKveFmZ+oYdqqPrKMth05hE9GUWvWavOW1edDSL55J7EoI03NVTVOedwNmVoATRM+ECD8E82tngJB2M9hAhOv7/m3k20uYo0uVP8MM65U= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1766508516; c=relaxed/simple; bh=3K4mB+d/27pfRMPWGKVtPWmsWriHASauDjTSjLxc3KI=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=o3qHDzvLCPT8zvEq9KZxp/EicfQxpIV3xKDcRmBolTBCgu84mnHbknbyf/Vfc4UAjpt7GTe17+dSeK+tx0tTOZoaTkZWA29rg/NHXe3DMNBdORh75rfUdB+B5LPO6Ti8OZlfFzChKvfZEBw0Qj3QXF+elZB18p/U8sR6o7An8dM= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 6E3B44BA2E05 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1766508516; 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; bh=eJEZkVy9WAm8SX3biY0MpVKqU68KeBVJGCjjsHyUbmQ=; b=dDEkQ99LGLMT//l2C4ZA3fPQlkraGTMage7/VIUUoPrX50YJTbt3S4YtBTLW2c42nn85Lf 5nScGYYkU4t0utan/vbUwKFTzIywHsn8jEy1dMosh9gm5My2WUr2h2MY0zBQ5UYnXcKJzJ NXjsUcbPnYaKUpZg3BAv8wFlNcSqtyg= Received: from mail-wr1-f72.google.com (mail-wr1-f72.google.com [209.85.221.72]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-250-Ih1_JUfLOpGC8KDv1BsMXQ-1; Tue, 23 Dec 2025 11:48:35 -0500 X-MC-Unique: Ih1_JUfLOpGC8KDv1BsMXQ-1 X-Mimecast-MFC-AGG-ID: Ih1_JUfLOpGC8KDv1BsMXQ_1766508514 Received: by mail-wr1-f72.google.com with SMTP id ffacd0b85a97d-42fdbba545fso3332062f8f.0 for <gdb-patches@sourceware.org>; Tue, 23 Dec 2025 08:48:34 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766508513; x=1767113313; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=eJEZkVy9WAm8SX3biY0MpVKqU68KeBVJGCjjsHyUbmQ=; b=K87FuT6fzyyqS+8X4D3rPtdm1NWTkbDDnPDvsKWrbETiIeFL2ZQxT8JprxoJgdplA4 u9p7YmIrqAskkyJ7iKxPNRsymV75/fYEPLE6HJutuIoI+h1yZ+M/9WevtEOT6t2cYmLW zQxbs7bUOgVDhTiLsTGauDpb9lpcI85+r12U6EV+VDMOooELbSAF28LlLFJ96InphIR1 tofjQHIkRXs5wngS6IHQ7+5pw/tnjOeQH2NhH1BM3iqWLdPkhYm2AJp6BeBtHuYekxQM 39CrOqhHmT5ORMyy06gVDZB26wwsslPLCbR5WDylCi4nfrK7oUdOkWF3/ZxtXa5FN7w7 mFnA== X-Gm-Message-State: AOJu0YwJSNmcyd2YO+jp+9HelkEMGbHfVUKq9e0xBa5Hwqjq5wKwfEw6 MZAucuHb+ee8pBFDd1Bm8uW5XrQc3Hjyvy5yvIrgM/7h0ylGNvegUH5Fy7HSATSb4Jyr7ozqJDK OjhCHk91SLVgNx3Msddpn+6sMAsOoziGyC2CLaFnb78a61lHmxyQ3wIWpY3njKmxkgD6+Csn5GF FpM/dlWhNYRLM7M2LgKVrJtRHeujl5WteROeC34FpBmW5bhCc= X-Gm-Gg: AY/fxX5SGONiOdLh3uTZic9y03kZxC9ss1uUBOweT7Fz4jlES011YByceJc8aEP1SG5 +6otGq5Y4ITc16ME62JUpaymfLuMr4j5ZRdArdmNrsw1KwNL5WyCt8vOaGWKN0KizNxeRfFOATl xtMiYhDp4hKHNm784zR4ypmL5OiMCgnEZB408q5gRE0u4qatimps+5crtzlGuQFtN1oC5FvFZoZ /QJYZSL1+YbgxZWJdv31qGSNg5CyneXl8ZAr8JJfjX9fBYw+IErMwGDXZZIlimOpE56dcT6IvtR EomWyZdeedUTLnhR7y/VdqwGe4rq95jmzJljgmEb5GPsV5ovSrQLT1zrvx89blm3U/qtmBla2bz 3Mw4fRtws X-Received: by 2002:a05:6000:2389:b0:430:fe6c:b1aa with SMTP id ffacd0b85a97d-432448c9ddemr22438502f8f.26.1766508513148; Tue, 23 Dec 2025 08:48:33 -0800 (PST) X-Google-Smtp-Source: AGHT+IHzHPqImUZ6JoNBjx3WxepC93Ok9tW51LFnZSL0ixLjzBqbojQyqnnbCpdV74E1H1ZmvDZduA== X-Received: by 2002:a05:6000:2389:b0:430:fe6c:b1aa with SMTP id ffacd0b85a97d-432448c9ddemr22438469f8f.26.1766508512649; Tue, 23 Dec 2025 08:48:32 -0800 (PST) Received: from localhost ([2a00:23c6:e10f:ad01:8fe5:ba5e:86b4:8726]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-4324eaa2bdfsm29442594f8f.32.2025.12.23.08.48.32 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 23 Dec 2025 08:48:32 -0800 (PST) From: Andrew Burgess <aburgess@redhat.com> To: gdb-patches@sourceware.org Cc: Andrew Burgess <aburgess@redhat.com> Subject: [PATCH] gdb: hold a target_ops_ref in scoped_finish_thread_state Date: Tue, 23 Dec 2025 16:48:30 +0000 Message-Id: <76728c018feaf3df1dd2703300f965f5666a036c.1766508488.git.aburgess@redhat.com> X-Mailer: git-send-email 2.25.4 MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: Pd455L9xnzWZsgZ9l7mp6IOOJ-QfLAPy9a1MraSq3j0_1766508514 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-11.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, HEXHASH_WORD, KAM_SHORT, RCVD_IN_DNSWL_BLOCKED, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP, URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list <gdb-patches.sourceware.org> List-Unsubscribe: <https://sourceware.org/mailman/options/gdb-patches>, <mailto:gdb-patches-request@sourceware.org?subject=unsubscribe> List-Archive: <https://sourceware.org/pipermail/gdb-patches/> List-Post: <mailto:gdb-patches@sourceware.org> List-Help: <mailto:gdb-patches-request@sourceware.org?subject=help> List-Subscribe: <https://sourceware.org/mailman/listinfo/gdb-patches>, <mailto:gdb-patches-request@sourceware.org?subject=subscribe> Errors-To: gdb-patches-bounces~patchwork=sourceware.org@sourceware.org |
| Series |
gdb: hold a target_ops_ref in scoped_finish_thread_state
|
|
Commit Message
Andrew Burgess
Dec. 23, 2025, 4:48 p.m. UTC
This commit fixes a use after free issue that was reported here:
https://inbox.sourceware.org/gdb-patches/68354b98-795a-4b50-9eac-e54aa1d01b9d@simark.ca
This issue was exposed by the gdb.replay/missing-thread.exp test that
was added in this commit:
commit 8bd08ee92c4a7bf2ad9e29c4da32a276ef2257fc
Date: Fri May 16 17:56:58 2025 +0100
gdb: crash if thread unexpectedly disappears from thread list
It is worth pointing out that the use after free issue existed before
this commit, this commit just introduced a test that exposed the issue
when GDB is run with the address sanitizer.
It has taken a while to get this fix ready for upstream as this fix
depended on the recently committed patch:
commit 43db8f70d86b2492b79f59342187b919fd58b3dd
Date: Thu Oct 23 16:34:20 2025 +0100
gdbsupport: remove undefined behaviour from (forward_)scope_exit
The problem is that the first commit above introduces a test which
causes the remote target to disconnect while processing an inferior
stop event, specifically, within normal_stop (infrun.c), GDB calls
update_thread_list, and it is during this call that the inferior
disconnects.
When the remote target disconnects, GDB immediately unpushes the
remote target. See remote_unpush_target and its uses in remote.c.
If this is the last use of the remote target, then unpushing it will
cause the target to be deleted.
This is a problem, because in normal_stop, we have an RAII variable
maybe_finish_thread_state, which is an optional
scoped_finish_thread_state, and in some cases, this will hold a
pointer to the process_startum_target which needs to be finished.
So the order of events is:
1. Call to normal_stop.
2. Create maybe_finish_thread_state with a pointer to the current
remote_target object.
3. Call update_thread_list.
4. Remote disconnects, GDB unpushes and deletes the current
remote_target object. GDB throws an exception.
5. The exception propagates back to normal_stop.
6. The destructor for maybe_finish_thread_state runs, and tries to
make use of its cached pointer to the (now deleted) remote_target
object. Badness ensues.
This bug isn't restricted to normal_stop. If a remote target
disconnects anywhere where there is a scoped_finish_thread_state in
the call stack then this issue could arise.
I think what we need to do is to ensure that the remote_target is not
actually deleted until after the scoped_finish_thread_state has been
cleaned up.
And so, to achieve this, I propose changing scoped_finish_thread_state
to hold a target_ops_ref rather than a pointer to the target_ops
object. Holding the reference will prevent the object from being
deleted.
The new scoped_finish_thread_state is defined within its own file, and
is a drop in replacement for the existing class.
On my local machine the gdb.replay/missing-thread.exp test passes
cleanly after this commit (with address sanitizers), but when I test
on some other machines with a more recent Fedora install, I'm still
seeing test failures (both before and after this patch), though not
relating to the address sanitizer (at least, I don't see an error from
the sanitizer). I don't think these other issues are directly related
to the problem being addressed in this commit, and so I'm proposing
this patch for inclusion anyway. I'll continue to look at the test
and see if I can fix the other failures too. Or maybe I'll end up
having to back out the test.
---
gdb/finish-thread-state.h | 64 +++++++++++++++++++++++++++++++++++++++
gdb/gdbthread.h | 5 ---
gdb/infcmd.c | 1 +
gdb/infrun.c | 1 +
gdb/remote.c | 1 +
5 files changed, 67 insertions(+), 5 deletions(-)
create mode 100644 gdb/finish-thread-state.h
base-commit: 5d8562e89a612f2d0e5c9e0813a0d37620eff78c
Comments
On 12/23/25 1:48 PM, Andrew Burgess wrote: > This commit fixes a use after free issue that was reported here: > > https://inbox.sourceware.org/gdb-patches/68354b98-795a-4b50-9eac-e54aa1d01b9d@simark.ca > > This issue was exposed by the gdb.replay/missing-thread.exp test that > was added in this commit: > > commit 8bd08ee92c4a7bf2ad9e29c4da32a276ef2257fc > Date: Fri May 16 17:56:58 2025 +0100 > > gdb: crash if thread unexpectedly disappears from thread list > > It is worth pointing out that the use after free issue existed before > this commit, this commit just introduced a test that exposed the issue > when GDB is run with the address sanitizer. > > It has taken a while to get this fix ready for upstream as this fix > depended on the recently committed patch: > > commit 43db8f70d86b2492b79f59342187b919fd58b3dd > Date: Thu Oct 23 16:34:20 2025 +0100 > > gdbsupport: remove undefined behaviour from (forward_)scope_exit > > The problem is that the first commit above introduces a test which > causes the remote target to disconnect while processing an inferior > stop event, specifically, within normal_stop (infrun.c), GDB calls > update_thread_list, and it is during this call that the inferior > disconnects. > > When the remote target disconnects, GDB immediately unpushes the > remote target. See remote_unpush_target and its uses in remote.c. > > If this is the last use of the remote target, then unpushing it will > cause the target to be deleted. > > This is a problem, because in normal_stop, we have an RAII variable > maybe_finish_thread_state, which is an optional > scoped_finish_thread_state, and in some cases, this will hold a > pointer to the process_startum_target which needs to be finished. > > So the order of events is: > > 1. Call to normal_stop. > > 2. Create maybe_finish_thread_state with a pointer to the current > remote_target object. > > 3. Call update_thread_list. > > 4. Remote disconnects, GDB unpushes and deletes the current > remote_target object. GDB throws an exception. > > 5. The exception propagates back to normal_stop. > > 6. The destructor for maybe_finish_thread_state runs, and tries to > make use of its cached pointer to the (now deleted) remote_target > object. Badness ensues. > > This bug isn't restricted to normal_stop. If a remote target > disconnects anywhere where there is a scoped_finish_thread_state in > the call stack then this issue could arise. > > I think what we need to do is to ensure that the remote_target is not > actually deleted until after the scoped_finish_thread_state has been > cleaned up. > > And so, to achieve this, I propose changing scoped_finish_thread_state > to hold a target_ops_ref rather than a pointer to the target_ops > object. Holding the reference will prevent the object from being > deleted. > > The new scoped_finish_thread_state is defined within its own file, and > is a drop in replacement for the existing class. > > On my local machine the gdb.replay/missing-thread.exp test passes > cleanly after this commit (with address sanitizers), but when I test > on some other machines with a more recent Fedora install, I'm still > seeing test failures (both before and after this patch), though not > relating to the address sanitizer (at least, I don't see an error from > the sanitizer). I don't think these other issues are directly related > to the problem being addressed in this commit, and so I'm proposing > this patch for inclusion anyway. I'll continue to look at the test > and see if I can fix the other failures too. Or maybe I'll end up > having to back out the test. Hi! Thanks for the great explanation. I think this makes sense, and as much as I could follow the code, it looks pretty straight-forward Reviewed-By: Guinevere Larsen <guinevere@redhat.com>
On 12/23/25 11:48 AM, Andrew Burgess wrote: > This commit fixes a use after free issue that was reported here: > > https://inbox.sourceware.org/gdb-patches/68354b98-795a-4b50-9eac-e54aa1d01b9d@simark.ca > > This issue was exposed by the gdb.replay/missing-thread.exp test that > was added in this commit: > > commit 8bd08ee92c4a7bf2ad9e29c4da32a276ef2257fc > Date: Fri May 16 17:56:58 2025 +0100 > > gdb: crash if thread unexpectedly disappears from thread list > > It is worth pointing out that the use after free issue existed before > this commit, this commit just introduced a test that exposed the issue > when GDB is run with the address sanitizer. > > It has taken a while to get this fix ready for upstream as this fix > depended on the recently committed patch: > > commit 43db8f70d86b2492b79f59342187b919fd58b3dd > Date: Thu Oct 23 16:34:20 2025 +0100 > > gdbsupport: remove undefined behaviour from (forward_)scope_exit > > The problem is that the first commit above introduces a test which > causes the remote target to disconnect while processing an inferior > stop event, specifically, within normal_stop (infrun.c), GDB calls > update_thread_list, and it is during this call that the inferior > disconnects. > > When the remote target disconnects, GDB immediately unpushes the > remote target. See remote_unpush_target and its uses in remote.c. > > If this is the last use of the remote target, then unpushing it will > cause the target to be deleted. > > This is a problem, because in normal_stop, we have an RAII variable > maybe_finish_thread_state, which is an optional > scoped_finish_thread_state, and in some cases, this will hold a > pointer to the process_startum_target which needs to be finished. > > So the order of events is: > > 1. Call to normal_stop. > > 2. Create maybe_finish_thread_state with a pointer to the current > remote_target object. > > 3. Call update_thread_list. > > 4. Remote disconnects, GDB unpushes and deletes the current > remote_target object. GDB throws an exception. > > 5. The exception propagates back to normal_stop. > > 6. The destructor for maybe_finish_thread_state runs, and tries to > make use of its cached pointer to the (now deleted) remote_target > object. Badness ensues. > > This bug isn't restricted to normal_stop. If a remote target > disconnects anywhere where there is a scoped_finish_thread_state in > the call stack then this issue could arise. > > I think what we need to do is to ensure that the remote_target is not > actually deleted until after the scoped_finish_thread_state has been > cleaned up. > > And so, to achieve this, I propose changing scoped_finish_thread_state > to hold a target_ops_ref rather than a pointer to the target_ops > object. Holding the reference will prevent the object from being > deleted. > > The new scoped_finish_thread_state is defined within its own file, and > is a drop in replacement for the existing class. This seems fine to me. I suppose that when this happens, finish_thread_state will do nothing, because all of the threads owned by that target will have been deleted. Approved-By: Simon Marchi <simon.marchi@efficios.com> See comments below. > diff --git a/gdb/finish-thread-state.h b/gdb/finish-thread-state.h > new file mode 100644 > index 00000000000..f025b1221a6 > --- /dev/null > +++ b/gdb/finish-thread-state.h > @@ -0,0 +1,64 @@ > +/* Copyright (C) 2025 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 "gdbsupport/scope-exit.h" > +#include "gdbthread.h" > +#include "target.h" Missing include guards (caught by the check-include-guards pre-commit check). > + > +namespace detail > +{ > + > +/* Policy class for use with scope_exit_base. Calls finish_thread_state on > + scope exit, unless release() is called to disengage. The release > + mechanism is supplied by scope_exit_base. */ > + > +struct scoped_finish_thread_state_policy > +{ > + /* Store TARG and PTID for a later call to finish_thread_state. For the > + meaning of these arguments, see the comments on finish_thread_state. */ > + explicit scoped_finish_thread_state_policy (process_stratum_target *targ, > + ptid_t ptid) > + : m_ptid (ptid) > + { > + if (targ != nullptr) > + m_target_ref = target_ops_ref::new_reference (targ); clangd complains about this line (can't pass a `process_stratum_target *` as a `target_ops *`), because when it analyzes this header file on its own, it probably only sees a forward declaration for process_stratum_target, not the full class, and therefore doesn't know that it is a subclass of target_ops. This probably compiles thanks for transitive includes. It would be good to add an include of "process-stratum-target.h" to make it "include what you use". > + } > + > + /* Called on scope exit unless release was called, see scope_exit_base > + for details. Calls finish_thread_state with stored target and ptid. */ > + void on_exit () > + { > + target_ops *t = m_target_ref.get (); > + process_stratum_target *p_target > + = gdb::checked_static_cast<process_stratum_target *> (t); Please add an include for "gdbsupport/gdb-checked-static-cast.h", otherwise, my clangd complains about checked_static_cast not existing :). Simo
Simon Marchi <simark@simark.ca> writes: > On 12/23/25 11:48 AM, Andrew Burgess wrote: >> This commit fixes a use after free issue that was reported here: >> >> https://inbox.sourceware.org/gdb-patches/68354b98-795a-4b50-9eac-e54aa1d01b9d@simark.ca >> >> This issue was exposed by the gdb.replay/missing-thread.exp test that >> was added in this commit: >> >> commit 8bd08ee92c4a7bf2ad9e29c4da32a276ef2257fc >> Date: Fri May 16 17:56:58 2025 +0100 >> >> gdb: crash if thread unexpectedly disappears from thread list >> >> It is worth pointing out that the use after free issue existed before >> this commit, this commit just introduced a test that exposed the issue >> when GDB is run with the address sanitizer. >> >> It has taken a while to get this fix ready for upstream as this fix >> depended on the recently committed patch: >> >> commit 43db8f70d86b2492b79f59342187b919fd58b3dd >> Date: Thu Oct 23 16:34:20 2025 +0100 >> >> gdbsupport: remove undefined behaviour from (forward_)scope_exit >> >> The problem is that the first commit above introduces a test which >> causes the remote target to disconnect while processing an inferior >> stop event, specifically, within normal_stop (infrun.c), GDB calls >> update_thread_list, and it is during this call that the inferior >> disconnects. >> >> When the remote target disconnects, GDB immediately unpushes the >> remote target. See remote_unpush_target and its uses in remote.c. >> >> If this is the last use of the remote target, then unpushing it will >> cause the target to be deleted. >> >> This is a problem, because in normal_stop, we have an RAII variable >> maybe_finish_thread_state, which is an optional >> scoped_finish_thread_state, and in some cases, this will hold a >> pointer to the process_startum_target which needs to be finished. >> >> So the order of events is: >> >> 1. Call to normal_stop. >> >> 2. Create maybe_finish_thread_state with a pointer to the current >> remote_target object. >> >> 3. Call update_thread_list. >> >> 4. Remote disconnects, GDB unpushes and deletes the current >> remote_target object. GDB throws an exception. >> >> 5. The exception propagates back to normal_stop. >> >> 6. The destructor for maybe_finish_thread_state runs, and tries to >> make use of its cached pointer to the (now deleted) remote_target >> object. Badness ensues. >> >> This bug isn't restricted to normal_stop. If a remote target >> disconnects anywhere where there is a scoped_finish_thread_state in >> the call stack then this issue could arise. >> >> I think what we need to do is to ensure that the remote_target is not >> actually deleted until after the scoped_finish_thread_state has been >> cleaned up. >> >> And so, to achieve this, I propose changing scoped_finish_thread_state >> to hold a target_ops_ref rather than a pointer to the target_ops >> object. Holding the reference will prevent the object from being >> deleted. >> >> The new scoped_finish_thread_state is defined within its own file, and >> is a drop in replacement for the existing class. > > This seems fine to me. I suppose that when this happens, > finish_thread_state will do nothing, because all of the threads owned by > that target will have been deleted. > > Approved-By: Simon Marchi <simon.marchi@efficios.com> > > See comments below. > >> diff --git a/gdb/finish-thread-state.h b/gdb/finish-thread-state.h >> new file mode 100644 >> index 00000000000..f025b1221a6 >> --- /dev/null >> +++ b/gdb/finish-thread-state.h >> @@ -0,0 +1,64 @@ >> +/* Copyright (C) 2025 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 "gdbsupport/scope-exit.h" >> +#include "gdbthread.h" >> +#include "target.h" > > Missing include guards (caught by the check-include-guards pre-commit > check). > >> + >> +namespace detail >> +{ >> + >> +/* Policy class for use with scope_exit_base. Calls finish_thread_state on >> + scope exit, unless release() is called to disengage. The release >> + mechanism is supplied by scope_exit_base. */ >> + >> +struct scoped_finish_thread_state_policy >> +{ >> + /* Store TARG and PTID for a later call to finish_thread_state. For the >> + meaning of these arguments, see the comments on finish_thread_state. */ >> + explicit scoped_finish_thread_state_policy (process_stratum_target *targ, >> + ptid_t ptid) >> + : m_ptid (ptid) >> + { >> + if (targ != nullptr) >> + m_target_ref = target_ops_ref::new_reference (targ); > > clangd complains about this line (can't pass a `process_stratum_target > *` as a `target_ops *`), because when it analyzes this header file on > its own, it probably only sees a forward declaration for > process_stratum_target, not the full class, and therefore doesn't know > that it is a subclass of target_ops. This probably compiles thanks for > transitive includes. It would be good to add an include of > "process-stratum-target.h" to make it "include what you use". > >> + } >> + >> + /* Called on scope exit unless release was called, see scope_exit_base >> + for details. Calls finish_thread_state with stored target and ptid. */ >> + void on_exit () >> + { >> + target_ops *t = m_target_ref.get (); >> + process_stratum_target *p_target >> + = gdb::checked_static_cast<process_stratum_target *> (t); > > Please add an include for "gdbsupport/gdb-checked-static-cast.h", > otherwise, my clangd complains about checked_static_cast not existing > :). I made the fixes you pointed out, and pushed this patch. Thanks, Andrew
diff --git a/gdb/finish-thread-state.h b/gdb/finish-thread-state.h new file mode 100644 index 00000000000..f025b1221a6 --- /dev/null +++ b/gdb/finish-thread-state.h @@ -0,0 +1,64 @@ +/* Copyright (C) 2025 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 "gdbsupport/scope-exit.h" +#include "gdbthread.h" +#include "target.h" + +namespace detail +{ + +/* Policy class for use with scope_exit_base. Calls finish_thread_state on + scope exit, unless release() is called to disengage. The release + mechanism is supplied by scope_exit_base. */ + +struct scoped_finish_thread_state_policy +{ + /* Store TARG and PTID for a later call to finish_thread_state. For the + meaning of these arguments, see the comments on finish_thread_state. */ + explicit scoped_finish_thread_state_policy (process_stratum_target *targ, + ptid_t ptid) + : m_ptid (ptid) + { + if (targ != nullptr) + m_target_ref = target_ops_ref::new_reference (targ); + } + + /* Called on scope exit unless release was called, see scope_exit_base + for details. Calls finish_thread_state with stored target and ptid. */ + void on_exit () + { + target_ops *t = m_target_ref.get (); + process_stratum_target *p_target + = gdb::checked_static_cast<process_stratum_target *> (t); + finish_thread_state (p_target, m_ptid); + } + +private: + + /* The process and target passed through to finish_thread_state. For + their use see the comments on that function. */ + ptid_t m_ptid; + target_ops_ref m_target_ref; +}; + +} + +/* Calls finish_thread_state on scope exit, unless release() is called to + disengage. */ +using scoped_finish_thread_state + = scope_exit_base<detail::scoped_finish_thread_state_policy>; diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h index 0f6c5b126b6..ec9436f2bcc 100644 --- a/gdb/gdbthread.h +++ b/gdb/gdbthread.h @@ -859,11 +859,6 @@ extern bool threads_are_executing (process_stratum_target *targ); Notifications are only emitted if the thread state did change. */ extern void finish_thread_state (process_stratum_target *targ, ptid_t ptid); -/* Calls finish_thread_state on scope exit, unless release() is called - to disengage. */ -using scoped_finish_thread_state - = FORWARD_SCOPE_EXIT (finish_thread_state); - /* Commands with a prefix of `thread'. */ extern struct cmd_list_element *thread_cmd_list; diff --git a/gdb/infcmd.c b/gdb/infcmd.c index 875bbe1ee69..9ad91bc6604 100644 --- a/gdb/infcmd.c +++ b/gdb/infcmd.c @@ -56,6 +56,7 @@ #include <optional> #include "source.h" #include "cli/cli-style.h" +#include "finish-thread-state.h" /* Local functions: */ diff --git a/gdb/infrun.c b/gdb/infrun.c index bd114e16b80..9e9fb9b9669 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -75,6 +75,7 @@ #include "extension.h" #include "disasm.h" #include "interps.h" +#include "finish-thread-state.h" /* Prototypes for local functions */ diff --git a/gdb/remote.c b/gdb/remote.c index dbe3bb197b3..4a800f3c391 100644 --- a/gdb/remote.c +++ b/gdb/remote.c @@ -84,6 +84,7 @@ #include "cli/cli-style.h" #include "gdbsupport/remote-args.h" #include "gdbsupport/gdb_argv_vec.h" +#include "finish-thread-state.h" /* The remote target. */