aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/target.c43
-rw-r--r--gdb/target.h4
-rw-r--r--gdb/testsuite/gdb.python/py-connection-removed.exp92
3 files changed, 118 insertions, 21 deletions
diff --git a/gdb/target.c b/gdb/target.c
index 1dd0f42..b3fd234 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -1182,23 +1182,28 @@ decref_target (target_ops *t)
void
target_stack::push (target_ops *t)
{
- t->incref ();
+ /* We must create a new reference first. It is possible that T is
+ already pushed on this target stack, in which case we will first
+ unpush it below, before re-pushing it. If we don't increment the
+ reference count now, then when we unpush it, we might end up deleting
+ T, which is not good. */
+ auto ref = target_ops_ref::new_reference (t);
strata stratum = t->stratum ();
- if (stratum == process_stratum)
- connection_list_add (as_process_stratum_target (t));
-
/* If there's already a target at this stratum, remove it. */
- if (m_stack[stratum] != NULL)
- unpush (m_stack[stratum]);
+ if (m_stack[stratum].get () != nullptr)
+ unpush (m_stack[stratum].get ());
/* Now add the new one. */
- m_stack[stratum] = t;
+ m_stack[stratum] = std::move (ref);
if (m_top < stratum)
m_top = stratum;
+
+ if (stratum == process_stratum)
+ connection_list_add (as_process_stratum_target (t));
}
/* See target.h. */
@@ -1223,19 +1228,19 @@ target_stack::unpush (target_ops *t)
return false;
}
- /* Unchain the target. */
- m_stack[stratum] = NULL;
-
if (m_top == stratum)
m_top = this->find_beneath (t)->stratum ();
- /* Finally close the target, if there are no inferiors
- referencing this target still. Note we do this after unchaining,
- so any target method calls from within the target_close
- implementation don't end up in T anymore. Do leave the target
- open if we have are other inferiors referencing this target
- still. */
- decref_target (t);
+ /* Move the target reference off the target stack, this sets the pointer
+ held in m_stack to nullptr, and places the reference in ref. When
+ ref goes out of scope its reference count will be decremented, which
+ might cause the target to close.
+
+ We have to do it this way, and not just set the value in m_stack to
+ nullptr directly, because doing so would decrement the reference
+ count first, which might close the target, and closing the target
+ does a check that the target is not on any inferiors target_stack. */
+ auto ref = std::move (m_stack[stratum]);
return true;
}
@@ -3612,8 +3617,8 @@ target_stack::find_beneath (const target_ops *t) const
{
/* Look for a non-empty slot at stratum levels beneath T's. */
for (int stratum = t->stratum () - 1; stratum >= 0; --stratum)
- if (m_stack[stratum] != NULL)
- return m_stack[stratum];
+ if (m_stack[stratum].get () != NULL)
+ return m_stack[stratum].get ();
return NULL;
}
diff --git a/gdb/target.h b/gdb/target.h
index 28aa927..b9888f8 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -1389,7 +1389,7 @@ public:
{ return at (t->stratum ()) == t; }
/* Return the target at STRATUM. */
- target_ops *at (strata stratum) const { return m_stack[stratum]; }
+ target_ops *at (strata stratum) const { return m_stack[stratum].get (); }
/* Return the target at the top of the stack. */
target_ops *top () const { return at (m_top); }
@@ -1404,7 +1404,7 @@ private:
/* The stack, represented as an array, with one slot per stratum.
If no target is pushed at some stratum, the corresponding slot is
null. */
- target_ops *m_stack[(int) debug_stratum + 1] {};
+ std::array<target_ops_ref, (int) debug_stratum + 1> m_stack;
};
/* Return the dummy target. */
diff --git a/gdb/testsuite/gdb.python/py-connection-removed.exp b/gdb/testsuite/gdb.python/py-connection-removed.exp
new file mode 100644
index 0000000..6a0dbd1
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-connection-removed.exp
@@ -0,0 +1,92 @@
+# Copyright (C) 2022 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 <http://www.gnu.org/licenses/>.
+
+# Check that the gdb.connect_removed event triggers when we expect it
+# too.
+#
+# Checking this event has wider implications that simply some corner
+# of the Python API working or not. The connection_removed event
+# triggers when the reference count of a process_stratum_target
+# reaches zero. If these events stop triggering when expected then
+# GDB might be getting the reference counting on target_ops objects
+# wrong.
+
+load_lib gdb-python.exp
+
+standard_testfile py-connection.c
+
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
+ return -1
+}
+
+# Skip all tests if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+if ![runto_main] then {
+ return 0
+}
+
+# Register a callback that will trigger when a connection is removed
+# (deleted) within GDB.
+gdb_test_multiline "Add connection_removed event" \
+ "python" "" \
+ "def connection_removed_handler(event):" "" \
+ " num = event.connection.num" "" \
+ " type = event.connection.type" "" \
+ " print('Connection %d (%s) removed' % (num, type))" "" \
+ "gdb.events.connection_removed.connect (connection_removed_handler)" "" \
+ "end" ""
+
+if { [target_info exists gdb_protocol] } {
+ if { [target_info gdb_protocol] == "extended-remote" } {
+ set connection_type "extended-remote"
+ } else {
+ set connection_type "remote"
+ }
+} else {
+ set connection_type "native"
+}
+
+# Add an inferior that shares a connection with inferior 1.
+gdb_test "add-inferior" "Added inferior 2 on connection 1 \[^\r\n\]+"
+
+# Add an inferior with no connection.
+gdb_test "add-inferior -no-connection" "Added inferior 3"
+
+# Kill the first inferior. If we are using the plain 'remote' protocol then
+# it as this point that the remote target connection is removed. For the
+# 'extended-remote' and 'native' targets the connection is removed later.
+if { $connection_type == "remote" } {
+ gdb_test "with confirm off -- kill" \
+ "Connection 1 \\(remote\\) removed\r\n.*" "kill inferior"
+} else {
+ gdb_test "with confirm off -- kill" "" "kill inferior"
+}
+
+# Switch to inferior 3 (the one with no connection).
+gdb_test "inferior 3"
+
+# Remove inferior 1, not expecting anything interesting at this point.
+gdb_test_no_output "remove-inferiors 1"
+
+# Now removed inferior 2. For the 'remote' target the connection has
+# already been removed (see above), but for 'extended-remote' and 'native'
+# targets, it is at this point that the connection is removed.
+if { $connection_type == "remote" } {
+ gdb_test_no_output "remove-inferiors 2"
+} else {
+ gdb_test "remove-inferiors 2" \
+ "Connection 1 \\(${connection_type}\\) removed"
+}