From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id 2gXXGtboJ2r5nD0AWB0awg (envelope-from ) for ; Tue, 09 Jun 2026 06:20:06 -0400 Authentication-Results: simark.ca; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=QaZ6KsWX; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 5A1671E0A3; Tue, 09 Jun 2026 06:20:06 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-6.4 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [IPv6:2620:52:6:3111::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 22E401E070 for ; Tue, 09 Jun 2026 06:20:05 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 2B6C54C31836 for ; Tue, 9 Jun 2026 10:20:04 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 2B6C54C31836 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=QaZ6KsWX 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 676C84BA2E19 for ; Tue, 9 Jun 2026 10:19:36 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 676C84BA2E19 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 676C84BA2E19 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1781000376; cv=none; b=RHl+jPmBwcLmZ8Tfqt2IgsZJ5viDsE1/kThwK1zFQWrwO26c1AevKFVottlnbEBNnrqDi2yFlq0GtBUkpKQufT3N/IskGyfqt/GLNsijxTJn5V9/fD3RYUOQyPD/v6N7a4qPtUgN76gOXArZtXqblPOV615nJE/fuOEO1D3g70Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1781000376; c=relaxed/simple; bh=1BRs5Bft37o/P7yVSn5SlYHaJiSv7MOVBkUS8lzdzaQ=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=OH7WBynuxBMy3KplJegccXQWpGsO1RF97n8F1N/zpXjNRQWY6SzHZOusAyfKlPozftwPnqYU67XaXKkQPZKBjYsDY+LXGzulB/Otiitey8mlbfeRQIwSwao5w6n44xFsMZGulzF3S/+EGjD1cqxDUfpFqMhVpEKatCyb9fZOIys= ARC-Authentication-Results: i=1; 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=QaZ6KsWX DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 676C84BA2E19 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1781000376; 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: in-reply-to:in-reply-to:references:references; bh=WEh+NQasQUt155qD7iQWwpGPBFC6An6iZlmTsVjwxFQ=; b=QaZ6KsWXuzO7pBMmMwxQvHWouWeycuPRFQ4LE9IA48XENisKIHiRduZWCrxverlAM3wHnj QYa9DsIrt/2v/vqaCUP4W7N7PVRnlZN0oulgYpHvndPHmK4OvMo37++twsD/ZFR5F3iBWb YD44sR10W/+x0DmsAB7tkhWlPdc8yGk= Received: from mail-wm1-f70.google.com (mail-wm1-f70.google.com [209.85.128.70]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-114-ytiAtAOdPqW42hvOZlsKyA-1; Tue, 09 Jun 2026 06:19:29 -0400 X-MC-Unique: ytiAtAOdPqW42hvOZlsKyA-1 X-Mimecast-MFC-AGG-ID: ytiAtAOdPqW42hvOZlsKyA_1781000369 Received: by mail-wm1-f70.google.com with SMTP id 5b1f17b1804b1-490ace40f13so23901565e9.3 for ; Tue, 09 Jun 2026 03:19:29 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781000368; x=1781605168; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=WEh+NQasQUt155qD7iQWwpGPBFC6An6iZlmTsVjwxFQ=; b=Yabpv+TQzJBDIhWrXYG666h7pD23c6BWTp/+D6St1lqmXehUdOS9qI9xDqGA0LBP+j VrVzl8qb5e/muTaCpFXlYoqUUdgMx71lQkEYG5CiY3xG3vmtsRGxAHxRspBHEVP6OW2b m8JJAC15wxfZoVFYhvrxejhqdEKYD8yl2EaSy0OHB8in/IoBZwfc2GXwl5VSdl9/oPXQ jGh9/5XMQNo4gD/MJkKHU6bjWivT5HyLBX1ulvy1nOr6ryC0pARh+LjbfVjKPIrxbGBh CsudOLO3ve4eMUTLbn1qxRGiT7NKtptDaXyMl1lEG09tbvVEAKAKaSfG3Ir5ZRvFxnS+ 9tyA== X-Gm-Message-State: AOJu0YwWDSdIL/QB6SCAHKMgu6DDZumj8Kat0nil3m7dzyovl8kq05Sw o9eDlu+SLqGfP7hlBSmw+PEdL2cJepIeCUty9JFJ2B2cmnEwLRPtqgirhM94Xnk3tbo4VmxttgU raU9f8CHAtZaTNG4hqUZ+SsTXgwHmGn4FHPR/G5BTU/uUYZ6nquj8tQanIMOkt6lQaH3mD0L34n ba32F71XW5hMH0den/UstrEkOy5R7d0I4WZVdF3Sq9RxYpHLU= X-Gm-Gg: Acq92OGH/4p/mGUboMs0VZ9XU3+nGS+06xCoMDIVBUGEjU97hy6rnRb74GV4tDgZY3H 8k/2MiUfTKl/9dpq9fXLgGB3UDe6kr5r9cZDmQpV1F3NcXsHxdLtHnyaZQldDS6KqmtnY+lAFua E6aD8RKXqb881CnWPFVI8ML5MA3NFEepsJ3PvqRznJSqSRhGqwMuGarVafA5IraBIwC1HvWcW9g Pxo0E8HXy9LfBSoDZNeUTzFYDDZ5+Uu30wvM1sK/GHftGYzyvlaZidNueZKXeQv8gr5PnIj2/8o SIkxb5cVswuyulKTGO3U/tCVjx38e9sudkxkqO4PC9qFyxBw5MqOlbY2siIhlS/tb0VLcME1g7Q pRDj/wZ0E80+t7E1R0JzP1BIoJg== X-Received: by 2002:a05:600c:b93:b0:48a:93f8:dd02 with SMTP id 5b1f17b1804b1-490c4e0414emr306546265e9.14.1781000368357; Tue, 09 Jun 2026 03:19:28 -0700 (PDT) X-Received: by 2002:a05:600c:b93:b0:48a:93f8:dd02 with SMTP id 5b1f17b1804b1-490c4e0414emr306545475e9.14.1781000367696; Tue, 09 Jun 2026 03:19:27 -0700 (PDT) Received: from localhost ([213.31.44.97]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-490bc3cbfe4sm480957985e9.7.2026.06.09.03.19.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 09 Jun 2026 03:19:27 -0700 (PDT) From: Andrew Burgess To: gdb-patches@sourceware.org Cc: Andrew Burgess Subject: [PATCHv3] gdb/python: fix 'exited' event when GDB exits from core file debugging Date: Tue, 9 Jun 2026 11:19:25 +0100 Message-Id: <7b7983979dfdb856c6968729ff8a4b37d90775aa.1781000323.git.aburgess@redhat.com> X-Mailer: git-send-email 2.25.4 In-Reply-To: <7db5bc5d0e3c7189881540ab2c775ea20fc03b5f.1780767159.git.aburgess@redhat.com> References: <7db5bc5d0e3c7189881540ab2c775ea20fc03b5f.1780767159.git.aburgess@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: JI5HPiVl1mTN0EoklZP3HEx56PWBt0q7ISx2-Tju3GE_1781000369 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true 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 In v3: - Extend testing to also cover the attach case. In v2: - Address Lancelot's review feedback. Use gcore to generate core file. --- This fixes an issue that was reported here: https://inbox.sourceware.org/gdb-patches/v3x4md2dg6rflq35ymzwrmmqf5uaem5exrnlbsp5dmhph2vihy@lq22ncu774yu After commit: commit 3780b9993c973a2b68b496b80eddb820c0932cc0 Date: Fri Mar 27 11:29:07 2026 +0000 gdb: refactor core_target ::close and ::detach functions it was observed that the Python 'exited' event was no longer being emitted when debugging a core file, and then exiting GDB. The problem is that, when GDB is exiting we eventually end up in quit_force (in top.c), which calls kill_or_detach for every inferior. In kill_or_detach we call either target_detach or target_kill, but only for non-core file targets. For core file targets, neither of these is called and kill_or_detach does nothing of interest. After the call to kill_or_detach, we call inferior::pop_all_targets, which calls inferior::pop_all_targets_above the dummy_stratum target, which means popping all targets. In inferior::pop_all_targets_above (in inferior.c), we call switch_to_inferior_no_thread, which ensures the correct inferior is selected, but makes it so that no thread is selected. Switching to no thread sets inferior_ptid to null_ptid. Now popping the core_target calls core_target::close, and within core_target::close we currently check inferior_ptid in order to determine if exit_core_file_inferior has already been called or not. We only call exit_core_file_inferior if inferior_ptid is not null_ptid, so in this case we will not call exit_core_file_inferior. The only other place that exit_core_file_inferior can be called from is core_target::detach, but remember we specifically avoided calling target_detach earlier in kill_or_detach. This means that exit_core_file_inferior ends up never being called. It is exit_core_file_inferior that calls exit_inferior, and it is from here that the Python 'exited' event is emitted. I don't see any reason why kill_or_detach couldn't call target_detach for a core file target, but I don't propose making that change in this commit. The check against inferior_ptid in core_target::close is clearly incorrect, checking this requires that a suitable thread within the inferior be selected, and that is not really a requirement for closing a core_target. Instead, we can just check the inferior::pid field. When we open a core_target we always set inferior::pid, even if we just assign a fake CORELOW_PID value, so checking inferior::pid against zero will tell us if the inferior has already been exited. Fixing this check is enough to resolve the reported bug and ensure that the 'exited' event is always emitted, which is why I don't propose changing kill_or_detach in this commit. An assert in core_target::exit_core_file_inferior has to go too for the same reason, the assert is checking that a thread is currently selected, and as discussed above, this is not always the case. There's a new test which checks that the 'exited' event is emitted for a core file debug session, a native debug session where the inferior is started by GDB, and a native debug session where GDB attaches to an already running inferior. Only the core file case was broken before this commit, but more testing is always a good thing. --- gdb/corelow.c | 13 +- .../gdb.python/py-inf-exited-at-exit.c | 43 +++++ .../gdb.python/py-inf-exited-at-exit.exp | 164 ++++++++++++++++++ .../gdb.python/py-inf-exited-at-exit.py | 20 +++ 4 files changed, 232 insertions(+), 8 deletions(-) create mode 100644 gdb/testsuite/gdb.python/py-inf-exited-at-exit.c create mode 100644 gdb/testsuite/gdb.python/py-inf-exited-at-exit.exp create mode 100644 gdb/testsuite/gdb.python/py-inf-exited-at-exit.py diff --git a/gdb/corelow.c b/gdb/corelow.c index d5a724ab551..a87e082db87 100644 --- a/gdb/corelow.c +++ b/gdb/corelow.c @@ -629,10 +629,6 @@ core_target::build_file_mappings () void core_target::exit_core_file_inferior () { - /* Opening a core file ensures that some thread, even if it's just a - "fake" thread, will have been selected. */ - gdb_assert (inferior_ptid != null_ptid); - /* Avoid confusion from thread stuff. */ switch_to_no_thread (); @@ -665,10 +661,11 @@ core_target::close () mostly harmless except it causes two 'exited' events to be emitted in the Python API, which isn't ideal. - As opening a core_target always ensures that some thread is selected, - then we can tell if exit_core_file_inferior has already been called by - checking if no thread is now selected. */ - if (inferior_ptid != null_ptid) + As opening a core_target always ensures that a pid is assigned to the + core file inferior, even if it is the fake CORELOW_PID, then we can + tell if exit_core_file_inferior has already been called by checking if + the inferior has a non-zero pid or not. */ + if (current_inferior ()->pid != 0) exit_core_file_inferior (); /* Core targets are heap-allocated (see core_target_open), so here diff --git a/gdb/testsuite/gdb.python/py-inf-exited-at-exit.c b/gdb/testsuite/gdb.python/py-inf-exited-at-exit.c new file mode 100644 index 00000000000..b5100b4cde9 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-inf-exited-at-exit.c @@ -0,0 +1,43 @@ +/* This testcase is part of GDB, the GNU debugger. + + 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 . */ + +#include + +/* GDB can set GLOBAL_VAR to non-zero to cause the inferior to exit. */ +volatile int global_var = 0; + +/* This is used just to create some content that GDB can break on. */ +volatile int other_var = 0; + +void +foo (void) +{ + while (global_var == 0) + { + sleep (1); + other_var = 42; /* Break here. */ + } +} + +int +main (void) +{ + alarm (300); + + foo (); + return 0; +} diff --git a/gdb/testsuite/gdb.python/py-inf-exited-at-exit.exp b/gdb/testsuite/gdb.python/py-inf-exited-at-exit.exp new file mode 100644 index 00000000000..ff2ba8b1e7f --- /dev/null +++ b/gdb/testsuite/gdb.python/py-inf-exited-at-exit.exp @@ -0,0 +1,164 @@ +# Copyright (C) 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 . + +# Check that the 'exited' event triggers when GDB exits. Test for +# both live inferiors, and for core files. + +require allow_python_tests + +load_lib gdb-python.exp + +standard_testfile + +if {[build_executable "build executable" $testfile $srcfile] == -1} { + return +} + +set remote_python_file \ + [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py] + +# Load the Python script for this test. Record the string +# representation of the current inferior. Then exit GDB. Ensure that +# during the exit we see a single Python 'exited' event associated +# with the expected inferior. +proc source_py_script_and_exit_checking_event {} { + gdb_test_no_output "source $::remote_python_file" \ + "load python script" + + set expected_inferior_string \ + [capture_command_output \ + "python print(str(gdb.selected_inferior()))" ""] + + set inferior_string "" + set event_count 0 + gdb_test_multiple "with confirm off -- exit" "exit gdb" { + -re "^EVENT: inferior exited event\\. Inferior is (\[^\r\n\]+)\r\n" { + set inferior_string $expect_out(1,string) + incr event_count + exp_continue + } + + eof { + verbose -log "GDB has now exited" + gdb_assert { $expected_inferior_string eq $inferior_string \ + && $event_count == 1 } $gdb_test_name + + # Clean up now that GDB has gone away. This prevents the + # generic support code from trying to shut down GDB again. + catch {wait -nowait -i $::gdb_spawn_id} + clean_up_spawn_id host $::gdb_spawn_id + unset ::gdb_spawn_id + } + + -re "^\[^\r\n\]*\r\n" { + exp_continue + } + } +} + +# Clean restart using global TESTFILE as the executable, then run to +# 'foo'. Return true on success, otherwise, return false. +proc clean_restart_and_runto_foo {} { + if {[clean_restart $::testfile] == -1} { + return false + } + + if {![runto foo]} { + return false + } + + return true +} + +# Check that the current inferior's backtrace is 'main -> foo'. +proc check_backtrace { testname } { + gdb_test "bt" \ + [multi_line \ + "#0 (?:$::hex in )?foo \\(\\) at \[^\r\n\]+" \ + "#1 (?:$::hex in )?main \\(\\) at \[^\r\n\]+"] \ + $testname +} + +# Create a core file. Start GDB and load the core file. Exit GDB. +# Check that we see an 'exited' event, and that it is associated with +# the correct gdb.Inferior. +proc_with_prefix check_with_corefile {} { + if {![clean_restart_and_runto_foo]} { + return + } + + check_backtrace "backtrace before generating core file" + + set corefile [host_standard_output_file $::testfile.core] + if {![gdb_gcore_cmd $corefile "dump core file"]} { + return + } + + clean_restart $::testfile + + gdb_core_cmd $corefile "load corefile" + + check_backtrace "backtrace after loading core file" + + source_py_script_and_exit_checking_event +} + +# Start the test program, attach to it, and then exit GDB. Check that +# we see an 'exited' event, and that it is associated with the correct +# gdb.Inferior. +proc_with_prefix check_with_attach {} { + if {![can_spawn_for_attach]} { + return + } + + set test_spawn_id [spawn_wait_for_attach $::binfile] + set testpid [spawn_id_get_pid $test_spawn_id] + + clean_restart $::testfile + + gdb_breakpoint [gdb_get_line_number "Break here."] + + gdb_test "attach $testpid" + + gdb_continue_to_breakpoint "continue to b/p in foo" + + check_backtrace "backtrace after attaching" + + # When GDB detaches on exit, this should ensure the test program + # runs to completion. + gdb_test "set global_var = 1" + + source_py_script_and_exit_checking_event + + # In case the test program doesn't self-terminate after detach, + # kill it. + kill_wait_spawned_process $test_spawn_id +} + +# Start a running inferior. Exit GDB. Check that we see an 'exited' +# event, and that it is associated with the correct gdb.Inferior. +proc_with_prefix check_with_live {} { + if {![clean_restart_and_runto_foo]} { + return + } + + check_backtrace "backtrace before exiting" + + source_py_script_and_exit_checking_event +} + +check_with_live +check_with_corefile +check_with_attach diff --git a/gdb/testsuite/gdb.python/py-inf-exited-at-exit.py b/gdb/testsuite/gdb.python/py-inf-exited-at-exit.py new file mode 100644 index 00000000000..b6fe39e4061 --- /dev/null +++ b/gdb/testsuite/gdb.python/py-inf-exited-at-exit.py @@ -0,0 +1,20 @@ +# Copyright (C) 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 . + +def exit_event_handler(event): + inf = event.inferior + print("EVENT: inferior exited event. Inferior is " + str(inf)) + +gdb.events.exited.connect(exit_event_handler) base-commit: 4562eab73d375e57f3c5a67f62d2678d7730d7ab -- 2.25.4