]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb: some process_stratum_target should not be shared
authorAndrew Burgess <aburgess@redhat.com>
Thu, 29 Sep 2022 11:45:26 +0000 (12:45 +0100)
committerAndrew Burgess <aburgess@redhat.com>
Tue, 30 Sep 2025 13:18:31 +0000 (14:18 +0100)
When multi-target support was added to GDB, an assumption was made
that all process_stratum_target sub-classes could be shared by
multiple inferiors.

For things like the Linux and FreeBSD native targets, this is
absolutely true (or was made true).  But some targets were either not
updated, or, due to design restrictions, cannot be shared.

This patch adds a target_ops::is_shareable member function.  When this
returns false then this indicates that an instance of a particular
target should only appear on a single target stack.  It is fine for
difference instances of a single target type to appear on different
target stacks though.

This is my second attempt at this patch.  The first attempt can be
found here:

  https://inbox.sourceware.org/gdb-patches/577f2c47793acb501c2611c0e6c7ea379f774830.1668789658.git.aburgess@redhat.com

The initial approach was to have all targets be shareable by default,
and to then mark those targets which I knew were problematic.
Specifically, the only target I was interested in was core_target,
which cannot be shared (details below).  During review Tom pointed out:

  I think there are a lot of other targets that can't be
  shared... remote-sim, all the trace targets, even I think windows-nat,
  since it isn't multi-inferior-capable yet.

The suggestion was that the default should be that targets were NOT
shareable, and we should then mark those targets which we know can be
shared.  That is the big change in this iteration of the patch.

The core_target is still non-shareable.  This target stores state
relating to the open core file in the core_target and in the current
inferior's program_space.  After an 'add-inferior' command, if we
share the core_target, the new inferior will have its own
program_space, but will share the core_target with the original
inferior.  This leaves the new inferior in an unexpected state where
the core BFD (from the new program_space) is NULL.  Attempting to make
use of the second inferior, e.g. to load a new executable, will (on
x86 at least) cause GDB to crash as it is not expecting the core BFD
to be NULL.

I am working to move the core file BFD into core_target, at which
point it might be possible to share the core_target, though I'm still
not entirely sure this makes sense; loading a core file will in many
cases, automatically set the executable in the program_space, creating
a new inferior would share the core_target, but the new inferior's
program space would not have the executable loaded.  But I figure we
can worry about this another day because ....

As Tom pointed out in his review of V1, there are other targets that
should be non-shareable (see quote above).  In addition, I believe
that the remote target can only be shared in some specific situations,
the 'add-inferior' case is one where the 'remote' target should NOT be
shared.

The 'remote' (not 'extended-remote') target doesn't allow new
inferior's to be started, you need to connect to an already running
target.  As such, it doesn't really make sense to allow a 'remote'
target to be shared over an 'add-inferior' call, what would the user
do with the new inferior?  They cannot start a new process.  They're
not debugging the same process as the original inferior.  This just
leaves GDB in a weird state.

However, 'remote' targets are a little weird in that, if the remote
inferior forks, and GDB is set to follow both the parent and the
child, then it does make sense to allow sharing.  In this case the new
inferior is automatically connected to the already running child
process.

So when we consider 'add-inferior' there are two things we need to
consider:

  1. Can the target be shared at all?  The new target_ops::is_shareable
     function tells us this.

  2. Can the target be used to start a new inferior?  The existing
     target_ops::can_create_inferior function tells us this.

If the process_stratum_target returns true for both of these functions
then it is OK to share it across an 'add-inferior' call.  If either
returns false then the target should not be shared.

When pushing a target onto an inferior's target stack, we only need to
consider target_ops::is_shareable, only shareable targets should be
pushed onto multiple target stacks.

The new target_ops::is_shareable function returns true as its default,
all the immediate sub-classes are shareable.

However, this is overridden in process_stratum_target::is_shareable, to
return false.  All process_stratum_target sub-classes are non-shareable
by default.

Finally, linux_nat_target, fbsd_nat_target, and remote_target, are all
marked as shareable.  This leaves all other process_stratum_target
sub-classes non-shareable.

I did some very light testing on Windows, and I don't believe that this
target supports multiple inferiors, but I could easily be wrong here.
My windows testing setup is really iffy, and I'm not 100% sure if I did
this right.

