[PATCHv3,7/7] gdb: some process_stratum_target should not be shared

Message ID 577f2c47793acb501c2611c0e6c7ea379f774830.1668789658.git.aburgess@redhat.com
State New
Headers
Series gdb: fix target_ops reference count for some cases |

Commit Message

Andrew Burgess Nov. 18, 2022, 4:42 p.m. UTC
  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, as well as the
remote target, this is absolutely true (or was made true).  But some
targets were never updated to be shareable, for example, the
core_target, which is used when reading core-files, stores some of its
state in the program_space, but also, the core-file and the executable
being debugged are closely related.

As each add-inferior call creates an inferior with a new
program_space, and doesn't automatically copy the executable, or the
current core-file, I don't think it really makes sense to "share"
core_target objects between inferiors.

Consider this session:

  $ gdb -q
  (gdb) file test1
  Reading symbols from test1...
  (gdb) core-file 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;
  (gdb) add-inferior
  [New inferior 2]
  Added inferior 2 on connection 1 (core)
  (gdb) info inferiors
    Num  Description       Connection           Executable
  * 1    process 433190    1 (core)             /tmp/multi-core/test1
    2    <null>            1 (core)
  (gdb) info connections
    Num  What  Description
  * 1    core  Local core dump file
  (gdb) inferior 2
  [Switching to inferior 2 [<null>] (<noexec>)]
  (gdb) file test2
  Reading symbols from test2...
  (gdb) core-file core.test2.433203
  [New LWP 433203]
  Core was generated by `./test2'.
  Program terminated with signal SIGSEGV, Segmentation fault.
  #0  0x0000000000401111 in main () at test2.c:6
  6	  return *global_ptr;
  (gdb) info inferiors
    Num  Description       Connection           Executable
    1    process 433190    1 (core)             /tmp/multi-core/test1
  * 2    process 433203    2 (core)             /tmp/multi-core/test2
  (gdb) info connections
    Num  What  Description
    1    core  Local core dump file
  * 2    core  Local core dump file
  (gdb)

After the 'add-inferior' the core_target connection is shared between
the inferiors.  However, as soon as the user sets up the core-file and
executable in the new inferior a new core connection has been created.

I think this behaviour might be confusing, so I'd like to have GDB not
initially share the core connection.  Instead, when the user tries to
add the new inferior a warning is given, and the new inferior is
created without a connection, like this:

  $ gdb -q
  (gdb) file test1
  Reading symbols from test1...
  (gdb) core-file 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;
  (gdb) add-inferior
  [New inferior 2]
  warning: can't share connection 1 (core) between inferiors
  Added inferior 2
  (gdb) info inferiors
    Num  Description       Connection           Executable
  * 1    process 433190    1 (core)             /tmp/multi-core/test1
    2    <null>
  (gdb)

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 inferior 2 with a different
executable and core file (or to do anything they like with the
inferior).

In an earlier version of this patch I had GDB error instead of giving
a warning.  However, the error message ended up being something like:

  can't share connection ..... between inferiors, use -no-connection
      option to create an inferior without sharing a connection.

but it seemed better to just create the inferior.

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.
---
 gdb/NEWS                                     |   7 +
 gdb/corelow.c                                |   5 +
 gdb/doc/gdb.texinfo                          |  37 +++-
 gdb/inferior.c                               |  16 ++
 gdb/inferior.h                               |   6 +-
 gdb/target.c                                 |  14 ++
 gdb/target.h                                 |   8 +
 gdb/testsuite/gdb.multi/multi-core-files-1.c |  37 ++++
 gdb/testsuite/gdb.multi/multi-core-files-2.c |  31 ++++
 gdb/testsuite/gdb.multi/multi-core-files.exp | 172 +++++++++++++++++++
 10 files changed, 329 insertions(+), 4 deletions(-)
 create mode 100644 gdb/testsuite/gdb.multi/multi-core-files-1.c
 create mode 100644 gdb/testsuite/gdb.multi/multi-core-files-2.c
 create mode 100644 gdb/testsuite/gdb.multi/multi-core-files.exp
  

Comments

Eli Zaretskii Nov. 18, 2022, 5:02 p.m. UTC | #1
> Cc: Andrew Burgess <aburgess@redhat.com>
> Date: Fri, 18 Nov 2022 16:42:58 +0000
> From: Andrew Burgess via Gdb-patches <gdb-patches@sourceware.org>
> 
> 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.
> ---
>  gdb/NEWS                                     |   7 +
>  gdb/corelow.c                                |   5 +
>  gdb/doc/gdb.texinfo                          |  37 +++-
>  gdb/inferior.c                               |  16 ++
>  gdb/inferior.h                               |   6 +-
>  gdb/target.c                                 |  14 ++
>  gdb/target.h                                 |   8 +
>  gdb/testsuite/gdb.multi/multi-core-files-1.c |  37 ++++
>  gdb/testsuite/gdb.multi/multi-core-files-2.c |  31 ++++
>  gdb/testsuite/gdb.multi/multi-core-files.exp | 172 +++++++++++++++++++
>  10 files changed, 329 insertions(+), 4 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.multi/multi-core-files-1.c
>  create mode 100644 gdb/testsuite/gdb.multi/multi-core-files-2.c
>  create mode 100644 gdb/testsuite/gdb.multi/multi-core-files.exp

OK for the documentation parts.

Thanks.
  
Tom Tromey Nov. 18, 2022, 6:04 p.m. UTC | #2
>>>>> "Andrew" == Andrew Burgess via Gdb-patches <gdb-patches@sourceware.org> writes:

Andrew> For things like the Linux and FreeBSD native targets, as well as the
Andrew> remote target, this is absolutely true (or was made true).  But some
Andrew> targets were never updated to be shareable, for example, the
Andrew> core_target, which is used when reading core-files, stores some of its
Andrew> state in the program_space, but also, the core-file and the executable
Andrew> being debugged are closely related.

Andrew> As each add-inferior call creates an inferior with a new
Andrew> program_space, and doesn't automatically copy the executable, or the
Andrew> current core-file, I don't think it really makes sense to "share"
Andrew> core_target objects between inferiors.

Yeah, probably the core target just doesn't even make sense to share.

I guess if we did want to share it, we could move the state into the
core target.  Maybe this is worthwhile to do anyway?  I see a bunch of
uses of core_bfd (which is a #define reaching into the program space),
but some, e.g. in linux_read_core_file_mappings, seem like they could be
replaced with a parameter.

Andrew> I think this behaviour might be confusing, so I'd like to have GDB not
Andrew> initially share the core connection.  Instead, when the user tries to
Andrew> add the new inferior a warning is given, and the new inferior is
Andrew> created without a connection, like this:

This makes sense to me.

Andrew> +  /* The core_target only works for the inferior in which it was initially
Andrew> +     opened, and can't be copied to some other inferior's target_stack.  */
Andrew> +  bool is_shareable () override
Andrew> +  { return false; }

However, why only mark the core target this way?

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.

So maybe the default implementation should be 'return false' and then
specific known-good targets should override it?

Tom
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 8176940ae1a..5c8dac12de7 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -81,6 +81,13 @@ 
 * New convenience variable $_inferior_thread_count contains the number
   of live threads in the current inferior.
 
+* 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, at the time the command is
+  given, is a core-file target.  The core-file target could never
+  really be shared between inferiors, GDB is now more vocal about what
+  is going on.
+
 * New commands
 
 maintenance set ignore-prologue-end-flag on|off
diff --git a/gdb/corelow.c b/gdb/corelow.c
index 293bc8d4f59..4d8393a3587 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -128,6 +128,11 @@  class core_target final : public process_stratum_target
   /* See definition.  */
   void info_proc_mappings (struct gdbarch *gdbarch);
 
+  /* The core_target only works for the inferior in which it was initially
+     opened, and can't be copied to some other inferior's target_stack.  */
+  bool is_shareable () override
+  { return false; }
+
 private: /* per-core data */
 
   /* Get rid of the core inferior.  */
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 566da9e7d92..bb33e60a22e 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3358,8 +3358,31 @@ 
 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
+
 @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
@@ -3384,6 +3407,13 @@ 
 
 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.
+
 @kindex remove-inferiors
 @item remove-inferiors @var{infno}@dots{}
 Removes the inferior or inferiors @var{infno}@dots{}.  It is not
@@ -37828,6 +37858,11 @@ 
 @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.
diff --git a/gdb/inferior.c b/gdb/inferior.c
index f4b9828a425..ec4c348f7e3 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -816,6 +816,22 @@  switch_to_inferior_and_push_target (inferior *new_inf,
      symbols.  */
   switch_to_inferior_no_thread (new_inf);
 
+  if (!no_connection && proc_target != nullptr
+      && !proc_target->is_shareable ())
+    {
+      if (proc_target->connection_string () != nullptr)
+	warning (_("can't share connection %d (%s %s) between inferiors"),
+		 proc_target->connection_number,
+		 proc_target->shortname (),
+		 proc_target->connection_string ());
+      else
+	warning (_("can't share connection %d (%s) between inferiors"),
+		 proc_target->connection_number,
+		 proc_target->shortname ());
+
+      proc_target = nullptr;
+    }
+
   /* Reuse the target for new inferior.  */
   if (!no_connection && proc_target != NULL)
     {
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 2917f42e624..a312cd2b9bb 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -759,9 +759,9 @@  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, 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);
diff --git a/gdb/target.c b/gdb/target.c
index b2af51551ba..ac0608645b4 100644
--- a/gdb/target.c
+++ b/gdb/target.c
@@ -1187,6 +1187,12 @@  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 ())
+      gdb_assert (!inf->target_is_pushed (t));
+
   /* Now add the new one.  */
   m_stack[stratum] = std::move (ref);
 
@@ -3221,6 +3227,14 @@  target_ops::fileio_readlink (struct inferior *inf, const char *filename,
 
 /* See target.h.  */
 
+bool
+target_ops::is_shareable ()
+{
+  return true;
+}
+
+/* See target.h.  */
+
 int
 target_fileio_open (struct inferior *inf, const char *filename,
 		    int flags, int mode, bool warn_if_slow, fileio_error *target_errno)
diff --git a/gdb/target.h b/gdb/target.h
index 547ee8a3bbd..30e5085a543 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -1321,6 +1321,14 @@  struct target_ops
     virtual bool store_memtags (CORE_ADDR address, size_t len,
 				const gdb::byte_vector &tags, int type)
       TARGET_DEFAULT_NORETURN (tcomplain ());
+
+    /* Return true if this target can be shared on multiple target_stacks,
+       or false if this target should only appear on a single target_stack.
+       When this function returns false multiple separate instances of the
+       same target_ops sub-class can still appear on different
+       target_stacks, but the same concrete instance can only appear on a
+       single target_stack.  */
+    virtual bool is_shareable ();
   };
 
 /* Deleter for std::unique_ptr.  See comments in
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
index 00000000000..f996973023e
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/multi-core-files-1.c
@@ -0,0 +1,37 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 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/>.  */
+
+#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
index 00000000000..fb99e137c3f
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/multi-core-files-2.c
@@ -0,0 +1,31 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 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/>.  */
+
+#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
index 00000000000..8c2610000a0
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/multi-core-files.exp
@@ -0,0 +1,172 @@ 
+# Copyright 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/>.
+
+# 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 binfile1 "${binfile}-1"
+set binfile2 "${binfile}-2"
+
+if {[build_executable "build first executable" $binfile1 $srcfile \
+	 debug] == -1} {
+    untested "failed to compile first executable"
+    return -1
+}
+
+if {[build_executable "build second executable" $binfile2 $srcfile2 \
+	 debug] == -1} {
+    untested "failed to compile second executable"
+    return -1
+}
+
+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 ${binfile1}
+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"