But for the Windows target, and any of the others, if this commit breaks
existing multi-inferior support, then the fix is as simple as adding an
is_shareable member function that returns true.

If the user tries to 'add-inferior' from an inferior with a
non-shareable target, or the 'remote' target as it cannot start new
inferiors, then they will get a warning, and the new inferior will be
created without a connection.

If the user explicitly asks for the new inferior to be created without
a connection, then no warning will be given.

At this point the user is free to setup the new inferior connection as
they see fit.

I've updated the docs, and added a NEWS entry for the new warning.  In
the docs for clone-inferior I've added reference to -no-connection,
which was previously missing.

Some tests needed fixing with this change, these were
gdb.base/quit-live.exp, gdb.mi/mi-add-inferior.exp,
gdb.mi/new-ui-mi-sync.exp, and gdb.python/py-connection-removed.exp.  In
all cases, when using the native-gdbserver board, these tests tried to
create a new inferior, and expected the new inferior to start sharing
the connection with the original inferior.  None of these tests actually
tried to run anything in the new inferior, if they did then they would
have discovered that the new inferior wasn't really sharing a
connection.  All the tests have been updated to understand that for
'remote' connections (not 'extended-remote') the connection will not be
shared.  These fixes are all pretty simple.

Approved-By: Tom Tromey <tom@tromey.com>
19 files changed:
gdb/NEWS
gdb/doc/gdb.texinfo
gdb/fbsd-nat.h
gdb/inferior.c
gdb/inferior.h
gdb/linux-nat.h
gdb/process-stratum-target.h
gdb/remote.c
gdb/target.c
gdb/target.h
gdb/testsuite/gdb.base/quit-live.exp
gdb/testsuite/gdb.mi/mi-add-inferior.exp
gdb/testsuite/gdb.mi/new-ui-mi-sync.exp
gdb/testsuite/gdb.multi/multi-core-files-1.c [new file with mode: 0644]
gdb/testsuite/gdb.multi/multi-core-files-2.c [new file with mode: 0644]
gdb/testsuite/gdb.multi/multi-core-files.exp [new file with mode: 0644]
gdb/testsuite/gdb.multi/multi-remote-target.c [new file with mode: 0644]
gdb/testsuite/gdb.multi/multi-remote-target.exp [new file with mode: 0644]
gdb/testsuite/gdb.python/py-connection-removed.exp

index b632cde7e0ddfd950bf4d7374fff176c33109b25..813443755af38212ef25e79c87169e22b3ad3783 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
   this flag is used gdbserver will not escape special shell characters
   within the inferior arguments.
 
+* The add-inferior, clone-inferior, and MI -add-inferior commands will
+  now give a warning, and create the new inferior without a
+  connection, when the current inferior's connection, at the time the
+  command is given, is unshareable.  For example, the core-file target
+  cannot be shared between inferiors, nor can the Window native
+  target.  These targets could never really be shared.  Attempting to
+  share them would usually lead to GDB crashing.  GDB now prevents
+  this invalid sharing.
+
 * New targets
 
 GNU/Linux/MicroBlaze (gdbserver) microblazeel-*linux*
index a13d5c0388ecb2a1ab7b4988e50b63bd6ffa6227..dfe3493399c61b3c6f0ca454cc1e79af9b34450b 100644 (file)
@@ -3508,8 +3508,40 @@ with no connection yet.  You can then for example use the @code{target
 remote} command to connect to some other @code{gdbserver} instance,
 use @code{run} to spawn a local program, etc.
 
+Not all connections can be shared between inferiors.  For example, the
+@code{target core} target is unique for each inferior.  That is,
+multiple inferiors can use @code{target core} at the same time, but
+each @code{target core} is different.  If you try to
+@code{add-inferior}, and the current inferior is @code{target core},
+then @value{GDBN} will give a warning and create the new inferior
+without a connection, like this@:
+
+@smallexample
+(@value{GDBP}) file test1
+Reading symbols from test1...
+(@value{GDBP}) target core core.test1.433190
+[New LWP 433190]
+Core was generated by `./test1'.
+Program terminated with signal SIGSEGV, Segmentation fault.
+#0  0x0000000000401111 in foo () at test1.c:6
+6        return *global_ptr;
+(@value{GDBP}) add-inferior
+[New inferior 2]
+warning: can't share connection 1 (core) between inferiors
+Added inferior 2
+@end smallexample
+
+Another target that cannot be shared is @code{target remote}
+(@pxref{Connecting,,Types of Remote Connections}).  A remote target
+doesn't allow new inferiors to be started, as such creating a new,
+non-running, inferior that shares a remote connection doesn't make
+sense.  As with the core target above, if you try to
+@code{add-inferior}, and the current inferior is @code{target remote},
+then @value{GDBN} will give a warning and create the new inferior
+without a connection.
+
 @kindex clone-inferior
-@item clone-inferior [ -copies @var{n} ] [ @var{infno} ]
+@item clone-inferior [ -copies @var{n} ] [ -no-connection ] [ @var{infno} ]
 Adds @var{n} inferiors ready to execute the same program as inferior
 @var{infno}; @var{n} defaults to 1, and @var{infno} defaults to the
 number of the current inferior.  This command copies the values of the
@@ -3534,6 +3566,13 @@ Added inferior 2.
 
 You can now simply switch focus to inferior 2 and run it.
 
+Like @code{add-inferior}, @code{clone-inferior} shares the connection
+with the inferior @var{infno}.  If the @var{-no-connection} option is
+given, then the new inferior will be created without a connection.  If
+the connection of inferior @var{infno} can't be shared, then
+@value{GDBN} will give a warning, and the new inferior will be created
+without a connection.
+
 @anchor{remove_inferiors_cli}
 @kindex remove-inferiors
 @item remove-inferiors @var{infno}@dots{}
@@ -39458,6 +39497,11 @@ with no connection yet.  You can then for example use the
 @code{gdbserver} instance, use @code{-exec-run} to spawn a local
 program, etc.
 
+If the connection of the current inferior cannot be shared, e.g.@: the
+@code{-target-select core} target cannot be shared between inferiors,
+then @value{GDBN} will give a warning and create the new inferior
+without a connection.
+
 The command response always has a field, @var{inferior}, whose value
 is the identifier of the thread group corresponding to the new
 inferior.
index fb81c5322e0b373da1410eb0db439b93752fb8ab..76d5aeefa4d3738141f6c8db559a3bdce7daa368 100644 (file)
@@ -122,6 +122,10 @@ public:
 
   bool supports_disable_randomization () override;
 
+  /* FreeBSD ptrace targets are shareable.  */
+  bool is_shareable () override final
+  { return true; }
+
   /* Methods meant to be overridden by arch-specific target
      classes.  */
 
index eb895f216914987bef9f0997b25a31f4944e0b4e..78ecb162228cac883c85a70cb914ac7a8647ffbd 100644 (file)
@@ -880,6 +880,19 @@ switch_to_inferior_and_push_target (inferior *new_inf,
      symbols.  */
   switch_to_inferior_no_thread (new_inf);
 
+  /* If the user didn't specify '-no-connection', and the ORG_INF has a
+     process stratum target, but that target cannot be shared, or cannot
+     start a new inferior, then don't try to share the target.  */
+  if (!no_connection && proc_target != nullptr
+      && (!proc_target->is_shareable ()
+         || !proc_target->can_create_inferior ()))
+    {
+      warning (_("can't share connection %d (%s) between inferiors"),
+              proc_target->connection_number,
+              make_target_connection_string (proc_target).c_str ());
+      proc_target = nullptr;
+    }
+
   /* Reuse the target for new inferior.  */
   if (!no_connection && proc_target != NULL)
     {
index 8455b791866769002cd30a586a8e9d9f56e631fe..0dffbb1480c035121ea86aefbcd8eaf5c1b8bdf9 100644 (file)
@@ -856,9 +856,10 @@ extern struct inferior *add_inferior_with_spaces (void);
 /* Print the current selected inferior.  */
 extern void print_selected_inferior (struct ui_out *uiout);
 
-/* Switch to inferior NEW_INF, a new inferior, and unless
-   NO_CONNECTION is true, push the process_stratum_target of ORG_INF
-   to NEW_INF.  */
+/* Switch to inferior NEW_INF, a new inferior, and unless NO_CONNECTION is
+   true, or the process_stratum_target of ORG_INF is not shareable, or the
+   process_stratum_target cannot start new inferiors, push the
+   process_stratum_target of ORG_INF to NEW_INF.  */
 
 extern void switch_to_inferior_and_push_target
   (inferior *new_inf, bool no_connection, inferior *org_inf);
index 7cbe9a98789c2333179be0c69aa8098e0ffcda80..ed128c0ab7c631084ce0ef7ec34cf595b218b77b 100644 (file)
@@ -137,6 +137,10 @@ public:
   std::vector<static_tracepoint_marker>
     static_tracepoint_markers_by_strid (const char *id) override;
 
+  /* Linux ptrace targets are shareable.  */
+  bool is_shareable () override final
+  { return true; }
+
   /* Methods that are meant to overridden by the concrete
      arch-specific target instance.  */
 
index 2545d4875bfdbe966a3e3758147d2a51834172c2..b646c117501bc604a9aa20b4bb28a087d2f76032 100644 (file)
@@ -79,6 +79,11 @@ public:
                    target_waitkind fork_kind, bool follow_child,
                    bool detach_on_fork) override;
 
+  /* Assume sub-classes are not shareable.  Those that are need to mark
+     themselves as such.  */
+  bool is_shareable () override
+  { return false; }
+
   /* True if any thread is, or may be executing.  We need to track
      this separately because until we fully sync the thread list, we
      won't know whether the target is fully stopped, even if we see
index 9fc8712f6990109038156783a7e01f2b6e9db423..5ee0d771419b24ee737ab5468b36790a74117b36 100644 (file)
@@ -1167,6 +1167,12 @@ public:
 
   bool is_address_tagged (gdbarch *gdbarch, CORE_ADDR address) override;
 
+  /* A remote target can be shared if it is able to create new inferiors.  */
+  bool is_shareable () override final
+  {
+    return true;
+  }
+
 public: /* Remote specific methods.  */
 
   void remote_download_command_source (int num, ULONGEST addr,
index f7c43f69a288728750b5dc85626742bca013e694..d12df7d96bfdfaad600b2d4981ff99f70dcbcca0 100644 (file)
@@ -1186,6 +1186,14 @@ target_stack::push (target_ops *t)
   if (m_stack[stratum].get () != nullptr)
     unpush (m_stack[stratum].get ());
 
+  /* If this target can't be shared, then check that the target doesn't
+     already appear on some other target stack.  */
+  if (!t->is_shareable ())
+    for (inferior *inf : all_inferiors ())
+      if (inf->target_is_pushed (t))
+       internal_error (_("Attempt to push unshareable target: %s."),
+                       t->shortname ());
+
   /* Now add the new one.  */
   m_stack[stratum] = std::move (ref);
 
index 365e894efe6918db9653218b178c9a36206829b1..3525a430aa087d8ed714485ea11fd760cca0d6c9 100644 (file)
@@ -1399,6 +1399,23 @@ struct target_ops
     virtual void displaced_step_restore_all_in_ptid (inferior *parent_inf,
                                                     ptid_t child_ptid)
       TARGET_DEFAULT_FUNC (default_displaced_step_restore_all_in_ptid);
+
+    /* Return true if an instance of this target can appear on multiple
+       target stacks, or false if an instance of this target can only
+       appear on a single target stack.
+
+       Returning false doesn't mean that GDB can't create multiple
+       instances of this target, just that each instance will only be used
+       by a single inferior.
+
+       The default return value for this function is true indicating
+       targets can be shared.  The only non-shareable targets are some of
+       the process_stratum_target sub-classes, as such, this default is
+       changed in process_stratum_target to return false, then those
+       process_stratum_target sub-classes that are shareable set this to
+       true.  */
+    virtual bool is_shareable ()
+    { return true; }
   };
 
 /* Deleter for std::unique_ptr.  See comments in
index 46579d4d4f30c4cc040d0a4171a9a2122a5e3a5e..e33d8bc1f163d379cd52ac1d71256c06aa340a8a 100644 (file)
@@ -120,8 +120,13 @@ proc quit_with_live_inferior {appear_how extra_inferior quit_how} {
     }
 
     if {$extra_inferior} {
-       gdb_test "add-inferior" "Added inferior 2 on connection .*" \
-           "add empty inferior 2"
+       if {[target_info gdb_protocol] ne "remote"} {
+           gdb_test "add-inferior" "Added inferior 2 on connection .*" \
+               "add empty inferior 2"
+       } else {
+           gdb_test "add-inferior -no-connection" "Added inferior 2" \
+               "add empty inferior 2"
+       }
        gdb_test "inferior 2" "Switching to inferior 2.*" \
            "switch to inferior 2"
     }
index 8c4926fcf15e068831aefaa6965a6ce220dd8d70..d7c959a95f249cae0bab92b17d909fc0fa8024c1 100644 (file)
@@ -73,14 +73,31 @@ set inf_line [string range "${inf_line}" $idx end]
 regexp "^(${decimal} \\(\[^)\]+\\))" $inf_line conn_info
 set conn_pattern [string_to_regexp "${conn_info}"]
 
+# When using the 'remote' protocol, the connection cannot be shared
+# between the original inferior, and the inferior created by
+# 'add-inferior'.  Remember, the 'remote' protocol doesn't allow new
+# inferiors to be started.  As a result, some of GDB's output will
+# change.
+set is_remote_conn [string equal [target_info gdb_protocol] "remote"]
+
 # Now add a new inferior, this should use the connection of the
 # current inferior.
-mi_gdb_test "-add-inferior" \
-    [multi_line "=thread-group-added,id=\"\[^\"\]+\"" \
-        "~\"\\\[New inferior 2\\\]\\\\n\"" \
-        "\~\"Added inferior 2 on connection ${conn_pattern}\\\\n\"" \
-        "\\^done,inferior=\"\[^\"\]+\",connection=\{number=\"$decimal\",name=\"\[^\"\]+\"\}" ] \
-    "mi add inferior"
+if { $is_remote_conn } {
+    mi_gdb_test "-add-inferior" \
+       [multi_line "=thread-group-added,id=\"\[^\"\]+\"" \
+            "~\"\\\[New inferior 2\\\]\\\\n\"" \
+            "&\"warning: can't share connection 1 \\(remote \[^\r\n\]+\\) between inferiors\\\\n\"" \
+            "\~\"Added inferior 2\\\\n\"" \
+            "\\^done,inferior=\"\[^\"\]+\"" ] \
+       "mi add inferior"
+} else {
+    mi_gdb_test "-add-inferior" \
+       [multi_line "=thread-group-added,id=\"\[^\"\]+\"" \
+            "~\"\\\[New inferior 2\\\]\\\\n\"" \
+            "\~\"Added inferior 2 on connection ${conn_pattern}\\\\n\"" \
+            "\\^done,inferior=\"\[^\"\]+\",connection=\{number=\"$decimal\",name=\"\[^\"\]+\"\}" ] \
+       "mi add inferior"
+}
 
 # Now run 'info inferiors' again to check that the currently selected
 # inferior has not changed.
@@ -107,7 +124,16 @@ gdb_test_multiple "info inferiors" \
        }
 
        -re "^~\"\\s+2\\s+\[^\r\n\]+\\s+${conn_pattern}\\s+\[^\r\n\]+\r\n" {
-           set saw_new_inferior true
+           if { ! $is_remote_conn } {
+               set saw_new_inferior true
+           }
+           exp_continue
+       }
+
+       -re "^~\"\\s+2\\s+<null>\\s*\[^\r\n\]+\r\n" {
+           if { $is_remote_conn } {
+               set saw_new_inferior true
+           }
            exp_continue
        }
 
index 7112248f6f2d397542e82f13f958d17d7460a00a..cda1e011024f63265e9414615fb69d54a8e0eaf9 100644 (file)
@@ -69,7 +69,9 @@ proc do_test {sync_command} {
     # in the separate MI UI.  Note the "run" variant usually triggers
     # =thread-group-started/=thread-created/=library-loaded as well.
     with_spawn_id $gdb_main_spawn_id {
-       gdb_test "add-inferior" "Added inferior 2 on connection .*"
+       # We don't need to share the connection with inferior 1 to
+       # trigger the async event.
+       gdb_test "add-inferior -no-connection" "Added inferior 2"
     }
 
     # Interrupt the program.
diff --git a/gdb/testsuite/gdb.multi/multi-core-files-1.c b/gdb/testsuite/gdb.multi/multi-core-files-1.c
new file mode 100644 (file)
index 0000000..5d24367
--- /dev/null
@@ -0,0 +1,37 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2025 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/>.  */
+
+#include <stdlib.h>
+
+int
+bar ()
+{
+  abort ();
+  return 0;
+}
+
+int
+baz ()
+{
+  return bar ();
+}
+
+int
+main ()
+{
+  return baz ();
+}
diff --git a/gdb/testsuite/gdb.multi/multi-core-files-2.c b/gdb/testsuite/gdb.multi/multi-core-files-2.c
new file mode 100644 (file)
index 0000000..cc05dc6
--- /dev/null
@@ -0,0 +1,31 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2025 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/>.  */
+
+#include <stdlib.h>
+
+int
+foo ()
+{
+  abort ();
+  return 0;
+}
+
+int
+main ()
+{
+  return foo ();
+}
diff --git a/gdb/testsuite/gdb.multi/multi-core-files.exp b/gdb/testsuite/gdb.multi/multi-core-files.exp
new file mode 100644 (file)
index 0000000..ed83b81
--- /dev/null
@@ -0,0 +1,171 @@
+# Copyright 2022-2025 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/>.
+
+# This script runs some basic tests that GDB can support multiple
+# inferiors each debugging different core files.
+#
+# We also check the behaviour of GDB if the user attempts to clone or
+# duplicate an inferior that is debugging a core file.
+
+standard_testfile -1.c -2.c
+
+set testfile1 "${testfile}-1"
+set testfile2 "${testfile}-2"
+
+set binfile1 [standard_output_file $testfile1]
+set binfile2 [standard_output_file $testfile2]
+
+if {[build_executable "build first executable" $binfile1 $srcfile] == -1} {
+    return
+}
+
+if {[build_executable "build second executable" $binfile2 $srcfile2] == -1} {
+    return
+}
+
+set corefile1 [core_find $binfile1]
+set corefile2 [core_find $binfile2]
+if { $corefile1 == "" || $corefile2 == "" } {
+    untested "Can't generate core files"
+    return
+}
+
+# Start GDB, and load the first executable and corefile into the first
+# inferior.
+clean_restart ${testfile1}
+gdb_test "core-file $corefile1" "Program terminated with .*" \
+    "load core file"
+gdb_test "bt" "bar \\(\\) at .*" \
+    "check backtrace in inferior 1"
+
+# The native-extended-remote board connects to the remote target as
+# soon as GDB is started, this means that connection 1 is to the
+# remote target, and the core target we create below will be
+# connection 2.
+#
+# In all other cases, the core target gets to be connection 1.
+if { [target_info gdb_protocol] == "extended-remote"} {
+    set conn_num 2
+} else {
+    set conn_num 1
+}
+
+# Try to use add-inferior and clone-inferior to create new
+# inferiors.  In both cases this will try to share the core_target
+# between inferior 1 and the new inferior.  As the core_target can't
+# be shared we should get a warning, and the inferior should be
+# created without a connection.
+gdb_test "add-inferior" \
+    [multi_line \
+        "\\\[New inferior 2\\\]" \
+        "warning: can't share connection ${conn_num} \\(core\\) between inferiors" \
+        "Added inferior 2"]
+gdb_test "clone-inferior" \
+    [multi_line \
+        "\\\[New inferior 3\\\]" \
+        "warning: can't share connection ${conn_num} \\(core\\) between inferiors" \
+        "Added inferior 3"]
+
+# Check the MI -add-inferior command.  Do this using interpreter-exec.
+# We're not doing a full MI test here, just checking this one command.
+gdb_test "interpreter-exec mi \"-add-inferior\"" \
+    [multi_line \
+        "~\"\\\[New inferior 4\\\]..\"" \
+        "&\"warning: can't share connection ${conn_num} \\(core\\) between inferiors..\"" \
+        "~\"Added inferior 4..\"" \
+        "\\^done,inferior=\"\[^\"\]+\""]
+
+# Now check that none of the new inferiors have a connection.
+gdb_test "info inferiors" \
+    [multi_line \
+        "\\*\\s+1\\s+\[^\r\n\]+\\s+${conn_num} \\(core\\)\\s+\[^\r\n\]+.*" \
+        "\\s+2\\s+<null>\\s+" \
+        "\\s+3\\s+<null>\\s+\[^\r\n\]+" \
+        "\\s+4\\s+<null>\\s+"] \
+    "first info inferiors call"
+
+# Now use add-inferior and clone-inferior but this time with the
+# -no-connection option, this should avoid issuing the warning.  We
+# also use interpreter-exec to test the MI version of this command.
+gdb_test "add-inferior -no-connection" \
+    [multi_line \
+        "\\\[New inferior 5\\\]" \
+        "Added inferior 5"]
+gdb_test "clone-inferior -no-connection" \
+    [multi_line \
+        "\\\[New inferior 6\\\]" \
+        "Added inferior 6"]
+gdb_test "interpreter-exec mi \"-add-inferior --no-connection\"" \
+    "\\\[New inferior 7\\\].*Added inferior 7.*"
+
+# Now check that none of the new inferiors have a connection.
+gdb_test "info inferiors" \
+    [multi_line \
+        "\\*\\s+1\\s+\[^\r\n\]+\\s+${conn_num} \\(core\\)\\s+\[^\r\n\]+.*" \
+        "\\s+2\\s+<null>\\s+" \
+        "\\s+3\\s+<null>\\s+\[^\r\n\]+" \
+        "\\s+4\\s+<null>\\s+" \
+        "\\s+5\\s+<null>\\s+" \
+        "\\s+6\\s+<null>\\s+\[^\r\n\]+" \
+        "\\s+7\\s+<null>\\s+"] \
+    "second info inferiors call"
+
+# Check after all the new inferiors have been created that we still
+# only have a single connection.
+gdb_test "info connections" \
+    "\\*\\s+${conn_num}\\s+\\s+core\\s+Local core dump file\\s*"
+
+# Now switch to inferior 2 and load the second executable and core
+# file.  Check the backtrace for the presence of function 'foo', this
+# indicates we are seeing the correct core file.
+gdb_test "inferior 2" "Switching to inferior 2 .*"
+gdb_test "file $binfile2" \
+    "Reading symbols from .*" \
+    "Loaded second test binary"
+gdb_test "core-file $corefile2" \
+    "Program terminated with signal SIGABRT, Aborted.*" \
+    "Loaded second core file"
+gdb_test "bt" "foo \\(\\) at .*" \
+    "check backtrace in inferior 2"
+
+# Switch to inferior 3, this one was cloned from inferior 1, so is
+# already debugging the first binary file.  Check its backtrace for
+# 'bar', which indicates we are debugging the correct core file.
+gdb_test "inferior 3" "Switching to inferior 3 .*"
+gdb_test "core-file $corefile1" \
+    "Program terminated with signal SIGABRT, Aborted.*" \
+    "Loaded first core file into inferior 3"
+gdb_test "bt" "bar \\(\\) at .*" \
+    "check backtrace in inferior 3"
+
+# Detach from some of the core files and delete some of the inferiors.
+gdb_test "detach" "No core file now\\." \
+    "detach from inferior 3 core file"
+gdb_test "inferior 2" "Switching to inferior 2 .*" \
+    "switch back to inferior 2"
+gdb_test_no_output "remove-inferiors 3 4"
+
+# Now detach in inferior 2, and delete the inferior.
+gdb_test "detach" "No core file now\\." \
+    "detach from inferior 2 core file"
+gdb_test "inferior 1" "Switching to inferior 1 .*" \
+    "switch back to inferior 1"
+gdb_test_no_output "remove-inferiors 2"
+
+# Finally, check that inferior 1 backtrace is still working.
+gdb_test "bt" "bar \\(\\) at .*" \
+    "check backtrace in inferior 1 again"
+gdb_test "detach" "No core file now\\." \
+    "detach from inferior 1 core file"
diff --git a/gdb/testsuite/gdb.multi/multi-remote-target.c b/gdb/testsuite/gdb.multi/multi-remote-target.c
new file mode 100644 (file)
index 0000000..6702a88
--- /dev/null
@@ -0,0 +1,71 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2025 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/>.  */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <assert.h>
+
+/* This is a simple, empty function designed to be a stable target
+   for a GDB breakpoint.  Both the parent and child processes will
+   call this function after the fork.  */
+
+void
+breakpt (void)
+{
+  /* Nothing.  */
+}
+
+int
+main (void)
+{
+  pid_t child_pid;
+
+  /* Create a new process.  */
+  child_pid = fork ();
+
+  assert (child_pid >= 0);
+
+  if (child_pid == 0)
+    {
+      /* This is the child process.  Call the breakpoint function.  */
+      breakpt ();
+
+      exit (0);
+    }
+  else
+    {
+      /* This is the parent process.  */
+      int child_status;
+
+      /* Call the breakpoint function.  */
+      breakpt ();
+
+      /* Wait for the child process to terminate.  */
+      waitpid (child_pid, &child_status, 0);
+
+      assert (WIFEXITED (child_status));
+      assert (WEXITSTATUS (child_status) == 0);
+
+      exit (0);
+    }
+
+  /* This line should not be reached.  */
+  return 1;
+}
diff --git a/gdb/testsuite/gdb.multi/multi-remote-target.exp b/gdb/testsuite/gdb.multi/multi-remote-target.exp
new file mode 100644 (file)
index 0000000..bcd5395
--- /dev/null
@@ -0,0 +1,89 @@
+# Copyright 2025 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 an attempt to share a 'remote' (not 'extended-remote')
+# will be prevented.  But also make sure that the remote connection
+# can be shared after the inferior does a 'fork'.
+
+load_lib gdbserver-support.exp
+
+require allow_gdbserver_tests
+
+standard_testfile
+
+save_vars { GDBFLAGS } {
+    # If GDB and GDBserver are both running locally, set the sysroot to avoid
+    # reading files via the remote protocol (the `is_remote target` check is
+    # already done above).
+    if { ![is_remote host] && ![is_remote target] } {
+       set GDBFLAGS "$GDBFLAGS -ex \"set sysroot\""
+    }
+    if {[prepare_for_testing "prepare" $testfile $srcfile] == -1} {
+       return
+    }
+}
+
+set target_binfile [gdb_remote_download target $binfile]
+
+# Make sure we're disconnected, in case we're testing with an
+# extended-remote board, therefore already connected.
+gdb_test "disconnect" ".*"
+
+# Start gdbserver and connect.
+set res [gdbserver_start "" $target_binfile]
+set gdbserver_addr [lindex $res 1]
+if { [gdb_target_cmd "remote" $gdbserver_addr] != 0 } {
+    unsupported "start gdbserver"
+    return
+}
+
+# The inferior will fork.  The following commands force GDB to create
+# a new inferior to follow the child process.  This ensures that GDB
+# is able to share the remote connection between inferiors after a
+# fork.
+gdb_test_no_output "set follow-fork-mode parent"
+gdb_test_no_output "set detach-on-fork off"
+gdb_breakpoint breakpt
+gdb_continue_to_breakpoint "runto breakpt function"
+
+# If we are using an extended-remote board, then an extended-remote
+# connection will have been setup when GDB initially started.  We then
+# disconnected, and setup a basic 'remote' connection above.  However,
+# the 'remote' connection will now be connection 2.
+#
+# For all other boards, the 'remote' connection will be number 1.
+if { [target_info gdb_protocol] == "extended-remote"} {
+    set conn_num 2
+} else {
+    set conn_num 1
+}
+
+# But as this is only a 'remote' (not 'extended-remote') connection,
+# then new inferiors cannot be started by the user.  This means that
+# when the user does 'add-inferior' there is no point sharing the
+# remote connection with the new inferior, it can never run anything.
+gdb_test "add-inferior" \
+    [multi_line \
+        "warning: can't share connection $conn_num \\(remote \[^\r\n\]+\\) between inferiors" \
+        "Added inferior 3"] \
+    "connection is dropped when adding a new inferior"
+
+# Ensure the new inferior shows no connection.
+gdb_test "info inferiors" \
+    [multi_line \
+        "\\*\\s+1\\s+\[^\r\n\]+\\s+${conn_num} \\(remote \[^\r\n\]+\\)\\s+\[^\r\n\]+" \
+        "\\s+2\\s+\[^\r\n\]+\\s+${conn_num} \\(remote \[^\r\n\]+\\)\\s+\[^\r\n\]+" \
+        "\\s+3\\s+<null>\\s+"] \
+    "third inferior has no connection"
index d60ebb008577280c471cff4e873993d835e72e05..5056eb1924f68feb7d4f2c89dc904c54753dda47 100644 (file)
@@ -58,8 +58,17 @@ if { [target_info exists gdb_protocol] } {
     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 that shares a connection with inferior 1.  If we are
+# using a 'remote' connection then this cannot be shared with the new
+# inferior, so we get a warning, and a new inferior with no connection.
+if { $connection_type == "remote" } {
+    gdb_test "add-inferior" \
+       [multi_line \
+            "warning: can't share connection 1 \\(remote \[^\r\n\]+\\) between inferiors" \
+            "Added inferior 2"]
+} else {
+    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"