public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH 0/6] Inferior specific breakpoints
@ 2022-11-28 11:25 Andrew Burgess
  2022-11-28 11:25 ` [PATCH 1/6] gdb/remote: announce thread exit events for remote targets Andrew Burgess
                   ` (7 more replies)
  0 siblings, 8 replies; 54+ messages in thread
From: Andrew Burgess @ 2022-11-28 11:25 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Adding inferior specific breakpoints to GDB.  These are like the
current thread or Ada task specific breakpoints, but cover inferiors.

Patches #1, #2, #3 are unreleated bug fixes that I ran into while
adding this feature.

Patch #4 is possibly a bug fix, or maybe a tightening of our CLI rules
for specifying thread/task breakpoints and watchpoints.  This one
could be dropped if it's not wanted.

Patch #5 is where I add inferior specific breakpoints.

Patch #6 converts the 'start' mechanism to use inferior specific
breakpoints.

---

Andrew Burgess (6):
  gdb/remote: announce thread exit events for remote targets
  gdb/testsuite: don't try to set non-stop mode on a running target
  gdb: fix display of thread condition for multi-location breakpoints
  gdb: error if 'thread' or 'task' keywords are overused
  gdb: add inferior-specific breakpoints and watchpoints
  gdb: convert the 'start' breakpoint to use inferior keyword

 gdb/NEWS                                      |  16 ++
 gdb/breakpoint.c                              | 188 +++++++++++++++---
 gdb/breakpoint.h                              |  10 +-
 gdb/doc/gdb.texinfo                           |  74 ++++++-
 gdb/doc/python.texi                           |  27 ++-
 gdb/dummy-frame.c                             |   1 +
 gdb/elfread.c                                 |   5 +-
 gdb/guile/scm-breakpoint.c                    |   5 +
 gdb/infcmd.c                                  |  10 +-
 gdb/inferior.h                                |  10 +
 gdb/infrun.c                                  |   2 +
 gdb/linespec.c                                |   4 +-
 gdb/python/py-breakpoint.c                    |  77 +++++++
 gdb/remote.c                                  |   4 +
 gdb/testsuite/gdb.ada/tasks.exp               |   4 +
 .../gdb.base/start-inferior-specific-1.c      |  32 +++
 .../gdb.base/start-inferior-specific-2.c      |  22 ++
 .../gdb.base/start-inferior-specific.exp      |  55 +++++
 gdb/testsuite/gdb.base/thread-bp-multi-loc.c  |  44 ++++
 .../gdb.base/thread-bp-multi-loc.exp          |  88 ++++++++
 gdb/testsuite/gdb.linespec/cpcompletion.exp   |   4 +-
 gdb/testsuite/gdb.linespec/explicit.exp       |   1 +
 gdb/testsuite/gdb.linespec/keywords.exp       |  10 +-
 .../gdb.multi/inferior-specific-bp-1.c        |  52 +++++
 .../gdb.multi/inferior-specific-bp-2.c        |  52 +++++
 .../gdb.multi/inferior-specific-bp.exp        | 183 +++++++++++++++++
 gdb/testsuite/gdb.python/py-breakpoint.exp    |  43 ++++
 .../gdb.threads/thread-specific-bp.exp        | 142 ++++++-------
 gdb/testsuite/gdb.threads/watchthreads2.exp   |   3 +
 gdb/testsuite/lib/completion-support.exp      |   2 +-
 30 files changed, 1041 insertions(+), 129 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/start-inferior-specific-1.c
 create mode 100644 gdb/testsuite/gdb.base/start-inferior-specific-2.c
 create mode 100644 gdb/testsuite/gdb.base/start-inferior-specific.exp
 create mode 100644 gdb/testsuite/gdb.base/thread-bp-multi-loc.c
 create mode 100644 gdb/testsuite/gdb.base/thread-bp-multi-loc.exp
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp.exp


base-commit: ac8df5a1921904b3928429e696ad8b40c612f829
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCH 1/6] gdb/remote: announce thread exit events for remote targets
  2022-11-28 11:25 [PATCH 0/6] Inferior specific breakpoints Andrew Burgess
@ 2022-11-28 11:25 ` Andrew Burgess
  2022-11-28 11:25 ` [PATCH 2/6] gdb/testsuite: don't try to set non-stop mode on a running target Andrew Burgess
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 54+ messages in thread
From: Andrew Burgess @ 2022-11-28 11:25 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

For some reason the "[Thread XXXX exited]" messages are not printed
inside thread.c from functions like delete_thread, etc as might be
expected.  Instead, each target seems to print the message before
calling delete_thread.

This doesn't seem ideal, and I can't help but feel that the printing
should be moved into thread.c, however, I have not tried to do that in
this commit, as I suspect there will be lots of fallout that needs
fixing up.

Instead, in this commit, I have added the printing code into remote.c,
so that the remote target will now correctly tell the user when a
thread has exited.

This fixes some test failures in gdb.threads/thread-specific-bp.exp
when run with the native-gdbserver and native-extended-gdbserver
board.

When using the native-extended-gdbserver board I still see 1 test
failure, but I think this is not related to the issue fixed in this
commit, so I'm ignoring that for now.
---
 gdb/remote.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/gdb/remote.c b/gdb/remote.c
index 5118ecd0a31..1737caa579c 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -3978,6 +3978,10 @@ remote_target::update_thread_list ()
 	      if (has_single_non_exited_thread (tp->inf))
 		continue;
 
+	      if (print_thread_events)
+		gdb_printf (_("[%s exited]\n"),
+			    target_pid_to_str (tp->ptid).c_str ());
+
 	      /* Not found.  */
 	      delete_thread (tp);
 	    }
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCH 2/6] gdb/testsuite: don't try to set non-stop mode on a running target
  2022-11-28 11:25 [PATCH 0/6] Inferior specific breakpoints Andrew Burgess
  2022-11-28 11:25 ` [PATCH 1/6] gdb/remote: announce thread exit events for remote targets Andrew Burgess
@ 2022-11-28 11:25 ` Andrew Burgess
  2022-11-28 11:25 ` [PATCH 3/6] gdb: fix display of thread condition for multi-location breakpoints Andrew Burgess
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 54+ messages in thread
From: Andrew Burgess @ 2022-11-28 11:25 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

The test gdb.threads/thread-specific-bp.exp tries to set non-stop mode
on a running target, something which the manual makes clear is not
allowed.

The consequence of this is that the gdb.threads/thread-specific-bp.exp
test has one failure when run with the native-extended-gdbserver
board.

This commit restructures the test a little, we now set the non-stop
mode as part of the GDBFLAGS, so the mode will be set before GDB
connects to the target.  As a consequence I'm able to move the
with_test_prefix out of the check_thread_specific_breakpoint proc.
The check_thread_specific_breakpoint proc is now called within a loop.

After this commit the gdb.threads/thread-specific-bp.exp test has zero
failures for me with native-extended-gdbserver, native-gdbserver, and
the unix board.
---
 .../gdb.threads/thread-specific-bp.exp        | 138 +++++++++---------
 1 file changed, 67 insertions(+), 71 deletions(-)

diff --git a/gdb/testsuite/gdb.threads/thread-specific-bp.exp b/gdb/testsuite/gdb.threads/thread-specific-bp.exp
index 863c139d6b2..dc822f37b7b 100644
--- a/gdb/testsuite/gdb.threads/thread-specific-bp.exp
+++ b/gdb/testsuite/gdb.threads/thread-specific-bp.exp
@@ -24,8 +24,6 @@ if {[gdb_compile_pthreads \
     return -1
 }
 
-clean_restart ${binfile}
-
 # Extract and return the thread ID of the thread stopped at function
 # FUNC.
 
@@ -45,86 +43,84 @@ proc get_thread_id {func} {
     return $thre
 }
 
-proc check_thread_specific_breakpoint {mode} {
-    with_test_prefix "$mode" {
-	global gdb_prompt
+proc check_thread_specific_breakpoint {non_stop} {
+    global gdb_prompt
 
-	if ![runto_main] {
-	    return -1
-	}
+    if ![runto_main] {
+	return -1
+    }
 
-	set main_thre [get_thread_id "main"]
-	if { $main_thre < 0 } {
-	    return -1
-	}
+    set main_thre [get_thread_id "main"]
+    if { $main_thre < 0 } {
+	return -1
+    }
 
-	gdb_breakpoint "start"
-	gdb_continue_to_breakpoint "start"
+    gdb_breakpoint "start"
+    gdb_continue_to_breakpoint "start"
 
-	set start_thre [get_thread_id "start"]
-	if { $start_thre < 0 } {
-	    return -1
-	}
+    set start_thre [get_thread_id "start"]
+    if { $start_thre < 0 } {
+	return -1
+    }
 
-	# Set a thread-specific breakpoint at "main".  This can't ever
-	# be hit, but that's OK.
-	gdb_breakpoint "main thread $start_thre"
-	gdb_test "info break" ".*breakpoint.*thread $start_thre" "breakpoint set"
-
-	# Set breakpoint at a place only reacheable after the "start"
-	# thread exits.
-	gdb_breakpoint "end"
-
-	# Switch back to the main thread, and resume all threads.  The
-	# "start" thread exits, and the main thread reaches "end".
-	gdb_test "thread $main_thre" \
-	    "Switching to thread $main_thre.*" \
-	    "thread $main_thre selected"
-
-	if { $mode == "non-stop" } {
-	    set cmd "continue -a"
-	} else {
-	    set cmd "continue"
-	}
-	set test "continue to end"
-	set thread_exited 0
-	set prompt 0
-	gdb_test_multiple "$cmd" $test -lbl {
-	    -re "(^|\r\n)\\\[Thread \[^\r\n\]* exited](?=\r\n)" {
-		if { $prompt } {
-		    pass $gdb_test_name
-		} else {
-		    set thread_exited 1
-		    exp_continue
-		}
+    # Set a thread-specific breakpoint at "main".  This can't ever
+    # be hit, but that's OK.
+    gdb_breakpoint "main thread $start_thre"
+    gdb_test "info break" ".*breakpoint.*thread $start_thre" "breakpoint set"
+
+    # Set breakpoint at a place only reacheable after the "start"
+    # thread exits.
+    gdb_breakpoint "end"
+
+    # Switch back to the main thread, and resume all threads.  The
+    # "start" thread exits, and the main thread reaches "end".
+    gdb_test "thread $main_thre" \
+	"Switching to thread $main_thre.*" \
+	"thread $main_thre selected"
+
+    if { $non_stop } {
+	set cmd "continue -a"
+    } else {
+	set cmd "continue"
+    }
+    set test "continue to end"
+    set thread_exited 0
+    set prompt 0
+    gdb_test_multiple "$cmd" $test -lbl {
+	-re "(^|\r\n)\\\[Thread \[^\r\n\]* exited](?=\r\n)" {
+	    if { $prompt } {
+		pass $gdb_test_name
+	    } else {
+		set thread_exited 1
+		exp_continue
 	    }
-	    -re "\r\n$gdb_prompt " {
-		if { $thread_exited } {
-		    pass $gdb_test_name
-		} else {
-		    set prompt 1
-		    exp_continue
-		}
+	}
+	-re "\r\n$gdb_prompt " {
+	    if { $thread_exited } {
+		pass $gdb_test_name
+	    } else {
+		set prompt 1
+		exp_continue
 	    }
 	}
+    }
 
-	set test "thread-specific breakpoint was deleted"
-	gdb_test_multiple "info breakpoint" $test {
-	    -re "thread $start_thre\n$gdb_prompt $" {
-		fail $test
-	    }
-	    -re "$gdb_prompt $" {
-		pass $test
-	    }
+    set test "thread-specific breakpoint was deleted"
+    gdb_test_multiple "info breakpoint" $test {
+	-re "thread $start_thre\n$gdb_prompt $" {
+	    fail $test
+	}
+	-re "$gdb_prompt $" {
+	    pass $test
 	}
     }
 }
 
-# Test all-stop mode.
-check_thread_specific_breakpoint "all-stop"
-
-clean_restart ${binfile}
+foreach_with_prefix non_stop {on off} {
+    save_vars { GDBFLAGS } {
+	append GDBFLAGS " -ex \"set non-stop $non_stop\""
+	clean_restart $binfile
+    }
 
-# Test non-stop mode.
-gdb_test_no_output "set non-stop on" "set non-stop mode"
-check_thread_specific_breakpoint "non-stop"
+    check_thread_specific_breakpoint $non_stop
+}
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCH 3/6] gdb: fix display of thread condition for multi-location breakpoints
  2022-11-28 11:25 [PATCH 0/6] Inferior specific breakpoints Andrew Burgess
  2022-11-28 11:25 ` [PATCH 1/6] gdb/remote: announce thread exit events for remote targets Andrew Burgess
  2022-11-28 11:25 ` [PATCH 2/6] gdb/testsuite: don't try to set non-stop mode on a running target Andrew Burgess
@ 2022-11-28 11:25 ` Andrew Burgess
  2022-12-23  8:37   ` Aktemur, Tankut Baris
  2022-11-28 11:25 ` [PATCH 4/6] gdb: error if 'thread' or 'task' keywords are overused Andrew Burgess
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2022-11-28 11:25 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

If a breakpoint with multiple locations has a thread condition, then
the 'info breakpoints' output is a little messed up, here's an example
of the current output:

  (gdb) break foo thread 1
  Breakpoint 2 at 0x401114: foo. (3 locations)
  (gdb) break bar thread 1
  Breakpoint 3 at 0x40110a: file /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c, line 32.
  (gdb) info breakpoints
  Num     Type           Disp Enb Address            What
  2       breakpoint     keep y   <MULTIPLE>          thread 1
          stop only in thread 1
  2.1                         y   0x0000000000401114 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
  2.2                         y   0x0000000000401146 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
  2.3                         y   0x0000000000401168 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
  3       breakpoint     keep y   0x000000000040110a in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32 thread 1
          stop only in thread 1

Notice that, at the end of the location for breakpoint 3, the 'thread
1' condition is printed.

In contrast, for breakpoint 2, the 'thread 1' appears randomly, in the
"What" column, though slightly offset.

I believe this is a bug in GDB, in the function
print_one_breakpoint_location, due to checking the local variable
part_of_multiple, instead of header_of_multiple.

If I fix this oversight, then the output is now:

  (gdb) break foo thread 1
  Breakpoint 2 at 0x401114: foo. (3 locations)
  (gdb) break bar thread 1
  Breakpoint 3 at 0x40110a: file /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c, line 32.
  (gdb) info breakpoints
  Num     Type           Disp Enb Address            What
  2       breakpoint     keep y   <MULTIPLE>
          stop only in thread 1
  2.1                         y   0x0000000000401114 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
  2.2                         y   0x0000000000401146 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
  2.3                         y   0x0000000000401168 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
  3       breakpoint     keep y   0x000000000040110a in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32 thread 1
          stop only in thread 1

The 'thread 1' condition is now displayed at the end of each location,
which makes the output the same for single location breakpoints and
multi-location breakpoints.
---
 gdb/breakpoint.c                              |  2 +-
 gdb/testsuite/gdb.base/thread-bp-multi-loc.c  | 44 ++++++++++
 .../gdb.base/thread-bp-multi-loc.exp          | 88 +++++++++++++++++++
 3 files changed, 133 insertions(+), 1 deletion(-)
 create mode 100644 gdb/testsuite/gdb.base/thread-bp-multi-loc.c
 create mode 100644 gdb/testsuite/gdb.base/thread-bp-multi-loc.exp

diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index f0276a963c0..2ec8ca364b6 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -6467,7 +6467,7 @@ print_one_breakpoint_location (struct breakpoint *b,
       output_thread_groups (uiout, "thread-groups", inf_nums, mi_only);
     }
 
-  if (!part_of_multiple)
+  if (!header_of_multiple)
     {
       if (b->thread != -1)
 	{
diff --git a/gdb/testsuite/gdb.base/thread-bp-multi-loc.c b/gdb/testsuite/gdb.base/thread-bp-multi-loc.c
new file mode 100644
index 00000000000..cab009c39ec
--- /dev/null
+++ b/gdb/testsuite/gdb.base/thread-bp-multi-loc.c
@@ -0,0 +1,44 @@
+/* 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/>.  */
+
+volatile int global_var = 0;
+
+__attribute__((__always_inline__)) static inline void
+foo (void)
+{
+  int i;
+
+  for (i = 0; i < 10; ++i)
+    global_var = i;
+}
+
+static void
+bar (void)
+{
+  global_var = 0;
+  foo ();
+}
+
+int
+main (void)
+{
+  global_var = 0;
+  foo ();
+  bar ();
+  foo ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp b/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp
new file mode 100644
index 00000000000..9abff5866d9
--- /dev/null
+++ b/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp
@@ -0,0 +1,88 @@
+# 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/>.
+
+# Create a multi-location breakpoint with a thread condition, then check the
+# output of 'info breakpoints' to ensure that the thread condition is
+# displayed correctly.
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
+    return
+}
+
+if ![runto_main] then {
+    return
+}
+
+gdb_test_no_output "with confirm off -- delete breakpoints"
+
+set bp_number ""
+gdb_test_multiple "break foo thread 1" "" {
+    -re "^break foo thread 1\r\n" {
+	exp_continue
+    }
+    -re "^Breakpoint ($decimal) at $hex: foo\[^\r\n\]+3 locations\[^\r\n\]+\r\n" {
+	set bp_number $expect_out(1,string)
+	exp_continue
+    }
+    -re "^$gdb_prompt $" {
+	gdb_assert { ![string eq $bp_number ""] }
+    }
+}
+
+if { $bp_number == "" } {
+    unresolved "breakpoint not placed correctly"
+    return
+}
+
+set saw_header false
+set saw_cond false
+set loc_count 0
+gdb_test_multiple "info breakpoints" "" {
+    -re "^info breakpoints\r\n" {
+	exp_continue
+    }
+
+    -re "^Num\\s+\[^\r\n\]+\r\n" {
+	exp_continue
+    }
+
+    -re "^$bp_number\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
+	set saw_header true
+	exp_continue
+    }
+
+    -re "^\\s+stop only in thread 1\r\n" {
+	set saw_cond true
+	exp_continue
+    }
+
+    -re "^$bp_number\\.\[123\]\\s+\[^\r\n\]+ thread 1\r\n" {
+	incr loc_count
+	exp_continue
+    }
+
+    -re "^$gdb_prompt $" {
+	with_test_prefix $gdb_test_name {
+	    gdb_assert { $saw_header } \
+		"saw header line"
+	    gdb_assert { $saw_cond } \
+		"saw b/p condition line"
+	    gdb_assert { $loc_count == 3 } \
+		"saw all three locations"
+	}
+    }
+}
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCH 4/6] gdb: error if 'thread' or 'task' keywords are overused
  2022-11-28 11:25 [PATCH 0/6] Inferior specific breakpoints Andrew Burgess
                   ` (2 preceding siblings ...)
  2022-11-28 11:25 ` [PATCH 3/6] gdb: fix display of thread condition for multi-location breakpoints Andrew Burgess
@ 2022-11-28 11:25 ` Andrew Burgess
  2022-11-28 13:10   ` Eli Zaretskii
  2022-11-28 11:25 ` [PATCH 5/6] gdb: add inferior-specific breakpoints and watchpoints Andrew Burgess
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2022-11-28 11:25 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

When creating a breakpoint or watchpoint, the 'thread' and 'task'
keywords can be used to create a thread or task specific breakpoint or
watchpoint.

Currently, a thread or task specific breakpoint can only apply for a
single thread or task, if multiple threads or tasks are specified when
creating the breakpoint (or watchpoint), then the last specified id
will be used.

The exception to the above is that when the 'thread' keyword is used
during the creation of a watchpoint, GDB will give an error if
'thread' is given more than once.

In this commit I propose making this behaviour consistent, if the
'thread' or 'task' keywords are used more than once when creating
either a breakpoint or watchpoint, then GDB will give an error.

I haven't updated the manual, we don't explicitly say that these
keywords can be repeated, and (to me), given the keyword takes a
single id, I don't think it makes much sense to repeat the keyword.
As such, I see this more as adding a missing error to GDB, rather than
making some big change.  However, I have added an entry to the NEWS
file as I guess it is possible that some people might hit this new
error with an existing (I claim, badly written) GDB script.

I've added some new tests to check for the new error.

Just one test needed updating, gdb.linespec/keywords.exp, this test
did use the 'thread' keyword twice, and expected the breakpoint to be
created.  Looking at what this test was for though, it was checking
the use of '-force-condition', and I don't think that being able to
repeat 'thread' was actually a critical part of this test.

As such, I've updated this test to expect the error when 'thread' is
repeated.
---
 gdb/NEWS                                         |  9 +++++++++
 gdb/breakpoint.c                                 |  9 +++++++++
 gdb/testsuite/gdb.ada/tasks.exp                  |  4 ++++
 gdb/testsuite/gdb.linespec/keywords.exp          | 10 ++++++++--
 gdb/testsuite/gdb.threads/thread-specific-bp.exp |  4 ++++
 gdb/testsuite/gdb.threads/watchthreads2.exp      |  3 +++
 6 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/gdb/NEWS b/gdb/NEWS
index dddef6525de..1f2233082ae 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -101,6 +101,15 @@
      (gdb) disable $_hit_bpnum
   are both disabling the breakpoint.
 
+* For the break command, multiple uses of the 'thread' or 'task'
+  keywords will now given an error instead of just using the thread or
+  task id from the last instance of the keyword.
+
+* For the watch command, multiple uses of the 'task' keyword will now
+  given an error instead of just using the task id from the last
+  instance of the keyword.  The 'thread' keyword already gave an error
+  when used multiple times with the watch command.
+
 * New commands
 
 maintenance set ignore-prologue-end-flag on|off
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 2ec8ca364b6..bf99297a250 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -8793,6 +8793,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  const char *tmptok;
 	  struct thread_info *thr;
 
+	  if (*thread != -1)
+	    error(_("You can specify only one thread."));
+
 	  tok = end_tok + 1;
 	  thr = parse_thread_id (tok, &tmptok);
 	  if (tok == tmptok)
@@ -8804,6 +8807,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	{
 	  char *tmptok;
 
+	  if (*task != 0)
+	    error(_("You can specify only one task."));
+
 	  tok = end_tok + 1;
 	  *task = strtol (tok, &tmptok, 0);
 	  if (tok == tmptok)
@@ -10086,6 +10092,9 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	    {
 	      char *tmp;
 
+	      if (task != 0)
+		error(_("You can specify only one task."));
+
 	      task = strtol (value_start, &tmp, 0);
 	      if (tmp == value_start)
 		error (_("Junk after task keyword."));
diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp
index 2ca03396d7c..c2ab6c6724d 100644
--- a/gdb/testsuite/gdb.ada/tasks.exp
+++ b/gdb/testsuite/gdb.ada/tasks.exp
@@ -39,6 +39,10 @@ gdb_test "info tasks" \
                "\r\n"] \
          "info tasks before inserting breakpoint"
 
+# Check that multiple uses of the 'task' keyword will give an error.
+gdb_test "break break_me task 1 task 3" "You can specify only one task\\."
+gdb_test "watch j task 1 task 3" "You can specify only one task\\."
+
 # Insert a breakpoint that should stop only if task 1 stops.  Since
 # task 1 never calls break_me, this shouldn't actually ever trigger.
 # The fact that this breakpoint is created _before_ the next one
diff --git a/gdb/testsuite/gdb.linespec/keywords.exp b/gdb/testsuite/gdb.linespec/keywords.exp
index 94536df4670..dca854d3f59 100644
--- a/gdb/testsuite/gdb.linespec/keywords.exp
+++ b/gdb/testsuite/gdb.linespec/keywords.exp
@@ -80,8 +80,14 @@ foreach prefix {"" "thread 1 "} {
     foreach suffix {"" " " " thread 1"} {
 	foreach cond {"" " if 1"} {
 	    with_test_prefix "prefix: '$prefix', suffix: '$suffix', cond: '$cond'" {
-		gdb_breakpoint "main ${prefix}-force-condition${suffix}${cond}"\
-		    "message"
+
+		if { [regexp thread $prefix] && [regexp thread $suffix] } {
+		    gdb_test "break main ${prefix}-force-condition${suffix}${cond}" \
+			"You can specify only one thread\\."
+		} else {
+		    gdb_breakpoint "main ${prefix}-force-condition${suffix}${cond}"\
+			"message"
+		}
 	    }
 	}
     }
diff --git a/gdb/testsuite/gdb.threads/thread-specific-bp.exp b/gdb/testsuite/gdb.threads/thread-specific-bp.exp
index dc822f37b7b..a300f3ef7ab 100644
--- a/gdb/testsuite/gdb.threads/thread-specific-bp.exp
+++ b/gdb/testsuite/gdb.threads/thread-specific-bp.exp
@@ -63,6 +63,10 @@ proc check_thread_specific_breakpoint {non_stop} {
 	return -1
     }
 
+    # Check that multiple uses of 'thread' keyword give an error.
+    gdb_test "break main thread $start_thre thread $main_thre" \
+	"You can specify only one thread\\."
+
     # Set a thread-specific breakpoint at "main".  This can't ever
     # be hit, but that's OK.
     gdb_breakpoint "main thread $start_thre"
diff --git a/gdb/testsuite/gdb.threads/watchthreads2.exp b/gdb/testsuite/gdb.threads/watchthreads2.exp
index 2c18b20257c..ef502507f9f 100644
--- a/gdb/testsuite/gdb.threads/watchthreads2.exp
+++ b/gdb/testsuite/gdb.threads/watchthreads2.exp
@@ -73,6 +73,9 @@ if { $nr_started == $NR_THREADS } {
     return -1
 }
 
+# Check that multiple uses of the 'thread' keyword will give an error.
+gdb_test "watch x thread 1 thread 2" "You can specify only one thread\\."
+
 # Watch X, it will be modified by all threads.
 # We want this watchpoint to be set *after* all threads are running.
 gdb_test "watch x" "Hardware watchpoint 3: x"
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCH 5/6] gdb: add inferior-specific breakpoints and watchpoints
  2022-11-28 11:25 [PATCH 0/6] Inferior specific breakpoints Andrew Burgess
                   ` (3 preceding siblings ...)
  2022-11-28 11:25 ` [PATCH 4/6] gdb: error if 'thread' or 'task' keywords are overused Andrew Burgess
@ 2022-11-28 11:25 ` Andrew Burgess
  2022-11-28 13:18   ` Eli Zaretskii
  2022-12-23 10:05   ` Aktemur, Tankut Baris
  2022-11-28 11:25 ` [PATCH 6/6] gdb: convert the 'start' breakpoint to use inferior keyword Andrew Burgess
                   ` (2 subsequent siblings)
  7 siblings, 2 replies; 54+ messages in thread
From: Andrew Burgess @ 2022-11-28 11:25 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

This commit extends the breakpoint mechanism to allow for inferior
specific breakpoints (and watchpoints).

As GDB gains better support for multiple connections, and so for
running multiple (possibly unrelated) inferiors, then it is not hard
to imagine that a user might wish to create breakpoints that apply to
any thread in a single inferior.  To achieve this currently, the user
would need to create a condition possibly making use of the $_inferior
convenience variable, which, though functional, isn't the most user
friendly.

This commit adds a new 'inferior' keyword that allows for the creation
of inferior specific breakpoints.

Like thread specific breakpoints, the inferior specific breakpoints
are automatically deleted once the inferior in question exits.
---
 gdb/NEWS                                      |   7 +
 gdb/breakpoint.c                              | 177 ++++++++++++++---
 gdb/breakpoint.h                              |  10 +-
 gdb/doc/gdb.texinfo                           |  74 ++++++-
 gdb/doc/python.texi                           |  27 ++-
 gdb/dummy-frame.c                             |   1 +
 gdb/elfread.c                                 |   5 +-
 gdb/guile/scm-breakpoint.c                    |   5 +
 gdb/inferior.h                                |  10 +
 gdb/infrun.c                                  |   2 +
 gdb/linespec.c                                |   4 +-
 gdb/python/py-breakpoint.c                    |  77 ++++++++
 gdb/testsuite/gdb.linespec/cpcompletion.exp   |   4 +-
 gdb/testsuite/gdb.linespec/explicit.exp       |   1 +
 .../gdb.multi/inferior-specific-bp-1.c        |  52 +++++
 .../gdb.multi/inferior-specific-bp-2.c        |  52 +++++
 .../gdb.multi/inferior-specific-bp.exp        | 183 ++++++++++++++++++
 gdb/testsuite/gdb.python/py-breakpoint.exp    |  43 ++++
 gdb/testsuite/lib/completion-support.exp      |   2 +-
 19 files changed, 688 insertions(+), 48 deletions(-)
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index 1f2233082ae..f3cb0f96f67 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -110,6 +110,13 @@
   instance of the keyword.  The 'thread' keyword already gave an error
   when used multiple times with the watch command.
 
+* Breakpoints can now be inferior-specific.  This is similar to the
+  existing thread-specific breakpoint support.  Breakpoint conditions
+  can include the 'inferior' keyword followed by an inferior id (as
+  displayed in the 'info inferiors' output).  It is invalid to use
+  both the 'inferior' and 'thread' keywords when creating a
+  breakpoint.
+
 * New commands
 
 maintenance set ignore-prologue-end-flag on|off
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index bf99297a250..307f4e7720f 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -96,7 +96,7 @@ static void create_breakpoints_sal (struct gdbarch *,
 				    gdb::unique_xmalloc_ptr<char>,
 				    gdb::unique_xmalloc_ptr<char>,
 				    enum bptype,
-				    enum bpdisp, int, int,
+				    enum bpdisp, int, int, int,
 				    int,
 				    int, int, int, unsigned);
 
@@ -1462,11 +1462,28 @@ breakpoint_set_thread (struct breakpoint *b, int thread)
 {
   int old_thread = b->thread;
 
+  gdb_assert (thread == -1 || b->inferior == -1);
+
   b->thread = thread;
   if (old_thread != thread)
     gdb::observers::breakpoint_modified.notify (b);
 }
 
+/* Set the inferior for breakpoint B to INFERIOR.  If INFERIOR is -1, make
+   the breakpoint work for any inferior.  */
+
+void
+breakpoint_set_inferior (struct breakpoint *b, int inferior)
+{
+  int old_inferior = b->inferior;
+
+  gdb_assert (inferior == -1 || b->thread == -1);
+
+  b->inferior = inferior;
+  if (old_inferior != inferior)
+    gdb::observers::breakpoint_modified.notify (b);
+}
+
 /* Set the task for this breakpoint.  If TASK is 0, make the
    breakpoint work for any task.  */
 
@@ -3151,6 +3168,12 @@ insert_breakpoint_locations (void)
 	  && !valid_global_thread_id (bl->owner->thread))
 	continue;
 
+      /* Or inferior specific breakpoints if the inferior no longer
+	 exists.  */
+      if (bl->owner->inferior != -1
+	  && !valid_global_inferior_id (bl->owner->inferior))
+	continue;
+
       switch_to_program_space_and_thread (bl->pspace);
 
       /* For targets that support global breakpoints, there's no need
@@ -3255,6 +3278,32 @@ Thread-specific breakpoint %d deleted - thread %s no longer in the thread list.\
     }
 }
 
+/* Called when inferior INF has exited.  Remove per-inferior breakpoints.  */
+
+static void
+remove_inferior_breakpoints (struct inferior *inf)
+{
+  for (breakpoint *b : all_breakpoints_safe ())
+    {
+      if (b->inferior == inf->num && user_breakpoint_p (b))
+	{
+	  /* Tell the user the breakpoint has been deleted.  But only for
+	     breakpoints that would not normally have been deleted at the
+	     next stop anyway.  */
+	  if (b->disposition != disp_del
+	      && b->disposition != disp_del_at_next_stop)
+	    gdb_printf (_("\
+Inferior-specific breakpoint %d deleted - inferior %d has exited.\n"),
+			b->number, inf->num);
+
+
+	  /* Hide it from the user and mark it for deletion.  */
+	  b->number = 0;
+	  b->disposition = disp_del_at_next_stop;
+       }
+    }
+}
+
 /* See breakpoint.h.  */
 
 void
@@ -5464,6 +5513,7 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread)
      evaluating the condition if this isn't the specified
      thread/task.  */
   if ((b->thread != -1 && b->thread != thread->global_num)
+      || (b->inferior != -1 && b->inferior != thread->inf->num)
       || (b->task != 0 && b->task != ada_get_task_number (thread)))
     {
       infrun_debug_printf ("incorrect thread or task, not stopping");
@@ -6481,6 +6531,11 @@ print_one_breakpoint_location (struct breakpoint *b,
 	  uiout->text (" task ");
 	  uiout->field_signed ("task", b->task);
 	}
+      else if (b->inferior != -1)
+	{
+	  uiout->text (" inferior ");
+	  uiout->field_signed ("inferior", b->inferior);
+	}
     }
 
   uiout->text ("\n");
@@ -6535,7 +6590,14 @@ print_one_breakpoint_location (struct breakpoint *b,
 	}
       uiout->text ("\n");
     }
-  
+
+  if (!part_of_multiple && b->inferior != -1)
+    {
+      uiout->text ("\tstop only in inferior ");
+      uiout->field_signed ("inferior", b->inferior);
+      uiout->text ("\n");
+    }
+
   if (!part_of_multiple)
     {
       if (b->hit_count)
@@ -7508,7 +7570,10 @@ delete_longjmp_breakpoint (int thread)
     if (b->type == bp_longjmp || b->type == bp_exception)
       {
 	if (b->thread == thread)
-	  delete_breakpoint (b);
+	  {
+	    gdb_assert (b->inferior == -1);
+	    delete_breakpoint (b);
+	  }
       }
 }
 
@@ -7519,7 +7584,10 @@ delete_longjmp_breakpoint_at_next_stop (int thread)
     if (b->type == bp_longjmp || b->type == bp_exception)
       {
 	if (b->thread == thread)
-	  b->disposition = disp_del_at_next_stop;
+	  {
+	    gdb_assert (b->inferior == -1);
+	    b->disposition = disp_del_at_next_stop;
+	  }
       }
 }
 
@@ -7572,6 +7640,7 @@ check_longjmp_breakpoint_for_call_dummy (struct thread_info *tp)
   ALL_BREAKPOINTS_SAFE (b, b_tmp)
     if (b->type == bp_longjmp_call_dummy && b->thread == tp->global_num)
       {
+	gdb_assert (b->inferior == -1);
 	struct breakpoint *dummy_b = b->related_breakpoint;
 
 	/* Find the bp_call_dummy breakpoint in the list of breakpoints
@@ -8404,7 +8473,8 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
 				  gdb::unique_xmalloc_ptr<char> cond_string_,
 				  gdb::unique_xmalloc_ptr<char> extra_string_,
 				  enum bpdisp disposition_,
-				  int thread_, int task_, int ignore_count_,
+				  int thread_, int task_, int inferior_,
+				  int ignore_count_,
 				  int from_tty,
 				  int enabled_, unsigned flags,
 				  int display_canonical_)
@@ -8430,6 +8500,11 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
 
   thread = thread_;
   task = task_;
+  inferior = inferior_;
+
+  /* A breakpoint can be thread specific, or inferior specific, but not
+     both.  This should be checked when the breakpoint condition is parsed.  */
+  gdb_assert (!(thread != -1 && inferior != -1));
 
   cond_string = std::move (cond_string_);
   extra_string = std::move (extra_string_);
@@ -8531,7 +8606,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
 		       gdb::unique_xmalloc_ptr<char> cond_string,
 		       gdb::unique_xmalloc_ptr<char> extra_string,
 		       enum bptype type, enum bpdisp disposition,
-		       int thread, int task, int ignore_count,
+		       int thread, int task, int inferior, int ignore_count,
 		       int from_tty,
 		       int enabled, int internal, unsigned flags,
 		       int display_canonical)
@@ -8545,7 +8620,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
 				std::move (cond_string),
 				std::move (extra_string),
 				disposition,
-				thread, task, ignore_count,
+				thread, task, inferior, ignore_count,
 				from_tty,
 				enabled, flags,
 				display_canonical);
@@ -8574,7 +8649,8 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
 			gdb::unique_xmalloc_ptr<char> cond_string,
 			gdb::unique_xmalloc_ptr<char> extra_string,
 			enum bptype type, enum bpdisp disposition,
-			int thread, int task, int ignore_count,
+			int thread, int task, int inferior,
+			int ignore_count,
 			int from_tty,
 			int enabled, int internal, unsigned flags)
 {
@@ -8598,7 +8674,7 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
 			     std::move (cond_string),
 			     std::move (extra_string),
 			     type, disposition,
-			     thread, task, ignore_count,
+			     thread, task, inferior, ignore_count,
 			     from_tty, enabled, internal, flags,
 			     canonical->special_display);
     }
@@ -8728,21 +8804,26 @@ check_fast_tracepoint_sals (struct gdbarch *gdbarch,
     }
 }
 
-/* Given TOK, a string specification of condition and thread, as
-   accepted by the 'break' command, extract the condition
-   string and thread number and set *COND_STRING and *THREAD.
-   PC identifies the context at which the condition should be parsed.
-   If no condition is found, *COND_STRING is set to NULL.
-   If no thread is found, *THREAD is set to -1.  */
+/* Given TOK, a string specification of condition and thread, as accepted
+   by the 'break' command, extract the condition string into *COND_STRING.
+   If no condition string is found then *COND_STRING is set to nullptr.
+
+   If the breakpoint specification has an associated thread, task, or
+   inferior, these are extracted into *THREAD, *TASK, and *INFERIOR
+   respectively, otherwise these arguments are set to -1 (for THREAD and
+   INFERIOR) or 0 (for TASK).
+
+   PC identifies the context at which the condition should be parsed.  */
 
 static void
 find_condition_and_thread (const char *tok, CORE_ADDR pc,
 			   gdb::unique_xmalloc_ptr<char> *cond_string,
-			   int *thread, int *task,
+			   int *thread, int *inferior, int *task,
 			   gdb::unique_xmalloc_ptr<char> *rest)
 {
   cond_string->reset ();
   *thread = -1;
+  *inferior = -1;
   *task = 0;
   rest->reset ();
   bool force = false;
@@ -8759,7 +8840,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
       if ((*tok == '"' || *tok == ',') && rest)
 	{
 	  rest->reset (savestring (tok, strlen (tok)));
-	  return;
+	  break;
 	}
 
       end_tok = skip_to_space (tok);
@@ -8803,6 +8884,18 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  *thread = thr->global_num;
 	  tok = tmptok;
 	}
+      else if (toklen >= 1 && strncmp (tok, "inferior", toklen) == 0)
+	{
+	  char *tmptok;
+
+	  tok = end_tok + 1;
+	  *inferior = strtol (tok, &tmptok, 0);
+	  if (tok == tmptok)
+	    error (_("Junk after inferior keyword."));
+	  if (!valid_global_inferior_id (*inferior))
+	    error (_("Unknown inferior number %d."), *inferior);
+	  tok = tmptok;
+	}
       else if (toklen >= 1 && strncmp (tok, "task", toklen) == 0)
 	{
 	  char *tmptok;
@@ -8821,11 +8914,15 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
       else if (rest)
 	{
 	  rest->reset (savestring (tok, strlen (tok)));
-	  return;
+	  break;
 	}
       else
 	error (_("Junk at end of arguments."));
     }
+
+  if (*thread != -1 && *inferior != -1)
+    error (_("Invalid use of both 'thread' and 'inferior' in "
+	     "breakpoint condition"));
 }
 
 /* Call 'find_condition_and_thread' for each sal in SALS until a parse
@@ -8837,7 +8934,7 @@ static void
 find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
 				    const char *input,
 				    gdb::unique_xmalloc_ptr<char> *cond_string,
-				    int *thread, int *task,
+				    int *thread, int *inferior, int *task,
 				    gdb::unique_xmalloc_ptr<char> *rest)
 {
   int num_failures = 0;
@@ -8845,6 +8942,7 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
     {
       gdb::unique_xmalloc_ptr<char> cond;
       int thread_id = 0;
+      int inferior_id = 0;
       int task_id = 0;
       gdb::unique_xmalloc_ptr<char> remaining;
 
@@ -8857,9 +8955,10 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
       try
 	{
 	  find_condition_and_thread (input, sal.pc, &cond, &thread_id,
-				     &task_id, &remaining);
+				     &inferior_id, &task_id, &remaining);
 	  *cond_string = std::move (cond);
 	  *thread = thread_id;
+	  *inferior = inferior_id;
 	  *task = task_id;
 	  *rest = std::move (remaining);
 	  break;
@@ -8961,6 +9060,7 @@ create_breakpoint (struct gdbarch *gdbarch,
   struct linespec_result canonical;
   bool pending = false;
   int task = 0;
+  int inferior = -1;
   int prev_bkpt_count = breakpoint_count;
 
   gdb_assert (ops != NULL);
@@ -9038,7 +9138,8 @@ create_breakpoint (struct gdbarch *gdbarch,
 	  const linespec_sals &lsal = canonical.lsals[0];
 
 	  find_condition_and_thread_for_sals (lsal.sals, extra_string,
-					      &cond, &thread, &task, &rest);
+					      &cond, &thread, &inferior,
+					      &task, &rest);
 	  cond_string_copy = std::move (cond);
 	  extra_string_copy = std::move (rest);
 	}
@@ -9088,7 +9189,7 @@ create_breakpoint (struct gdbarch *gdbarch,
 				   std::move (extra_string_copy),
 				   type_wanted,
 				   tempflag ? disp_del : disp_donttouch,
-				   thread, task, ignore_count,
+				   thread, task, inferior, ignore_count,
 				   from_tty, enabled, internal, flags);
     }
   else
@@ -10028,6 +10129,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
   const char *cond_end = NULL;
   enum bptype bp_type;
   int thread = -1;
+  int inferior = -1;
   /* Flag to indicate whether we are going to use masks for
      the hardware watchpoint.  */
   bool use_mask = false;
@@ -10068,6 +10170,10 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	  tok++;
 	  toklen = end_tok - tok + 1;
 
+	  if (thread != -1 && inferior != -1)
+	    error (_("Invalid use of both 'thread' and 'inferior' in "
+		     "watchpoint condition"));
+
 	  if (toklen == 6 && startswith (tok, "thread"))
 	    {
 	      struct thread_info *thr;
@@ -10081,10 +10187,8 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 
 	      /* Extract the thread ID from the next token.  */
 	      thr = parse_thread_id (value_start, &endp);
-
-	      /* Check if the user provided a valid thread ID.  */
-	      if (*endp != ' ' && *endp != '\t' && *endp != '\0')
-		invalid_thread_id_error (value_start);
+	      if (value_start == endp)
+		error (_("Junk after thread keyword."));
 
 	      thread = thr->global_num;
 	    }
@@ -10101,6 +10205,16 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	      if (!valid_task_id (task))
 		error (_("Unknown task %d."), task);
 	    }
+	  else if (toklen == 8 && startswith (tok, "inferior"))
+	    {
+	      char *tmp;
+
+	      inferior = strtol (value_start, &tmp, 0);
+	      if (tmp == value_start)
+		error (_("Junk after inferior keyword."));
+	      if (!valid_global_inferior_id (inferior))
+		error (_("Unknown inferior number %d."), inferior);
+	    }
 	  else if (toklen == 4 && startswith (tok, "mask"))
 	    {
 	      /* We've found a "mask" token, which means the user wants to
@@ -10271,6 +10385,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
     w.reset (new watchpoint (nullptr, bp_type));
 
   w->thread = thread;
+  w->inferior = inferior;
   w->task = task;
   w->disposition = disp_donttouch;
   w->pspace = current_program_space;
@@ -12182,7 +12297,8 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
 				      enum bptype type_wanted,
 				      enum bpdisp disposition,
 				      int thread,
-				      int task, int ignore_count,
+				      int task, int inferior,
+				      int ignore_count,
 				      int from_tty, int enabled,
 				      int internal, unsigned flags)
 {
@@ -12208,7 +12324,7 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
 			 std::move (cond_string),
 			 std::move (extra_string),
 			 disposition,
-			 thread, task, ignore_count,
+			 thread, task, inferior, ignore_count,
 			 from_tty, enabled, flags,
 			 canonical->special_display));
 
@@ -12841,10 +12957,11 @@ code_breakpoint::location_spec_to_sals (location_spec *locspec,
       if (condition_not_parsed && extra_string != NULL)
 	{
 	  gdb::unique_xmalloc_ptr<char> local_cond, local_extra;
-	  int local_thread, local_task;
+	  int local_thread, local_task, local_inferior;
 
 	  find_condition_and_thread_for_sals (sals, extra_string.get (),
 					      &local_cond, &local_thread,
+					      &local_inferior,
 					      &local_task, &local_extra);
 	  gdb_assert (cond_string == nullptr);
 	  if (local_cond != nullptr)
@@ -14930,4 +15047,6 @@ This is useful for formatted output in user-defined commands."));
 					   "breakpoint");
   gdb::observers::thread_exit.attach (remove_threaded_breakpoints,
 				      "breakpoint");
+  gdb::observers::inferior_exit.attach (remove_inferior_breakpoints,
+					"breakpoint");
 }
diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
index f7633d29cbf..7ee914ef1bd 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -584,7 +584,7 @@ struct breakpoint_ops
 				  struct linespec_result *,
 				  gdb::unique_xmalloc_ptr<char>,
 				  gdb::unique_xmalloc_ptr<char>,
-				  enum bptype, enum bpdisp, int, int,
+				  enum bptype, enum bpdisp, int, int, int,
 				  int, int, int, int, unsigned);
 };
 
@@ -801,6 +801,10 @@ struct breakpoint
      care.  */
   int thread = -1;
 
+  /* Inferior number for inferior-specific breakpoint, or -1 if this
+     breakpoint is for all inferiors.  */
+  int inferior = -1;
+
   /* Ada task number for task-specific breakpoint, or 0 if don't
      care.  */
   int task = 0;
@@ -856,7 +860,7 @@ struct code_breakpoint : public breakpoint
 		   gdb::unique_xmalloc_ptr<char> cond_string,
 		   gdb::unique_xmalloc_ptr<char> extra_string,
 		   enum bpdisp disposition,
-		   int thread, int task, int ignore_count,
+		   int thread, int task, int inferior, int ignore_count,
 		   int from_tty,
 		   int enabled, unsigned flags,
 		   int display_canonical);
@@ -1668,6 +1672,8 @@ extern void breakpoint_set_silent (struct breakpoint *b, int silent);
 
 extern void breakpoint_set_thread (struct breakpoint *b, int thread);
 
+extern void breakpoint_set_inferior (struct breakpoint *b, int inferior);
+
 extern void breakpoint_set_task (struct breakpoint *b, int task);
 
 /* Clear the "inserted" flag in all breakpoints.  */
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index bababf3c7ff..5cf6c43393f 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3500,6 +3500,58 @@
 space as a result of inferior 1 having executed a @code{vfork} call.
 @end table
 
+@menu
+* Inferior-Specific Breakpoints::	Controlling breakpoints
+@end menu
+
+@node Inferior-Specific Breakpoints
+@subsection Inferior-Specific Breakpoints
+
+When debugging multiple inferiors, you can choose whether to set
+breakpoints for all inferiors, or for a particular inferior.
+
+@table @code
+@cindex breakpoints and inferiors
+@cindex inferior breakpoints
+@kindex break @dots{} inferior @var{inferior-id}
+@item break @var{locspec} inferior @var{inferior-id}
+@itemx break @var{locspec} inferior @var{inferior-id} if @dots{}
+@var{locspec} specifies a code location or locations in your program.
+@xref{Location Specifications}, for details.
+
+Use the qualifier @samp{inferior @var{inferior-id}} with a breakpoint
+command to specify that you only want @value{GDBN} to stop when a
+particular inferior reaches this breakpoint.  The @var{inferior-id}
+specifier is one of the inferior identifiers assigned by @value{GDBN},
+shown in the first column of the @samp{info inferiors} output.
+
+If you do not specify @samp{inferior @var{inferior-id}} when you set a
+breakpoint, the breakpoint applies to @emph{all} inferiors of your
+program.
+
+You can use the @code{inferior} qualifier on conditional breakpoints as
+well; in this case, place @samp{inferior @var{inferior-id}} before or
+after the breakpoint condition, like this:
+
+@smallexample
+(@value{GDBP}) break frik.c:13 inferior 2 if bartab > lim
+@end smallexample
+@end table
+
+Inferior-specific breakpoints are automatically deleted when
+@value{GDBN} detects the corresponding inferior has exited.  For
+example:
+
+@smallexample
+(@value{GDBP}) c
+Inferior-specific breakpoint 3 deleted - inferior 2 has exited.
+@end smallexample
+
+A breakpoint can't be both inferior-specific and thread-specific
+(@pxref{Thread-Specific Breakpoints}) using both the @code{inferior}
+and @code{thread} keywords when creating a breakpoint will give an
+error.
+
 @node Threads
 @section Debugging Programs with Multiple Threads
 
@@ -4462,8 +4514,9 @@
 situation.
 
 It is also possible to insert a breakpoint that will stop the program
-only if a specific thread (@pxref{Thread-Specific Breakpoints})
-or a specific task (@pxref{Ada Tasks}) hits that breakpoint.
+only if a specific thread (@pxref{Thread-Specific Breakpoints}),
+specific inferior (@pxref{Inferior-Specific Breakpoints}), or a
+specific task (@pxref{Ada Tasks}) hits that breakpoint.
 
 @item break
 When called without any arguments, @code{break} sets a breakpoint at
@@ -4979,7 +5032,7 @@
 
 @table @code
 @kindex watch
-@item watch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}mask @var{maskvalue}@r{]} @r{[}task @var{task-id}@r{]}
+@item watch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}inferior @var{inferior-id}@r{]} @r{[}mask @var{maskvalue}@r{]} @r{[}task @var{task-id}@r{]}
 Set a watchpoint for an expression.  @value{GDBN} will break when the
 expression @var{expr} is written into by the program and its value
 changes.  The simplest (and the most popular) use of this command is
@@ -4996,8 +5049,10 @@
 that watchpoints restricted to a single thread in this way only work
 with Hardware Watchpoints.
 
-Similarly, if the @code{task} argument is given, then the watchpoint
-will be specific to the indicated Ada task (@pxref{Ada Tasks}).
+Similarly, if the @code{inferior} argument is given, then the
+watchpoint will trigger only for the specific inferior, or if the
+@code{task} argument is given, then the watchpoint will be specific to
+the indicated Ada task (@pxref{Ada Tasks}).
 
 Ordinarily a watchpoint respects the scope of variables in @var{expr}
 (see below).  The @code{-location} argument tells @value{GDBN} to
@@ -5026,12 +5081,12 @@
 @end smallexample
 
 @kindex rwatch
-@item rwatch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}mask @var{maskvalue}@r{]}
+@item rwatch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}inferior @var{inferior-id}@r{]} @r{[}mask @var{maskvalue}@r{]}
 Set a watchpoint that will break when the value of @var{expr} is read
 by the program.
 
 @kindex awatch
-@item awatch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}mask @var{maskvalue}@r{]}
+@item awatch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}inferior @var{inferior-id}@r{]} @r{[}mask @var{maskvalue}@r{]}
 Set a watchpoint that will break when @var{expr} is either read from
 or written into by the program.
 
@@ -7313,6 +7368,11 @@
 explictly asks for the thread list with the @code{info threads}
 command.
 
+A breakpoint can't be both thread-specific and inferior-specific
+(@pxref{Inferior-Specific Breakpoints}) using both the @code{thread}
+and @code{inferior} keywords when creating a breakpoint will give an
+error.
+
 @node Interrupted System Calls
 @subsection Interrupted System Calls 
 
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 9cbb2f9f57d..396d0f7f7af 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -3281,7 +3281,9 @@
 A @code{gdb.Inferior} object has the following attributes:
 
 @defvar Inferior.num
-ID of inferior, as assigned by GDB.
+ID of inferior, as assigned by GDB.  You can use this to
+make Python breakpoints inferior-specific, for example
+(@pxref{python_breakpoint_inferior,,The Breakpoint.inferior attribute}).
 @end defvar
 
 @anchor{gdbpy_inferior_connection}
@@ -6077,9 +6079,26 @@
 
 @anchor{python_breakpoint_thread}
 @defvar Breakpoint.thread
-If the breakpoint is thread-specific, this attribute holds the
-thread's global id.  If the breakpoint is not thread-specific, this
-attribute is @code{None}.  This attribute is writable.
+If the breakpoint is thread-specific (@pxref{Thread-Specific
+Breakpoints}), this attribute holds the thread's global id.  If the
+breakpoint is not thread-specific, this attribute is @code{None}.
+This attribute is writable.
+
+Only one of @code{Breakpoint.thread} or @code{Breakpoint.inferior} can
+be set to a valid id at any time, that is, a breakpoint can be thread
+specific, or inferior specific, but not both.
+@end defvar
+
+@anchor{python_breakpoint_inferior}
+@defvar Breakpoint.inferior
+If the breakpoint is inferior-specific (@pxref{Inferior-Specific
+Breakpoints}), this attribute holds the inferior's id.  If the
+breakpoint is not inferior-specific, this attribute is @code{None}.
+This attribute is writable.
+
+Only one of @code{Breakpoint.thread} or @code{Breakpoint.inferior} can
+be set to a valid id at any time, that is, a breakpoint can be thread
+specific, or inferior specific, but not both.
 @end defvar
 
 @defvar Breakpoint.task
diff --git a/gdb/dummy-frame.c b/gdb/dummy-frame.c
index 42c4bf23431..75ebf940a0c 100644
--- a/gdb/dummy-frame.c
+++ b/gdb/dummy-frame.c
@@ -132,6 +132,7 @@ pop_dummy_frame_bpt (struct breakpoint *b, struct dummy_frame *dummy)
   if (b->thread == dummy->id.thread->global_num
       && b->disposition == disp_del && b->frame_id == dummy->id.id)
     {
+      gdb_assert (b->inferior == -1);
       while (b->related_breakpoint != b)
 	delete_breakpoint (b->related_breakpoint);
 
diff --git a/gdb/elfread.c b/gdb/elfread.c
index 64aeb239670..ed3cad0cd02 100644
--- a/gdb/elfread.c
+++ b/gdb/elfread.c
@@ -968,7 +968,10 @@ elf_gnu_ifunc_resolver_stop (code_breakpoint *b)
       if (b_return->thread == thread_id
 	  && b_return->loc->requested_address == prev_pc
 	  && b_return->frame_id == prev_frame_id)
-	break;
+	{
+	  gdb_assert (b_return->inferior == -1);
+	  break;
+	}
     }
 
   if (b_return == b)
diff --git a/gdb/guile/scm-breakpoint.c b/gdb/guile/scm-breakpoint.c
index a7799416c50..90b66a7af86 100644
--- a/gdb/guile/scm-breakpoint.c
+++ b/gdb/guile/scm-breakpoint.c
@@ -779,6 +779,11 @@ gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue)
   else
     SCM_ASSERT_TYPE (0, newvalue, SCM_ARG2, FUNC_NAME, _("integer or #f"));
 
+  if (bp_smob->bp->inferior != -1)
+    scm_misc_error (FUNC_NAME,
+		    _("Cannot have both thread and inferior conditions "
+		      "on a breakpoint"), SCM_EOL);
+
   breakpoint_set_thread (bp_smob->bp, id);
 
   return SCM_UNSPECIFIED;
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 69525a2e053..3b25403d83c 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -746,4 +746,14 @@ extern void print_selected_inferior (struct ui_out *uiout);
 extern void switch_to_inferior_and_push_target
   (inferior *new_inf, bool no_connection, inferior *org_inf);
 
+/* Return true if ID is a valid global inferior number.  */
+
+inline bool valid_global_inferior_id (int id)
+{
+  for (inferior *inf : all_inferiors ())
+    if (inf->num == id)
+      return true;
+  return false;
+}
+
 #endif /* !defined (INFERIOR_H) */
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 96346e1f25b..0650b3c1528 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -8039,6 +8039,7 @@ insert_exception_resume_breakpoint (struct thread_info *tp,
 	  frame = nullptr;
 
 	  bp->thread = tp->global_num;
+	  bp->inferior = -1;
 	  inferior_thread ()->control.exception_resume_breakpoint = bp;
 	}
     }
@@ -8072,6 +8073,7 @@ insert_exception_resume_from_probe (struct thread_info *tp,
   bp = set_momentary_breakpoint_at_pc (get_frame_arch (frame),
 				       handler, bp_exception_resume).release ();
   bp->thread = tp->global_num;
+  bp->inferior = -1;
   inferior_thread ()->control.exception_resume_breakpoint = bp;
 }
 
diff --git a/gdb/linespec.c b/gdb/linespec.c
index 3db35998f7e..b01423107f9 100644
--- a/gdb/linespec.c
+++ b/gdb/linespec.c
@@ -254,9 +254,9 @@ enum linespec_token_type
 
 /* List of keywords.  This is NULL-terminated so that it can be used
    as enum completer.  */
-const char * const linespec_keywords[] = { "if", "thread", "task", "-force-condition", NULL };
+const char * const linespec_keywords[] = { "if", "thread", "task", "inferior", "-force-condition", NULL };
 #define IF_KEYWORD_INDEX 0
-#define FORCE_KEYWORD_INDEX 3
+#define FORCE_KEYWORD_INDEX 4
 
 /* A token of the linespec lexer  */
 
diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
index 7a757432948..31d447a468f 100644
--- a/gdb/python/py-breakpoint.c
+++ b/gdb/python/py-breakpoint.c
@@ -280,11 +280,69 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure)
       return -1;
     }
 
+  if (self_bp->bp->inferior != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both thread and inferior conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
   breakpoint_set_thread (self_bp->bp, id);
 
   return 0;
 }
 
+/* Python function to set the inferior of a breakpoint.  */
+
+static int
+bppy_set_inferior (PyObject *self, PyObject *newvalue, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+  long id;
+
+  BPPY_SET_REQUIRE_VALID (self_bp);
+
+  if (newvalue == NULL)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("Cannot delete `inferior' attribute."));
+      return -1;
+    }
+  else if (PyLong_Check (newvalue))
+    {
+      if (!gdb_py_int_as_long (newvalue, &id))
+	return -1;
+
+      if (!valid_global_inferior_id (id))
+	{
+	  PyErr_SetString (PyExc_RuntimeError,
+			   _("Invalid inferior ID."));
+	  return -1;
+	}
+    }
+  else if (newvalue == Py_None)
+    id = -1;
+  else
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("The value of `inferior' must be an integer or None."));
+      return -1;
+    }
+
+  if (self_bp->bp->thread != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both thread and inferior conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
+  breakpoint_set_inferior (self_bp->bp, id);
+
+  return 0;
+}
+
 /* Python function to set the (Ada) task of a breakpoint.  */
 static int
 bppy_set_task (PyObject *self, PyObject *newvalue, void *closure)
@@ -689,6 +747,20 @@ bppy_get_thread (PyObject *self, void *closure)
   return gdb_py_object_from_longest (self_bp->bp->thread).release ();
 }
 
+/* Python function to get the breakpoint's inferior ID.  */
+static PyObject *
+bppy_get_inferior (PyObject *self, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+
+  BPPY_REQUIRE_VALID (self_bp);
+
+  if (self_bp->bp->inferior == -1)
+    Py_RETURN_NONE;
+
+  return gdb_py_object_from_longest (self_bp->bp->inferior).release ();
+}
+
 /* Python function to get the breakpoint's task ID (in Ada).  */
 static PyObject *
 bppy_get_task (PyObject *self, void *closure)
@@ -1335,6 +1407,11 @@ static gdb_PyGetSetDef breakpoint_object_getset[] = {
 If the value is a thread ID (integer), then this is a thread-specific breakpoint.\n\
 If the value is None, then this breakpoint is not thread-specific.\n\
 No other type of value can be used.", NULL },
+  { "inferior", bppy_get_inferior, bppy_set_inferior,
+    "Inferior ID for the breakpoint.\n\
+If the value is an inferior ID (integer), then this is an inferior-specific\n\
+breakpoint.  If the value is None, then this breakpoint is not\n\
+inferior-specific.  No other type of value can be used.", NULL },
   { "task", bppy_get_task, bppy_set_task,
     "Thread ID for the breakpoint.\n\
 If the value is a task ID (integer), then this is an Ada task-specific breakpoint.\n\
diff --git a/gdb/testsuite/gdb.linespec/cpcompletion.exp b/gdb/testsuite/gdb.linespec/cpcompletion.exp
index 0383469828c..650d0e75ea3 100644
--- a/gdb/testsuite/gdb.linespec/cpcompletion.exp
+++ b/gdb/testsuite/gdb.linespec/cpcompletion.exp
@@ -1259,8 +1259,8 @@ proc_with_prefix function-labels {} {
 }
 
 # Test that completion after a function name offers keyword
-# (if/task/thread/-force-condition) matches in linespec mode, and also
-# the explicit location options in explicit locations mode.
+# (if/inferior/task/thread/-force-condition) matches in linespec mode,
+# and also the explicit location options in explicit locations mode.
 
 proc_with_prefix keywords-after-function {} {
     set explicit_list \
diff --git a/gdb/testsuite/gdb.linespec/explicit.exp b/gdb/testsuite/gdb.linespec/explicit.exp
index 9064c137e13..2e4c42f3aba 100644
--- a/gdb/testsuite/gdb.linespec/explicit.exp
+++ b/gdb/testsuite/gdb.linespec/explicit.exp
@@ -412,6 +412,7 @@ namespace eval $testfile {
 	    "-qualified"
 	    "-source"
 	    "if"
+	    "inferior"
 	    "task"
 	    "thread"
 	}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
new file mode 100644
index 00000000000..26cb01911d2
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
@@ -0,0 +1,52 @@
+/* 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/>.  */
+
+volatile int global_var = 0;
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+static inline void __attribute__((__always_inline__))
+foo (void)
+{
+  int i;
+
+  for (i = 0; i < 10; ++i)
+    global_var = 0;
+}
+
+static void
+bar (void)
+{
+  global_var = 0;
+
+  foo ();
+}
+
+
+int
+main (void)
+{
+  global_var = 0;
+  foo ();
+  bar ();
+  stop_breakpt ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
new file mode 100644
index 00000000000..3ddb1a17446
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
@@ -0,0 +1,52 @@
+/* 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/>.  */
+
+static int bar (void);
+static int baz (void);
+static int foo (void);
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+int
+main (void)
+{
+  int ret = baz ();
+  stop_breakpt ();
+  return ret;
+}
+
+static int
+bar (void)
+{
+  return baz ();
+}
+
+static int
+foo (void)
+{
+  return 0;
+}
+
+static int
+baz (void)
+{
+  return foo ();
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
new file mode 100644
index 00000000000..5d65f19b88c
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
@@ -0,0 +1,183 @@
+# 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/>.
+
+# Test inferior-specific breakpoints.
+
+standard_testfile -1.c -2.c
+
+if { [use_gdb_stub] } {
+    return
+}
+
+set srcfile1 ${srcfile}
+set binfile1 ${binfile}-1
+set binfile2 ${binfile}-2
+
+if { [build_executable ${testfile}.exp ${binfile1} "${srcfile1}"] != 0 } {
+    return -1
+}
+
+if { [build_executable ${testfile}.exp ${binfile2} "${srcfile2}"] != 0 } {
+    return -1
+}
+
+# Start the first inferior.
+clean_restart ${binfile1}
+if {![runto_main]} {
+    return
+}
+
+# Add a second inferior, and start this one too.
+gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2"
+gdb_load $binfile2
+if {![runto_main]} {
+    return
+}
+
+# Try to create a breakpoint using both the 'inferior' and 'thread' keywords,
+# this should fail.  Try with the keywords in both orders just in case the
+# parser has a bug.
+gdb_test "break foo thread 1.1 inferior 1" \
+    "Invalid use of both 'thread' and 'inferior' in breakpoint condition"
+gdb_test "break foo inferior 1 thread 1.1" \
+    "Invalid use of both 'thread' and 'inferior' in breakpoint condition"
+
+# While w're here, check that we can't create a watchpoint with both thread
+# and inferior keywords.  As above, test with keywords in both orders.
+foreach type {watch rwatch awatch} {
+    gdb_test "$type global_var thread 1.1 inferior 1" \
+	"Invalid use of both 'thread' and 'inferior' in watchpoint condition"
+    gdb_test "$type global_var inferior 1 thread 1.1" \
+	"Invalid use of both 'thread' and 'inferior' in watchpoint condition"
+}
+
+# Clear out any other breakpoints.
+gdb_test "with confirm off -- delete breakpoints"
+
+# Create an inferior specific breakpoint.
+gdb_test "break foo inferior 1" \
+    "Breakpoint $decimal at $hex: foo\\. \\(3 locations\\)"
+
+set saw_header false
+set location_count 0
+set saw_inf_cond false
+gdb_test_multiple "info breakpoints" "" {
+    -re "^info breakpoints\r\n" {
+	exp_continue
+    }
+
+    -re "^Num\\s+\[^\r\n\]+\r\n" {
+	exp_continue
+    }
+
+    -re "^$decimal\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
+	set saw_header true
+	exp_continue
+    }
+
+    -re "^\\s+stop only in inferior 1\r\n" {
+	set saw_inf_cond true
+	exp_continue
+    }
+
+    -re "^$decimal\\.\[123\]\\s+y\\s+ $hex in foo at \[^\r\n\]+ inf \[12\] inferior 1\r\n" {
+	incr location_count
+	exp_continue
+    }
+
+    -re "^$gdb_prompt $" {
+	with_test_prefix $gdb_test_name {
+	    gdb_assert { $saw_header }
+	    gdb_assert { $location_count == 3 }
+	    gdb_assert { $saw_inf_cond }
+	}
+    }
+}
+
+# Create a multi-inferior breakpoint to stop at.
+gdb_test "break stop_breakpt"
+
+# Now resume inferior 2, this should reach 'stop_breakpt'.
+gdb_test "continue" "hit Breakpoint $decimal\.$decimal, stop_breakpt \\(\\) .*" \
+    "continue in inferior 2"
+
+# Switch to inferior 1, and try there.
+gdb_test "inferior 1" ".*" \
+    "select inferior 1 to check the inferior-specific b/p works"
+gdb_test "continue " "hit Breakpoint $decimal\.$decimal, foo \\(\\) .*" \
+    "first continue in inferior 1"
+
+# Now back to inferior 2, let the inferior exit, the inferior-specific
+# breakpoint should not be deleted.
+gdb_test "inferior 2" ".*" \
+    "switch back to allow inferior 2 to exit"
+gdb_test "continue" "Inferior 2 \[^\r\n\]+ exited normally.*" \
+    "allow inferior 2 to exit"
+
+gdb_test "inferior 1" ".*" \
+    "select inferior 1 to check inferior-specific b/p still works"
+gdb_test "continue " "hit Breakpoint $decimal\.$decimal, foo \\(\\) .*" \
+    "second continue in inferior 1"
+gdb_test "continue " "hit Breakpoint $decimal\.$decimal, stop_breakpt \\(\\) .*" \
+    "third continue in inferior 1"
+
+# Now allow inferior 1 to exit, the inferior specific breakpoint should be
+# deleted.
+gdb_test "continue" \
+    [multi_line \
+	 "\\\[Inferior 1 \[^\r\n\]+ exited normally\\\]" \
+	 "Inferior-specific breakpoint $decimal deleted - inferior 1 has exited\\."]
+
+set saw_header false
+set location_count 0
+set saw_inf_cond false
+gdb_test_multiple "info breakpoints" "info breakpoint after inferior 1 exited" {
+    -re "^info breakpoints\r\n" {
+	exp_continue
+    }
+
+    -re "^Num\\s+\[^\r\n\]+\r\n" {
+	exp_continue
+    }
+
+    -re "^$decimal\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
+	set saw_header true
+	exp_continue
+    }
+
+    -re "^\\s+stop only in inferior 1\r\n" {
+	# This should not happen, this breakpoint should have been deleted.
+	set saw_inf_cond true
+	exp_continue
+    }
+
+    -re "^\\s+breakpoint already hit 2 times\r\n" {
+	exp_continue
+    }
+
+    -re "^$decimal\\.\[12\]\\s+y\\s+ $hex in stop_breakpt at \[^\r\n\]+ inf \[12\]\r\n" {
+	incr location_count
+	exp_continue
+    }
+
+    -re "^$gdb_prompt $" {
+	with_test_prefix $gdb_test_name {
+	    gdb_assert { $saw_header }
+	    gdb_assert { $location_count == 2 }
+	    gdb_assert { !$saw_inf_cond }
+	}
+    }
+}
diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp
index c3215b13d5c..8906dea6655 100644
--- a/gdb/testsuite/gdb.python/py-breakpoint.exp
+++ b/gdb/testsuite/gdb.python/py-breakpoint.exp
@@ -112,6 +112,8 @@ proc_with_prefix test_bkpt_basic { } {
 	"Get Breakpoint List" 0
     gdb_test "python print (blist\[1\].thread)" \
 	"None" "Check breakpoint thread"
+    gdb_test "python print (blist\[1\].inferior)" \
+	"None" "Check breakpoint inferior"
     gdb_test "python print (blist\[1\].type == gdb.BP_BREAKPOINT)" \
 	"True" "Check breakpoint type"
     gdb_test "python print (blist\[0\].number)" \
@@ -214,6 +216,46 @@ proc_with_prefix test_bkpt_cond_and_cmds { } {
 	"check number of lines in commands"
 }
 
+# Test breakpoint thread and inferior attributes.
+proc_with_prefix test_bkpt_thread_and_inferior { } {
+    global srcfile testfile hex decimal
+
+    # Start with a fresh gdb.
+    clean_restart ${testfile}
+
+    if ![runto_main] then {
+	return 0
+    }
+
+    with_test_prefix "thread" {
+	delete_breakpoints
+	gdb_test "break multiply thread 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "1"
+	gdb_test "python print(bp.inferior)" "None"
+	gdb_test "python bp.inferior = 1" \
+	    "RuntimeError: Cannot have both thread and inferior conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.thread = None"
+	gdb_test_no_output "python bp.inferior = 1" \
+	    "set the inferior now the thread has been cleared"
+	gdb_test "info breakpoints" "stop only in inferior 1\r\n.*"
+    }
+
+    with_test_prefix "inferior" {
+	delete_breakpoints
+	gdb_test "break multiply inferior 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "None"
+	gdb_test "python print(bp.inferior)" "1"
+	gdb_test "python bp.thread = 1" \
+	    "RuntimeError: Cannot have both thread and inferior conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.inferior = None"
+	gdb_test_no_output "python bp.thread = 1" \
+	    "set the thread now the inferior has been cleared"
+	gdb_test "info breakpoints" "stop only in thread 1\r\n.*"
+    }
+}
+
 proc_with_prefix test_bkpt_invisible { } {
     global srcfile testfile hex decimal
 
@@ -849,6 +891,7 @@ proc_with_prefix test_bkpt_auto_disable { } {
 test_bkpt_basic
 test_bkpt_deletion
 test_bkpt_cond_and_cmds
+test_bkpt_thread_and_inferior
 test_bkpt_invisible
 test_hardware_breakpoints
 test_catchpoints
diff --git a/gdb/testsuite/lib/completion-support.exp b/gdb/testsuite/lib/completion-support.exp
index 0c03d0f035e..135649ed4d9 100644
--- a/gdb/testsuite/lib/completion-support.exp
+++ b/gdb/testsuite/lib/completion-support.exp
@@ -27,7 +27,7 @@ namespace eval completion {
     # List of all quote chars, including no-quote at all.
     variable maybe_quoted_list {"" "'" "\""}
 
-    variable keyword_list {"-force-condition" "if" "task" "thread"}
+    variable keyword_list {"-force-condition" "if" "inferior" "task" "thread"}
 
     variable explicit_opts_list \
 	{"-function" "-label" "-line" "-qualified" "-source"}
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCH 6/6] gdb: convert the 'start' breakpoint to use inferior keyword
  2022-11-28 11:25 [PATCH 0/6] Inferior specific breakpoints Andrew Burgess
                   ` (4 preceding siblings ...)
  2022-11-28 11:25 ` [PATCH 5/6] gdb: add inferior-specific breakpoints and watchpoints Andrew Burgess
@ 2022-11-28 11:25 ` Andrew Burgess
  2022-12-23 10:17   ` Aktemur, Tankut Baris
  2022-12-23 10:55 ` [PATCH 0/6] Inferior specific breakpoints Aktemur, Tankut Baris
  2023-01-20  9:46 ` [PATCHv2 " Andrew Burgess
  7 siblings, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2022-11-28 11:25 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Now that we have support for inferior specific breakpoints, the
breakpoint created for the 'start' command can make use of this
keyword.

In most cases the observed functionality should be unchanged from a
user's point of view, though the code in GDB is a little cleaner now,
we no longer need to change the expression used based on the
language.

I do wonder if the current mechanism could run into problems if we had
different inferiors of different languages.  For example, if we had an
Ada inferior and a C inferior and followed a process a little like
this:

  1. Start C inferior, the inferior stops before main for some reason,

  2. Start the Ada inferior, this runs to main,

  3. I think when we hit main, the condition for both breakpoints will
  be evaluated, this will include evaluating the expression for the C
  'start' breakpoint, which uses '==' and is, I guess, not valid for
  Ada.

I haven't tried to create a testcase for this situation, but it's a
possibly interesting edge case.

One place where the observed behaviour is different, is that inferior
breakpoints, like thread breakpoints, will be auto-deleted when the
contained inferior exits.

As a result, if an inferior exits before hitting the start breakpoint,
then the 'start' breakpoint will be auto-deleted.

I have got a test that covers this situation.
---
 gdb/infcmd.c                                  | 10 +---
 .../gdb.base/start-inferior-specific-1.c      | 32 +++++++++++
 .../gdb.base/start-inferior-specific-2.c      | 22 ++++++++
 .../gdb.base/start-inferior-specific.exp      | 55 +++++++++++++++++++
 4 files changed, 112 insertions(+), 7 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/start-inferior-specific-1.c
 create mode 100644 gdb/testsuite/gdb.base/start-inferior-specific-2.c
 create mode 100644 gdb/testsuite/gdb.base/start-inferior-specific.exp

diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index f7bce0d0399..7aa9a959bdb 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -429,13 +429,9 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
 	 have proper inferior-specific breakpoint support, in the breakpoint
 	 machinery.  We could then avoid inserting a breakpoint in the program
 	 spaces unrelated to this inferior.  */
-      const char *op
-	= ((current_language->la_language == language_ada
-	    || current_language->la_language == language_pascal
-	    || current_language->la_language == language_m2) ? "=" : "==");
-      std::string arg = string_printf
-	("-qualified %s if $_inferior %s %d", main_name (), op,
-	 current_inferior ()->num);
+      std::string arg = string_printf ("-qualified %s inferior %d",
+				       main_name (),
+				       current_inferior ()->num);
       tbreak_command (arg.c_str (), 0);
     }
 
diff --git a/gdb/testsuite/gdb.base/start-inferior-specific-1.c b/gdb/testsuite/gdb.base/start-inferior-specific-1.c
new file mode 100644
index 00000000000..1717a82b75d
--- /dev/null
+++ b/gdb/testsuite/gdb.base/start-inferior-specific-1.c
@@ -0,0 +1,32 @@
+/* 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 <unistd.h>
+#include <stdlib.h>
+
+__attribute__((constructor))
+static void
+ctor (void)
+{
+  exit (1);
+}
+
+int
+main ()
+{
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/start-inferior-specific-2.c b/gdb/testsuite/gdb.base/start-inferior-specific-2.c
new file mode 100644
index 00000000000..b69e218962a
--- /dev/null
+++ b/gdb/testsuite/gdb.base/start-inferior-specific-2.c
@@ -0,0 +1,22 @@
+/* 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/>.  */
+
+int
+main ()
+{
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/start-inferior-specific.exp b/gdb/testsuite/gdb.base/start-inferior-specific.exp
new file mode 100644
index 00000000000..50e4e109116
--- /dev/null
+++ b/gdb/testsuite/gdb.base/start-inferior-specific.exp
@@ -0,0 +1,55 @@
+# 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/>.
+
+# Check that a breakpoint created for the 'start' command, will be
+# silently deleted if the inferior being started exits before reaching
+# main.
+
+standard_testfile -1.c -2.c
+
+if {[use_gdb_stub]} {
+    return
+}
+
+set srcfile1 ${srcfile}
+
+set binfile1 ${binfile}-1
+set binfile2 ${binfile}-2
+
+if {[build_executable ${testfile}.exp ${binfile1} "${srcfile1}"] != 0} {
+    return -1
+}
+
+if {[build_executable ${testfile}.exp ${binfile2} "${srcfile2}"] != 0} {
+    return -1
+}
+
+clean_restart ${binfile1}
+
+# Start the first inferior, this will exit before hitting the 'start'
+# breakpoint in main.
+gdb_test "start" \
+    [multi_line \
+	 "Temporary breakpoint $decimal at \[^\r\n\]+" \
+	 "Starting program: \[^\r\n\]+" \
+	 "\\\[Inferior $decimal \[^\r\n\]+ exited with code 01\\\]"]
+
+# Now load a different binary and run it.  This inferior should run
+# all the way to completion without hitting a breakpoint in main.
+gdb_load ${binfile2}
+gdb_test "run" \
+    [multi_line \
+	 "Starting program: \[^\r\n\]+" \
+	 "\\\[Inferior $decimal \[^\r\n\]+ exited normally\\\]"]
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH 4/6] gdb: error if 'thread' or 'task' keywords are overused
  2022-11-28 11:25 ` [PATCH 4/6] gdb: error if 'thread' or 'task' keywords are overused Andrew Burgess
@ 2022-11-28 13:10   ` Eli Zaretskii
  0 siblings, 0 replies; 54+ messages in thread
From: Eli Zaretskii @ 2022-11-28 13:10 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches

> Cc: Andrew Burgess <aburgess@redhat.com>
> Date: Mon, 28 Nov 2022 11:25:36 +0000
> From: Andrew Burgess via Gdb-patches <gdb-patches@sourceware.org>
> 
> +* For the break command, multiple uses of the 'thread' or 'task'
> +  keywords will now given an error instead of just using the thread or
                       ^^^^^
A typo.

> +  task id from the last instance of the keyword.
> +
> +* For the watch command, multiple uses of the 'task' keyword will now
> +  given an error instead of just using the task id from the last
     ^^^^^
And another one.

Otherwise, OK for the NEWS part; thanks.

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCH 5/6] gdb: add inferior-specific breakpoints and watchpoints
  2022-11-28 11:25 ` [PATCH 5/6] gdb: add inferior-specific breakpoints and watchpoints Andrew Burgess
@ 2022-11-28 13:18   ` Eli Zaretskii
  2022-12-23 10:05   ` Aktemur, Tankut Baris
  1 sibling, 0 replies; 54+ messages in thread
From: Eli Zaretskii @ 2022-11-28 13:18 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches

> Cc: Andrew Burgess <aburgess@redhat.com>
> Date: Mon, 28 Nov 2022 11:25:37 +0000
> From: Andrew Burgess via Gdb-patches <gdb-patches@sourceware.org>
> 
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 1f2233082ae..f3cb0f96f67 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -110,6 +110,13 @@
>    instance of the keyword.  The 'thread' keyword already gave an error
>    when used multiple times with the watch command.
>  
> +* Breakpoints can now be inferior-specific.  This is similar to the
> +  existing thread-specific breakpoint support.  Breakpoint conditions
> +  can include the 'inferior' keyword followed by an inferior id (as
> +  displayed in the 'info inferiors' output).  It is invalid to use
> +  both the 'inferior' and 'thread' keywords when creating a
> +  breakpoint.
> +

This part is OK.

> +@node Inferior-Specific Breakpoints
> +@subsection Inferior-Specific Breakpoints
> +
> +When debugging multiple inferiors, you can choose whether to set
> +breakpoints for all inferiors, or for a particular inferior.
> +
> +@table @code
> +@cindex breakpoints and inferiors
> +@cindex inferior breakpoints

The last one should be "@cindex inferior-specific breakpoints", since
"inferior breakpoints" can be interpreted in a very confusing way.

> +Inferior-specific breakpoints are automatically deleted when
> +@value{GDBN} detects the corresponding inferior has exited.  For

"detects that the corresponding inferior has exited."  The "that" part is
missing.

> +A breakpoint can't be both thread-specific and inferior-specific
> +(@pxref{Inferior-Specific Breakpoints}) using both the @code{thread}
                                         ^^
Please add a semi-colon there.

> --- a/gdb/doc/python.texi
> +++ b/gdb/doc/python.texi
> @@ -3281,7 +3281,9 @@
>  A @code{gdb.Inferior} object has the following attributes:
>  
>  @defvar Inferior.num
> -ID of inferior, as assigned by GDB.
> +ID of inferior, as assigned by GDB.  You can use this to
                                  ^^^
"@value{GDBN}"

>  @anchor{python_breakpoint_thread}
>  @defvar Breakpoint.thread
> -If the breakpoint is thread-specific, this attribute holds the
> -thread's global id.  If the breakpoint is not thread-specific, this
> -attribute is @code{None}.  This attribute is writable.
> +If the breakpoint is thread-specific (@pxref{Thread-Specific
> +Breakpoints}), this attribute holds the thread's global id.  If the
> +breakpoint is not thread-specific, this attribute is @code{None}.
> +This attribute is writable.
> +
> +Only one of @code{Breakpoint.thread} or @code{Breakpoint.inferior} can
> +be set to a valid id at any time, that is, a breakpoint can be thread
> +specific, or inferior specific, but not both.
> +@end defvar
> +
> +@anchor{python_breakpoint_inferior}
> +@defvar Breakpoint.inferior
> +If the breakpoint is inferior-specific (@pxref{Inferior-Specific
> +Breakpoints}), this attribute holds the inferior's id.  If the
> +breakpoint is not inferior-specific, this attribute is @code{None}.
> +This attribute is writable.
> +
> +Only one of @code{Breakpoint.thread} or @code{Breakpoint.inferior} can
> +be set to a valid id at any time, that is, a breakpoint can be thread
> +specific, or inferior specific, but not both.

Please only keep the second paragraph of the two that start with "Only one".
It is redundant to have two identical paragraphs so close to one another.

The documentation part is OK with these nits fixed.

Thanks.

^ permalink raw reply	[flat|nested] 54+ messages in thread

* RE: [PATCH 3/6] gdb: fix display of thread condition for multi-location breakpoints
  2022-11-28 11:25 ` [PATCH 3/6] gdb: fix display of thread condition for multi-location breakpoints Andrew Burgess
@ 2022-12-23  8:37   ` Aktemur, Tankut Baris
  0 siblings, 0 replies; 54+ messages in thread
From: Aktemur, Tankut Baris @ 2022-12-23  8:37 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On Monday, November 28, 2022 12:26 PM, Andrew Burgess wrote:
> If a breakpoint with multiple locations has a thread condition, then
> the 'info breakpoints' output is a little messed up, here's an example
> of the current output:
> 
>   (gdb) break foo thread 1
>   Breakpoint 2 at 0x401114: foo. (3 locations)
>   (gdb) break bar thread 1
>   Breakpoint 3 at 0x40110a: file /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c, line
> 32.
>   (gdb) info breakpoints
>   Num     Type           Disp Enb Address            What
>   2       breakpoint     keep y   <MULTIPLE>          thread 1
>           stop only in thread 1
>   2.1                         y   0x0000000000401114 in foo at
> /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>   2.2                         y   0x0000000000401146 in foo at
> /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>   2.3                         y   0x0000000000401168 in foo at
> /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>   3       breakpoint     keep y   0x000000000040110a in bar at
> /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32 thread 1
>           stop only in thread 1
> 
> Notice that, at the end of the location for breakpoint 3, the 'thread
> 1' condition is printed.
> 
> In contrast, for breakpoint 2, the 'thread 1' appears randomly, in the
> "What" column, though slightly offset.
> 
> I believe this is a bug in GDB, in the function
> print_one_breakpoint_location, due to checking the local variable
> part_of_multiple, instead of header_of_multiple.
> 
> If I fix this oversight, then the output is now:
> 
>   (gdb) break foo thread 1
>   Breakpoint 2 at 0x401114: foo. (3 locations)
>   (gdb) break bar thread 1
>   Breakpoint 3 at 0x40110a: file /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c, line
> 32.
>   (gdb) info breakpoints
>   Num     Type           Disp Enb Address            What
>   2       breakpoint     keep y   <MULTIPLE>
>           stop only in thread 1
>   2.1                         y   0x0000000000401114 in foo at
> /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
>   2.2                         y   0x0000000000401146 in foo at
> /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
>   2.3                         y   0x0000000000401168 in foo at
> /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
>   3       breakpoint     keep y   0x000000000040110a in bar at
> /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32 thread 1
>           stop only in thread 1

We already state the condition in "stop only in thread 1".
Do we need to repeat it also as part of the location?

> The 'thread 1' condition is now displayed at the end of each location,
> which makes the output the same for single location breakpoints and
> multi-location breakpoints.
> ---
>  gdb/breakpoint.c                              |  2 +-
>  gdb/testsuite/gdb.base/thread-bp-multi-loc.c  | 44 ++++++++++
>  .../gdb.base/thread-bp-multi-loc.exp          | 88 +++++++++++++++++++
>  3 files changed, 133 insertions(+), 1 deletion(-)
>  create mode 100644 gdb/testsuite/gdb.base/thread-bp-multi-loc.c
>  create mode 100644 gdb/testsuite/gdb.base/thread-bp-multi-loc.exp
> 
> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
> index f0276a963c0..2ec8ca364b6 100644
> --- a/gdb/breakpoint.c
> +++ b/gdb/breakpoint.c
> @@ -6467,7 +6467,7 @@ print_one_breakpoint_location (struct breakpoint *b,
>        output_thread_groups (uiout, "thread-groups", inf_nums, mi_only);
>      }
> 
> -  if (!part_of_multiple)
> +  if (!header_of_multiple)
>      {
>        if (b->thread != -1)
>  	{
> diff --git a/gdb/testsuite/gdb.base/thread-bp-multi-loc.c b/gdb/testsuite/gdb.base/thread-
> bp-multi-loc.c
> new file mode 100644
> index 00000000000..cab009c39ec
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/thread-bp-multi-loc.c
> @@ -0,0 +1,44 @@
> +/* 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/>.  */
> +
> +volatile int global_var = 0;
> +
> +__attribute__((__always_inline__)) static inline void
> +foo (void)
> +{
> +  int i;
> +
> +  for (i = 0; i < 10; ++i)
> +    global_var = i;
> +}
> +
> +static void
> +bar (void)
> +{
> +  global_var = 0;
> +  foo ();
> +}
> +
> +int
> +main (void)
> +{
> +  global_var = 0;
> +  foo ();
> +  bar ();
> +  foo ();
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp b/gdb/testsuite/gdb.base/thread-
> bp-multi-loc.exp
> new file mode 100644
> index 00000000000..9abff5866d9
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp
> @@ -0,0 +1,88 @@
> +# 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/>.
> +
> +# Create a multi-location breakpoint with a thread condition, then check the
> +# output of 'info breakpoints' to ensure that the thread condition is
> +# displayed correctly.
> +
> +standard_testfile
> +
> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
> +    return
> +}
> +
> +if ![runto_main] then {

Maybe use {} here, too, for consistency.

> +    return

The testsuite is unfortunately highly inconsistent about what to do here.
There is "return 0", "return 1", "return -1", and "return".
If we cannot run to the main function, "return -1" seems a better choice
to me.  What do you think?

> +}
> +
> +gdb_test_no_output "with confirm off -- delete breakpoints"

Why not use delete_breakpoints?

> +
> +set bp_number ""
> +gdb_test_multiple "break foo thread 1" "" {
> +    -re "^break foo thread 1\r\n" {
> +	exp_continue
> +    }
> +    -re "^Breakpoint ($decimal) at $hex: foo\[^\r\n\]+3 locations\[^\r\n\]+\r\n" {
> +	set bp_number $expect_out(1,string)
> +	exp_continue
> +    }
> +    -re "^$gdb_prompt $" {
> +	gdb_assert { ![string eq $bp_number ""] }
> +    }
> +}
> +
> +if { $bp_number == "" } {
> +    unresolved "breakpoint not placed correctly"
> +    return
> +}

An alternative could be to use "gdb_breakpoint" followed by

set bp_number [get_integer_valueof "\$bpnum" 0]


> +
> +set saw_header false
> +set saw_cond false
> +set loc_count 0
> +gdb_test_multiple "info breakpoints" "" {
> +    -re "^info breakpoints\r\n" {
> +	exp_continue
> +    }
> +
> +    -re "^Num\\s+\[^\r\n\]+\r\n" {

If we remove "^" at the beginning, we can also get rid of the 
first regexp above for a bit shorter code.

Thanks
-Baris

> +	exp_continue
> +    }
> +
> +    -re "^$bp_number\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
> +	set saw_header true
> +	exp_continue
> +    }
> +
> +    -re "^\\s+stop only in thread 1\r\n" {
> +	set saw_cond true
> +	exp_continue
> +    }
> +
> +    -re "^$bp_number\\.\[123\]\\s+\[^\r\n\]+ thread 1\r\n" {
> +	incr loc_count
> +	exp_continue
> +    }
> +
> +    -re "^$gdb_prompt $" {
> +	with_test_prefix $gdb_test_name {
> +	    gdb_assert { $saw_header } \
> +		"saw header line"
> +	    gdb_assert { $saw_cond } \
> +		"saw b/p condition line"
> +	    gdb_assert { $loc_count == 3 } \
> +		"saw all three locations"
> +	}
> +    }
> +}
> --
> 2.25.4

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 54+ messages in thread

* RE: [PATCH 5/6] gdb: add inferior-specific breakpoints and watchpoints
  2022-11-28 11:25 ` [PATCH 5/6] gdb: add inferior-specific breakpoints and watchpoints Andrew Burgess
  2022-11-28 13:18   ` Eli Zaretskii
@ 2022-12-23 10:05   ` Aktemur, Tankut Baris
  2023-01-19 19:13     ` Andrew Burgess
  1 sibling, 1 reply; 54+ messages in thread
From: Aktemur, Tankut Baris @ 2022-12-23 10:05 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On Monday, November 28, 2022 12:26 PM, Andrew Burgess wrote:
> This commit extends the breakpoint mechanism to allow for inferior
> specific breakpoints (and watchpoints).
> 
> As GDB gains better support for multiple connections, and so for
> running multiple (possibly unrelated) inferiors, then it is not hard

Nit: IMHO, removing "then" makes the sentence sound better.

> to imagine that a user might wish to create breakpoints that apply to
> any thread in a single inferior.  To achieve this currently, the user
> would need to create a condition possibly making use of the $_inferior
> convenience variable, which, though functional, isn't the most user
> friendly.

An important difference of an inferior-specific breakpoint wrt using
conditions that contain the 'thread' keyword or the '$_inferior' variable
could be that the breakpoints would not be inserted at all on other inferiors.
For inferiors that have a large number of threads, this could save a
substantial amount of overhead of stopping, evaluating the condition, and
resuming.  IMHO, it is worth considering this for inferior-specific breakpoints.

In a downstream debugger, we had included this feature:
https://github.com/intel/gdb/commit/7d87ac91308cd7a8984ba7b0e333a6689790972d
(Please see the modifications in 'create_breakpoint').

With this perspective, I also think that allowing the use of both 'thread'
and 'inferior' clauses makes sense, because they would have different advantages.

More comments are inlined below.

Thanks
-Baris
 
> @@ -3255,6 +3278,32 @@ Thread-specific breakpoint %d deleted - thread %s no longer in the
> thread list.\
>      }
>  }
> 
> +/* Called when inferior INF has exited.  Remove per-inferior breakpoints.  */
> +
> +static void
> +remove_inferior_breakpoints (struct inferior *inf)
> +{
> +  for (breakpoint *b : all_breakpoints_safe ())
> +    {
> +      if (b->inferior == inf->num && user_breakpoint_p (b))
> +	{
> +	  /* Tell the user the breakpoint has been deleted.  But only for
> +	     breakpoints that would not normally have been deleted at the
> +	     next stop anyway.  */
> +	  if (b->disposition != disp_del
> +	      && b->disposition != disp_del_at_next_stop)
> +	    gdb_printf (_("\
> +Inferior-specific breakpoint %d deleted - inferior %d has exited.\n"),
> +			b->number, inf->num);
> +
> +

It seems one of the blank lines is redundant.

> @@ -8430,6 +8500,11 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
> 
>    thread = thread_;
>    task = task_;
> +  inferior = inferior_;
> +
> +  /* A breakpoint can be thread specific, or inferior specific, but not
> +     both.  This should be checked when the breakpoint condition is parsed.  */
> +  gdb_assert (!(thread != -1 && inferior != -1));

The previous assertion expressions are easier to read, I think:

  gdb_assert (thread == -1 || inferior == -1);

> @@ -8821,11 +8914,15 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
>        else if (rest)
>  	{
>  	  rest->reset (savestring (tok, strlen (tok)));
> -	  return;
> +	  break;
>  	}
>        else
>  	error (_("Junk at end of arguments."));
>      }
> +
> +  if (*thread != -1 && *inferior != -1)
> +    error (_("Invalid use of both 'thread' and 'inferior' in "
> +	     "breakpoint condition"));

Nit: This error message sounds to me like "the conditions you used
both in the 'thread' and the 'inferior' clauses are incorrect."  But 
in fact the problem is, using both clauses at the same time is not
allowed.  Maybe this would be clearer:
"Using the 'thread' and 'inferior' conditions together is not allowed."

> @@ -10068,6 +10170,10 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
>  	  tok++;
>  	  toklen = end_tok - tok + 1;
> 
> +	  if (thread != -1 && inferior != -1)
> +	    error (_("Invalid use of both 'thread' and 'inferior' in "
> +		     "watchpoint condition"));

Same comment here.

> diff --git a/gdb/inferior.h b/gdb/inferior.h
> index 69525a2e053..3b25403d83c 100644
> --- a/gdb/inferior.h
> +++ b/gdb/inferior.h
> @@ -746,4 +746,14 @@ extern void print_selected_inferior (struct ui_out *uiout);
>  extern void switch_to_inferior_and_push_target
>    (inferior *new_inf, bool no_connection, inferior *org_inf);
> 
> +/* Return true if ID is a valid global inferior number.  */
> +
> +inline bool valid_global_inferior_id (int id)

Function name should be at column 0.

> @@ -280,11 +280,69 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure)
>        return -1;
>      }
> 
> +  if (self_bp->bp->inferior != -1 && id != -1)
> +    {
> +      PyErr_SetString (PyExc_RuntimeError,
> +		       _("Cannot have both thread and inferior conditions "
> +			 "on a breakpoint"));

This error message is clearer than the one I commented above and I think
it would make sense to align the two.

> diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
> b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
> new file mode 100644
> index 00000000000..5d65f19b88c
> --- /dev/null
> +++ b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
> @@ -0,0 +1,183 @@
> +# 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/>.
> +
> +# Test inferior-specific breakpoints.
> +
> +standard_testfile -1.c -2.c
> +
> +if { [use_gdb_stub] } {
> +    return
> +}
> +
> +set srcfile1 ${srcfile}
> +set binfile1 ${binfile}-1
> +set binfile2 ${binfile}-2
> +
> +if { [build_executable ${testfile}.exp ${binfile1} "${srcfile1}"] != 0 } {
> +    return -1
> +}
> +
> +if { [build_executable ${testfile}.exp ${binfile2} "${srcfile2}"] != 0 } {
> +    return -1
> +}
> +
> +# Start the first inferior.
> +clean_restart ${binfile1}
> +if {![runto_main]} {

In the if-statements above, there are spaces around the condition.

> +    return
> +}
> +
> +# Add a second inferior, and start this one too.
> +gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
> +gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2"
> +gdb_load $binfile2
> +if {![runto_main]} {
> +    return
> +}
> +
> +# Try to create a breakpoint using both the 'inferior' and 'thread' keywords,
> +# this should fail.  Try with the keywords in both orders just in case the
> +# parser has a bug.
> +gdb_test "break foo thread 1.1 inferior 1" \
> +    "Invalid use of both 'thread' and 'inferior' in breakpoint condition"
> +gdb_test "break foo inferior 1 thread 1.1" \
> +    "Invalid use of both 'thread' and 'inferior' in breakpoint condition"
> +
> +# While w're here, check that we can't create a watchpoint with both thread

Typo: w're

> +# and inferior keywords.  As above, test with keywords in both orders.
> +foreach type {watch rwatch awatch} {
> +    gdb_test "$type global_var thread 1.1 inferior 1" \
> +	"Invalid use of both 'thread' and 'inferior' in watchpoint condition"
> +    gdb_test "$type global_var inferior 1 thread 1.1" \
> +	"Invalid use of both 'thread' and 'inferior' in watchpoint condition"
> +}
> +
> +# Clear out any other breakpoints.
> +gdb_test "with confirm off -- delete breakpoints"

Why not use "delete_breakpoints"?

> +# Create an inferior specific breakpoint.

Nit: This should be spelled "inferior-specific", as far as I know.

> +gdb_test "break foo inferior 1" \
> +    "Breakpoint $decimal at $hex: foo\\. \\(3 locations\\)"

Why not use 'gdb_breakpoint'?

> +set saw_header false
> +set location_count 0
> +set saw_inf_cond false
> +gdb_test_multiple "info breakpoints" "" {
> +    -re "^info breakpoints\r\n" {
> +	exp_continue
> +    }
> +
> +    -re "^Num\\s+\[^\r\n\]+\r\n" {

Removing "^" at the beginning could help eliminate the first regexp
above to obtain shorter code.

> +	exp_continue
> +    }
> +
> +    -re "^$decimal\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
> +	set saw_header true
> +	exp_continue
> +    }
> +
> +    -re "^\\s+stop only in inferior 1\r\n" {
> +	set saw_inf_cond true
> +	exp_continue
> +    }
> +
> +    -re "^$decimal\\.\[123\]\\s+y\\s+ $hex in foo at \[^\r\n\]+ inf \[12\] inferior 1\r\n"
> {
> +	incr location_count
> +	exp_continue
> +    }
> +
> +    -re "^$gdb_prompt $" {
> +	with_test_prefix $gdb_test_name {
> +	    gdb_assert { $saw_header }
> +	    gdb_assert { $location_count == 3 }
> +	    gdb_assert { $saw_inf_cond }
> +	}
> +    }
> +}
> +
> +# Create a multi-inferior breakpoint to stop at.
> +gdb_test "break stop_breakpt"
> +
> +# Now resume inferior 2, this should reach 'stop_breakpt'.
> +gdb_test "continue" "hit Breakpoint $decimal\.$decimal, stop_breakpt \\(\\) .*" \
> +    "continue in inferior 2"
> +
> +# Switch to inferior 1, and try there.
> +gdb_test "inferior 1" ".*" \
> +    "select inferior 1 to check the inferior-specific b/p works"
> +gdb_test "continue " "hit Breakpoint $decimal\.$decimal, foo \\(\\) .*" \
> +    "first continue in inferior 1"

Maybe we can check here explicitly, for extra confidence, that the hit
came actually from Thread 1.*.

> +
> +# Now back to inferior 2, let the inferior exit, the inferior-specific
> +# breakpoint should not be deleted.
> +gdb_test "inferior 2" ".*" \
> +    "switch back to allow inferior 2 to exit"
> +gdb_test "continue" "Inferior 2 \[^\r\n\]+ exited normally.*" \
> +    "allow inferior 2 to exit"
> +
> +gdb_test "inferior 1" ".*" \
> +    "select inferior 1 to check inferior-specific b/p still works"
> +gdb_test "continue " "hit Breakpoint $decimal\.$decimal, foo \\(\\) .*" \
> +    "second continue in inferior 1"
> +gdb_test "continue " "hit Breakpoint $decimal\.$decimal, stop_breakpt \\(\\) .*" \
> +    "third continue in inferior 1"
> +
> +# Now allow inferior 1 to exit, the inferior specific breakpoint should be
> +# deleted.
> +gdb_test "continue" \
> +    [multi_line \
> +	 "\\\[Inferior 1 \[^\r\n\]+ exited normally\\\]" \
> +	 "Inferior-specific breakpoint $decimal deleted - inferior 1 has exited\\."]
> +
> +set saw_header false
> +set location_count 0
> +set saw_inf_cond false
> +gdb_test_multiple "info breakpoints" "info breakpoint after inferior 1 exited" {
> +    -re "^info breakpoints\r\n" {
> +	exp_continue
> +    }
> +
> +    -re "^Num\\s+\[^\r\n\]+\r\n" {
> +	exp_continue
> +    }
> +
> +    -re "^$decimal\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
> +	set saw_header true
> +	exp_continue
> +    }
> +
> +    -re "^\\s+stop only in inferior 1\r\n" {
> +	# This should not happen, this breakpoint should have been deleted.
> +	set saw_inf_cond true
> +	exp_continue
> +    }
> +
> +    -re "^\\s+breakpoint already hit 2 times\r\n" {
> +	exp_continue
> +    }
> +
> +    -re "^$decimal\\.\[12\]\\s+y\\s+ $hex in stop_breakpt at \[^\r\n\]+ inf \[12\]\r\n" {
> +	incr location_count
> +	exp_continue
> +    }
> +
> +    -re "^$gdb_prompt $" {
> +	with_test_prefix $gdb_test_name {
> +	    gdb_assert { $saw_header }
> +	    gdb_assert { $location_count == 2 }
> +	    gdb_assert { !$saw_inf_cond }
> +	}
> +    }
> +}
> diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-
> breakpoint.exp
> index c3215b13d5c..8906dea6655 100644
> --- a/gdb/testsuite/gdb.python/py-breakpoint.exp
> +++ b/gdb/testsuite/gdb.python/py-breakpoint.exp
> @@ -112,6 +112,8 @@ proc_with_prefix test_bkpt_basic { } {
>  	"Get Breakpoint List" 0
>      gdb_test "python print (blist\[1\].thread)" \
>  	"None" "Check breakpoint thread"
> +    gdb_test "python print (blist\[1\].inferior)" \
> +	"None" "Check breakpoint inferior"
>      gdb_test "python print (blist\[1\].type == gdb.BP_BREAKPOINT)" \
>  	"True" "Check breakpoint type"
>      gdb_test "python print (blist\[0\].number)" \
> @@ -214,6 +216,46 @@ proc_with_prefix test_bkpt_cond_and_cmds { } {
>  	"check number of lines in commands"
>  }
> 
> +# Test breakpoint thread and inferior attributes.
> +proc_with_prefix test_bkpt_thread_and_inferior { } {
> +    global srcfile testfile hex decimal
> +
> +    # Start with a fresh gdb.
> +    clean_restart ${testfile}
> +
> +    if ![runto_main] then {

I thought you had removed the uses of 'then'. :)

> +	return 0

As I wrote previously, the testsuite does not look consistent
about what to return here.  But "-1" makes more sense to me, because
not being able to run to main sounds like a major problem.

> +    }
> +
> +    with_test_prefix "thread" {
> +	delete_breakpoints
> +	gdb_test "break multiply thread 1"
> +	gdb_test "python bp = gdb.breakpoints ()\[0\]"
> +	gdb_test "python print(bp.thread)" "1"
> +	gdb_test "python print(bp.inferior)" "None"
> +	gdb_test "python bp.inferior = 1" \
> +	    "RuntimeError: Cannot have both thread and inferior conditions on a breakpoint.*"
> +	gdb_test_no_output "python bp.thread = None"
> +	gdb_test_no_output "python bp.inferior = 1" \
> +	    "set the inferior now the thread has been cleared"
> +	gdb_test "info breakpoints" "stop only in inferior 1\r\n.*"
> +    }
> +
> +    with_test_prefix "inferior" {
> +	delete_breakpoints
> +	gdb_test "break multiply inferior 1"
> +	gdb_test "python bp = gdb.breakpoints ()\[0\]"
> +	gdb_test "python print(bp.thread)" "None"
> +	gdb_test "python print(bp.inferior)" "1"
> +	gdb_test "python bp.thread = 1" \
> +	    "RuntimeError: Cannot have both thread and inferior conditions on a breakpoint.*"
> +	gdb_test_no_output "python bp.inferior = None"
> +	gdb_test_no_output "python bp.thread = 1" \
> +	    "set the thread now the inferior has been cleared"
> +	gdb_test "info breakpoints" "stop only in thread 1\r\n.*"
> +    }
> +}
> +
>  proc_with_prefix test_bkpt_invisible { } {
>      global srcfile testfile hex decimal
> 
> @@ -849,6 +891,7 @@ proc_with_prefix test_bkpt_auto_disable { } {
>  test_bkpt_basic
>  test_bkpt_deletion
>  test_bkpt_cond_and_cmds
> +test_bkpt_thread_and_inferior
>  test_bkpt_invisible
>  test_hardware_breakpoints
>  test_catchpoints
> diff --git a/gdb/testsuite/lib/completion-support.exp b/gdb/testsuite/lib/completion-
> support.exp
> index 0c03d0f035e..135649ed4d9 100644
> --- a/gdb/testsuite/lib/completion-support.exp
> +++ b/gdb/testsuite/lib/completion-support.exp
> @@ -27,7 +27,7 @@ namespace eval completion {
>      # List of all quote chars, including no-quote at all.
>      variable maybe_quoted_list {"" "'" "\""}
> 
> -    variable keyword_list {"-force-condition" "if" "task" "thread"}
> +    variable keyword_list {"-force-condition" "if" "inferior" "task" "thread"}
> 
>      variable explicit_opts_list \
>  	{"-function" "-label" "-line" "-qualified" "-source"}
> --
> 2.25.4

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 54+ messages in thread

* RE: [PATCH 6/6] gdb: convert the 'start' breakpoint to use inferior keyword
  2022-11-28 11:25 ` [PATCH 6/6] gdb: convert the 'start' breakpoint to use inferior keyword Andrew Burgess
@ 2022-12-23 10:17   ` Aktemur, Tankut Baris
  0 siblings, 0 replies; 54+ messages in thread
From: Aktemur, Tankut Baris @ 2022-12-23 10:17 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On Monday, November 28, 2022 12:26 PM, Andrew Burgess wrote:
> Now that we have support for inferior specific breakpoints, the
> breakpoint created for the 'start' command can make use of this
> keyword.
...
> diff --git a/gdb/testsuite/gdb.base/start-inferior-specific.exp
> b/gdb/testsuite/gdb.base/start-inferior-specific.exp
> new file mode 100644
> index 00000000000..50e4e109116
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/start-inferior-specific.exp
> @@ -0,0 +1,55 @@
> +# 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/>.
> +
> +# Check that a breakpoint created for the 'start' command, will be
> +# silently deleted if the inferior being started exits before reaching
> +# main.
> +
> +standard_testfile -1.c -2.c
> +
> +if {[use_gdb_stub]} {
> +    return
> +}
> +
> +set srcfile1 ${srcfile}
> +
> +set binfile1 ${binfile}-1
> +set binfile2 ${binfile}-2
> +
> +if {[build_executable ${testfile}.exp ${binfile1} "${srcfile1}"] != 0} {
> +    return -1
> +}
> +
> +if {[build_executable ${testfile}.exp ${binfile2} "${srcfile2}"] != 0} {
> +    return -1
> +}
> +
> +clean_restart ${binfile1}
> +
> +# Start the first inferior, this will exit before hitting the 'start'
> +# breakpoint in main.
> +gdb_test "start" \
> +    [multi_line \
> +	 "Temporary breakpoint $decimal at \[^\r\n\]+" \
> +	 "Starting program: \[^\r\n\]+" \
> +	 "\\\[Inferior $decimal \[^\r\n\]+ exited with code 01\\\]"]

Can $inferior_exited_re be used here?

> +
> +# Now load a different binary and run it.  This inferior should run
> +# all the way to completion without hitting a breakpoint in main.
> +gdb_load ${binfile2}
> +gdb_test "run" \
> +    [multi_line \
> +	 "Starting program: \[^\r\n\]+" \
> +	 "\\\[Inferior $decimal \[^\r\n\]+ exited normally\\\]"]

Also here.

Thanks
-Baris

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 54+ messages in thread

* RE: [PATCH 0/6] Inferior specific breakpoints
  2022-11-28 11:25 [PATCH 0/6] Inferior specific breakpoints Andrew Burgess
                   ` (5 preceding siblings ...)
  2022-11-28 11:25 ` [PATCH 6/6] gdb: convert the 'start' breakpoint to use inferior keyword Andrew Burgess
@ 2022-12-23 10:55 ` Aktemur, Tankut Baris
  2023-01-20  9:46 ` [PATCHv2 " Andrew Burgess
  7 siblings, 0 replies; 54+ messages in thread
From: Aktemur, Tankut Baris @ 2022-12-23 10:55 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On Monday, November 28, 2022 12:26 PM, Andrew Burgess wrote:
> Adding inferior specific breakpoints to GDB.  These are like the
> current thread or Ada task specific breakpoints, but cover inferiors.
> 
> Patches #1, #2, #3 are unreleated bug fixes that I ran into while
> adding this feature.
> 
> Patch #4 is possibly a bug fix, or maybe a tightening of our CLI rules
> for specifying thread/task breakpoints and watchpoints.  This one
> could be dropped if it's not wanted.
> 
> Patch #5 is where I add inferior specific breakpoints.
> 
> Patch #6 converts the 'start' mechanism to use inferior specific
> breakpoints.

FWIW, I've gone through this series and posted my comments for patches
3, 5, and 6.  Patches 1, 2, and 4 looked fine to me.  I haven't run the
tests.

Regards
-Baris


Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 54+ messages in thread

* RE: [PATCH 5/6] gdb: add inferior-specific breakpoints and watchpoints
  2022-12-23 10:05   ` Aktemur, Tankut Baris
@ 2023-01-19 19:13     ` Andrew Burgess
  2023-01-20 13:12       ` Aktemur, Tankut Baris
  0 siblings, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2023-01-19 19:13 UTC (permalink / raw)
  To: Aktemur, Tankut Baris, gdb-patches


"Aktemur, Tankut Baris" <tankut.baris.aktemur@intel.com> writes:

> On Monday, November 28, 2022 12:26 PM, Andrew Burgess wrote:
>> This commit extends the breakpoint mechanism to allow for inferior
>> specific breakpoints (and watchpoints).
>> 
>> As GDB gains better support for multiple connections, and so for
>> running multiple (possibly unrelated) inferiors, then it is not hard
>
> Nit: IMHO, removing "then" makes the sentence sound better.
>
>> to imagine that a user might wish to create breakpoints that apply to
>> any thread in a single inferior.  To achieve this currently, the user
>> would need to create a condition possibly making use of the $_inferior
>> convenience variable, which, though functional, isn't the most user
>> friendly.
>
> An important difference of an inferior-specific breakpoint wrt using
> conditions that contain the 'thread' keyword or the '$_inferior' variable
> could be that the breakpoints would not be inserted at all on other inferiors.
> For inferiors that have a large number of threads, this could save a
> substantial amount of overhead of stopping, evaluating the condition, and
> resuming.  IMHO, it is worth considering this for inferior-specific breakpoints.
>
> In a downstream debugger, we had included this feature:
> https://github.com/intel/gdb/commit/7d87ac91308cd7a8984ba7b0e333a6689790972d
> (Please see the modifications in 'create_breakpoint').

Neat.  I wasn't aware Intel had already worked on this feature.

I had also thought about not inserting breakpoints into non-matching
inferiors.  In the end I decided to leave that for a follow up patch,
but it's nice to see that Intel have been doing this for a few years now.

>
> With this perspective, I also think that allowing the use of both 'thread'
> and 'inferior' clauses makes sense, because they would have different advantages.

Except, isn't the thread-id passed to a 'thread' condition a global
thread-id?  i.e. "break foo thread 1" isn't thread 1 in every inferior,
it's GDB's global thread 1, which is one thread in one inferior.

So we could (if we implemented it) already limit into which inferiors a
thread specific breakpoint is inserted by just figuring out which
inferior that thread is in.

I think it makes sense, at least initially, to prevent use of 'thread'
and 'inferior' together.  If we decide to relax this restriction later,
then that's no problem.  It's much harder to add more restrictions
later.

>
> More comments are inlined below.
>
> Thanks
> -Baris
>  
>> @@ -3255,6 +3278,32 @@ Thread-specific breakpoint %d deleted - thread %s no longer in the
>> thread list.\
>>      }
>>  }
>> 
>> +/* Called when inferior INF has exited.  Remove per-inferior breakpoints.  */
>> +
>> +static void
>> +remove_inferior_breakpoints (struct inferior *inf)
>> +{
>> +  for (breakpoint *b : all_breakpoints_safe ())
>> +    {
>> +      if (b->inferior == inf->num && user_breakpoint_p (b))
>> +	{
>> +	  /* Tell the user the breakpoint has been deleted.  But only for
>> +	     breakpoints that would not normally have been deleted at the
>> +	     next stop anyway.  */
>> +	  if (b->disposition != disp_del
>> +	      && b->disposition != disp_del_at_next_stop)
>> +	    gdb_printf (_("\
>> +Inferior-specific breakpoint %d deleted - inferior %d has exited.\n"),
>> +			b->number, inf->num);
>> +
>> +
>
> It seems one of the blank lines is redundant.
>
>> @@ -8430,6 +8500,11 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
>> 
>>    thread = thread_;
>>    task = task_;
>> +  inferior = inferior_;
>> +
>> +  /* A breakpoint can be thread specific, or inferior specific, but not
>> +     both.  This should be checked when the breakpoint condition is parsed.  */
>> +  gdb_assert (!(thread != -1 && inferior != -1));
>
> The previous assertion expressions are easier to read, I think:
>
>   gdb_assert (thread == -1 || inferior == -1);
>
>> @@ -8821,11 +8914,15 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
>>        else if (rest)
>>  	{
>>  	  rest->reset (savestring (tok, strlen (tok)));
>> -	  return;
>> +	  break;
>>  	}
>>        else
>>  	error (_("Junk at end of arguments."));
>>      }
>> +
>> +  if (*thread != -1 && *inferior != -1)
>> +    error (_("Invalid use of both 'thread' and 'inferior' in "
>> +	     "breakpoint condition"));
>
> Nit: This error message sounds to me like "the conditions you used
> both in the 'thread' and the 'inferior' clauses are incorrect."  But 
> in fact the problem is, using both clauses at the same time is not
> allowed.  Maybe this would be clearer:
> "Using the 'thread' and 'inferior' conditions together is not allowed."
>
>> @@ -10068,6 +10170,10 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
>>  	  tok++;
>>  	  toklen = end_tok - tok + 1;
>> 
>> +	  if (thread != -1 && inferior != -1)
>> +	    error (_("Invalid use of both 'thread' and 'inferior' in "
>> +		     "watchpoint condition"));
>
> Same comment here.
>
>> diff --git a/gdb/inferior.h b/gdb/inferior.h
>> index 69525a2e053..3b25403d83c 100644
>> --- a/gdb/inferior.h
>> +++ b/gdb/inferior.h
>> @@ -746,4 +746,14 @@ extern void print_selected_inferior (struct ui_out *uiout);
>>  extern void switch_to_inferior_and_push_target
>>    (inferior *new_inf, bool no_connection, inferior *org_inf);
>> 
>> +/* Return true if ID is a valid global inferior number.  */
>> +
>> +inline bool valid_global_inferior_id (int id)
>
> Function name should be at column 0.
>
>> @@ -280,11 +280,69 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure)
>>        return -1;
>>      }
>> 
>> +  if (self_bp->bp->inferior != -1 && id != -1)
>> +    {
>> +      PyErr_SetString (PyExc_RuntimeError,
>> +		       _("Cannot have both thread and inferior conditions "
>> +			 "on a breakpoint"));
>
> This error message is clearer than the one I commented above and I think
> it would make sense to align the two.
>
>> diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
>> b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
>> new file mode 100644
>> index 00000000000..5d65f19b88c
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
>> @@ -0,0 +1,183 @@
>> +# 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/>.
>> +
>> +# Test inferior-specific breakpoints.
>> +
>> +standard_testfile -1.c -2.c
>> +
>> +if { [use_gdb_stub] } {
>> +    return
>> +}
>> +
>> +set srcfile1 ${srcfile}
>> +set binfile1 ${binfile}-1
>> +set binfile2 ${binfile}-2
>> +
>> +if { [build_executable ${testfile}.exp ${binfile1} "${srcfile1}"] != 0 } {
>> +    return -1
>> +}
>> +
>> +if { [build_executable ${testfile}.exp ${binfile2} "${srcfile2}"] != 0 } {
>> +    return -1
>> +}
>> +
>> +# Start the first inferior.
>> +clean_restart ${binfile1}
>> +if {![runto_main]} {
>
> In the if-statements above, there are spaces around the condition.
>
>> +    return
>> +}
>> +
>> +# Add a second inferior, and start this one too.
>> +gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
>> +gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2"
>> +gdb_load $binfile2
>> +if {![runto_main]} {
>> +    return
>> +}
>> +
>> +# Try to create a breakpoint using both the 'inferior' and 'thread' keywords,
>> +# this should fail.  Try with the keywords in both orders just in case the
>> +# parser has a bug.
>> +gdb_test "break foo thread 1.1 inferior 1" \
>> +    "Invalid use of both 'thread' and 'inferior' in breakpoint condition"
>> +gdb_test "break foo inferior 1 thread 1.1" \
>> +    "Invalid use of both 'thread' and 'inferior' in breakpoint condition"
>> +
>> +# While w're here, check that we can't create a watchpoint with both thread
>
> Typo: w're
>
>> +# and inferior keywords.  As above, test with keywords in both orders.
>> +foreach type {watch rwatch awatch} {
>> +    gdb_test "$type global_var thread 1.1 inferior 1" \
>> +	"Invalid use of both 'thread' and 'inferior' in watchpoint condition"
>> +    gdb_test "$type global_var inferior 1 thread 1.1" \
>> +	"Invalid use of both 'thread' and 'inferior' in watchpoint condition"
>> +}
>> +
>> +# Clear out any other breakpoints.
>> +gdb_test "with confirm off -- delete breakpoints"
>
> Why not use "delete_breakpoints"?
>
>> +# Create an inferior specific breakpoint.
>
> Nit: This should be spelled "inferior-specific", as far as I know.
>
>> +gdb_test "break foo inferior 1" \
>> +    "Breakpoint $decimal at $hex: foo\\. \\(3 locations\\)"
>
> Why not use 'gdb_breakpoint'?
>
>> +set saw_header false
>> +set location_count 0
>> +set saw_inf_cond false
>> +gdb_test_multiple "info breakpoints" "" {
>> +    -re "^info breakpoints\r\n" {
>> +	exp_continue
>> +    }
>> +
>> +    -re "^Num\\s+\[^\r\n\]+\r\n" {
>
> Removing "^" at the beginning could help eliminate the first regexp
> above to obtain shorter code.
>
>> +	exp_continue
>> +    }
>> +
>> +    -re "^$decimal\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
>> +	set saw_header true
>> +	exp_continue
>> +    }
>> +
>> +    -re "^\\s+stop only in inferior 1\r\n" {
>> +	set saw_inf_cond true
>> +	exp_continue
>> +    }
>> +
>> +    -re "^$decimal\\.\[123\]\\s+y\\s+ $hex in foo at \[^\r\n\]+ inf \[12\] inferior 1\r\n"
>> {
>> +	incr location_count
>> +	exp_continue
>> +    }
>> +
>> +    -re "^$gdb_prompt $" {
>> +	with_test_prefix $gdb_test_name {
>> +	    gdb_assert { $saw_header }
>> +	    gdb_assert { $location_count == 3 }
>> +	    gdb_assert { $saw_inf_cond }
>> +	}
>> +    }
>> +}
>> +
>> +# Create a multi-inferior breakpoint to stop at.
>> +gdb_test "break stop_breakpt"
>> +
>> +# Now resume inferior 2, this should reach 'stop_breakpt'.
>> +gdb_test "continue" "hit Breakpoint $decimal\.$decimal, stop_breakpt \\(\\) .*" \
>> +    "continue in inferior 2"
>> +
>> +# Switch to inferior 1, and try there.
>> +gdb_test "inferior 1" ".*" \
>> +    "select inferior 1 to check the inferior-specific b/p works"
>> +gdb_test "continue " "hit Breakpoint $decimal\.$decimal, foo \\(\\) .*" \
>> +    "first continue in inferior 1"
>
> Maybe we can check here explicitly, for extra confidence, that the hit
> came actually from Thread 1.*.
>
>> +
>> +# Now back to inferior 2, let the inferior exit, the inferior-specific
>> +# breakpoint should not be deleted.
>> +gdb_test "inferior 2" ".*" \
>> +    "switch back to allow inferior 2 to exit"
>> +gdb_test "continue" "Inferior 2 \[^\r\n\]+ exited normally.*" \
>> +    "allow inferior 2 to exit"
>> +
>> +gdb_test "inferior 1" ".*" \
>> +    "select inferior 1 to check inferior-specific b/p still works"
>> +gdb_test "continue " "hit Breakpoint $decimal\.$decimal, foo \\(\\) .*" \
>> +    "second continue in inferior 1"
>> +gdb_test "continue " "hit Breakpoint $decimal\.$decimal, stop_breakpt \\(\\) .*" \
>> +    "third continue in inferior 1"
>> +
>> +# Now allow inferior 1 to exit, the inferior specific breakpoint should be
>> +# deleted.
>> +gdb_test "continue" \
>> +    [multi_line \
>> +	 "\\\[Inferior 1 \[^\r\n\]+ exited normally\\\]" \
>> +	 "Inferior-specific breakpoint $decimal deleted - inferior 1 has exited\\."]
>> +
>> +set saw_header false
>> +set location_count 0
>> +set saw_inf_cond false
>> +gdb_test_multiple "info breakpoints" "info breakpoint after inferior 1 exited" {
>> +    -re "^info breakpoints\r\n" {
>> +	exp_continue
>> +    }
>> +
>> +    -re "^Num\\s+\[^\r\n\]+\r\n" {
>> +	exp_continue
>> +    }
>> +
>> +    -re "^$decimal\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
>> +	set saw_header true
>> +	exp_continue
>> +    }
>> +
>> +    -re "^\\s+stop only in inferior 1\r\n" {
>> +	# This should not happen, this breakpoint should have been deleted.
>> +	set saw_inf_cond true
>> +	exp_continue
>> +    }
>> +
>> +    -re "^\\s+breakpoint already hit 2 times\r\n" {
>> +	exp_continue
>> +    }
>> +
>> +    -re "^$decimal\\.\[12\]\\s+y\\s+ $hex in stop_breakpt at \[^\r\n\]+ inf \[12\]\r\n" {
>> +	incr location_count
>> +	exp_continue
>> +    }
>> +
>> +    -re "^$gdb_prompt $" {
>> +	with_test_prefix $gdb_test_name {
>> +	    gdb_assert { $saw_header }
>> +	    gdb_assert { $location_count == 2 }
>> +	    gdb_assert { !$saw_inf_cond }
>> +	}
>> +    }
>> +}
>> diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-
>> breakpoint.exp
>> index c3215b13d5c..8906dea6655 100644
>> --- a/gdb/testsuite/gdb.python/py-breakpoint.exp
>> +++ b/gdb/testsuite/gdb.python/py-breakpoint.exp
>> @@ -112,6 +112,8 @@ proc_with_prefix test_bkpt_basic { } {
>>  	"Get Breakpoint List" 0
>>      gdb_test "python print (blist\[1\].thread)" \
>>  	"None" "Check breakpoint thread"
>> +    gdb_test "python print (blist\[1\].inferior)" \
>> +	"None" "Check breakpoint inferior"
>>      gdb_test "python print (blist\[1\].type == gdb.BP_BREAKPOINT)" \
>>  	"True" "Check breakpoint type"
>>      gdb_test "python print (blist\[0\].number)" \
>> @@ -214,6 +216,46 @@ proc_with_prefix test_bkpt_cond_and_cmds { } {
>>  	"check number of lines in commands"
>>  }
>> 
>> +# Test breakpoint thread and inferior attributes.
>> +proc_with_prefix test_bkpt_thread_and_inferior { } {
>> +    global srcfile testfile hex decimal
>> +
>> +    # Start with a fresh gdb.
>> +    clean_restart ${testfile}
>> +
>> +    if ![runto_main] then {
>
> I thought you had removed the uses of 'then'. :)
>
>> +	return 0
>
> As I wrote previously, the testsuite does not look consistent
> about what to return here.  But "-1" makes more sense to me, because
> not being able to run to main sounds like a major problem.

The difference in the other patch where you pointed this out is that the
return as at the top level of the script (I fixed that other case as per
your suggestion).

In this case the return is from a function.  We don't actually check the
return value, but I'd rather leave this return consistent with all the
other similar return statements, in all the other functions, in this
test script.

I've made all the other updates you suggested, these will be included in
a V2 series shortly.

Thanks,
Andrew

>
>> +    }
>> +
>> +    with_test_prefix "thread" {
>> +	delete_breakpoints
>> +	gdb_test "break multiply thread 1"
>> +	gdb_test "python bp = gdb.breakpoints ()\[0\]"
>> +	gdb_test "python print(bp.thread)" "1"
>> +	gdb_test "python print(bp.inferior)" "None"
>> +	gdb_test "python bp.inferior = 1" \
>> +	    "RuntimeError: Cannot have both thread and inferior conditions on a breakpoint.*"
>> +	gdb_test_no_output "python bp.thread = None"
>> +	gdb_test_no_output "python bp.inferior = 1" \
>> +	    "set the inferior now the thread has been cleared"
>> +	gdb_test "info breakpoints" "stop only in inferior 1\r\n.*"
>> +    }
>> +
>> +    with_test_prefix "inferior" {
>> +	delete_breakpoints
>> +	gdb_test "break multiply inferior 1"
>> +	gdb_test "python bp = gdb.breakpoints ()\[0\]"
>> +	gdb_test "python print(bp.thread)" "None"
>> +	gdb_test "python print(bp.inferior)" "1"
>> +	gdb_test "python bp.thread = 1" \
>> +	    "RuntimeError: Cannot have both thread and inferior conditions on a breakpoint.*"
>> +	gdb_test_no_output "python bp.inferior = None"
>> +	gdb_test_no_output "python bp.thread = 1" \
>> +	    "set the thread now the inferior has been cleared"
>> +	gdb_test "info breakpoints" "stop only in thread 1\r\n.*"
>> +    }
>> +}
>> +
>>  proc_with_prefix test_bkpt_invisible { } {
>>      global srcfile testfile hex decimal
>> 
>> @@ -849,6 +891,7 @@ proc_with_prefix test_bkpt_auto_disable { } {
>>  test_bkpt_basic
>>  test_bkpt_deletion
>>  test_bkpt_cond_and_cmds
>> +test_bkpt_thread_and_inferior
>>  test_bkpt_invisible
>>  test_hardware_breakpoints
>>  test_catchpoints
>> diff --git a/gdb/testsuite/lib/completion-support.exp b/gdb/testsuite/lib/completion-
>> support.exp
>> index 0c03d0f035e..135649ed4d9 100644
>> --- a/gdb/testsuite/lib/completion-support.exp
>> +++ b/gdb/testsuite/lib/completion-support.exp
>> @@ -27,7 +27,7 @@ namespace eval completion {
>>      # List of all quote chars, including no-quote at all.
>>      variable maybe_quoted_list {"" "'" "\""}
>> 
>> -    variable keyword_list {"-force-condition" "if" "task" "thread"}
>> +    variable keyword_list {"-force-condition" "if" "inferior" "task" "thread"}
>> 
>>      variable explicit_opts_list \
>>  	{"-function" "-label" "-line" "-qualified" "-source"}
>> --
>> 2.25.4
>
> Intel Deutschland GmbH
> Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
> Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
> Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
> Chairperson of the Supervisory Board: Nicole Lau
> Registered Office: Munich
> Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCHv2 0/6] Inferior specific breakpoints
  2022-11-28 11:25 [PATCH 0/6] Inferior specific breakpoints Andrew Burgess
                   ` (6 preceding siblings ...)
  2022-12-23 10:55 ` [PATCH 0/6] Inferior specific breakpoints Aktemur, Tankut Baris
@ 2023-01-20  9:46 ` Andrew Burgess
  2023-01-20  9:46   ` [PATCHv2 1/6] gdb/remote: announce thread exit events for remote targets Andrew Burgess
                     ` (6 more replies)
  7 siblings, 7 replies; 54+ messages in thread
From: Andrew Burgess @ 2023-01-20  9:46 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Thanks for all the feedback on v1.

Changes in v2:

  - Biggest changes are in patch #3, as suggested by Baris, I've now
    removed the 'thread' and 'inferior' tags from the end of each
    location in the 'info breakpoints' output.  The information was
    already available in the condition line, so this removes some
    duplication.  For Ada task conditions I've added a condition line
    as this was missing.

  - Doc updates in #4 and #5 to fix the issues Eli pointed out.  These
    all seemed pretty minor so I don't think there should be anything
    too surprising there.

  - Updated patch #5 based on feedback from Baris, this was all pretty
    minor stuff though, formatting, and rewording some error messages,

  - Updated patch #6 based on feedback from Baris, this is a minor
    test cleanup.

Thanks,
Andrew

---

Andrew Burgess (6):
  gdb/remote: announce thread exit events for remote targets
  gdb/testsuite: don't try to set non-stop mode on a running target
  gdb: fix display of thread condition for multi-location breakpoints
  gdb: error if 'thread' or 'task' keywords are overused
  gdb: add inferior-specific breakpoints and watchpoints
  gdb: convert the 'start' breakpoint to use inferior keyword

 gdb/NEWS                                      |  16 ++
 gdb/breakpoint.c                              | 210 ++++++++++++++----
 gdb/breakpoint.h                              |  10 +-
 gdb/doc/gdb.texinfo                           |  74 +++++-
 gdb/doc/python.texi                           |  24 +-
 gdb/dummy-frame.c                             |   1 +
 gdb/elfread.c                                 |   5 +-
 gdb/guile/scm-breakpoint.c                    |   5 +
 gdb/infcmd.c                                  |  10 +-
 gdb/inferior.h                                |  11 +
 gdb/infrun.c                                  |   2 +
 gdb/linespec.c                                |   4 +-
 gdb/python/py-breakpoint.c                    |  77 +++++++
 gdb/remote.c                                  |   4 +
 gdb/testsuite/gdb.ada/tasks.exp               |  19 +-
 gdb/testsuite/gdb.base/save-bp.exp            |   2 +-
 .../gdb.base/start-inferior-specific-1.c      |  32 +++
 .../gdb.base/start-inferior-specific-2.c      |  22 ++
 .../gdb.base/start-inferior-specific.exp      |  55 +++++
 gdb/testsuite/gdb.base/thread-bp-multi-loc.c  |  44 ++++
 .../gdb.base/thread-bp-multi-loc.exp          |  72 ++++++
 gdb/testsuite/gdb.linespec/cpcompletion.exp   |   4 +-
 gdb/testsuite/gdb.linespec/explicit.exp       |   1 +
 gdb/testsuite/gdb.linespec/keywords.exp       |  10 +-
 .../gdb.multi/inferior-specific-bp-1.c        |  52 +++++
 .../gdb.multi/inferior-specific-bp-2.c        |  52 +++++
 .../gdb.multi/inferior-specific-bp.exp        | 182 +++++++++++++++
 gdb/testsuite/gdb.python/py-breakpoint.exp    |  43 ++++
 .../gdb.threads/thread-specific-bp.exp        | 142 ++++++------
 gdb/testsuite/gdb.threads/watchthreads2.exp   |   3 +
 gdb/testsuite/lib/completion-support.exp      |   2 +-
 31 files changed, 1041 insertions(+), 149 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/start-inferior-specific-1.c
 create mode 100644 gdb/testsuite/gdb.base/start-inferior-specific-2.c
 create mode 100644 gdb/testsuite/gdb.base/start-inferior-specific.exp
 create mode 100644 gdb/testsuite/gdb.base/thread-bp-multi-loc.c
 create mode 100644 gdb/testsuite/gdb.base/thread-bp-multi-loc.exp
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp.exp


base-commit: 1a4cbc46d66812121ef359b1b0e954f4373ea8d0
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCHv2 1/6] gdb/remote: announce thread exit events for remote targets
  2023-01-20  9:46 ` [PATCHv2 " Andrew Burgess
@ 2023-01-20  9:46   ` Andrew Burgess
  2023-02-02 17:50     ` Pedro Alves
  2023-01-20  9:46   ` [PATCHv2 2/6] gdb/testsuite: don't try to set non-stop mode on a running target Andrew Burgess
                     ` (5 subsequent siblings)
  6 siblings, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2023-01-20  9:46 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

For some reason the "[Thread XXXX exited]" messages are not printed
inside thread.c from functions like delete_thread, etc as might be
expected.  Instead, each target seems to print the message before
calling delete_thread.

This doesn't seem ideal, and I can't help but feel that the printing
should be moved into thread.c, however, I have not tried to do that in
this commit, as I suspect there will be lots of fallout that needs
fixing up.

Instead, in this commit, I have added the printing code into remote.c,
so that the remote target will now correctly tell the user when a
thread has exited.

This fixes some test failures in gdb.threads/thread-specific-bp.exp
when run with the native-gdbserver and native-extended-gdbserver
board.

When using the native-extended-gdbserver board I still see 1 test
failure, but I think this is not related to the issue fixed in this
commit, so I'm ignoring that for now.
---
 gdb/remote.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/gdb/remote.c b/gdb/remote.c
index 0a6e293c095..4a508981a96 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -3977,6 +3977,10 @@ remote_target::update_thread_list ()
 	      if (has_single_non_exited_thread (tp->inf))
 		continue;
 
+	      if (print_thread_events)
+		gdb_printf (_("[%s exited]\n"),
+			    target_pid_to_str (tp->ptid).c_str ());
+
 	      /* Not found.  */
 	      delete_thread (tp);
 	    }
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCHv2 2/6] gdb/testsuite: don't try to set non-stop mode on a running target
  2023-01-20  9:46 ` [PATCHv2 " Andrew Burgess
  2023-01-20  9:46   ` [PATCHv2 1/6] gdb/remote: announce thread exit events for remote targets Andrew Burgess
@ 2023-01-20  9:46   ` Andrew Burgess
  2023-02-04 16:22     ` Andrew Burgess
  2023-01-20  9:46   ` [PATCHv2 3/6] gdb: fix display of thread condition for multi-location breakpoints Andrew Burgess
                     ` (4 subsequent siblings)
  6 siblings, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2023-01-20  9:46 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

The test gdb.threads/thread-specific-bp.exp tries to set non-stop mode
on a running target, something which the manual makes clear is not
allowed.

The consequence of this is that the gdb.threads/thread-specific-bp.exp
test has one failure when run with the native-extended-gdbserver
board.

This commit restructures the test a little, we now set the non-stop
mode as part of the GDBFLAGS, so the mode will be set before GDB
connects to the target.  As a consequence I'm able to move the
with_test_prefix out of the check_thread_specific_breakpoint proc.
The check_thread_specific_breakpoint proc is now called within a loop.

After this commit the gdb.threads/thread-specific-bp.exp test has zero
failures for me with native-extended-gdbserver, native-gdbserver, and
the unix board.
---
 .../gdb.threads/thread-specific-bp.exp        | 138 +++++++++---------
 1 file changed, 67 insertions(+), 71 deletions(-)

diff --git a/gdb/testsuite/gdb.threads/thread-specific-bp.exp b/gdb/testsuite/gdb.threads/thread-specific-bp.exp
index f603b24fa31..d33b4f47258 100644
--- a/gdb/testsuite/gdb.threads/thread-specific-bp.exp
+++ b/gdb/testsuite/gdb.threads/thread-specific-bp.exp
@@ -24,8 +24,6 @@ if {[gdb_compile_pthreads \
     return -1
 }
 
-clean_restart ${binfile}
-
 # Extract and return the thread ID of the thread stopped at function
 # FUNC.
 
@@ -45,86 +43,84 @@ proc get_thread_id {func} {
     return $thre
 }
 
-proc check_thread_specific_breakpoint {mode} {
-    with_test_prefix "$mode" {
-	global gdb_prompt
+proc check_thread_specific_breakpoint {non_stop} {
+    global gdb_prompt
 
-	if ![runto_main] {
-	    return -1
-	}
+    if ![runto_main] {
+	return -1
+    }
 
-	set main_thre [get_thread_id "main"]
-	if { $main_thre < 0 } {
-	    return -1
-	}
+    set main_thre [get_thread_id "main"]
+    if { $main_thre < 0 } {
+	return -1
+    }
 
-	gdb_breakpoint "start"
-	gdb_continue_to_breakpoint "start"
+    gdb_breakpoint "start"
+    gdb_continue_to_breakpoint "start"
 
-	set start_thre [get_thread_id "start"]
-	if { $start_thre < 0 } {
-	    return -1
-	}
+    set start_thre [get_thread_id "start"]
+    if { $start_thre < 0 } {
+	return -1
+    }
 
-	# Set a thread-specific breakpoint at "main".  This can't ever
-	# be hit, but that's OK.
-	gdb_breakpoint "main thread $start_thre"
-	gdb_test "info break" ".*breakpoint.*thread $start_thre" "breakpoint set"
-
-	# Set breakpoint at a place only reacheable after the "start"
-	# thread exits.
-	gdb_breakpoint "end"
-
-	# Switch back to the main thread, and resume all threads.  The
-	# "start" thread exits, and the main thread reaches "end".
-	gdb_test "thread $main_thre" \
-	    "Switching to thread $main_thre.*" \
-	    "thread $main_thre selected"
-
-	if { $mode == "non-stop" } {
-	    set cmd "continue -a"
-	} else {
-	    set cmd "continue"
-	}
-	set test "continue to end"
-	set thread_exited 0
-	set prompt 0
-	gdb_test_multiple "$cmd" $test -lbl {
-	    -re "(^|\r\n)\\\[Thread \[^\r\n\]* exited](?=\r\n)" {
-		if { $prompt } {
-		    pass $gdb_test_name
-		} else {
-		    set thread_exited 1
-		    exp_continue
-		}
+    # Set a thread-specific breakpoint at "main".  This can't ever
+    # be hit, but that's OK.
+    gdb_breakpoint "main thread $start_thre"
+    gdb_test "info break" ".*breakpoint.*thread $start_thre" "breakpoint set"
+
+    # Set breakpoint at a place only reacheable after the "start"
+    # thread exits.
+    gdb_breakpoint "end"
+
+    # Switch back to the main thread, and resume all threads.  The
+    # "start" thread exits, and the main thread reaches "end".
+    gdb_test "thread $main_thre" \
+	"Switching to thread $main_thre.*" \
+	"thread $main_thre selected"
+
+    if { $non_stop } {
+	set cmd "continue -a"
+    } else {
+	set cmd "continue"
+    }
+    set test "continue to end"
+    set thread_exited 0
+    set prompt 0
+    gdb_test_multiple "$cmd" $test -lbl {
+	-re "(^|\r\n)\\\[Thread \[^\r\n\]* exited](?=\r\n)" {
+	    if { $prompt } {
+		pass $gdb_test_name
+	    } else {
+		set thread_exited 1
+		exp_continue
 	    }
-	    -re "\r\n$gdb_prompt " {
-		if { $thread_exited } {
-		    pass $gdb_test_name
-		} else {
-		    set prompt 1
-		    exp_continue
-		}
+	}
+	-re "\r\n$gdb_prompt " {
+	    if { $thread_exited } {
+		pass $gdb_test_name
+	    } else {
+		set prompt 1
+		exp_continue
 	    }
 	}
+    }
 
-	set test "thread-specific breakpoint was deleted"
-	gdb_test_multiple "info breakpoint" $test {
-	    -re "thread $start_thre\n$gdb_prompt $" {
-		fail $test
-	    }
-	    -re "$gdb_prompt $" {
-		pass $test
-	    }
+    set test "thread-specific breakpoint was deleted"
+    gdb_test_multiple "info breakpoint" $test {
+	-re "thread $start_thre\n$gdb_prompt $" {
+	    fail $test
+	}
+	-re "$gdb_prompt $" {
+	    pass $test
 	}
     }
 }
 
-# Test all-stop mode.
-check_thread_specific_breakpoint "all-stop"
-
-clean_restart ${binfile}
+foreach_with_prefix non_stop {on off} {
+    save_vars { GDBFLAGS } {
+	append GDBFLAGS " -ex \"set non-stop $non_stop\""
+	clean_restart $binfile
+    }
 
-# Test non-stop mode.
-gdb_test_no_output "set non-stop on" "set non-stop mode"
-check_thread_specific_breakpoint "non-stop"
+    check_thread_specific_breakpoint $non_stop
+}
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCHv2 3/6] gdb: fix display of thread condition for multi-location breakpoints
  2023-01-20  9:46 ` [PATCHv2 " Andrew Burgess
  2023-01-20  9:46   ` [PATCHv2 1/6] gdb/remote: announce thread exit events for remote targets Andrew Burgess
  2023-01-20  9:46   ` [PATCHv2 2/6] gdb/testsuite: don't try to set non-stop mode on a running target Andrew Burgess
@ 2023-01-20  9:46   ` Andrew Burgess
  2023-02-02 18:13     ` Pedro Alves
  2023-01-20  9:46   ` [PATCHv2 4/6] gdb: error if 'thread' or 'task' keywords are overused Andrew Burgess
                     ` (3 subsequent siblings)
  6 siblings, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2023-01-20  9:46 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

If a breakpoint with multiple locations has a thread condition, then
the 'info breakpoints' output is a little messed up, here's an example
of the current output:

  (gdb) break foo thread 1
  Breakpoint 2 at 0x401114: foo. (3 locations)
  (gdb) break bar thread 1
  Breakpoint 3 at 0x40110a: file /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c, line 32.
  (gdb) info breakpoints
  Num     Type           Disp Enb Address            What
  2       breakpoint     keep y   <MULTIPLE>          thread 1
          stop only in thread 1
  2.1                         y   0x0000000000401114 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
  2.2                         y   0x0000000000401146 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
  2.3                         y   0x0000000000401168 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
  3       breakpoint     keep y   0x000000000040110a in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32 thread 1
          stop only in thread 1

Notice that, at the end of the location for breakpoint 3, the 'thread
1' condition is printed, but this is then repeated on the next line
with 'stop only in thread 1'.

In contrast, for breakpoint 2, the 'thread 1' appears randomly, in the
"What" column, though slightly offset, non of the separate locations
have the 'thread 1' information.  Additionally for breakpoint 2 we
also get a 'stop only in thread 1' line.

There's two things going on here.  First the randomly placed 'thread
1' for breakpoint 2 is due to a bug in print_one_breakpoint_location,
where we check the variable part_of_multiple instead of
header_of_multiple.

If I fix this oversight, then the output is now:

  (gdb) break foo thread 1
  Breakpoint 2 at 0x401114: foo. (3 locations)
  (gdb) break bar thread 1
  Breakpoint 3 at 0x40110a: file /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c, line 32.
  (gdb) info breakpoints
  Num     Type           Disp Enb Address            What
  2       breakpoint     keep y   <MULTIPLE>
          stop only in thread 1
  2.1                         y   0x0000000000401114 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
  2.2                         y   0x0000000000401146 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
  2.3                         y   0x0000000000401168 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
  3       breakpoint     keep y   0x000000000040110a in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32 thread 1
          stop only in thread 1

The 'thread 1' condition is now displayed at the end of each location,
which makes the output the same for single location breakpoints and
multi-location breakpoints.

However, there's still some duplication here.  Both breakpoints 2 and
3 include a 'stop only in thread 1' line, and it feels like the
additional 'thread 1' is redundant.  In fact, there's a comment to
this very effect in the code:

  /* FIXME: This seems to be redundant and lost here; see the
     "stop only in" line a little further down.  */

So, lets fix this FIXME.  The new plan is to remove all the trailing
'thread 1' markers from the CLI output, we now get this:

  (gdb) break foo thread 1
  Breakpoint 2 at 0x401114: foo. (3 locations)
  (gdb) break bar thread 1
  Breakpoint 3 at 0x40110a: file /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c, line 32.
  (gdb) info breakpoints
  Num     Type           Disp Enb Address            What
  2       breakpoint     keep y   <MULTIPLE>
          stop only in thread 1
  2.1                         y   0x0000000000401114 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
  2.2                         y   0x0000000000401146 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
  2.3                         y   0x0000000000401168 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
  3       breakpoint     keep y   0x000000000040110a in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32
          stop only in thread 1

All of the above points are also true for the Ada 'task' breakpoint
condition, and the changes I've made also update how the task
information is printed, though in the case of the Ada task there was
no 'stop only in task XXX' line printed, so I've added one of those.

Obviously it can't be quite that easy.  For MI backwards compatibility
I've retained the existing code (but now only for MI like outputs),
which ensures we should generate backwards compatible output.

I've extended an Ada test to cover the new task related output, and
updated all the tests I could find that checked for the old output.
---
 gdb/breakpoint.c                              | 30 ++++----
 gdb/testsuite/gdb.ada/tasks.exp               | 15 ++--
 gdb/testsuite/gdb.base/save-bp.exp            |  2 +-
 gdb/testsuite/gdb.base/thread-bp-multi-loc.c  | 44 ++++++++++++
 .../gdb.base/thread-bp-multi-loc.exp          | 72 +++++++++++++++++++
 5 files changed, 141 insertions(+), 22 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/thread-bp-multi-loc.c
 create mode 100644 gdb/testsuite/gdb.base/thread-bp-multi-loc.exp

diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 00cc2ab401c..b2cd89511fb 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -6468,20 +6468,19 @@ print_one_breakpoint_location (struct breakpoint *b,
       output_thread_groups (uiout, "thread-groups", inf_nums, mi_only);
     }
 
-  if (!part_of_multiple)
+  /* In the MI output, each location of a thread or task specific
+     breakpoint is with the relevant thread or task ID.  This is done for
+     backwards compatibility reasons.
+
+     For the CLI output, the thread/task information is printed on a
+     separate line, see the 'stop only in thread' and 'stop only in task'
+     output below.  */
+  if (!header_of_multiple && uiout->is_mi_like_p ())
     {
       if (b->thread != -1)
-	{
-	  /* FIXME: This seems to be redundant and lost here; see the
-	     "stop only in" line a little further down.  */
-	  uiout->text (" thread ");
-	  uiout->field_signed ("thread", b->thread);
-	}
+	uiout->field_signed ("thread", b->thread);
       else if (b->task != 0)
-	{
-	  uiout->text (" task ");
-	  uiout->field_signed ("task", b->task);
-	}
+	uiout->field_signed ("task", b->task);
     }
 
   uiout->text ("\n");
@@ -6536,7 +6535,14 @@ print_one_breakpoint_location (struct breakpoint *b,
 	}
       uiout->text ("\n");
     }
-  
+
+  if (!part_of_multiple && b->task != 0)
+    {
+      uiout->text ("\tstop only in task ");
+      uiout->field_signed ("task", b->task);
+      uiout->text ("\n");
+    }
+
   if (!part_of_multiple)
     {
       if (b->hit_count)
diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp
index a9b58f20cf6..23bf3937a20 100644
--- a/gdb/testsuite/gdb.ada/tasks.exp
+++ b/gdb/testsuite/gdb.ada/tasks.exp
@@ -46,21 +46,18 @@ gdb_test "info tasks" \
 # breakpoint in the list that matched the triggered-breakpoint's
 # address, no matter which task it was specific to.
 gdb_test "break break_me task 1" "Breakpoint .* at .*"
+gdb_test "info breakpoints" "foo.adb:${decimal}\r\n\\s+stop only in task 1" \
+    "check info breakpoints for task 1 breakpoint"
 
 # Now, insert a breakpoint that should stop only if task 3 stops, and
 # extract its number.
-set bp_number -1
-set test "break break_me task 3"
-gdb_test_multiple $test $test {
-    -re "Breakpoint (.*) at .*$gdb_prompt $" {
-	set bp_number $expect_out(1,string)
-	pass $test
-    }
-}
-
+gdb_breakpoint "break_me task 3"
+set bp_number [get_integer_valueof "\$bpnum" -1]
 if {$bp_number < 0} {
     return
 }
+gdb_test "info breakpoints" "foo.adb:${decimal}\r\n\\s+stop only in task 3" \
+    "check info breakpoints for task 3 breakpoint"
 
 # Continue to that breakpoint.  Task 2 should hit it first, and GDB
 # is expected to ignore that hit and resume the execution.  Only then
diff --git a/gdb/testsuite/gdb.base/save-bp.exp b/gdb/testsuite/gdb.base/save-bp.exp
index a39712c7f5c..41d71837fb6 100644
--- a/gdb/testsuite/gdb.base/save-bp.exp
+++ b/gdb/testsuite/gdb.base/save-bp.exp
@@ -79,7 +79,7 @@ gdb_test_sequence "info break" "info break" [list				\
   "\[\r\n\]+Num +Type +Disp +Enb +Address +What"				\
   "\[\r\n\]+$bp_row_start break_me at \[^\r\n\]*$srcfile:\[0-9\]+"		\
   "\[\r\n\]+$bp_row_start main at \[^\r\n\]*$srcfile:$loc_bp2"			\
-  "\[\r\n\]+$bp_row_start main at \[^\r\n\]*$srcfile:$loc_bp3 +thread 1"	\
+  "\[\r\n\]+$bp_row_start main at \[^\r\n\]*$srcfile:$loc_bp3"			\
   "\[\r\n\]+\[ \t]+stop only in thread 1"					\
   "\[\r\n\]+$bp_row_start main at \[^\r\n\]*$srcfile:$loc_bp4"			\
   "\[\r\n\]+\[ \t\]+stop only if i == 1( \\((host|target) evals\\))?"		\
diff --git a/gdb/testsuite/gdb.base/thread-bp-multi-loc.c b/gdb/testsuite/gdb.base/thread-bp-multi-loc.c
new file mode 100644
index 00000000000..cab009c39ec
--- /dev/null
+++ b/gdb/testsuite/gdb.base/thread-bp-multi-loc.c
@@ -0,0 +1,44 @@
+/* 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/>.  */
+
+volatile int global_var = 0;
+
+__attribute__((__always_inline__)) static inline void
+foo (void)
+{
+  int i;
+
+  for (i = 0; i < 10; ++i)
+    global_var = i;
+}
+
+static void
+bar (void)
+{
+  global_var = 0;
+  foo ();
+}
+
+int
+main (void)
+{
+  global_var = 0;
+  foo ();
+  bar ();
+  foo ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp b/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp
new file mode 100644
index 00000000000..9665fe9e21f
--- /dev/null
+++ b/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp
@@ -0,0 +1,72 @@
+# 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/>.
+
+# Create a multi-location breakpoint with a thread condition, then check the
+# output of 'info breakpoints' to ensure that the thread condition is
+# displayed correctly.
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
+    return -1
+}
+
+if {![runto_main]} {
+    return -1
+}
+
+delete_breakpoints
+
+gdb_breakpoint "foo thread 1"
+set bp_number [get_integer_valueof "\$bpnum" 0]
+if { $bp_number == 0 } {
+    unresolved "breakpoint not placed correctly"
+    return -1
+}
+
+set saw_header false
+set saw_cond false
+set loc_count 0
+gdb_test_multiple "info breakpoints" "" {
+    -re "\r\nNum\\s+\[^\r\n\]+\r\n" {
+	exp_continue
+    }
+
+    -re "^$bp_number\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
+	set saw_header true
+	exp_continue
+    }
+
+    -re "^\\s+stop only in thread 1\r\n" {
+	set saw_cond true
+	exp_continue
+    }
+
+    -re "^$bp_number\\.\[123\]\\s+\[^\r\n\]+:${decimal}\r\n" {
+	incr loc_count
+	exp_continue
+    }
+
+    -re "^$gdb_prompt $" {
+	with_test_prefix $gdb_test_name {
+	    gdb_assert { $saw_header } \
+		"saw header line"
+	    gdb_assert { $saw_cond } \
+		"saw b/p condition line"
+	    gdb_assert { $loc_count == 3 } \
+		"saw all three locations"
+	}
+    }
+}
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCHv2 4/6] gdb: error if 'thread' or 'task' keywords are overused
  2023-01-20  9:46 ` [PATCHv2 " Andrew Burgess
                     ` (2 preceding siblings ...)
  2023-01-20  9:46   ` [PATCHv2 3/6] gdb: fix display of thread condition for multi-location breakpoints Andrew Burgess
@ 2023-01-20  9:46   ` Andrew Burgess
  2023-01-20 13:22     ` Eli Zaretskii
  2023-02-02 18:21     ` Pedro Alves
  2023-01-20  9:46   ` [PATCHv2 5/6] gdb: add inferior-specific breakpoints and watchpoints Andrew Burgess
                     ` (2 subsequent siblings)
  6 siblings, 2 replies; 54+ messages in thread
From: Andrew Burgess @ 2023-01-20  9:46 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

When creating a breakpoint or watchpoint, the 'thread' and 'task'
keywords can be used to create a thread or task specific breakpoint or
watchpoint.

Currently, a thread or task specific breakpoint can only apply for a
single thread or task, if multiple threads or tasks are specified when
creating the breakpoint (or watchpoint), then the last specified id
will be used.

The exception to the above is that when the 'thread' keyword is used
during the creation of a watchpoint, GDB will give an error if
'thread' is given more than once.

In this commit I propose making this behaviour consistent, if the
'thread' or 'task' keywords are used more than once when creating
either a breakpoint or watchpoint, then GDB will give an error.

I haven't updated the manual, we don't explicitly say that these
keywords can be repeated, and (to me), given the keyword takes a
single id, I don't think it makes much sense to repeat the keyword.
As such, I see this more as adding a missing error to GDB, rather than
making some big change.  However, I have added an entry to the NEWS
file as I guess it is possible that some people might hit this new
error with an existing (I claim, badly written) GDB script.

I've added some new tests to check for the new error.

Just one test needed updating, gdb.linespec/keywords.exp, this test
did use the 'thread' keyword twice, and expected the breakpoint to be
created.  Looking at what this test was for though, it was checking
the use of '-force-condition', and I don't think that being able to
repeat 'thread' was actually a critical part of this test.

As such, I've updated this test to expect the error when 'thread' is
repeated.
---
 gdb/NEWS                                         |  9 +++++++++
 gdb/breakpoint.c                                 |  9 +++++++++
 gdb/testsuite/gdb.ada/tasks.exp                  |  4 ++++
 gdb/testsuite/gdb.linespec/keywords.exp          | 10 ++++++++--
 gdb/testsuite/gdb.threads/thread-specific-bp.exp |  4 ++++
 gdb/testsuite/gdb.threads/watchthreads2.exp      |  3 +++
 6 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/gdb/NEWS b/gdb/NEWS
index c0aac212e30..fb49f62f7e6 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -9,6 +9,15 @@
   This support requires that GDB be built with Python scripting
   enabled.
 
+* For the break command, multiple uses of the 'thread' or 'task'
+  keywords will now give an error instead of just using the thread or
+  task id from the last instance of the keyword.
+
+* For the watch command, multiple uses of the 'task' keyword will now
+  give an error instead of just using the task id from the last
+  instance of the keyword.  The 'thread' keyword already gave an error
+  when used multiple times with the watch command, this remains unchanged.
+
 * New commands
 
 maintenance print record-instruction [ N ]
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index b2cd89511fb..1400fd49642 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -8801,6 +8801,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  const char *tmptok;
 	  struct thread_info *thr;
 
+	  if (*thread != -1)
+	    error(_("You can specify only one thread."));
+
 	  tok = end_tok + 1;
 	  thr = parse_thread_id (tok, &tmptok);
 	  if (tok == tmptok)
@@ -8812,6 +8815,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	{
 	  char *tmptok;
 
+	  if (*task != 0)
+	    error(_("You can specify only one task."));
+
 	  tok = end_tok + 1;
 	  *task = strtol (tok, &tmptok, 0);
 	  if (tok == tmptok)
@@ -10094,6 +10100,9 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	    {
 	      char *tmp;
 
+	      if (task != 0)
+		error(_("You can specify only one task."));
+
 	      task = strtol (value_start, &tmp, 0);
 	      if (tmp == value_start)
 		error (_("Junk after task keyword."));
diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp
index 23bf3937a20..4441d92719c 100644
--- a/gdb/testsuite/gdb.ada/tasks.exp
+++ b/gdb/testsuite/gdb.ada/tasks.exp
@@ -39,6 +39,10 @@ gdb_test "info tasks" \
                "\r\n"] \
          "info tasks before inserting breakpoint"
 
+# Check that multiple uses of the 'task' keyword will give an error.
+gdb_test "break break_me task 1 task 3" "You can specify only one task\\."
+gdb_test "watch j task 1 task 3" "You can specify only one task\\."
+
 # Insert a breakpoint that should stop only if task 1 stops.  Since
 # task 1 never calls break_me, this shouldn't actually ever trigger.
 # The fact that this breakpoint is created _before_ the next one
diff --git a/gdb/testsuite/gdb.linespec/keywords.exp b/gdb/testsuite/gdb.linespec/keywords.exp
index bff64249542..dc66e32237c 100644
--- a/gdb/testsuite/gdb.linespec/keywords.exp
+++ b/gdb/testsuite/gdb.linespec/keywords.exp
@@ -80,8 +80,14 @@ foreach prefix {"" "thread 1 "} {
     foreach suffix {"" " " " thread 1"} {
 	foreach cond {"" " if 1"} {
 	    with_test_prefix "prefix: '$prefix', suffix: '$suffix', cond: '$cond'" {
-		gdb_breakpoint "main ${prefix}-force-condition${suffix}${cond}"\
-		    "message"
+
+		if { [regexp thread $prefix] && [regexp thread $suffix] } {
+		    gdb_test "break main ${prefix}-force-condition${suffix}${cond}" \
+			"You can specify only one thread\\."
+		} else {
+		    gdb_breakpoint "main ${prefix}-force-condition${suffix}${cond}"\
+			"message"
+		}
 	    }
 	}
     }
diff --git a/gdb/testsuite/gdb.threads/thread-specific-bp.exp b/gdb/testsuite/gdb.threads/thread-specific-bp.exp
index d33b4f47258..008ae5ed05e 100644
--- a/gdb/testsuite/gdb.threads/thread-specific-bp.exp
+++ b/gdb/testsuite/gdb.threads/thread-specific-bp.exp
@@ -63,6 +63,10 @@ proc check_thread_specific_breakpoint {non_stop} {
 	return -1
     }
 
+    # Check that multiple uses of 'thread' keyword give an error.
+    gdb_test "break main thread $start_thre thread $main_thre" \
+	"You can specify only one thread\\."
+
     # Set a thread-specific breakpoint at "main".  This can't ever
     # be hit, but that's OK.
     gdb_breakpoint "main thread $start_thre"
diff --git a/gdb/testsuite/gdb.threads/watchthreads2.exp b/gdb/testsuite/gdb.threads/watchthreads2.exp
index 09858aee486..a1398d668a4 100644
--- a/gdb/testsuite/gdb.threads/watchthreads2.exp
+++ b/gdb/testsuite/gdb.threads/watchthreads2.exp
@@ -71,6 +71,9 @@ if { $nr_started == $NR_THREADS } {
     return -1
 }
 
+# Check that multiple uses of the 'thread' keyword will give an error.
+gdb_test "watch x thread 1 thread 2" "You can specify only one thread\\."
+
 # Watch X, it will be modified by all threads.
 # We want this watchpoint to be set *after* all threads are running.
 gdb_test "watch x" "Hardware watchpoint 3: x"
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCHv2 5/6] gdb: add inferior-specific breakpoints and watchpoints
  2023-01-20  9:46 ` [PATCHv2 " Andrew Burgess
                     ` (3 preceding siblings ...)
  2023-01-20  9:46   ` [PATCHv2 4/6] gdb: error if 'thread' or 'task' keywords are overused Andrew Burgess
@ 2023-01-20  9:46   ` Andrew Burgess
  2023-01-20 13:25     ` Eli Zaretskii
                       ` (2 more replies)
  2023-01-20  9:46   ` [PATCHv2 6/6] gdb: convert the 'start' breakpoint to use inferior keyword Andrew Burgess
  2023-03-16 17:03   ` [PATCHv3 0/2] Inferior specific breakpoints Andrew Burgess
  6 siblings, 3 replies; 54+ messages in thread
From: Andrew Burgess @ 2023-01-20  9:46 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

This commit extends the breakpoint mechanism to allow for inferior
specific breakpoints (and watchpoints).

As GDB gains better support for multiple connections, and so for
running multiple (possibly unrelated) inferiors, then it is not hard
to imagine that a user might wish to create breakpoints that apply to
any thread in a single inferior.  To achieve this currently, the user
would need to create a condition possibly making use of the $_inferior
convenience variable, which, though functional, isn't the most user
friendly.

This commit adds a new 'inferior' keyword that allows for the creation
of inferior specific breakpoints.

Like thread specific breakpoints, the inferior specific breakpoints
are automatically deleted once the inferior in question exits.
---
 gdb/NEWS                                      |   7 +
 gdb/breakpoint.c                              | 171 +++++++++++++---
 gdb/breakpoint.h                              |  10 +-
 gdb/doc/gdb.texinfo                           |  74 ++++++-
 gdb/doc/python.texi                           |  24 ++-
 gdb/dummy-frame.c                             |   1 +
 gdb/elfread.c                                 |   5 +-
 gdb/guile/scm-breakpoint.c                    |   5 +
 gdb/inferior.h                                |  11 ++
 gdb/infrun.c                                  |   2 +
 gdb/linespec.c                                |   4 +-
 gdb/python/py-breakpoint.c                    |  77 ++++++++
 gdb/testsuite/gdb.linespec/cpcompletion.exp   |   4 +-
 gdb/testsuite/gdb.linespec/explicit.exp       |   1 +
 .../gdb.multi/inferior-specific-bp-1.c        |  52 +++++
 .../gdb.multi/inferior-specific-bp-2.c        |  52 +++++
 .../gdb.multi/inferior-specific-bp.exp        | 182 ++++++++++++++++++
 gdb/testsuite/gdb.python/py-breakpoint.exp    |  43 +++++
 gdb/testsuite/lib/completion-support.exp      |   2 +-
 19 files changed, 680 insertions(+), 47 deletions(-)
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index fb49f62f7e6..cde9a835b2e 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -18,6 +18,13 @@
   instance of the keyword.  The 'thread' keyword already gave an error
   when used multiple times with the watch command, this remains unchanged.
 
+* Breakpoints can now be inferior-specific.  This is similar to the
+  existing thread-specific breakpoint support.  Breakpoint conditions
+  can include the 'inferior' keyword followed by an inferior id (as
+  displayed in the 'info inferiors' output).  It is invalid to use
+  both the 'inferior' and 'thread' keywords when creating a
+  breakpoint.
+
 * New commands
 
 maintenance print record-instruction [ N ]
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 1400fd49642..a7c7c27017b 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -96,7 +96,7 @@ static void create_breakpoints_sal (struct gdbarch *,
 				    gdb::unique_xmalloc_ptr<char>,
 				    gdb::unique_xmalloc_ptr<char>,
 				    enum bptype,
-				    enum bpdisp, int, int,
+				    enum bpdisp, int, int, int,
 				    int,
 				    int, int, int, unsigned);
 
@@ -1462,11 +1462,28 @@ breakpoint_set_thread (struct breakpoint *b, int thread)
 {
   int old_thread = b->thread;
 
+  gdb_assert (thread == -1 || b->inferior == -1);
+
   b->thread = thread;
   if (old_thread != thread)
     gdb::observers::breakpoint_modified.notify (b);
 }
 
+/* Set the inferior for breakpoint B to INFERIOR.  If INFERIOR is -1, make
+   the breakpoint work for any inferior.  */
+
+void
+breakpoint_set_inferior (struct breakpoint *b, int inferior)
+{
+  int old_inferior = b->inferior;
+
+  gdb_assert (inferior == -1 || b->thread == -1);
+
+  b->inferior = inferior;
+  if (old_inferior != inferior)
+    gdb::observers::breakpoint_modified.notify (b);
+}
+
 /* Set the task for this breakpoint.  If TASK is 0, make the
    breakpoint work for any task.  */
 
@@ -3152,6 +3169,12 @@ insert_breakpoint_locations (void)
 	  && !valid_global_thread_id (bl->owner->thread))
 	continue;
 
+      /* Or inferior specific breakpoints if the inferior no longer
+	 exists.  */
+      if (bl->owner->inferior != -1
+	  && !valid_global_inferior_id (bl->owner->inferior))
+	continue;
+
       switch_to_program_space_and_thread (bl->pspace);
 
       /* For targets that support global breakpoints, there's no need
@@ -3256,6 +3279,31 @@ Thread-specific breakpoint %d deleted - thread %s no longer in the thread list.\
     }
 }
 
+/* Called when inferior INF has exited.  Remove per-inferior breakpoints.  */
+
+static void
+remove_inferior_breakpoints (struct inferior *inf)
+{
+  for (breakpoint *b : all_breakpoints_safe ())
+    {
+      if (b->inferior == inf->num && user_breakpoint_p (b))
+	{
+	  /* Tell the user the breakpoint has been deleted.  But only for
+	     breakpoints that would not normally have been deleted at the
+	     next stop anyway.  */
+	  if (b->disposition != disp_del
+	      && b->disposition != disp_del_at_next_stop)
+	    gdb_printf (_("\
+Inferior-specific breakpoint %d deleted - inferior %d has exited.\n"),
+			b->number, inf->num);
+
+	  /* Hide it from the user and mark it for deletion.  */
+	  b->number = 0;
+	  b->disposition = disp_del_at_next_stop;
+       }
+    }
+}
+
 /* See breakpoint.h.  */
 
 void
@@ -5465,6 +5513,7 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread)
      evaluating the condition if this isn't the specified
      thread/task.  */
   if ((b->thread != -1 && b->thread != thread->global_num)
+      || (b->inferior != -1 && b->inferior != thread->inf->num)
       || (b->task != 0 && b->task != ada_get_task_number (thread)))
     {
       infrun_debug_printf ("incorrect thread or task, not stopping");
@@ -6481,6 +6530,8 @@ print_one_breakpoint_location (struct breakpoint *b,
 	uiout->field_signed ("thread", b->thread);
       else if (b->task != 0)
 	uiout->field_signed ("task", b->task);
+      else if (b->inferior != -1)
+	uiout->field_signed ("inferior", b->inferior);
     }
 
   uiout->text ("\n");
@@ -6543,6 +6594,13 @@ print_one_breakpoint_location (struct breakpoint *b,
       uiout->text ("\n");
     }
 
+  if (!part_of_multiple && b->inferior != -1)
+    {
+      uiout->text ("\tstop only in inferior ");
+      uiout->field_signed ("inferior", b->inferior);
+      uiout->text ("\n");
+    }
+
   if (!part_of_multiple)
     {
       if (b->hit_count)
@@ -7516,7 +7574,10 @@ delete_longjmp_breakpoint (int thread)
     if (b->type == bp_longjmp || b->type == bp_exception)
       {
 	if (b->thread == thread)
-	  delete_breakpoint (b);
+	  {
+	    gdb_assert (b->inferior == -1);
+	    delete_breakpoint (b);
+	  }
       }
 }
 
@@ -7527,7 +7588,10 @@ delete_longjmp_breakpoint_at_next_stop (int thread)
     if (b->type == bp_longjmp || b->type == bp_exception)
       {
 	if (b->thread == thread)
-	  b->disposition = disp_del_at_next_stop;
+	  {
+	    gdb_assert (b->inferior == -1);
+	    b->disposition = disp_del_at_next_stop;
+	  }
       }
 }
 
@@ -7580,6 +7644,7 @@ check_longjmp_breakpoint_for_call_dummy (struct thread_info *tp)
   ALL_BREAKPOINTS_SAFE (b, b_tmp)
     if (b->type == bp_longjmp_call_dummy && b->thread == tp->global_num)
       {
+	gdb_assert (b->inferior == -1);
 	struct breakpoint *dummy_b = b->related_breakpoint;
 
 	/* Find the bp_call_dummy breakpoint in the list of breakpoints
@@ -8412,7 +8477,8 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
 				  gdb::unique_xmalloc_ptr<char> cond_string_,
 				  gdb::unique_xmalloc_ptr<char> extra_string_,
 				  enum bpdisp disposition_,
-				  int thread_, int task_, int ignore_count_,
+				  int thread_, int task_, int inferior_,
+				  int ignore_count_,
 				  int from_tty,
 				  int enabled_, unsigned flags,
 				  int display_canonical_)
@@ -8438,6 +8504,11 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
 
   thread = thread_;
   task = task_;
+  inferior = inferior_;
+
+  /* A breakpoint can be thread specific, or inferior specific, but not
+     both.  This should be checked when the breakpoint condition is parsed.  */
+  gdb_assert (thread == -1 || inferior == -1);
 
   cond_string = std::move (cond_string_);
   extra_string = std::move (extra_string_);
@@ -8539,7 +8610,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
 		       gdb::unique_xmalloc_ptr<char> cond_string,
 		       gdb::unique_xmalloc_ptr<char> extra_string,
 		       enum bptype type, enum bpdisp disposition,
-		       int thread, int task, int ignore_count,
+		       int thread, int task, int inferior, int ignore_count,
 		       int from_tty,
 		       int enabled, int internal, unsigned flags,
 		       int display_canonical)
@@ -8553,7 +8624,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
 				std::move (cond_string),
 				std::move (extra_string),
 				disposition,
-				thread, task, ignore_count,
+				thread, task, inferior, ignore_count,
 				from_tty,
 				enabled, flags,
 				display_canonical);
@@ -8582,7 +8653,8 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
 			gdb::unique_xmalloc_ptr<char> cond_string,
 			gdb::unique_xmalloc_ptr<char> extra_string,
 			enum bptype type, enum bpdisp disposition,
-			int thread, int task, int ignore_count,
+			int thread, int task, int inferior,
+			int ignore_count,
 			int from_tty,
 			int enabled, int internal, unsigned flags)
 {
@@ -8606,7 +8678,7 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
 			     std::move (cond_string),
 			     std::move (extra_string),
 			     type, disposition,
-			     thread, task, ignore_count,
+			     thread, task, inferior, ignore_count,
 			     from_tty, enabled, internal, flags,
 			     canonical->special_display);
     }
@@ -8736,21 +8808,26 @@ check_fast_tracepoint_sals (struct gdbarch *gdbarch,
     }
 }
 
-/* Given TOK, a string specification of condition and thread, as
-   accepted by the 'break' command, extract the condition
-   string and thread number and set *COND_STRING and *THREAD.
-   PC identifies the context at which the condition should be parsed.
-   If no condition is found, *COND_STRING is set to NULL.
-   If no thread is found, *THREAD is set to -1.  */
+/* Given TOK, a string specification of condition and thread, as accepted
+   by the 'break' command, extract the condition string into *COND_STRING.
+   If no condition string is found then *COND_STRING is set to nullptr.
+
+   If the breakpoint specification has an associated thread, task, or
+   inferior, these are extracted into *THREAD, *TASK, and *INFERIOR
+   respectively, otherwise these arguments are set to -1 (for THREAD and
+   INFERIOR) or 0 (for TASK).
+
+   PC identifies the context at which the condition should be parsed.  */
 
 static void
 find_condition_and_thread (const char *tok, CORE_ADDR pc,
 			   gdb::unique_xmalloc_ptr<char> *cond_string,
-			   int *thread, int *task,
+			   int *thread, int *inferior, int *task,
 			   gdb::unique_xmalloc_ptr<char> *rest)
 {
   cond_string->reset ();
   *thread = -1;
+  *inferior = -1;
   *task = 0;
   rest->reset ();
   bool force = false;
@@ -8767,7 +8844,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
       if ((*tok == '"' || *tok == ',') && rest)
 	{
 	  rest->reset (savestring (tok, strlen (tok)));
-	  return;
+	  break;
 	}
 
       end_tok = skip_to_space (tok);
@@ -8811,6 +8888,18 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  *thread = thr->global_num;
 	  tok = tmptok;
 	}
+      else if (toklen >= 1 && strncmp (tok, "inferior", toklen) == 0)
+	{
+	  char *tmptok;
+
+	  tok = end_tok + 1;
+	  *inferior = strtol (tok, &tmptok, 0);
+	  if (tok == tmptok)
+	    error (_("Junk after inferior keyword."));
+	  if (!valid_global_inferior_id (*inferior))
+	    error (_("Unknown inferior number %d."), *inferior);
+	  tok = tmptok;
+	}
       else if (toklen >= 1 && strncmp (tok, "task", toklen) == 0)
 	{
 	  char *tmptok;
@@ -8829,11 +8918,15 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
       else if (rest)
 	{
 	  rest->reset (savestring (tok, strlen (tok)));
-	  return;
+	  break;
 	}
       else
 	error (_("Junk at end of arguments."));
     }
+
+  if (*thread != -1 && *inferior != -1)
+    error (_("Cannot have both 'thread' and 'inferior' conditions on "
+	     "a breakpoint"));
 }
 
 /* Call 'find_condition_and_thread' for each sal in SALS until a parse
@@ -8845,7 +8938,7 @@ static void
 find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
 				    const char *input,
 				    gdb::unique_xmalloc_ptr<char> *cond_string,
-				    int *thread, int *task,
+				    int *thread, int *inferior, int *task,
 				    gdb::unique_xmalloc_ptr<char> *rest)
 {
   int num_failures = 0;
@@ -8853,6 +8946,7 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
     {
       gdb::unique_xmalloc_ptr<char> cond;
       int thread_id = 0;
+      int inferior_id = 0;
       int task_id = 0;
       gdb::unique_xmalloc_ptr<char> remaining;
 
@@ -8865,9 +8959,10 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
       try
 	{
 	  find_condition_and_thread (input, sal.pc, &cond, &thread_id,
-				     &task_id, &remaining);
+				     &inferior_id, &task_id, &remaining);
 	  *cond_string = std::move (cond);
 	  *thread = thread_id;
+	  *inferior = inferior_id;
 	  *task = task_id;
 	  *rest = std::move (remaining);
 	  break;
@@ -8969,6 +9064,7 @@ create_breakpoint (struct gdbarch *gdbarch,
   struct linespec_result canonical;
   bool pending = false;
   int task = 0;
+  int inferior = -1;
   int prev_bkpt_count = breakpoint_count;
 
   gdb_assert (ops != NULL);
@@ -9046,7 +9142,8 @@ create_breakpoint (struct gdbarch *gdbarch,
 	  const linespec_sals &lsal = canonical.lsals[0];
 
 	  find_condition_and_thread_for_sals (lsal.sals, extra_string,
-					      &cond, &thread, &task, &rest);
+					      &cond, &thread, &inferior,
+					      &task, &rest);
 	  cond_string_copy = std::move (cond);
 	  extra_string_copy = std::move (rest);
 	}
@@ -9096,7 +9193,7 @@ create_breakpoint (struct gdbarch *gdbarch,
 				   std::move (extra_string_copy),
 				   type_wanted,
 				   tempflag ? disp_del : disp_donttouch,
-				   thread, task, ignore_count,
+				   thread, task, inferior, ignore_count,
 				   from_tty, enabled, internal, flags);
     }
   else
@@ -10036,6 +10133,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
   const char *cond_end = NULL;
   enum bptype bp_type;
   int thread = -1;
+  int inferior = -1;
   /* Flag to indicate whether we are going to use masks for
      the hardware watchpoint.  */
   bool use_mask = false;
@@ -10076,6 +10174,10 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	  tok++;
 	  toklen = end_tok - tok + 1;
 
+	  if (thread != -1 && inferior != -1)
+	    error (_("Cannot have both 'thread' and 'inferior' conditions "
+		     "on a watchpoint"));
+
 	  if (toklen == 6 && startswith (tok, "thread"))
 	    {
 	      struct thread_info *thr;
@@ -10089,10 +10191,8 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 
 	      /* Extract the thread ID from the next token.  */
 	      thr = parse_thread_id (value_start, &endp);
-
-	      /* Check if the user provided a valid thread ID.  */
-	      if (*endp != ' ' && *endp != '\t' && *endp != '\0')
-		invalid_thread_id_error (value_start);
+	      if (value_start == endp)
+		error (_("Junk after thread keyword."));
 
 	      thread = thr->global_num;
 	    }
@@ -10109,6 +10209,16 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	      if (!valid_task_id (task))
 		error (_("Unknown task %d."), task);
 	    }
+	  else if (toklen == 8 && startswith (tok, "inferior"))
+	    {
+	      char *tmp;
+
+	      inferior = strtol (value_start, &tmp, 0);
+	      if (tmp == value_start)
+		error (_("Junk after inferior keyword."));
+	      if (!valid_global_inferior_id (inferior))
+		error (_("Unknown inferior number %d."), inferior);
+	    }
 	  else if (toklen == 4 && startswith (tok, "mask"))
 	    {
 	      /* We've found a "mask" token, which means the user wants to
@@ -10279,6 +10389,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
     w.reset (new watchpoint (nullptr, bp_type));
 
   w->thread = thread;
+  w->inferior = inferior;
   w->task = task;
   w->disposition = disp_donttouch;
   w->pspace = current_program_space;
@@ -12189,7 +12300,8 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
 				      enum bptype type_wanted,
 				      enum bpdisp disposition,
 				      int thread,
-				      int task, int ignore_count,
+				      int task, int inferior,
+				      int ignore_count,
 				      int from_tty, int enabled,
 				      int internal, unsigned flags)
 {
@@ -12215,7 +12327,7 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
 			 std::move (cond_string),
 			 std::move (extra_string),
 			 disposition,
-			 thread, task, ignore_count,
+			 thread, task, inferior, ignore_count,
 			 from_tty, enabled, flags,
 			 canonical->special_display));
 
@@ -12848,10 +12960,11 @@ code_breakpoint::location_spec_to_sals (location_spec *locspec,
       if (condition_not_parsed && extra_string != NULL)
 	{
 	  gdb::unique_xmalloc_ptr<char> local_cond, local_extra;
-	  int local_thread, local_task;
+	  int local_thread, local_task, local_inferior;
 
 	  find_condition_and_thread_for_sals (sals, extra_string.get (),
 					      &local_cond, &local_thread,
+					      &local_inferior,
 					      &local_task, &local_extra);
 	  gdb_assert (cond_string == nullptr);
 	  if (local_cond != nullptr)
@@ -14937,4 +15050,6 @@ This is useful for formatted output in user-defined commands."));
 					   "breakpoint");
   gdb::observers::thread_exit.attach (remove_threaded_breakpoints,
 				      "breakpoint");
+  gdb::observers::inferior_exit.attach (remove_inferior_breakpoints,
+					"breakpoint");
 }
diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
index 399bd037977..0ec7c4fe8e1 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -584,7 +584,7 @@ struct breakpoint_ops
 				  struct linespec_result *,
 				  gdb::unique_xmalloc_ptr<char>,
 				  gdb::unique_xmalloc_ptr<char>,
-				  enum bptype, enum bpdisp, int, int,
+				  enum bptype, enum bpdisp, int, int, int,
 				  int, int, int, int, unsigned);
 };
 
@@ -802,6 +802,10 @@ struct breakpoint
      care.  */
   int thread = -1;
 
+  /* Inferior number for inferior-specific breakpoint, or -1 if this
+     breakpoint is for all inferiors.  */
+  int inferior = -1;
+
   /* Ada task number for task-specific breakpoint, or 0 if don't
      care.  */
   int task = 0;
@@ -857,7 +861,7 @@ struct code_breakpoint : public breakpoint
 		   gdb::unique_xmalloc_ptr<char> cond_string,
 		   gdb::unique_xmalloc_ptr<char> extra_string,
 		   enum bpdisp disposition,
-		   int thread, int task, int ignore_count,
+		   int thread, int task, int inferior, int ignore_count,
 		   int from_tty,
 		   int enabled, unsigned flags,
 		   int display_canonical);
@@ -1673,6 +1677,8 @@ extern void breakpoint_set_silent (struct breakpoint *b, int silent);
 
 extern void breakpoint_set_thread (struct breakpoint *b, int thread);
 
+extern void breakpoint_set_inferior (struct breakpoint *b, int inferior);
+
 extern void breakpoint_set_task (struct breakpoint *b, int task);
 
 /* Clear the "inserted" flag in all breakpoints.  */
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 9c0018ea5c1..6f07193af77 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3504,6 +3504,58 @@
 space as a result of inferior 1 having executed a @code{vfork} call.
 @end table
 
+@menu
+* Inferior-Specific Breakpoints::	Controlling breakpoints
+@end menu
+
+@node Inferior-Specific Breakpoints
+@subsection Inferior-Specific Breakpoints
+
+When debugging multiple inferiors, you can choose whether to set
+breakpoints for all inferiors, or for a particular inferior.
+
+@table @code
+@cindex breakpoints and inferiors
+@cindex inferior-specific breakpoints
+@kindex break @dots{} inferior @var{inferior-id}
+@item break @var{locspec} inferior @var{inferior-id}
+@itemx break @var{locspec} inferior @var{inferior-id} if @dots{}
+@var{locspec} specifies a code location or locations in your program.
+@xref{Location Specifications}, for details.
+
+Use the qualifier @samp{inferior @var{inferior-id}} with a breakpoint
+command to specify that you only want @value{GDBN} to stop when a
+particular inferior reaches this breakpoint.  The @var{inferior-id}
+specifier is one of the inferior identifiers assigned by @value{GDBN},
+shown in the first column of the @samp{info inferiors} output.
+
+If you do not specify @samp{inferior @var{inferior-id}} when you set a
+breakpoint, the breakpoint applies to @emph{all} inferiors of your
+program.
+
+You can use the @code{inferior} qualifier on conditional breakpoints as
+well; in this case, place @samp{inferior @var{inferior-id}} before or
+after the breakpoint condition, like this:
+
+@smallexample
+(@value{GDBP}) break frik.c:13 inferior 2 if bartab > lim
+@end smallexample
+@end table
+
+Inferior-specific breakpoints are automatically deleted when
+@value{GDBN} detects that the corresponding inferior has exited.  For
+example:
+
+@smallexample
+(@value{GDBP}) c
+Inferior-specific breakpoint 3 deleted - inferior 2 has exited.
+@end smallexample
+
+A breakpoint can't be both inferior-specific and thread-specific
+(@pxref{Thread-Specific Breakpoints}); using both the @code{inferior}
+and @code{thread} keywords when creating a breakpoint will give an
+error.
+
 @node Threads
 @section Debugging Programs with Multiple Threads
 
@@ -4466,8 +4518,9 @@
 situation.
 
 It is also possible to insert a breakpoint that will stop the program
-only if a specific thread (@pxref{Thread-Specific Breakpoints})
-or a specific task (@pxref{Ada Tasks}) hits that breakpoint.
+only if a specific thread (@pxref{Thread-Specific Breakpoints}),
+specific inferior (@pxref{Inferior-Specific Breakpoints}), or a
+specific task (@pxref{Ada Tasks}) hits that breakpoint.
 
 @item break
 When called without any arguments, @code{break} sets a breakpoint at
@@ -4983,7 +5036,7 @@
 
 @table @code
 @kindex watch
-@item watch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}mask @var{maskvalue}@r{]} @r{[}task @var{task-id}@r{]}
+@item watch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}inferior @var{inferior-id}@r{]} @r{[}mask @var{maskvalue}@r{]} @r{[}task @var{task-id}@r{]}
 Set a watchpoint for an expression.  @value{GDBN} will break when the
 expression @var{expr} is written into by the program and its value
 changes.  The simplest (and the most popular) use of this command is
@@ -5000,8 +5053,10 @@
 that watchpoints restricted to a single thread in this way only work
 with Hardware Watchpoints.
 
-Similarly, if the @code{task} argument is given, then the watchpoint
-will be specific to the indicated Ada task (@pxref{Ada Tasks}).
+Similarly, if the @code{inferior} argument is given, then the
+watchpoint will trigger only for the specific inferior, or if the
+@code{task} argument is given, then the watchpoint will be specific to
+the indicated Ada task (@pxref{Ada Tasks}).
 
 Ordinarily a watchpoint respects the scope of variables in @var{expr}
 (see below).  The @code{-location} argument tells @value{GDBN} to
@@ -5030,12 +5085,12 @@
 @end smallexample
 
 @kindex rwatch
-@item rwatch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}mask @var{maskvalue}@r{]}
+@item rwatch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}inferior @var{inferior-id}@r{]} @r{[}mask @var{maskvalue}@r{]}
 Set a watchpoint that will break when the value of @var{expr} is read
 by the program.
 
 @kindex awatch
-@item awatch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}mask @var{maskvalue}@r{]}
+@item awatch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}inferior @var{inferior-id}@r{]} @r{[}mask @var{maskvalue}@r{]}
 Set a watchpoint that will break when @var{expr} is either read from
 or written into by the program.
 
@@ -7318,6 +7373,11 @@
 explictly asks for the thread list with the @code{info threads}
 command.
 
+A breakpoint can't be both thread-specific and inferior-specific
+(@pxref{Inferior-Specific Breakpoints}) using both the @code{thread}
+and @code{inferior} keywords when creating a breakpoint will give an
+error.
+
 @node Interrupted System Calls
 @subsection Interrupted System Calls 
 
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 7208af3ee7d..5420324ce2c 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -3281,7 +3281,10 @@
 A @code{gdb.Inferior} object has the following attributes:
 
 @defvar Inferior.num
-ID of inferior, as assigned by GDB.
+ID of inferior, as assigned by @value{GDBN}.  You can use this to make
+Python breakpoints inferior-specific, for example
+(@pxref{python_breakpoint_inferior,,The Breakpoint.inferior
+attribute}).
 @end defvar
 
 @anchor{gdbpy_inferior_connection}
@@ -6077,9 +6080,22 @@
 
 @anchor{python_breakpoint_thread}
 @defvar Breakpoint.thread
-If the breakpoint is thread-specific, this attribute holds the
-thread's global id.  If the breakpoint is not thread-specific, this
-attribute is @code{None}.  This attribute is writable.
+If the breakpoint is thread-specific (@pxref{Thread-Specific
+Breakpoints}), this attribute holds the thread's global id.  If the
+breakpoint is not thread-specific, this attribute is @code{None}.
+This attribute is writable.
+
+Only one of @code{Breakpoint.thread} or @code{Breakpoint.inferior} can
+be set to a valid id at any time, that is, a breakpoint can be thread
+specific, or inferior specific, but not both.
+@end defvar
+
+@anchor{python_breakpoint_inferior}
+@defvar Breakpoint.inferior
+If the breakpoint is inferior-specific (@pxref{Inferior-Specific
+Breakpoints}), this attribute holds the inferior's id.  If the
+breakpoint is not inferior-specific, this attribute is @code{None}.
+This attribute is writable.
 @end defvar
 
 @defvar Breakpoint.task
diff --git a/gdb/dummy-frame.c b/gdb/dummy-frame.c
index 784ac103eec..ad861d8d87f 100644
--- a/gdb/dummy-frame.c
+++ b/gdb/dummy-frame.c
@@ -132,6 +132,7 @@ pop_dummy_frame_bpt (struct breakpoint *b, struct dummy_frame *dummy)
   if (b->thread == dummy->id.thread->global_num
       && b->disposition == disp_del && b->frame_id == dummy->id.id)
     {
+      gdb_assert (b->inferior == -1);
       while (b->related_breakpoint != b)
 	delete_breakpoint (b->related_breakpoint);
 
diff --git a/gdb/elfread.c b/gdb/elfread.c
index ccee37406c8..cc1b339da75 100644
--- a/gdb/elfread.c
+++ b/gdb/elfread.c
@@ -968,7 +968,10 @@ elf_gnu_ifunc_resolver_stop (code_breakpoint *b)
       if (b_return->thread == thread_id
 	  && b_return->loc->requested_address == prev_pc
 	  && b_return->frame_id == prev_frame_id)
-	break;
+	{
+	  gdb_assert (b_return->inferior == -1);
+	  break;
+	}
     }
 
   if (b_return == b)
diff --git a/gdb/guile/scm-breakpoint.c b/gdb/guile/scm-breakpoint.c
index a7e043d847b..4c6fff070cf 100644
--- a/gdb/guile/scm-breakpoint.c
+++ b/gdb/guile/scm-breakpoint.c
@@ -779,6 +779,11 @@ gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue)
   else
     SCM_ASSERT_TYPE (0, newvalue, SCM_ARG2, FUNC_NAME, _("integer or #f"));
 
+  if (bp_smob->bp->inferior != -1)
+    scm_misc_error (FUNC_NAME,
+		    _("Cannot have both thread and inferior conditions "
+		      "on a breakpoint"), SCM_EOL);
+
   breakpoint_set_thread (bp_smob->bp, id);
 
   return SCM_UNSPECIFIED;
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 4d001b0ad50..e728a3481ad 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -787,4 +787,15 @@ extern void print_selected_inferior (struct ui_out *uiout);
 extern void switch_to_inferior_and_push_target
   (inferior *new_inf, bool no_connection, inferior *org_inf);
 
+/* Return true if ID is a valid global inferior number.  */
+
+inline bool
+valid_global_inferior_id (int id)
+{
+  for (inferior *inf : all_inferiors ())
+    if (inf->num == id)
+      return true;
+  return false;
+}
+
 #endif /* !defined (INFERIOR_H) */
diff --git a/gdb/infrun.c b/gdb/infrun.c
index edfb5ab0a91..6cfb097c03a 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -8004,6 +8004,7 @@ insert_exception_resume_breakpoint (struct thread_info *tp,
 	  frame = nullptr;
 
 	  bp->thread = tp->global_num;
+	  bp->inferior = -1;
 	  inferior_thread ()->control.exception_resume_breakpoint = bp;
 	}
     }
@@ -8037,6 +8038,7 @@ insert_exception_resume_from_probe (struct thread_info *tp,
   bp = set_momentary_breakpoint_at_pc (get_frame_arch (frame),
 				       handler, bp_exception_resume).release ();
   bp->thread = tp->global_num;
+  bp->inferior = -1;
   inferior_thread ()->control.exception_resume_breakpoint = bp;
 }
 
diff --git a/gdb/linespec.c b/gdb/linespec.c
index d3def7ae070..93cc68f28e8 100644
--- a/gdb/linespec.c
+++ b/gdb/linespec.c
@@ -254,9 +254,9 @@ enum linespec_token_type
 
 /* List of keywords.  This is NULL-terminated so that it can be used
    as enum completer.  */
-const char * const linespec_keywords[] = { "if", "thread", "task", "-force-condition", NULL };
+const char * const linespec_keywords[] = { "if", "thread", "task", "inferior", "-force-condition", NULL };
 #define IF_KEYWORD_INDEX 0
-#define FORCE_KEYWORD_INDEX 3
+#define FORCE_KEYWORD_INDEX 4
 
 /* A token of the linespec lexer  */
 
diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
index 1b10ccd5415..fc99bbf162c 100644
--- a/gdb/python/py-breakpoint.c
+++ b/gdb/python/py-breakpoint.c
@@ -280,11 +280,69 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure)
       return -1;
     }
 
+  if (self_bp->bp->inferior != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both thread and inferior conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
   breakpoint_set_thread (self_bp->bp, id);
 
   return 0;
 }
 
+/* Python function to set the inferior of a breakpoint.  */
+
+static int
+bppy_set_inferior (PyObject *self, PyObject *newvalue, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+  long id;
+
+  BPPY_SET_REQUIRE_VALID (self_bp);
+
+  if (newvalue == NULL)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("Cannot delete `inferior' attribute."));
+      return -1;
+    }
+  else if (PyLong_Check (newvalue))
+    {
+      if (!gdb_py_int_as_long (newvalue, &id))
+	return -1;
+
+      if (!valid_global_inferior_id (id))
+	{
+	  PyErr_SetString (PyExc_RuntimeError,
+			   _("Invalid inferior ID."));
+	  return -1;
+	}
+    }
+  else if (newvalue == Py_None)
+    id = -1;
+  else
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("The value of `inferior' must be an integer or None."));
+      return -1;
+    }
+
+  if (self_bp->bp->thread != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both thread and inferior conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
+  breakpoint_set_inferior (self_bp->bp, id);
+
+  return 0;
+}
+
 /* Python function to set the (Ada) task of a breakpoint.  */
 static int
 bppy_set_task (PyObject *self, PyObject *newvalue, void *closure)
@@ -689,6 +747,20 @@ bppy_get_thread (PyObject *self, void *closure)
   return gdb_py_object_from_longest (self_bp->bp->thread).release ();
 }
 
+/* Python function to get the breakpoint's inferior ID.  */
+static PyObject *
+bppy_get_inferior (PyObject *self, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+
+  BPPY_REQUIRE_VALID (self_bp);
+
+  if (self_bp->bp->inferior == -1)
+    Py_RETURN_NONE;
+
+  return gdb_py_object_from_longest (self_bp->bp->inferior).release ();
+}
+
 /* Python function to get the breakpoint's task ID (in Ada).  */
 static PyObject *
 bppy_get_task (PyObject *self, void *closure)
@@ -1333,6 +1405,11 @@ static gdb_PyGetSetDef breakpoint_object_getset[] = {
 If the value is a thread ID (integer), then this is a thread-specific breakpoint.\n\
 If the value is None, then this breakpoint is not thread-specific.\n\
 No other type of value can be used.", NULL },
+  { "inferior", bppy_get_inferior, bppy_set_inferior,
+    "Inferior ID for the breakpoint.\n\
+If the value is an inferior ID (integer), then this is an inferior-specific\n\
+breakpoint.  If the value is None, then this breakpoint is not\n\
+inferior-specific.  No other type of value can be used.", NULL },
   { "task", bppy_get_task, bppy_set_task,
     "Thread ID for the breakpoint.\n\
 If the value is a task ID (integer), then this is an Ada task-specific breakpoint.\n\
diff --git a/gdb/testsuite/gdb.linespec/cpcompletion.exp b/gdb/testsuite/gdb.linespec/cpcompletion.exp
index fbe19b49d43..f005707b9da 100644
--- a/gdb/testsuite/gdb.linespec/cpcompletion.exp
+++ b/gdb/testsuite/gdb.linespec/cpcompletion.exp
@@ -1259,8 +1259,8 @@ proc_with_prefix function-labels {} {
 }
 
 # Test that completion after a function name offers keyword
-# (if/task/thread/-force-condition) matches in linespec mode, and also
-# the explicit location options in explicit locations mode.
+# (if/inferior/task/thread/-force-condition) matches in linespec mode,
+# and also the explicit location options in explicit locations mode.
 
 proc_with_prefix keywords-after-function {} {
     set explicit_list \
diff --git a/gdb/testsuite/gdb.linespec/explicit.exp b/gdb/testsuite/gdb.linespec/explicit.exp
index 637470e6763..3845bab8bc6 100644
--- a/gdb/testsuite/gdb.linespec/explicit.exp
+++ b/gdb/testsuite/gdb.linespec/explicit.exp
@@ -412,6 +412,7 @@ namespace eval $testfile {
 	    "-qualified"
 	    "-source"
 	    "if"
+	    "inferior"
 	    "task"
 	    "thread"
 	}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
new file mode 100644
index 00000000000..26cb01911d2
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
@@ -0,0 +1,52 @@
+/* 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/>.  */
+
+volatile int global_var = 0;
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+static inline void __attribute__((__always_inline__))
+foo (void)
+{
+  int i;
+
+  for (i = 0; i < 10; ++i)
+    global_var = 0;
+}
+
+static void
+bar (void)
+{
+  global_var = 0;
+
+  foo ();
+}
+
+
+int
+main (void)
+{
+  global_var = 0;
+  foo ();
+  bar ();
+  stop_breakpt ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
new file mode 100644
index 00000000000..3ddb1a17446
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
@@ -0,0 +1,52 @@
+/* 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/>.  */
+
+static int bar (void);
+static int baz (void);
+static int foo (void);
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+int
+main (void)
+{
+  int ret = baz ();
+  stop_breakpt ();
+  return ret;
+}
+
+static int
+bar (void)
+{
+  return baz ();
+}
+
+static int
+foo (void)
+{
+  return 0;
+}
+
+static int
+baz (void)
+{
+  return foo ();
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
new file mode 100644
index 00000000000..4e80adf76ae
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
@@ -0,0 +1,182 @@
+# 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/>.
+
+# Test inferior-specific breakpoints.
+
+standard_testfile -1.c -2.c
+
+if {[use_gdb_stub]} {
+    return
+}
+
+set srcfile1 ${srcfile}
+set binfile1 ${binfile}-1
+set binfile2 ${binfile}-2
+
+if {[build_executable ${testfile}.exp ${binfile1} "${srcfile1}"] != 0} {
+    return -1
+}
+
+if {[build_executable ${testfile}.exp ${binfile2} "${srcfile2}"] != 0} {
+    return -1
+}
+
+# Start the first inferior.
+clean_restart ${binfile1}
+if {![runto_main]} {
+    return
+}
+
+# Add a second inferior, and start this one too.
+gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2"
+gdb_load $binfile2
+if {![runto_main]} {
+    return
+}
+
+# Try to create a breakpoint using both the 'inferior' and 'thread' keywords,
+# this should fail.  Try with the keywords in both orders just in case the
+# parser has a bug.
+gdb_test "break foo thread 1.1 inferior 1" \
+    "Cannot have both 'thread' and 'inferior' conditions on a breakpoint"
+gdb_test "break foo inferior 1 thread 1.1" \
+    "Cannot have both 'thread' and 'inferior' conditions on a breakpoint"
+
+# While we're here, check that we can't create a watchpoint with both thread
+# and inferior keywords.  As above, test with keywords in both orders.
+foreach type {watch rwatch awatch} {
+    gdb_test "$type global_var thread 1.1 inferior 1" \
+	"Cannot have both 'thread' and 'inferior' conditions on a watchpoint"
+    gdb_test "$type global_var inferior 1 thread 1.1" \
+	"Cannot have both 'thread' and 'inferior' conditions on a watchpoint"
+}
+
+# Clear out any other breakpoints.
+delete_breakpoints
+
+# Create an inferior-specific breakpoint.  Use gdb_test instead of
+# gdb_breakpoint here as we want to check the breakpoint was placed in
+# multiple locations.
+gdb_test "break foo inferior 1" \
+    "Breakpoint $decimal at $hex: foo\\. \\(3 locations\\)"
+
+set saw_header false
+set location_count 0
+set saw_inf_cond false
+gdb_test_multiple "info breakpoints" "" {
+    -re "\r\nNum\\s+\[^\r\n\]+\r\n" {
+	exp_continue
+    }
+
+    -re "^$decimal\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
+	set saw_header true
+	exp_continue
+    }
+
+    -re "^\\s+stop only in inferior 1\r\n" {
+	set saw_inf_cond true
+	exp_continue
+    }
+
+    -re "^$decimal\\.\[123\]\\s+y\\s+ $hex in foo at \[^\r\n\]+ inf \[12\]\r\n" {
+	incr location_count
+	exp_continue
+    }
+
+    -re "^$gdb_prompt $" {
+	with_test_prefix $gdb_test_name {
+	    gdb_assert { $saw_header }
+	    gdb_assert { $location_count == 3 }
+	    gdb_assert { $saw_inf_cond }
+	}
+    }
+}
+
+# Create a multi-inferior breakpoint to stop at.
+gdb_test "break stop_breakpt"
+
+# Now resume inferior 2, this should reach 'stop_breakpt'.
+gdb_test "continue" "hit Breakpoint $decimal\.$decimal, stop_breakpt \\(\\) .*" \
+    "continue in inferior 2"
+
+# Switch to inferior 1, and try there.
+gdb_test "inferior 1" ".*" \
+    "select inferior 1 to check the inferior-specific b/p works"
+gdb_test "continue " \
+    "Thread 1\\.${decimal}\[^\r\n\]* hit Breakpoint $decimal\.$decimal, foo \\(\\) .*" \
+    "first continue in inferior 1"
+
+# Now back to inferior 2, let the inferior exit, the inferior-specific
+# breakpoint should not be deleted.
+gdb_test "inferior 2" ".*" \
+    "switch back to allow inferior 2 to exit"
+gdb_test "continue" "Inferior 2 \[^\r\n\]+ exited normally.*" \
+    "allow inferior 2 to exit"
+
+gdb_test "inferior 1" ".*" \
+    "select inferior 1 to check inferior-specific b/p still works"
+gdb_test "continue " "hit Breakpoint $decimal\.$decimal, foo \\(\\) .*" \
+    "second continue in inferior 1"
+gdb_test "continue " "hit Breakpoint $decimal\.$decimal, stop_breakpt \\(\\) .*" \
+    "third continue in inferior 1"
+
+# Now allow inferior 1 to exit, the inferior specific breakpoint should be
+# deleted.
+gdb_test "continue" \
+    [multi_line \
+	 "\\\[Inferior 1 \[^\r\n\]+ exited normally\\\]" \
+	 "Inferior-specific breakpoint $decimal deleted - inferior 1 has exited\\."]
+
+set saw_header false
+set location_count 0
+set saw_inf_cond false
+gdb_test_multiple "info breakpoints" "info breakpoint after inferior 1 exited" {
+    -re "^info breakpoints\r\n" {
+	exp_continue
+    }
+
+    -re "^Num\\s+\[^\r\n\]+\r\n" {
+	exp_continue
+    }
+
+    -re "^$decimal\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
+	set saw_header true
+	exp_continue
+    }
+
+    -re "^\\s+stop only in inferior 1\r\n" {
+	# This should not happen, this breakpoint should have been deleted.
+	set saw_inf_cond true
+	exp_continue
+    }
+
+    -re "^\\s+breakpoint already hit 2 times\r\n" {
+	exp_continue
+    }
+
+    -re "^$decimal\\.\[12\]\\s+y\\s+ $hex in stop_breakpt at \[^\r\n\]+ inf \[12\]\r\n" {
+	incr location_count
+	exp_continue
+    }
+
+    -re "^$gdb_prompt $" {
+	with_test_prefix $gdb_test_name {
+	    gdb_assert { $saw_header }
+	    gdb_assert { $location_count == 2 }
+	    gdb_assert { !$saw_inf_cond }
+	}
+    }
+}
diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp
index 9535040e3a2..30e2ab1e4d8 100644
--- a/gdb/testsuite/gdb.python/py-breakpoint.exp
+++ b/gdb/testsuite/gdb.python/py-breakpoint.exp
@@ -111,6 +111,8 @@ proc_with_prefix test_bkpt_basic { } {
 	"Get Breakpoint List" 0
     gdb_test "python print (blist\[1\].thread)" \
 	"None" "Check breakpoint thread"
+    gdb_test "python print (blist\[1\].inferior)" \
+	"None" "Check breakpoint inferior"
     gdb_test "python print (blist\[1\].type == gdb.BP_BREAKPOINT)" \
 	"True" "Check breakpoint type"
     gdb_test "python print (blist\[0\].number)" \
@@ -213,6 +215,46 @@ proc_with_prefix test_bkpt_cond_and_cmds { } {
 	"check number of lines in commands"
 }
 
+# Test breakpoint thread and inferior attributes.
+proc_with_prefix test_bkpt_thread_and_inferior { } {
+    global srcfile testfile hex decimal
+
+    # Start with a fresh gdb.
+    clean_restart ${testfile}
+
+    if {![runto_main]} {
+	return 0
+    }
+
+    with_test_prefix "thread" {
+	delete_breakpoints
+	gdb_test "break multiply thread 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "1"
+	gdb_test "python print(bp.inferior)" "None"
+	gdb_test "python bp.inferior = 1" \
+	    "RuntimeError: Cannot have both thread and inferior conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.thread = None"
+	gdb_test_no_output "python bp.inferior = 1" \
+	    "set the inferior now the thread has been cleared"
+	gdb_test "info breakpoints" "stop only in inferior 1\r\n.*"
+    }
+
+    with_test_prefix "inferior" {
+	delete_breakpoints
+	gdb_test "break multiply inferior 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "None"
+	gdb_test "python print(bp.inferior)" "1"
+	gdb_test "python bp.thread = 1" \
+	    "RuntimeError: Cannot have both thread and inferior conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.inferior = None"
+	gdb_test_no_output "python bp.thread = 1" \
+	    "set the thread now the inferior has been cleared"
+	gdb_test "info breakpoints" "stop only in thread 1\r\n.*"
+    }
+}
+
 proc_with_prefix test_bkpt_invisible { } {
     global srcfile testfile hex decimal
 
@@ -849,6 +891,7 @@ proc_with_prefix test_bkpt_auto_disable { } {
 test_bkpt_basic
 test_bkpt_deletion
 test_bkpt_cond_and_cmds
+test_bkpt_thread_and_inferior
 test_bkpt_invisible
 test_hardware_breakpoints
 test_catchpoints
diff --git a/gdb/testsuite/lib/completion-support.exp b/gdb/testsuite/lib/completion-support.exp
index bf9c5ad352c..5f68d198adc 100644
--- a/gdb/testsuite/lib/completion-support.exp
+++ b/gdb/testsuite/lib/completion-support.exp
@@ -27,7 +27,7 @@ namespace eval completion {
     # List of all quote chars, including no-quote at all.
     variable maybe_quoted_list {"" "'" "\""}
 
-    variable keyword_list {"-force-condition" "if" "task" "thread"}
+    variable keyword_list {"-force-condition" "if" "inferior" "task" "thread"}
 
     variable explicit_opts_list \
 	{"-function" "-label" "-line" "-qualified" "-source"}
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCHv2 6/6] gdb: convert the 'start' breakpoint to use inferior keyword
  2023-01-20  9:46 ` [PATCHv2 " Andrew Burgess
                     ` (4 preceding siblings ...)
  2023-01-20  9:46   ` [PATCHv2 5/6] gdb: add inferior-specific breakpoints and watchpoints Andrew Burgess
@ 2023-01-20  9:46   ` Andrew Burgess
  2023-02-16 12:59     ` Aktemur, Tankut Baris
  2023-03-16 17:03   ` [PATCHv3 0/2] Inferior specific breakpoints Andrew Burgess
  6 siblings, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2023-01-20  9:46 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Now that we have support for inferior specific breakpoints, the
breakpoint created for the 'start' command can make use of this
keyword.

In most cases the observed functionality should be unchanged from a
user's point of view, though the code in GDB is a little cleaner now,
we no longer need to change the expression used based on the
language.

I do wonder if the current mechanism could run into problems if we had
different inferiors of different languages.  For example, if we had an
Ada inferior and a C inferior and followed a process a little like
this:

  1. Start C inferior, the inferior stops before main for some reason,

  2. Start the Ada inferior, this runs to main,

  3. I think when we hit main, the condition for both breakpoints will
  be evaluated, this will include evaluating the expression for the C
  'start' breakpoint, which uses '==' and is, I guess, not valid for
  Ada.

I haven't tried to create a testcase for this situation, but it's a
possibly interesting edge case.

One place where the observed behaviour is different, is that inferior
breakpoints, like thread breakpoints, will be auto-deleted when the
contained inferior exits.

As a result, if an inferior exits before hitting the start breakpoint,
then the 'start' breakpoint will be auto-deleted.

I have got a test that covers this situation.
---
 gdb/infcmd.c                                  | 10 +---
 .../gdb.base/start-inferior-specific-1.c      | 32 +++++++++++
 .../gdb.base/start-inferior-specific-2.c      | 22 ++++++++
 .../gdb.base/start-inferior-specific.exp      | 55 +++++++++++++++++++
 4 files changed, 112 insertions(+), 7 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/start-inferior-specific-1.c
 create mode 100644 gdb/testsuite/gdb.base/start-inferior-specific-2.c
 create mode 100644 gdb/testsuite/gdb.base/start-inferior-specific.exp

diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 7d5ec77ff57..7dfef4b5a23 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -429,13 +429,9 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
 	 have proper inferior-specific breakpoint support, in the breakpoint
 	 machinery.  We could then avoid inserting a breakpoint in the program
 	 spaces unrelated to this inferior.  */
-      const char *op
-	= ((current_language->la_language == language_ada
-	    || current_language->la_language == language_pascal
-	    || current_language->la_language == language_m2) ? "=" : "==");
-      std::string arg = string_printf
-	("-qualified %s if $_inferior %s %d", main_name (), op,
-	 current_inferior ()->num);
+      std::string arg = string_printf ("-qualified %s inferior %d",
+				       main_name (),
+				       current_inferior ()->num);
       tbreak_command (arg.c_str (), 0);
     }
 
diff --git a/gdb/testsuite/gdb.base/start-inferior-specific-1.c b/gdb/testsuite/gdb.base/start-inferior-specific-1.c
new file mode 100644
index 00000000000..1717a82b75d
--- /dev/null
+++ b/gdb/testsuite/gdb.base/start-inferior-specific-1.c
@@ -0,0 +1,32 @@
+/* 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 <unistd.h>
+#include <stdlib.h>
+
+__attribute__((constructor))
+static void
+ctor (void)
+{
+  exit (1);
+}
+
+int
+main ()
+{
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/start-inferior-specific-2.c b/gdb/testsuite/gdb.base/start-inferior-specific-2.c
new file mode 100644
index 00000000000..b69e218962a
--- /dev/null
+++ b/gdb/testsuite/gdb.base/start-inferior-specific-2.c
@@ -0,0 +1,22 @@
+/* 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/>.  */
+
+int
+main ()
+{
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/start-inferior-specific.exp b/gdb/testsuite/gdb.base/start-inferior-specific.exp
new file mode 100644
index 00000000000..7271f6d49d3
--- /dev/null
+++ b/gdb/testsuite/gdb.base/start-inferior-specific.exp
@@ -0,0 +1,55 @@
+# 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/>.
+
+# Check that a breakpoint created for the 'start' command, will be
+# silently deleted if the inferior being started exits before reaching
+# main.
+
+standard_testfile -1.c -2.c
+
+if {[use_gdb_stub]} {
+    return
+}
+
+set srcfile1 ${srcfile}
+
+set binfile1 ${binfile}-1
+set binfile2 ${binfile}-2
+
+if {[build_executable ${testfile}.exp ${binfile1} "${srcfile1}"] != 0} {
+    return -1
+}
+
+if {[build_executable ${testfile}.exp ${binfile2} "${srcfile2}"] != 0} {
+    return -1
+}
+
+clean_restart ${binfile1}
+
+# Start the first inferior, this will exit before hitting the 'start'
+# breakpoint in main.
+gdb_test "start" \
+    [multi_line \
+	 "Temporary breakpoint $decimal at \[^\r\n\]+" \
+	 "Starting program: \[^\r\n\]+" \
+	 "$inferior_exited_re with code 01\\\]"]
+
+# Now load a different binary and run it.  This inferior should run
+# all the way to completion without hitting a breakpoint in main.
+gdb_load ${binfile2}
+gdb_test "run" \
+    [multi_line \
+	 "Starting program: \[^\r\n\]+" \
+	 "$inferior_exited_re normally\\\]"]
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* RE: [PATCH 5/6] gdb: add inferior-specific breakpoints and watchpoints
  2023-01-19 19:13     ` Andrew Burgess
@ 2023-01-20 13:12       ` Aktemur, Tankut Baris
  0 siblings, 0 replies; 54+ messages in thread
From: Aktemur, Tankut Baris @ 2023-01-20 13:12 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On Thursday, January 19, 2023 8:14 PM, Andrew Burgess wrote:
> "Aktemur, Tankut Baris" <tankut.baris.aktemur@intel.com> writes:
> 
> > On Monday, November 28, 2022 12:26 PM, Andrew Burgess wrote:
> >> This commit extends the breakpoint mechanism to allow for inferior
> >> specific breakpoints (and watchpoints).
> >>
> >> As GDB gains better support for multiple connections, and so for
> >> running multiple (possibly unrelated) inferiors, then it is not hard
> >
> > Nit: IMHO, removing "then" makes the sentence sound better.
> >
> >> to imagine that a user might wish to create breakpoints that apply to
> >> any thread in a single inferior.  To achieve this currently, the user
> >> would need to create a condition possibly making use of the $_inferior
> >> convenience variable, which, though functional, isn't the most user
> >> friendly.
> >
> > An important difference of an inferior-specific breakpoint wrt using
> > conditions that contain the 'thread' keyword or the '$_inferior' variable
> > could be that the breakpoints would not be inserted at all on other inferiors.
> > For inferiors that have a large number of threads, this could save a
> > substantial amount of overhead of stopping, evaluating the condition, and
> > resuming.  IMHO, it is worth considering this for inferior-specific breakpoints.
> >
> > In a downstream debugger, we had included this feature:
> > https://github.com/intel/gdb/commit/7d87ac91308cd7a8984ba7b0e333a6689790972d
> > (Please see the modifications in 'create_breakpoint').
> 
> Neat.  I wasn't aware Intel had already worked on this feature.
> 
> I had also thought about not inserting breakpoints into non-matching
> inferiors.  In the end I decided to leave that for a follow up patch,
> but it's nice to see that Intel have been doing this for a few years now.

We should&could have submitted the patch to upstream earlier, but this was
delayed due to several factors.  Sorry about that.  This could have saved some
efforts.

Avoiding the insertion of the bp in other inferiors can certainly be done
in a follow-up, IMHO.

> >
> > With this perspective, I also think that allowing the use of both 'thread'
> > and 'inferior' clauses makes sense, because they would have different advantages.
> 
> Except, isn't the thread-id passed to a 'thread' condition a global
> thread-id?  i.e. "break foo thread 1" isn't thread 1 in every inferior,
> it's GDB's global thread 1, which is one thread in one inferior.
>
> So we could (if we implemented it) already limit into which inferiors a
> thread specific breakpoint is inserted by just figuring out which
> inferior that thread is in.

Yes, this would make sense.
 
> I think it makes sense, at least initially, to prevent use of 'thread'
> and 'inferior' together.  If we decide to relax this restriction later,
> then that's no problem.  It's much harder to add more restrictions
> later.

Ok, that's right.

...
> >> +	return 0
> >
> > As I wrote previously, the testsuite does not look consistent
> > about what to return here.  But "-1" makes more sense to me, because
> > not being able to run to main sounds like a major problem.
> 
> The difference in the other patch where you pointed this out is that the
> return as at the top level of the script (I fixed that other case as per
> your suggestion).
> 
> In this case the return is from a function.  We don't actually check the
> return value, but I'd rather leave this return consistent with all the
> other similar return statements, in all the other functions, in this
> test script.

I missed that this was returning from a function.  I see your point and it
makes sense.

> I've made all the other updates you suggested, these will be included in
> a V2 series shortly.

I'll go over the series once more as soon as I can.

Thanks,
-Baris


Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 4/6] gdb: error if 'thread' or 'task' keywords are overused
  2023-01-20  9:46   ` [PATCHv2 4/6] gdb: error if 'thread' or 'task' keywords are overused Andrew Burgess
@ 2023-01-20 13:22     ` Eli Zaretskii
  2023-02-02 14:08       ` Andrew Burgess
  2023-02-02 18:21     ` Pedro Alves
  1 sibling, 1 reply; 54+ messages in thread
From: Eli Zaretskii @ 2023-01-20 13:22 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches

> Cc: Andrew Burgess <aburgess@redhat.com>
> Date: Fri, 20 Jan 2023 09:46:27 +0000
> From: Andrew Burgess via Gdb-patches <gdb-patches@sourceware.org>
> 
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -9,6 +9,15 @@
>    This support requires that GDB be built with Python scripting
>    enabled.
>  
> +* For the break command, multiple uses of the 'thread' or 'task'
> +  keywords will now give an error instead of just using the thread or
> +  task id from the last instance of the keyword.
> +
> +* For the watch command, multiple uses of the 'task' keyword will now
> +  give an error instead of just using the task id from the last
> +  instance of the keyword.  The 'thread' keyword already gave an error
> +  when used multiple times with the watch command, this remains unchanged.

This part is OK, but I wonder whether we should provide an example of
such incorrect usage which will now be flagged, because I'm not sure
everyone will understand what "multiple uses" means in this context.

Thanks.

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 5/6] gdb: add inferior-specific breakpoints and watchpoints
  2023-01-20  9:46   ` [PATCHv2 5/6] gdb: add inferior-specific breakpoints and watchpoints Andrew Burgess
@ 2023-01-20 13:25     ` Eli Zaretskii
  2023-02-02 19:17     ` Pedro Alves
  2023-02-16 12:56     ` Aktemur, Tankut Baris
  2 siblings, 0 replies; 54+ messages in thread
From: Eli Zaretskii @ 2023-01-20 13:25 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches

> Cc: Andrew Burgess <aburgess@redhat.com>
> Date: Fri, 20 Jan 2023 09:46:28 +0000
> From: Andrew Burgess via Gdb-patches <gdb-patches@sourceware.org>
> 
> This commit extends the breakpoint mechanism to allow for inferior
> specific breakpoints (and watchpoints).
> 
> As GDB gains better support for multiple connections, and so for
> running multiple (possibly unrelated) inferiors, then it is not hard
> to imagine that a user might wish to create breakpoints that apply to
> any thread in a single inferior.  To achieve this currently, the user
> would need to create a condition possibly making use of the $_inferior
> convenience variable, which, though functional, isn't the most user
> friendly.
> 
> This commit adds a new 'inferior' keyword that allows for the creation
> of inferior specific breakpoints.
> 
> Like thread specific breakpoints, the inferior specific breakpoints
> are automatically deleted once the inferior in question exits.
> ---
>  gdb/NEWS                                      |   7 +
>  gdb/breakpoint.c                              | 171 +++++++++++++---
>  gdb/breakpoint.h                              |  10 +-
>  gdb/doc/gdb.texinfo                           |  74 ++++++-
>  gdb/doc/python.texi                           |  24 ++-
>  gdb/dummy-frame.c                             |   1 +
>  gdb/elfread.c                                 |   5 +-
>  gdb/guile/scm-breakpoint.c                    |   5 +
>  gdb/inferior.h                                |  11 ++
>  gdb/infrun.c                                  |   2 +
>  gdb/linespec.c                                |   4 +-
>  gdb/python/py-breakpoint.c                    |  77 ++++++++
>  gdb/testsuite/gdb.linespec/cpcompletion.exp   |   4 +-
>  gdb/testsuite/gdb.linespec/explicit.exp       |   1 +
>  .../gdb.multi/inferior-specific-bp-1.c        |  52 +++++
>  .../gdb.multi/inferior-specific-bp-2.c        |  52 +++++
>  .../gdb.multi/inferior-specific-bp.exp        | 182 ++++++++++++++++++
>  gdb/testsuite/gdb.python/py-breakpoint.exp    |  43 +++++
>  gdb/testsuite/lib/completion-support.exp      |   2 +-
>  19 files changed, 680 insertions(+), 47 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
>  create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
>  create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp.exp

Thanks, the documentation parts are OK.

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 4/6] gdb: error if 'thread' or 'task' keywords are overused
  2023-01-20 13:22     ` Eli Zaretskii
@ 2023-02-02 14:08       ` Andrew Burgess
  2023-02-02 14:31         ` Eli Zaretskii
  0 siblings, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2023-02-02 14:08 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: gdb-patches

Eli Zaretskii <eliz@gnu.org> writes:

>> Cc: Andrew Burgess <aburgess@redhat.com>
>> Date: Fri, 20 Jan 2023 09:46:27 +0000
>> From: Andrew Burgess via Gdb-patches <gdb-patches@sourceware.org>
>> 
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -9,6 +9,15 @@
>>    This support requires that GDB be built with Python scripting
>>    enabled.
>>  
>> +* For the break command, multiple uses of the 'thread' or 'task'
>> +  keywords will now give an error instead of just using the thread or
>> +  task id from the last instance of the keyword.
>> +
>> +* For the watch command, multiple uses of the 'task' keyword will now
>> +  give an error instead of just using the task id from the last
>> +  instance of the keyword.  The 'thread' keyword already gave an error
>> +  when used multiple times with the watch command, this remains unchanged.
>
> This part is OK, but I wonder whether we should provide an example of
> such incorrect usage which will now be flagged, because I'm not sure
> everyone will understand what "multiple uses" means in this context.

I've now written:

  * For the break command, multiple uses of the 'thread' or 'task'
    keywords will now give an error instead of just using the thread or
    task id from the last instance of the keyword. e.g.:
      break foo thread 1 thread 2
    will now give an error rather than using 'thread 2'.
  
  * For the watch command, multiple uses of the 'task' keyword will now
    give an error instead of just using the task id from the last
    instance of the keyword. e.g.:
      watch my_var task 1 task 2
    will now give an error rather than using 'task 2'.  The 'thread'
    keyword already gave an error when used multiple times with the
    watch command, this remains unchanged.

How's that?

Thanks,
Andrew


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 4/6] gdb: error if 'thread' or 'task' keywords are overused
  2023-02-02 14:08       ` Andrew Burgess
@ 2023-02-02 14:31         ` Eli Zaretskii
  0 siblings, 0 replies; 54+ messages in thread
From: Eli Zaretskii @ 2023-02-02 14:31 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches

> From: Andrew Burgess <aburgess@redhat.com>
> Cc: gdb-patches@sourceware.org
> Date: Thu, 02 Feb 2023 14:08:08 +0000
> 
>   * For the break command, multiple uses of the 'thread' or 'task'
>     keywords will now give an error instead of just using the thread or
>     task id from the last instance of the keyword. e.g.:
>       break foo thread 1 thread 2
>     will now give an error rather than using 'thread 2'.
>   
>   * For the watch command, multiple uses of the 'task' keyword will now
>     give an error instead of just using the task id from the last
>     instance of the keyword. e.g.:
>       watch my_var task 1 task 2
>     will now give an error rather than using 'task 2'.  The 'thread'
>     keyword already gave an error when used multiple times with the
>     watch command, this remains unchanged.
> 
> How's that?

That's fine, but please capitalize "E.g." and leave 2 spaces before
it.

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 1/6] gdb/remote: announce thread exit events for remote targets
  2023-01-20  9:46   ` [PATCHv2 1/6] gdb/remote: announce thread exit events for remote targets Andrew Burgess
@ 2023-02-02 17:50     ` Pedro Alves
  2023-02-04 15:46       ` Andrew Burgess
  0 siblings, 1 reply; 54+ messages in thread
From: Pedro Alves @ 2023-02-02 17:50 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 2023-01-20 9:46 a.m., Andrew Burgess via Gdb-patches wrote:
> For some reason the "[Thread XXXX exited]" messages are not printed
> inside thread.c from functions like delete_thread, etc as might be
> expected.  Instead, each target seems to print the message before
> calling delete_thread.
> 
> This doesn't seem ideal, and I can't help but feel that the printing
> should be moved into thread.c, however, I have not tried to do that in
> this commit, as I suspect there will be lots of fallout that needs
> fixing up.

I had done exactly that in the step over clone/exit series:

  Centralize "[Thread ...exited]" notifications
  https://inbox.sourceware.org/gdb-patches/20221212203101.1034916-1-pedro@palves.net/T/#m9f9858077e6b2b817148bfb9c32ec15bec724931

Pedro Alves

> 
> Instead, in this commit, I have added the printing code into remote.c,
> so that the remote target will now correctly tell the user when a
> thread has exited.
> 
> This fixes some test failures in gdb.threads/thread-specific-bp.exp
> when run with the native-gdbserver and native-extended-gdbserver
> board.
> 
> When using the native-extended-gdbserver board I still see 1 test
> failure, but I think this is not related to the issue fixed in this
> commit, so I'm ignoring that for now.
> ---
>  gdb/remote.c | 4 ++++
>  1 file changed, 4 insertions(+)
> 
> diff --git a/gdb/remote.c b/gdb/remote.c
> index 0a6e293c095..4a508981a96 100644
> --- a/gdb/remote.c
> +++ b/gdb/remote.c
> @@ -3977,6 +3977,10 @@ remote_target::update_thread_list ()
>  	      if (has_single_non_exited_thread (tp->inf))
>  		continue;
>  
> +	      if (print_thread_events)
> +		gdb_printf (_("[%s exited]\n"),
> +			    target_pid_to_str (tp->ptid).c_str ());
> +
>  	      /* Not found.  */
>  	      delete_thread (tp);
>  	    }
> 


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 3/6] gdb: fix display of thread condition for multi-location breakpoints
  2023-01-20  9:46   ` [PATCHv2 3/6] gdb: fix display of thread condition for multi-location breakpoints Andrew Burgess
@ 2023-02-02 18:13     ` Pedro Alves
  2023-02-06 14:48       ` Andrew Burgess
  0 siblings, 1 reply; 54+ messages in thread
From: Pedro Alves @ 2023-02-02 18:13 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 2023-01-20 9:46 a.m., Andrew Burgess via Gdb-patches wrote:
> If a breakpoint with multiple locations has a thread condition, then
> the 'info breakpoints' output is a little messed up, here's an example
> of the current output:
> 
>   (gdb) break foo thread 1
>   Breakpoint 2 at 0x401114: foo. (3 locations)
>   (gdb) break bar thread 1
>   Breakpoint 3 at 0x40110a: file /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c, line 32.
>   (gdb) info breakpoints
>   Num     Type           Disp Enb Address            What
>   2       breakpoint     keep y   <MULTIPLE>          thread 1
>           stop only in thread 1
>   2.1                         y   0x0000000000401114 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>   2.2                         y   0x0000000000401146 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>   2.3                         y   0x0000000000401168 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>   3       breakpoint     keep y   0x000000000040110a in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32 thread 1
>           stop only in thread 1
> 
> Notice that, at the end of the location for breakpoint 3, the 'thread
> 1' condition is printed, but this is then repeated on the next line
> with 'stop only in thread 1'.
> 
> In contrast, for breakpoint 2, the 'thread 1' appears randomly, in the
> "What" column, though slightly offset, non of the separate locations
> have the 'thread 1' information.  Additionally for breakpoint 2 we
> also get a 'stop only in thread 1' line.
> 
> There's two things going on here.  First the randomly placed 'thread
> 1' for breakpoint 2 is due to a bug in print_one_breakpoint_location,
> where we check the variable part_of_multiple instead of
> header_of_multiple.
> 
> If I fix this oversight, then the output is now:
> 
>   (gdb) break foo thread 1
>   Breakpoint 2 at 0x401114: foo. (3 locations)
>   (gdb) break bar thread 1
>   Breakpoint 3 at 0x40110a: file /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c, line 32.
>   (gdb) info breakpoints
>   Num     Type           Disp Enb Address            What
>   2       breakpoint     keep y   <MULTIPLE>
>           stop only in thread 1
>   2.1                         y   0x0000000000401114 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
>   2.2                         y   0x0000000000401146 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
>   2.3                         y   0x0000000000401168 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
>   3       breakpoint     keep y   0x000000000040110a in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32 thread 1
>           stop only in thread 1
> 
> The 'thread 1' condition is now displayed at the end of each location,
> which makes the output the same for single location breakpoints and
> multi-location breakpoints.
> 
> However, there's still some duplication here.  Both breakpoints 2 and
> 3 include a 'stop only in thread 1' line, and it feels like the
> additional 'thread 1' is redundant.  In fact, there's a comment to
> this very effect in the code:
> 
>   /* FIXME: This seems to be redundant and lost here; see the
>      "stop only in" line a little further down.  */
> 
> So, lets fix this FIXME.  The new plan is to remove all the trailing
> 'thread 1' markers from the CLI output, we now get this:
> 
>   (gdb) break foo thread 1
>   Breakpoint 2 at 0x401114: foo. (3 locations)
>   (gdb) break bar thread 1
>   Breakpoint 3 at 0x40110a: file /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c, line 32.
>   (gdb) info breakpoints
>   Num     Type           Disp Enb Address            What
>   2       breakpoint     keep y   <MULTIPLE>
>           stop only in thread 1
>   2.1                         y   0x0000000000401114 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>   2.2                         y   0x0000000000401146 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>   2.3                         y   0x0000000000401168 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>   3       breakpoint     keep y   0x000000000040110a in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32
>           stop only in thread 1
> 

This is the right direction, IMO.  The "thread 1" string is at best part of the breakpoint's
location spec, not of the code locations that spec resolved to.

If we went forward with my proposal to always show breakpoints using the multi-locations mode,
and, to include the location spec in the "What" column of the breakpoint header, we could show:

   (gdb) info breakpoints
   Num     Type           Disp Enb Address            What
   2       breakpoint     keep y                      foo thread 1
           stop only in thread 1   
    2.1                        y   0x0000000000401114  in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
    2.2                        y   0x0000000000401146  in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
    2.3                        y   0x0000000000401168  in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
   3       breakpoint     keep y                      bar thread 1
           stop only in thread 1
    3.1    breakpoint     keep y   0x000000000040110a  in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32

... for instance.  Or not include the "thread 1".  But my point is that if we were to show it, that's where we would
show it, not in the breapoint locations.

> All of the above points are also true for the Ada 'task' breakpoint
> condition, and the changes I've made also update how the task
> information is printed, though in the case of the Ada task there was
> no 'stop only in task XXX' line printed, so I've added one of those.
> 
> Obviously it can't be quite that easy.  For MI backwards compatibility
> I've retained the existing code (but now only for MI like outputs),
> which ensures we should generate backwards compatible output.
> 
> I've extended an Ada test to cover the new task related output, and
> updated all the tests I could find that checked for the old output.
> ---
>  gdb/breakpoint.c                              | 30 ++++----
>  gdb/testsuite/gdb.ada/tasks.exp               | 15 ++--
>  gdb/testsuite/gdb.base/save-bp.exp            |  2 +-
>  gdb/testsuite/gdb.base/thread-bp-multi-loc.c  | 44 ++++++++++++
>  .../gdb.base/thread-bp-multi-loc.exp          | 72 +++++++++++++++++++
>  5 files changed, 141 insertions(+), 22 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.base/thread-bp-multi-loc.c
>  create mode 100644 gdb/testsuite/gdb.base/thread-bp-multi-loc.exp
> 
> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
> index 00cc2ab401c..b2cd89511fb 100644
> --- a/gdb/breakpoint.c
> +++ b/gdb/breakpoint.c
> @@ -6468,20 +6468,19 @@ print_one_breakpoint_location (struct breakpoint *b,
>        output_thread_groups (uiout, "thread-groups", inf_nums, mi_only);
>      }
>  
> -  if (!part_of_multiple)
> +  /* In the MI output, each location of a thread or task specific
> +     breakpoint is with the relevant thread or task ID.  This is done for
> +     backwards compatibility reasons.

Is "is with" idiomatic English meaning "includes"?  I don't think I ever heard that.
Or is some word missing somewhere?  I'd suggest using "includes" or some
such instead.

> +
> +     For the CLI output, the thread/task information is printed on a
> +     separate line, see the 'stop only in thread' and 'stop only in task'
> +     output below.  */
> +  if (!header_of_multiple && uiout->is_mi_like_p ())
>      {
>        if (b->thread != -1)
> -	{
> -	  /* FIXME: This seems to be redundant and lost here; see the
> -	     "stop only in" line a little further down.  */
> -	  uiout->text (" thread ");
> -	  uiout->field_signed ("thread", b->thread);
> -	}
> +	uiout->field_signed ("thread", b->thread);
>        else if (b->task != 0)
> -	{
> -	  uiout->text (" task ");
> -	  uiout->field_signed ("task", b->task);
> -	}
> +	uiout->field_signed ("task", b->task);
>      }
>  
>    uiout->text ("\n");
> @@ -6536,7 +6535,14 @@ print_one_breakpoint_location (struct breakpoint *b,
>  	}
>        uiout->text ("\n");
>      }
> -  
> +
> +  if (!part_of_multiple && b->task != 0)
> +    {
> +      uiout->text ("\tstop only in task ");
> +      uiout->field_signed ("task", b->task);
> +      uiout->text ("\n");
> +    }
> +
>    if (!part_of_multiple)
>      {
>        if (b->hit_count)
> diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp
> index a9b58f20cf6..23bf3937a20 100644
> --- a/gdb/testsuite/gdb.ada/tasks.exp
> +++ b/gdb/testsuite/gdb.ada/tasks.exp
> @@ -46,21 +46,18 @@ gdb_test "info tasks" \
>  # breakpoint in the list that matched the triggered-breakpoint's
>  # address, no matter which task it was specific to.
>  gdb_test "break break_me task 1" "Breakpoint .* at .*"
> +gdb_test "info breakpoints" "foo.adb:${decimal}\r\n\\s+stop only in task 1" \
> +    "check info breakpoints for task 1 breakpoint"
>  
>  # Now, insert a breakpoint that should stop only if task 3 stops, and
>  # extract its number.
> -set bp_number -1
> -set test "break break_me task 3"
> -gdb_test_multiple $test $test {
> -    -re "Breakpoint (.*) at .*$gdb_prompt $" {
> -	set bp_number $expect_out(1,string)
> -	pass $test
> -    }
> -}
> -
> +gdb_breakpoint "break_me task 3"
> +set bp_number [get_integer_valueof "\$bpnum" -1]
>  if {$bp_number < 0} {
>      return
>  }
> +gdb_test "info breakpoints" "foo.adb:${decimal}\r\n\\s+stop only in task 3" \
> +    "check info breakpoints for task 3 breakpoint"
>  
>  # Continue to that breakpoint.  Task 2 should hit it first, and GDB
>  # is expected to ignore that hit and resume the execution.  Only then
> diff --git a/gdb/testsuite/gdb.base/save-bp.exp b/gdb/testsuite/gdb.base/save-bp.exp
> index a39712c7f5c..41d71837fb6 100644
> --- a/gdb/testsuite/gdb.base/save-bp.exp
> +++ b/gdb/testsuite/gdb.base/save-bp.exp
> @@ -79,7 +79,7 @@ gdb_test_sequence "info break" "info break" [list				\
>    "\[\r\n\]+Num +Type +Disp +Enb +Address +What"				\
>    "\[\r\n\]+$bp_row_start break_me at \[^\r\n\]*$srcfile:\[0-9\]+"		\
>    "\[\r\n\]+$bp_row_start main at \[^\r\n\]*$srcfile:$loc_bp2"			\
> -  "\[\r\n\]+$bp_row_start main at \[^\r\n\]*$srcfile:$loc_bp3 +thread 1"	\
> +  "\[\r\n\]+$bp_row_start main at \[^\r\n\]*$srcfile:$loc_bp3"			\
>    "\[\r\n\]+\[ \t]+stop only in thread 1"					\
>    "\[\r\n\]+$bp_row_start main at \[^\r\n\]*$srcfile:$loc_bp4"			\
>    "\[\r\n\]+\[ \t\]+stop only if i == 1( \\((host|target) evals\\))?"		\
> diff --git a/gdb/testsuite/gdb.base/thread-bp-multi-loc.c b/gdb/testsuite/gdb.base/thread-bp-multi-loc.c
> new file mode 100644
> index 00000000000..cab009c39ec
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/thread-bp-multi-loc.c
> @@ -0,0 +1,44 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2022 Free Software Foundation, Inc.

Needs to include 2023 in range now.

> +
> +   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/>.  */
> +
> +volatile int global_var = 0;
> +
> +__attribute__((__always_inline__)) static inline void
> +foo (void)
> +{
> +  int i;
> +
> +  for (i = 0; i < 10; ++i)
> +    global_var = i;
> +}
> +
> +static void
> +bar (void)
> +{
> +  global_var = 0;
> +  foo ();
> +}
> +
> +int
> +main (void)
> +{
> +  global_var = 0;
> +  foo ();
> +  bar ();
> +  foo ();
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp b/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp
> new file mode 100644
> index 00000000000..9665fe9e21f
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp
> @@ -0,0 +1,72 @@
> +# Copyright 2022 Free Software Foundation, Inc.

Ditto.

> +
> +# 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/>.
> +
> +# Create a multi-location breakpoint with a thread condition, then check the
> +# output of 'info breakpoints' to ensure that the thread condition is
> +# displayed correctly.
> +
> +standard_testfile
> +
> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
> +    return -1
> +}
> +
> +if {![runto_main]} {
> +    return -1
> +}
> +
> +delete_breakpoints
> +
> +gdb_breakpoint "foo thread 1"
> +set bp_number [get_integer_valueof "\$bpnum" 0]
> +if { $bp_number == 0 } {
> +    unresolved "breakpoint not placed correctly"
> +    return -1
> +}
> +
> +set saw_header false
> +set saw_cond false
> +set loc_count 0
> +gdb_test_multiple "info breakpoints" "" {
> +    -re "\r\nNum\\s+\[^\r\n\]+\r\n" {
> +	exp_continue
> +    }
> +
> +    -re "^$bp_number\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
> +	set saw_header true
> +	exp_continue
> +    }
> +
> +    -re "^\\s+stop only in thread 1\r\n" {
> +	set saw_cond true
> +	exp_continue
> +    }
> +
> +    -re "^$bp_number\\.\[123\]\\s+\[^\r\n\]+:${decimal}\r\n" {
> +	incr loc_count
> +	exp_continue
> +    }
> +
> +    -re "^$gdb_prompt $" {
> +	with_test_prefix $gdb_test_name {
> +	    gdb_assert { $saw_header } \
> +		"saw header line"
> +	    gdb_assert { $saw_cond } \
> +		"saw b/p condition line"
> +	    gdb_assert { $loc_count == 3 } \
> +		"saw all three locations"

It's better if the tests are written such that FAIL/PASSes match.  In this case,
if gdb_test_multiple hits an internal pattern, then we'll have a single

 FAIL: info breakpoints

To fix that, we should merge those three gdb_asserts into a single gdb_assert,
and we should $gdb_test_name for its test name, along with passing a meaningful
test name to gdb_test_multiple, so internal FAILs get that meaningful name
as well.  If we want to show the individual conditions, that can
still be done by outputting them to the log.

Pedro Alves

> +	}
> +    }
> +}
> 


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 4/6] gdb: error if 'thread' or 'task' keywords are overused
  2023-01-20  9:46   ` [PATCHv2 4/6] gdb: error if 'thread' or 'task' keywords are overused Andrew Burgess
  2023-01-20 13:22     ` Eli Zaretskii
@ 2023-02-02 18:21     ` Pedro Alves
  2023-02-03 16:41       ` Andrew Burgess
  2023-02-06 11:06       ` Andrew Burgess
  1 sibling, 2 replies; 54+ messages in thread
From: Pedro Alves @ 2023-02-02 18:21 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 2023-01-20 9:46 a.m., Andrew Burgess via Gdb-patches wrote:
> When creating a breakpoint or watchpoint, the 'thread' and 'task'
> keywords can be used to create a thread or task specific breakpoint or
> watchpoint.
> 
> Currently, a thread or task specific breakpoint can only apply for a
> single thread or task, if multiple threads or tasks are specified when
> creating the breakpoint (or watchpoint), then the last specified id
> will be used.
> 
> The exception to the above is that when the 'thread' keyword is used
> during the creation of a watchpoint, GDB will give an error if
> 'thread' is given more than once.
> 
> In this commit I propose making this behaviour consistent, if the
> 'thread' or 'task' keywords are used more than once when creating
> either a breakpoint or watchpoint, then GDB will give an error.

The patch looks fine, but does it make sense to allow both thread and task
at the same time?

For instance, with gdb.ada/tasks.exp :

(gdb) b foo thread 1 task 2
Breakpoint 1 at 0x555555557bd6: file /home/pedro/gdb/rocm/gdb/src/gdb/testsuite/gdb.ada/tasks/foo.adb, line 16.
(gdb) info breakpoints 
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000555555557bd6 in foo at /home/pedro/gdb/rocm/gdb/src/gdb/testsuite/gdb.ada/tasks/foo.adb:16 thread 1
        stop only in thread 1

Pedro Alves

> 
> I haven't updated the manual, we don't explicitly say that these
> keywords can be repeated, and (to me), given the keyword takes a
> single id, I don't think it makes much sense to repeat the keyword.
> As such, I see this more as adding a missing error to GDB, rather than
> making some big change.  However, I have added an entry to the NEWS
> file as I guess it is possible that some people might hit this new
> error with an existing (I claim, badly written) GDB script.
> 
> I've added some new tests to check for the new error.
> 
> Just one test needed updating, gdb.linespec/keywords.exp, this test
> did use the 'thread' keyword twice, and expected the breakpoint to be
> created.  Looking at what this test was for though, it was checking
> the use of '-force-condition', and I don't think that being able to
> repeat 'thread' was actually a critical part of this test.
> 
> As such, I've updated this test to expect the error when 'thread' is
> repeated.
> ---
>  gdb/NEWS                                         |  9 +++++++++
>  gdb/breakpoint.c                                 |  9 +++++++++
>  gdb/testsuite/gdb.ada/tasks.exp                  |  4 ++++
>  gdb/testsuite/gdb.linespec/keywords.exp          | 10 ++++++++--
>  gdb/testsuite/gdb.threads/thread-specific-bp.exp |  4 ++++
>  gdb/testsuite/gdb.threads/watchthreads2.exp      |  3 +++
>  6 files changed, 37 insertions(+), 2 deletions(-)
> 
> diff --git a/gdb/NEWS b/gdb/NEWS
> index c0aac212e30..fb49f62f7e6 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -9,6 +9,15 @@
>    This support requires that GDB be built with Python scripting
>    enabled.
>  
> +* For the break command, multiple uses of the 'thread' or 'task'
> +  keywords will now give an error instead of just using the thread or
> +  task id from the last instance of the keyword.
> +
> +* For the watch command, multiple uses of the 'task' keyword will now
> +  give an error instead of just using the task id from the last
> +  instance of the keyword.  The 'thread' keyword already gave an error
> +  when used multiple times with the watch command, this remains unchanged.
> +
>  * New commands
>  
>  maintenance print record-instruction [ N ]
> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
> index b2cd89511fb..1400fd49642 100644
> --- a/gdb/breakpoint.c
> +++ b/gdb/breakpoint.c
> @@ -8801,6 +8801,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
>  	  const char *tmptok;
>  	  struct thread_info *thr;
>  
> +	  if (*thread != -1)
> +	    error(_("You can specify only one thread."));
> +
>  	  tok = end_tok + 1;
>  	  thr = parse_thread_id (tok, &tmptok);
>  	  if (tok == tmptok)
> @@ -8812,6 +8815,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
>  	{
>  	  char *tmptok;
>  
> +	  if (*task != 0)
> +	    error(_("You can specify only one task."));
> +
>  	  tok = end_tok + 1;
>  	  *task = strtol (tok, &tmptok, 0);
>  	  if (tok == tmptok)
> @@ -10094,6 +10100,9 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
>  	    {
>  	      char *tmp;
>  
> +	      if (task != 0)
> +		error(_("You can specify only one task."));
> +
>  	      task = strtol (value_start, &tmp, 0);
>  	      if (tmp == value_start)
>  		error (_("Junk after task keyword."));
> diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp
> index 23bf3937a20..4441d92719c 100644
> --- a/gdb/testsuite/gdb.ada/tasks.exp
> +++ b/gdb/testsuite/gdb.ada/tasks.exp
> @@ -39,6 +39,10 @@ gdb_test "info tasks" \
>                 "\r\n"] \
>           "info tasks before inserting breakpoint"
>  
> +# Check that multiple uses of the 'task' keyword will give an error.
> +gdb_test "break break_me task 1 task 3" "You can specify only one task\\."
> +gdb_test "watch j task 1 task 3" "You can specify only one task\\."
> +
>  # Insert a breakpoint that should stop only if task 1 stops.  Since
>  # task 1 never calls break_me, this shouldn't actually ever trigger.
>  # The fact that this breakpoint is created _before_ the next one
> diff --git a/gdb/testsuite/gdb.linespec/keywords.exp b/gdb/testsuite/gdb.linespec/keywords.exp
> index bff64249542..dc66e32237c 100644
> --- a/gdb/testsuite/gdb.linespec/keywords.exp
> +++ b/gdb/testsuite/gdb.linespec/keywords.exp
> @@ -80,8 +80,14 @@ foreach prefix {"" "thread 1 "} {
>      foreach suffix {"" " " " thread 1"} {
>  	foreach cond {"" " if 1"} {
>  	    with_test_prefix "prefix: '$prefix', suffix: '$suffix', cond: '$cond'" {
> -		gdb_breakpoint "main ${prefix}-force-condition${suffix}${cond}"\
> -		    "message"
> +
> +		if { [regexp thread $prefix] && [regexp thread $suffix] } {
> +		    gdb_test "break main ${prefix}-force-condition${suffix}${cond}" \
> +			"You can specify only one thread\\."
> +		} else {
> +		    gdb_breakpoint "main ${prefix}-force-condition${suffix}${cond}"\
> +			"message"
> +		}
>  	    }
>  	}
>      }
> diff --git a/gdb/testsuite/gdb.threads/thread-specific-bp.exp b/gdb/testsuite/gdb.threads/thread-specific-bp.exp
> index d33b4f47258..008ae5ed05e 100644
> --- a/gdb/testsuite/gdb.threads/thread-specific-bp.exp
> +++ b/gdb/testsuite/gdb.threads/thread-specific-bp.exp
> @@ -63,6 +63,10 @@ proc check_thread_specific_breakpoint {non_stop} {
>  	return -1
>      }
>  
> +    # Check that multiple uses of 'thread' keyword give an error.
> +    gdb_test "break main thread $start_thre thread $main_thre" \
> +	"You can specify only one thread\\."
> +
>      # Set a thread-specific breakpoint at "main".  This can't ever
>      # be hit, but that's OK.
>      gdb_breakpoint "main thread $start_thre"
> diff --git a/gdb/testsuite/gdb.threads/watchthreads2.exp b/gdb/testsuite/gdb.threads/watchthreads2.exp
> index 09858aee486..a1398d668a4 100644
> --- a/gdb/testsuite/gdb.threads/watchthreads2.exp
> +++ b/gdb/testsuite/gdb.threads/watchthreads2.exp
> @@ -71,6 +71,9 @@ if { $nr_started == $NR_THREADS } {
>      return -1
>  }
>  
> +# Check that multiple uses of the 'thread' keyword will give an error.
> +gdb_test "watch x thread 1 thread 2" "You can specify only one thread\\."
> +
>  # Watch X, it will be modified by all threads.
>  # We want this watchpoint to be set *after* all threads are running.
>  gdb_test "watch x" "Hardware watchpoint 3: x"
> 


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 5/6] gdb: add inferior-specific breakpoints and watchpoints
  2023-01-20  9:46   ` [PATCHv2 5/6] gdb: add inferior-specific breakpoints and watchpoints Andrew Burgess
  2023-01-20 13:25     ` Eli Zaretskii
@ 2023-02-02 19:17     ` Pedro Alves
  2023-02-03 16:55       ` Andrew Burgess
  2023-02-16 12:56     ` Aktemur, Tankut Baris
  2 siblings, 1 reply; 54+ messages in thread
From: Pedro Alves @ 2023-02-02 19:17 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 2023-01-20 9:46 a.m., Andrew Burgess via Gdb-patches wrote:
> This commit extends the breakpoint mechanism to allow for inferior
> specific breakpoints (and watchpoints).
> 
> As GDB gains better support for multiple connections, and so for
> running multiple (possibly unrelated) inferiors, then it is not hard
> to imagine that a user might wish to create breakpoints that apply to
> any thread in a single inferior.  To achieve this currently, the user
> would need to create a condition possibly making use of the $_inferior
> convenience variable, which, though functional, isn't the most user
> friendly.

So IIUC, if you do:

(gdb) break foo inferior 1

and foo exists in other inferiors, the resulting breakpoint will still
have locations for the other inferiors.  Is that so?

I think that instead, we should not get any location for other inferiors.
I.e., we should do location pre-filtering, simply by not looking for
locations in pspaces other than the inferior's.  I think that can be
done easily by simply passing the inferior's pspace as search_pspace argument
to decode_line_full.

And then it might be interesting to have a testcase that makes sure that after
following a vfork, the breakpoint still only triggers in the inferior the user
specified, even though the breakpoint is physicaly planted in both
parent/child (because they share the address space).

> @@ -1462,11 +1462,28 @@ breakpoint_set_thread (struct breakpoint *b, int thread)
>  {
>    int old_thread = b->thread;
>  
> +  gdb_assert (thread == -1 || b->inferior == -1);
> +
>    b->thread = thread;
>    if (old_thread != thread)
>      gdb::observers::breakpoint_modified.notify (b);
>  }
>  
> +/* Set the inferior for breakpoint B to INFERIOR.  If INFERIOR is -1, make
> +   the breakpoint work for any inferior.  */
> +
> +void
> +breakpoint_set_inferior (struct breakpoint *b, int inferior)
> +{
> +  int old_inferior = b->inferior;
> +
> +  gdb_assert (inferior == -1 || b->thread == -1);
> +
> +  b->inferior = inferior;
> +  if (old_inferior != inferior)
> +    gdb::observers::breakpoint_modified.notify (b);
> +}
> +
>  /* Set the task for this breakpoint.  If TASK is 0, make the
>     breakpoint work for any task.  */
>  
> @@ -3152,6 +3169,12 @@ insert_breakpoint_locations (void)
>  	  && !valid_global_thread_id (bl->owner->thread))
>  	continue;
>  
> +      /* Or inferior specific breakpoints if the inferior no longer
> +	 exists.  */
> +      if (bl->owner->inferior != -1
> +	  && !valid_global_inferior_id (bl->owner->inferior))
> +	continue;
> +
>        switch_to_program_space_and_thread (bl->pspace);
>  
>        /* For targets that support global breakpoints, there's no need
> @@ -3256,6 +3279,31 @@ Thread-specific breakpoint %d deleted - thread %s no longer in the thread list.\
>      }
>  }
>  
> +/* Called when inferior INF has exited.  Remove per-inferior breakpoints.  */

We remove thread-specific breapoints when the thread exits, because when the
thread exits, we delete it (the thread).  But when the inferior exits, the inferior
is not deleted, it still exists, ready for a re-run.  So why would we remove the breakpoints
when the inferior has exited, if the inferior still exists?  I'd have thought we'd
remove the breakpoints when the inferior is _removed_, instead.

If removing then on exit, then it feels like it could be done from 
breakpoint_init_inferior, as that is called when mourning the inferior, and it
is responsible for deleting breakpoints that no longer make sense to carry
for the inferior.

But I'm not convinced we should delete the breakpoint on exit, anyhow.

> +
> +static void
> +remove_inferior_breakpoints (struct inferior *inf)
> +{
> +  for (breakpoint *b : all_breakpoints_safe ())
> +    {
> +      if (b->inferior == inf->num && user_breakpoint_p (b))
> +	{
> +	  /* Tell the user the breakpoint has been deleted.  But only for
> +	     breakpoints that would not normally have been deleted at the
> +	     next stop anyway.  */
> +	  if (b->disposition != disp_del
> +	      && b->disposition != disp_del_at_next_stop)
> +	    gdb_printf (_("\
> +Inferior-specific breakpoint %d deleted - inferior %d has exited.\n"),
> +			b->number, inf->num);
> +
> +	  /* Hide it from the user and mark it for deletion.  */
> +	  b->number = 0;

Doesn't this mess up MI breakpoint deleted notifications?

> +	  b->disposition = disp_del_at_next_stop;

If the inferior is gone, can't we delete the breakpoint straight away?  Maybe
we'd need to call mark_breakpoints_out ?


> +       }
> +    }
> +}
> +
>  /* See breakpoint.h.  */
>  
>  void


>  @item break
>  When called without any arguments, @code{break} sets a breakpoint at
> @@ -4983,7 +5036,7 @@
>  
>  @table @code
>  @kindex watch
> -@item watch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}mask @var{maskvalue}@r{]} @r{[}task @var{task-id}@r{]}
> +@item watch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}inferior @var{inferior-id}@r{]} @r{[}mask @var{maskvalue}@r{]} @r{[}task @var{task-id}@r{]}
>  Set a watchpoint for an expression.  @value{GDBN} will break when the
>  expression @var{expr} is written into by the program and its value
>  changes.  The simplest (and the most popular) use of this command is
> @@ -5000,8 +5053,10 @@
>  that watchpoints restricted to a single thread in this way only work
>  with Hardware Watchpoints.
>  
> -Similarly, if the @code{task} argument is given, then the watchpoint
> -will be specific to the indicated Ada task (@pxref{Ada Tasks}).
> +Similarly, if the @code{inferior} argument is given, then the
> +watchpoint will trigger only for the specific inferior, or if the
> +@code{task} argument is given, then the watchpoint will be specific to
> +the indicated Ada task (@pxref{Ada Tasks}).

A watchpoint is already per-pspace, so inherently per-inferior in most scenarios -- we only
evaluate the watched expression in the pspace where the watchpoint was created, and only
track the resulting value for changes in one pspace.   Note what happens currently if you
set a watchpoint and you have multiple inferiors:

 (gdb) info inferiors 
   Num  Description       Connection           Executable        
   1    process 3974843   1 (native)           /home/pedro/watch 
 * 2    process 3975163   1 (native)           /home/pedro/watch 
 (gdb) watch global
 Hardware watchpoint 3: global
 (gdb) info breakpoints 
 Num     Type           Disp Enb Address            What
 3       hw watchpoint  keep y                      global inf 2
                                                           ^^^^^
 (gdb) 

The only case where the new feature is useful for watchpoints, is if
you're debugging multiple inferiors that share the address space, like right
after following a vfork.  In that case, here's what you get today:

$ gdb ./testsuite/outputs/gdb.base/foll-vfork/foll-vfork
GNU gdb (GDB) 14.0.50.20230127-git
...
(gdb) start
...
Temporary breakpoint 1, main (argc=1, argv=0x7fffffffdbe8) at /home/pedro/rocm/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/foll-vfork.c:31
31        pid = 1 + argc;
(gdb) set detach-on-fork off 
(gdb) set follow-fork-mode child 
(gdb) info inferiors 
  Num  Description       Connection           Executable        
* 1    process 3979825   1 (native)           /home/pedro/rocm/gdb/build/gdb/testsuite/outputs/gdb.base/foll-vfork/foll-vfork 
(gdb) n
[Attaching after Thread 0x7ffff7d6f740 (LWP 3979825) vfork to child process 3980120]
[New inferior 2 (process 3980120)]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
44      in ../sysdeps/unix/sysv/linux/x86_64/vfork.S
(gdb) info inferiors 
  Num  Description       Connection           Executable        
  1    process 3979825   1 (native)           /home/pedro/rocm/gdb/build/gdb/testsuite/outputs/gdb.base/foll-vfork/foll-vfork 
        is vfork parent of inferior 2
* 2    process 3980120   1 (native)           /home/pedro/rocm/gdb/build/gdb/testsuite/outputs/gdb.base/foll-vfork/foll-vfork 
        is vfork child of inferior 1
(gdb) up
#1  0x000055555555523f in main (argc=1, argv=0x7fffffffdbe8) at /home/pedro/rocm/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/foll-vfork.c:32
32        pid = vfork (); /* VFORK */
(gdb) watch pid
Hardware watchpoint 2: pid
(gdb) info breakpoints 
Num     Type           Disp Enb Address            What
2       hw watchpoint  keep y                      pid inf 1, 2
                                                       ^^^^^^^^

so gdb is saying that the watchpoint will trigger in either inferior, because they are
both sharing the same address space.

I think the manual need to explain this right, otherwise the user might be confused
about whether watchpoints triggers on all inferiors if you don't use
"watch ... inferior".  Also, this deserves a testcase.  I'd think it is best even to
split watchpoints support to a separate patch, as it's sufficiently different from
breakpoints.

I noticed that the NEWS entry does not mention watchpoints.

Pedro Alves

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 4/6] gdb: error if 'thread' or 'task' keywords are overused
  2023-02-02 18:21     ` Pedro Alves
@ 2023-02-03 16:41       ` Andrew Burgess
  2023-02-04  5:52         ` Joel Brobecker
  2023-02-06 11:06       ` Andrew Burgess
  1 sibling, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2023-02-03 16:41 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> On 2023-01-20 9:46 a.m., Andrew Burgess via Gdb-patches wrote:
>> When creating a breakpoint or watchpoint, the 'thread' and 'task'
>> keywords can be used to create a thread or task specific breakpoint or
>> watchpoint.
>> 
>> Currently, a thread or task specific breakpoint can only apply for a
>> single thread or task, if multiple threads or tasks are specified when
>> creating the breakpoint (or watchpoint), then the last specified id
>> will be used.
>> 
>> The exception to the above is that when the 'thread' keyword is used
>> during the creation of a watchpoint, GDB will give an error if
>> 'thread' is given more than once.
>> 
>> In this commit I propose making this behaviour consistent, if the
>> 'thread' or 'task' keywords are used more than once when creating
>> either a breakpoint or watchpoint, then GDB will give an error.
>
> The patch looks fine, but does it make sense to allow both thread and task
> at the same time?

I don't know enough about Ada tasks to comment here.  If someone who
knows can say categorically that threads and tasks can't coexist than
I'd be happy to add a patch to prevent them being used together.

Thanks,
Andrew


>
> For instance, with gdb.ada/tasks.exp :
>
> (gdb) b foo thread 1 task 2
> Breakpoint 1 at 0x555555557bd6: file /home/pedro/gdb/rocm/gdb/src/gdb/testsuite/gdb.ada/tasks/foo.adb, line 16.
> (gdb) info breakpoints 
> Num     Type           Disp Enb Address            What
> 1       breakpoint     keep y   0x0000555555557bd6 in foo at /home/pedro/gdb/rocm/gdb/src/gdb/testsuite/gdb.ada/tasks/foo.adb:16 thread 1
>         stop only in thread 1
>
> Pedro Alves
>
>> 
>> I haven't updated the manual, we don't explicitly say that these
>> keywords can be repeated, and (to me), given the keyword takes a
>> single id, I don't think it makes much sense to repeat the keyword.
>> As such, I see this more as adding a missing error to GDB, rather than
>> making some big change.  However, I have added an entry to the NEWS
>> file as I guess it is possible that some people might hit this new
>> error with an existing (I claim, badly written) GDB script.
>> 
>> I've added some new tests to check for the new error.
>> 
>> Just one test needed updating, gdb.linespec/keywords.exp, this test
>> did use the 'thread' keyword twice, and expected the breakpoint to be
>> created.  Looking at what this test was for though, it was checking
>> the use of '-force-condition', and I don't think that being able to
>> repeat 'thread' was actually a critical part of this test.
>> 
>> As such, I've updated this test to expect the error when 'thread' is
>> repeated.
>> ---
>>  gdb/NEWS                                         |  9 +++++++++
>>  gdb/breakpoint.c                                 |  9 +++++++++
>>  gdb/testsuite/gdb.ada/tasks.exp                  |  4 ++++
>>  gdb/testsuite/gdb.linespec/keywords.exp          | 10 ++++++++--
>>  gdb/testsuite/gdb.threads/thread-specific-bp.exp |  4 ++++
>>  gdb/testsuite/gdb.threads/watchthreads2.exp      |  3 +++
>>  6 files changed, 37 insertions(+), 2 deletions(-)
>> 
>> diff --git a/gdb/NEWS b/gdb/NEWS
>> index c0aac212e30..fb49f62f7e6 100644
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -9,6 +9,15 @@
>>    This support requires that GDB be built with Python scripting
>>    enabled.
>>  
>> +* For the break command, multiple uses of the 'thread' or 'task'
>> +  keywords will now give an error instead of just using the thread or
>> +  task id from the last instance of the keyword.
>> +
>> +* For the watch command, multiple uses of the 'task' keyword will now
>> +  give an error instead of just using the task id from the last
>> +  instance of the keyword.  The 'thread' keyword already gave an error
>> +  when used multiple times with the watch command, this remains unchanged.
>> +
>>  * New commands
>>  
>>  maintenance print record-instruction [ N ]
>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>> index b2cd89511fb..1400fd49642 100644
>> --- a/gdb/breakpoint.c
>> +++ b/gdb/breakpoint.c
>> @@ -8801,6 +8801,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
>>  	  const char *tmptok;
>>  	  struct thread_info *thr;
>>  
>> +	  if (*thread != -1)
>> +	    error(_("You can specify only one thread."));
>> +
>>  	  tok = end_tok + 1;
>>  	  thr = parse_thread_id (tok, &tmptok);
>>  	  if (tok == tmptok)
>> @@ -8812,6 +8815,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
>>  	{
>>  	  char *tmptok;
>>  
>> +	  if (*task != 0)
>> +	    error(_("You can specify only one task."));
>> +
>>  	  tok = end_tok + 1;
>>  	  *task = strtol (tok, &tmptok, 0);
>>  	  if (tok == tmptok)
>> @@ -10094,6 +10100,9 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
>>  	    {
>>  	      char *tmp;
>>  
>> +	      if (task != 0)
>> +		error(_("You can specify only one task."));
>> +
>>  	      task = strtol (value_start, &tmp, 0);
>>  	      if (tmp == value_start)
>>  		error (_("Junk after task keyword."));
>> diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp
>> index 23bf3937a20..4441d92719c 100644
>> --- a/gdb/testsuite/gdb.ada/tasks.exp
>> +++ b/gdb/testsuite/gdb.ada/tasks.exp
>> @@ -39,6 +39,10 @@ gdb_test "info tasks" \
>>                 "\r\n"] \
>>           "info tasks before inserting breakpoint"
>>  
>> +# Check that multiple uses of the 'task' keyword will give an error.
>> +gdb_test "break break_me task 1 task 3" "You can specify only one task\\."
>> +gdb_test "watch j task 1 task 3" "You can specify only one task\\."
>> +
>>  # Insert a breakpoint that should stop only if task 1 stops.  Since
>>  # task 1 never calls break_me, this shouldn't actually ever trigger.
>>  # The fact that this breakpoint is created _before_ the next one
>> diff --git a/gdb/testsuite/gdb.linespec/keywords.exp b/gdb/testsuite/gdb.linespec/keywords.exp
>> index bff64249542..dc66e32237c 100644
>> --- a/gdb/testsuite/gdb.linespec/keywords.exp
>> +++ b/gdb/testsuite/gdb.linespec/keywords.exp
>> @@ -80,8 +80,14 @@ foreach prefix {"" "thread 1 "} {
>>      foreach suffix {"" " " " thread 1"} {
>>  	foreach cond {"" " if 1"} {
>>  	    with_test_prefix "prefix: '$prefix', suffix: '$suffix', cond: '$cond'" {
>> -		gdb_breakpoint "main ${prefix}-force-condition${suffix}${cond}"\
>> -		    "message"
>> +
>> +		if { [regexp thread $prefix] && [regexp thread $suffix] } {
>> +		    gdb_test "break main ${prefix}-force-condition${suffix}${cond}" \
>> +			"You can specify only one thread\\."
>> +		} else {
>> +		    gdb_breakpoint "main ${prefix}-force-condition${suffix}${cond}"\
>> +			"message"
>> +		}
>>  	    }
>>  	}
>>      }
>> diff --git a/gdb/testsuite/gdb.threads/thread-specific-bp.exp b/gdb/testsuite/gdb.threads/thread-specific-bp.exp
>> index d33b4f47258..008ae5ed05e 100644
>> --- a/gdb/testsuite/gdb.threads/thread-specific-bp.exp
>> +++ b/gdb/testsuite/gdb.threads/thread-specific-bp.exp
>> @@ -63,6 +63,10 @@ proc check_thread_specific_breakpoint {non_stop} {
>>  	return -1
>>      }
>>  
>> +    # Check that multiple uses of 'thread' keyword give an error.
>> +    gdb_test "break main thread $start_thre thread $main_thre" \
>> +	"You can specify only one thread\\."
>> +
>>      # Set a thread-specific breakpoint at "main".  This can't ever
>>      # be hit, but that's OK.
>>      gdb_breakpoint "main thread $start_thre"
>> diff --git a/gdb/testsuite/gdb.threads/watchthreads2.exp b/gdb/testsuite/gdb.threads/watchthreads2.exp
>> index 09858aee486..a1398d668a4 100644
>> --- a/gdb/testsuite/gdb.threads/watchthreads2.exp
>> +++ b/gdb/testsuite/gdb.threads/watchthreads2.exp
>> @@ -71,6 +71,9 @@ if { $nr_started == $NR_THREADS } {
>>      return -1
>>  }
>>  
>> +# Check that multiple uses of the 'thread' keyword will give an error.
>> +gdb_test "watch x thread 1 thread 2" "You can specify only one thread\\."
>> +
>>  # Watch X, it will be modified by all threads.
>>  # We want this watchpoint to be set *after* all threads are running.
>>  gdb_test "watch x" "Hardware watchpoint 3: x"
>> 


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 5/6] gdb: add inferior-specific breakpoints and watchpoints
  2023-02-02 19:17     ` Pedro Alves
@ 2023-02-03 16:55       ` Andrew Burgess
  2023-02-06 17:24         ` Pedro Alves
  0 siblings, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2023-02-03 16:55 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> On 2023-01-20 9:46 a.m., Andrew Burgess via Gdb-patches wrote:
>> This commit extends the breakpoint mechanism to allow for inferior
>> specific breakpoints (and watchpoints).
>> 
>> As GDB gains better support for multiple connections, and so for
>> running multiple (possibly unrelated) inferiors, then it is not hard
>> to imagine that a user might wish to create breakpoints that apply to
>> any thread in a single inferior.  To achieve this currently, the user
>> would need to create a condition possibly making use of the $_inferior
>> convenience variable, which, though functional, isn't the most user
>> friendly.
>
> So IIUC, if you do:
>
> (gdb) break foo inferior 1
>
> and foo exists in other inferiors, the resulting breakpoint will still
> have locations for the other inferiors.  Is that so?
>
> I think that instead, we should not get any location for other inferiors.
> I.e., we should do location pre-filtering, simply by not looking for
> locations in pspaces other than the inferior's.  I think that can be
> done easily by simply passing the inferior's pspace as search_pspace argument
> to decode_line_full.

Thanks for all your great feedback.  I'll work through the rest of your
comments, but I want to follow up on this one.

Baris already raised the point of filtering per-inferior breakpoints so
a to avoid adding extra locations.

I 100% agree that this is something that we should do, and this is on my
plan if/when I land this series.

But I don't see any particular difference between per-inferior and
per-thread breakpoints in this regard.  Please correct me if I'm wrong,
but a per-thread breakpoint will insert into every inferior despite only
ever triggering for a single thread in a single inferior (per thread
breakpoints are on the global thread-id).

So, because this oddity already exists in GDB, my plan was to land this
feature, then follow up with a patch to limit both per-thread and
per-inferior breakpoints to only insert into the correct pspace.

Thanks,
Andrew

>
> And then it might be interesting to have a testcase that makes sure that after
> following a vfork, the breakpoint still only triggers in the inferior the user
> specified, even though the breakpoint is physicaly planted in both
> parent/child (because they share the address space).
>
>> @@ -1462,11 +1462,28 @@ breakpoint_set_thread (struct breakpoint *b, int thread)
>>  {
>>    int old_thread = b->thread;
>>  
>> +  gdb_assert (thread == -1 || b->inferior == -1);
>> +
>>    b->thread = thread;
>>    if (old_thread != thread)
>>      gdb::observers::breakpoint_modified.notify (b);
>>  }
>>  
>> +/* Set the inferior for breakpoint B to INFERIOR.  If INFERIOR is -1, make
>> +   the breakpoint work for any inferior.  */
>> +
>> +void
>> +breakpoint_set_inferior (struct breakpoint *b, int inferior)
>> +{
>> +  int old_inferior = b->inferior;
>> +
>> +  gdb_assert (inferior == -1 || b->thread == -1);
>> +
>> +  b->inferior = inferior;
>> +  if (old_inferior != inferior)
>> +    gdb::observers::breakpoint_modified.notify (b);
>> +}
>> +
>>  /* Set the task for this breakpoint.  If TASK is 0, make the
>>     breakpoint work for any task.  */
>>  
>> @@ -3152,6 +3169,12 @@ insert_breakpoint_locations (void)
>>  	  && !valid_global_thread_id (bl->owner->thread))
>>  	continue;
>>  
>> +      /* Or inferior specific breakpoints if the inferior no longer
>> +	 exists.  */
>> +      if (bl->owner->inferior != -1
>> +	  && !valid_global_inferior_id (bl->owner->inferior))
>> +	continue;
>> +
>>        switch_to_program_space_and_thread (bl->pspace);
>>  
>>        /* For targets that support global breakpoints, there's no need
>> @@ -3256,6 +3279,31 @@ Thread-specific breakpoint %d deleted - thread %s no longer in the thread list.\
>>      }
>>  }
>>  
>> +/* Called when inferior INF has exited.  Remove per-inferior breakpoints.  */
>
> We remove thread-specific breapoints when the thread exits, because when the
> thread exits, we delete it (the thread).  But when the inferior exits, the inferior
> is not deleted, it still exists, ready for a re-run.  So why would we remove the breakpoints
> when the inferior has exited, if the inferior still exists?  I'd have thought we'd
> remove the breakpoints when the inferior is _removed_, instead.
>
> If removing then on exit, then it feels like it could be done from 
> breakpoint_init_inferior, as that is called when mourning the inferior, and it
> is responsible for deleting breakpoints that no longer make sense to carry
> for the inferior.
>
> But I'm not convinced we should delete the breakpoint on exit, anyhow.
>
>> +
>> +static void
>> +remove_inferior_breakpoints (struct inferior *inf)
>> +{
>> +  for (breakpoint *b : all_breakpoints_safe ())
>> +    {
>> +      if (b->inferior == inf->num && user_breakpoint_p (b))
>> +	{
>> +	  /* Tell the user the breakpoint has been deleted.  But only for
>> +	     breakpoints that would not normally have been deleted at the
>> +	     next stop anyway.  */
>> +	  if (b->disposition != disp_del
>> +	      && b->disposition != disp_del_at_next_stop)
>> +	    gdb_printf (_("\
>> +Inferior-specific breakpoint %d deleted - inferior %d has exited.\n"),
>> +			b->number, inf->num);
>> +
>> +	  /* Hide it from the user and mark it for deletion.  */
>> +	  b->number = 0;
>
> Doesn't this mess up MI breakpoint deleted notifications?
>
>> +	  b->disposition = disp_del_at_next_stop;
>
> If the inferior is gone, can't we delete the breakpoint straight away?  Maybe
> we'd need to call mark_breakpoints_out ?
>
>
>> +       }
>> +    }
>> +}
>> +
>>  /* See breakpoint.h.  */
>>  
>>  void
>
>
>>  @item break
>>  When called without any arguments, @code{break} sets a breakpoint at
>> @@ -4983,7 +5036,7 @@
>>  
>>  @table @code
>>  @kindex watch
>> -@item watch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}mask @var{maskvalue}@r{]} @r{[}task @var{task-id}@r{]}
>> +@item watch @r{[}-l@r{|}-location@r{]} @var{expr} @r{[}thread @var{thread-id}@r{]} @r{[}inferior @var{inferior-id}@r{]} @r{[}mask @var{maskvalue}@r{]} @r{[}task @var{task-id}@r{]}
>>  Set a watchpoint for an expression.  @value{GDBN} will break when the
>>  expression @var{expr} is written into by the program and its value
>>  changes.  The simplest (and the most popular) use of this command is
>> @@ -5000,8 +5053,10 @@
>>  that watchpoints restricted to a single thread in this way only work
>>  with Hardware Watchpoints.
>>  
>> -Similarly, if the @code{task} argument is given, then the watchpoint
>> -will be specific to the indicated Ada task (@pxref{Ada Tasks}).
>> +Similarly, if the @code{inferior} argument is given, then the
>> +watchpoint will trigger only for the specific inferior, or if the
>> +@code{task} argument is given, then the watchpoint will be specific to
>> +the indicated Ada task (@pxref{Ada Tasks}).
>
> A watchpoint is already per-pspace, so inherently per-inferior in most scenarios -- we only
> evaluate the watched expression in the pspace where the watchpoint was created, and only
> track the resulting value for changes in one pspace.   Note what happens currently if you
> set a watchpoint and you have multiple inferiors:
>
>  (gdb) info inferiors 
>    Num  Description       Connection           Executable        
>    1    process 3974843   1 (native)           /home/pedro/watch 
>  * 2    process 3975163   1 (native)           /home/pedro/watch 
>  (gdb) watch global
>  Hardware watchpoint 3: global
>  (gdb) info breakpoints 
>  Num     Type           Disp Enb Address            What
>  3       hw watchpoint  keep y                      global inf 2
>                                                            ^^^^^
>  (gdb) 
>
> The only case where the new feature is useful for watchpoints, is if
> you're debugging multiple inferiors that share the address space, like right
> after following a vfork.  In that case, here's what you get today:
>
> $ gdb ./testsuite/outputs/gdb.base/foll-vfork/foll-vfork
> GNU gdb (GDB) 14.0.50.20230127-git
> ...
> (gdb) start
> ...
> Temporary breakpoint 1, main (argc=1, argv=0x7fffffffdbe8) at /home/pedro/rocm/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/foll-vfork.c:31
> 31        pid = 1 + argc;
> (gdb) set detach-on-fork off 
> (gdb) set follow-fork-mode child 
> (gdb) info inferiors 
>   Num  Description       Connection           Executable        
> * 1    process 3979825   1 (native)           /home/pedro/rocm/gdb/build/gdb/testsuite/outputs/gdb.base/foll-vfork/foll-vfork 
> (gdb) n
> [Attaching after Thread 0x7ffff7d6f740 (LWP 3979825) vfork to child process 3980120]
> [New inferior 2 (process 3980120)]
> [Thread debugging using libthread_db enabled]
> Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
> 44      in ../sysdeps/unix/sysv/linux/x86_64/vfork.S
> (gdb) info inferiors 
>   Num  Description       Connection           Executable        
>   1    process 3979825   1 (native)           /home/pedro/rocm/gdb/build/gdb/testsuite/outputs/gdb.base/foll-vfork/foll-vfork 
>         is vfork parent of inferior 2
> * 2    process 3980120   1 (native)           /home/pedro/rocm/gdb/build/gdb/testsuite/outputs/gdb.base/foll-vfork/foll-vfork 
>         is vfork child of inferior 1
> (gdb) up
> #1  0x000055555555523f in main (argc=1, argv=0x7fffffffdbe8) at /home/pedro/rocm/gdb/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/foll-vfork.c:32
> 32        pid = vfork (); /* VFORK */
> (gdb) watch pid
> Hardware watchpoint 2: pid
> (gdb) info breakpoints 
> Num     Type           Disp Enb Address            What
> 2       hw watchpoint  keep y                      pid inf 1, 2
>                                                        ^^^^^^^^
>
> so gdb is saying that the watchpoint will trigger in either inferior, because they are
> both sharing the same address space.
>
> I think the manual need to explain this right, otherwise the user might be confused
> about whether watchpoints triggers on all inferiors if you don't use
> "watch ... inferior".  Also, this deserves a testcase.  I'd think it is best even to
> split watchpoints support to a separate patch, as it's sufficiently different from
> breakpoints.
>
> I noticed that the NEWS entry does not mention watchpoints.
>
> Pedro Alves


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 4/6] gdb: error if 'thread' or 'task' keywords are overused
  2023-02-03 16:41       ` Andrew Burgess
@ 2023-02-04  5:52         ` Joel Brobecker
  2023-02-04 15:40           ` Andrew Burgess
  0 siblings, 1 reply; 54+ messages in thread
From: Joel Brobecker @ 2023-02-04  5:52 UTC (permalink / raw)
  To: Andrew Burgess via Gdb-patches; +Cc: Pedro Alves, Joel Brobecker

Hi Andrew,

> > On 2023-01-20 9:46 a.m., Andrew Burgess via Gdb-patches wrote:
> >> When creating a breakpoint or watchpoint, the 'thread' and 'task'
> >> keywords can be used to create a thread or task specific breakpoint or
> >> watchpoint.
> >> 
> >> Currently, a thread or task specific breakpoint can only apply for a
> >> single thread or task, if multiple threads or tasks are specified when
> >> creating the breakpoint (or watchpoint), then the last specified id
> >> will be used.
> >> 
> >> The exception to the above is that when the 'thread' keyword is used
> >> during the creation of a watchpoint, GDB will give an error if
> >> 'thread' is given more than once.
> >> 
> >> In this commit I propose making this behaviour consistent, if the
> >> 'thread' or 'task' keywords are used more than once when creating
> >> either a breakpoint or watchpoint, then GDB will give an error.
> >
> > The patch looks fine, but does it make sense to allow both thread and task
> > at the same time?
> 
> I don't know enough about Ada tasks to comment here.  If someone who
> knows can say categorically that threads and tasks can't coexist than
> I'd be happy to add a patch to prevent them being used together.

Pedro raises a good question!

At the language level, I don't think there is anything that says
that Ada tasks can't be implemented independently of threads.
In fact, on baremetal targets, that's what we have to do, since
we don't have an underlying thread layer.

With that said, for GDB itself, the implementation of the ada tasking
layer is done on top of the GDB's thread layer. In simple terms,
what the ada-task.c module does is simply translating Ada task IDs
into thread ptid-s. So, when we say "switch to task X", or "break on
task X", internally, it essentially translates "task X" into "thread Y".

Based on this implementation, it is always suspicious if someone
uses both a thread ID and a task ID in the same command (or I would
view those as "additive", but that's not the direction taken by
your patch series). I would therefore indeed raise an error if
both are used in the same command.

One side note about the baremetal platforms, since I mentioned them:
While the platform itself doesn't provide threads [1], you might ask
yourself how the Ada tasking layer might be implemented. This is where
the ravenscar-thread layer kicks in. It actually relies on the Ada
runtime to determine the list of tasks that exists, and constructs
a list of threads from that data, thus providing the threads that
the ada-task module needs to function.

[1] On baremetal target, we've seen multicore system report each CPU
    as a thread. That's what QEMU does, for instance.

I hope this helps!
-- 
Joel

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 4/6] gdb: error if 'thread' or 'task' keywords are overused
  2023-02-04  5:52         ` Joel Brobecker
@ 2023-02-04 15:40           ` Andrew Burgess
  0 siblings, 0 replies; 54+ messages in thread
From: Andrew Burgess @ 2023-02-04 15:40 UTC (permalink / raw)
  To: Joel Brobecker via Gdb-patches, Andrew Burgess via Gdb-patches
  Cc: Pedro Alves, Joel Brobecker

Joel Brobecker via Gdb-patches <gdb-patches@sourceware.org> writes:

> Hi Andrew,
>
>> > On 2023-01-20 9:46 a.m., Andrew Burgess via Gdb-patches wrote:
>> >> When creating a breakpoint or watchpoint, the 'thread' and 'task'
>> >> keywords can be used to create a thread or task specific breakpoint or
>> >> watchpoint.
>> >> 
>> >> Currently, a thread or task specific breakpoint can only apply for a
>> >> single thread or task, if multiple threads or tasks are specified when
>> >> creating the breakpoint (or watchpoint), then the last specified id
>> >> will be used.
>> >> 
>> >> The exception to the above is that when the 'thread' keyword is used
>> >> during the creation of a watchpoint, GDB will give an error if
>> >> 'thread' is given more than once.
>> >> 
>> >> In this commit I propose making this behaviour consistent, if the
>> >> 'thread' or 'task' keywords are used more than once when creating
>> >> either a breakpoint or watchpoint, then GDB will give an error.
>> >
>> > The patch looks fine, but does it make sense to allow both thread and task
>> > at the same time?
>> 
>> I don't know enough about Ada tasks to comment here.  If someone who
>> knows can say categorically that threads and tasks can't coexist than
>> I'd be happy to add a patch to prevent them being used together.
>
> Pedro raises a good question!
>
> At the language level, I don't think there is anything that says
> that Ada tasks can't be implemented independently of threads.
> In fact, on baremetal targets, that's what we have to do, since
> we don't have an underlying thread layer.
>
> With that said, for GDB itself, the implementation of the ada tasking
> layer is done on top of the GDB's thread layer. In simple terms,
> what the ada-task.c module does is simply translating Ada task IDs
> into thread ptid-s. So, when we say "switch to task X", or "break on
> task X", internally, it essentially translates "task X" into "thread Y".
>
> Based on this implementation, it is always suspicious if someone
> uses both a thread ID and a task ID in the same command (or I would
> view those as "additive", but that's not the direction taken by
> your patch series). I would therefore indeed raise an error if
> both are used in the same command.
>
> One side note about the baremetal platforms, since I mentioned them:
> While the platform itself doesn't provide threads [1], you might ask
> yourself how the Ada tasking layer might be implemented. This is where
> the ravenscar-thread layer kicks in. It actually relies on the Ada
> runtime to determine the list of tasks that exists, and constructs
> a list of threads from that data, thus providing the threads that
> the ada-task module needs to function.
>
> [1] On baremetal target, we've seen multicore system report each CPU
>     as a thread. That's what QEMU does, for instance.
>
> I hope this helps!

Thanks for the detailed write up.  I'm going to keep the original patch
as is for now, but will follow up with an additional patch that will
give an error if a user tries to use 'task' and 'thread' in the same
command.

Thanks,
Andrew


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 1/6] gdb/remote: announce thread exit events for remote targets
  2023-02-02 17:50     ` Pedro Alves
@ 2023-02-04 15:46       ` Andrew Burgess
  0 siblings, 0 replies; 54+ messages in thread
From: Andrew Burgess @ 2023-02-04 15:46 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> On 2023-01-20 9:46 a.m., Andrew Burgess via Gdb-patches wrote:
>> For some reason the "[Thread XXXX exited]" messages are not printed
>> inside thread.c from functions like delete_thread, etc as might be
>> expected.  Instead, each target seems to print the message before
>> calling delete_thread.
>> 
>> This doesn't seem ideal, and I can't help but feel that the printing
>> should be moved into thread.c, however, I have not tried to do that in
>> this commit, as I suspect there will be lots of fallout that needs
>> fixing up.
>
> I had done exactly that in the step over clone/exit series:
>
>   Centralize "[Thread ...exited]" notifications
>   https://inbox.sourceware.org/gdb-patches/20221212203101.1034916-1-pedro@palves.net/T/#m9f9858077e6b2b817148bfb9c32ec15bec724931
>

OK.  I guess I'll try to move this series forward without this patch for
now, and wait for your work to land, hopefully we can get your series in
soon.

Thanks,
Andrew


> Pedro Alves
>
>> 
>> Instead, in this commit, I have added the printing code into remote.c,
>> so that the remote target will now correctly tell the user when a
>> thread has exited.
>> 
>> This fixes some test failures in gdb.threads/thread-specific-bp.exp
>> when run with the native-gdbserver and native-extended-gdbserver
>> board.
>> 
>> When using the native-extended-gdbserver board I still see 1 test
>> failure, but I think this is not related to the issue fixed in this
>> commit, so I'm ignoring that for now.
>> ---
>>  gdb/remote.c | 4 ++++
>>  1 file changed, 4 insertions(+)
>> 
>> diff --git a/gdb/remote.c b/gdb/remote.c
>> index 0a6e293c095..4a508981a96 100644
>> --- a/gdb/remote.c
>> +++ b/gdb/remote.c
>> @@ -3977,6 +3977,10 @@ remote_target::update_thread_list ()
>>  	      if (has_single_non_exited_thread (tp->inf))
>>  		continue;
>>  
>> +	      if (print_thread_events)
>> +		gdb_printf (_("[%s exited]\n"),
>> +			    target_pid_to_str (tp->ptid).c_str ());
>> +
>>  	      /* Not found.  */
>>  	      delete_thread (tp);
>>  	    }
>> 


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 2/6] gdb/testsuite: don't try to set non-stop mode on a running target
  2023-01-20  9:46   ` [PATCHv2 2/6] gdb/testsuite: don't try to set non-stop mode on a running target Andrew Burgess
@ 2023-02-04 16:22     ` Andrew Burgess
  0 siblings, 0 replies; 54+ messages in thread
From: Andrew Burgess @ 2023-02-04 16:22 UTC (permalink / raw)
  To: gdb-patches

Andrew Burgess <aburgess@redhat.com> writes:

> The test gdb.threads/thread-specific-bp.exp tries to set non-stop mode
> on a running target, something which the manual makes clear is not
> allowed.
>
> The consequence of this is that the gdb.threads/thread-specific-bp.exp
> test has one failure when run with the native-extended-gdbserver
> board.
>
> This commit restructures the test a little, we now set the non-stop
> mode as part of the GDBFLAGS, so the mode will be set before GDB
> connects to the target.  As a consequence I'm able to move the
> with_test_prefix out of the check_thread_specific_breakpoint proc.
> The check_thread_specific_breakpoint proc is now called within a loop.
>
> After this commit the gdb.threads/thread-specific-bp.exp test has zero
> failures for me with native-extended-gdbserver, native-gdbserver, and
> the unix board.

Given that the preceding patch[1] appears to be on hold in favour of
Pedro's work[2], I've gone ahead and pushed this patch with a slightly
updated commit message.  This means that when Pedro gets to push his
patch, he'll see the gdb.threads/thread-specific-bp.exp suddenly start
passing.

Thanks,
Andrew

[1] https://sourceware.org/pipermail/gdb-patches/2023-January/195971.html
[2] https://sourceware.org/pipermail/gdb-patches/2022-December/194694.html


---

commit 79436bfc5aa71973f154b855f7440ed62ddd9582
Author: Andrew Burgess <aburgess@redhat.com>
Date:   Thu Nov 24 19:36:23 2022 +0000

    gdb/testsuite: don't try to set non-stop mode on a running target
    
    The test gdb.threads/thread-specific-bp.exp tries to set non-stop mode
    on a running target, something which the manual makes clear is not
    allowed.
    
    This commit restructures the test a little, we now set the non-stop
    mode as part of the GDBFLAGS, so the mode will be set before GDB
    connects to the target.  As a consequence I'm able to move the
    with_test_prefix out of the check_thread_specific_breakpoint proc.
    The check_thread_specific_breakpoint proc is now called within a loop.
    
    After this commit the gdb.threads/thread-specific-bp.exp test still
    has some failures, this is because of an issue GDB currently has
    printing "Thread ... exited" messages.  This problem should be
    addressed by this patch:
    
      https://sourceware.org/pipermail/gdb-patches/2022-December/194694.html
    
    when it is merged.

diff --git a/gdb/testsuite/gdb.threads/thread-specific-bp.exp b/gdb/testsuite/gdb.threads/thread-specific-bp.exp
index f603b24fa31..f440574d780 100644
--- a/gdb/testsuite/gdb.threads/thread-specific-bp.exp
+++ b/gdb/testsuite/gdb.threads/thread-specific-bp.exp
@@ -24,8 +24,6 @@ if {[gdb_compile_pthreads \
     return -1
 }
 
-clean_restart ${binfile}
-
 # Extract and return the thread ID of the thread stopped at function
 # FUNC.
 
@@ -45,86 +43,84 @@ proc get_thread_id {func} {
     return $thre
 }
 
-proc check_thread_specific_breakpoint {mode} {
-    with_test_prefix "$mode" {
-	global gdb_prompt
+proc check_thread_specific_breakpoint {non_stop} {
+    global gdb_prompt
 
-	if ![runto_main] {
-	    return -1
-	}
+    if { ![runto_main] } {
+	return -1
+    }
 
-	set main_thre [get_thread_id "main"]
-	if { $main_thre < 0 } {
-	    return -1
-	}
+    set main_thre [get_thread_id "main"]
+    if { $main_thre < 0 } {
+	return -1
+    }
 
-	gdb_breakpoint "start"
-	gdb_continue_to_breakpoint "start"
+    gdb_breakpoint "start"
+    gdb_continue_to_breakpoint "start"
 
-	set start_thre [get_thread_id "start"]
-	if { $start_thre < 0 } {
-	    return -1
-	}
+    set start_thre [get_thread_id "start"]
+    if { $start_thre < 0 } {
+	return -1
+    }
 
-	# Set a thread-specific breakpoint at "main".  This can't ever
-	# be hit, but that's OK.
-	gdb_breakpoint "main thread $start_thre"
-	gdb_test "info break" ".*breakpoint.*thread $start_thre" "breakpoint set"
-
-	# Set breakpoint at a place only reacheable after the "start"
-	# thread exits.
-	gdb_breakpoint "end"
-
-	# Switch back to the main thread, and resume all threads.  The
-	# "start" thread exits, and the main thread reaches "end".
-	gdb_test "thread $main_thre" \
-	    "Switching to thread $main_thre.*" \
-	    "thread $main_thre selected"
-
-	if { $mode == "non-stop" } {
-	    set cmd "continue -a"
-	} else {
-	    set cmd "continue"
-	}
-	set test "continue to end"
-	set thread_exited 0
-	set prompt 0
-	gdb_test_multiple "$cmd" $test -lbl {
-	    -re "(^|\r\n)\\\[Thread \[^\r\n\]* exited](?=\r\n)" {
-		if { $prompt } {
-		    pass $gdb_test_name
-		} else {
-		    set thread_exited 1
-		    exp_continue
-		}
+    # Set a thread-specific breakpoint at "main".  This can't ever
+    # be hit, but that's OK.
+    gdb_breakpoint "main thread $start_thre"
+    gdb_test "info break" ".*breakpoint.*thread $start_thre" "breakpoint set"
+
+    # Set breakpoint at a place only reacheable after the "start"
+    # thread exits.
+    gdb_breakpoint "end"
+
+    # Switch back to the main thread, and resume all threads.  The
+    # "start" thread exits, and the main thread reaches "end".
+    gdb_test "thread $main_thre" \
+	"Switching to thread $main_thre.*" \
+	"thread $main_thre selected"
+
+    if { $non_stop } {
+	set cmd "continue -a"
+    } else {
+	set cmd "continue"
+    }
+    set test "continue to end"
+    set thread_exited 0
+    set prompt 0
+    gdb_test_multiple "$cmd" $test -lbl {
+	-re "(^|\r\n)\\\[Thread \[^\r\n\]* exited](?=\r\n)" {
+	    if { $prompt } {
+		pass $gdb_test_name
+	    } else {
+		set thread_exited 1
+		exp_continue
 	    }
-	    -re "\r\n$gdb_prompt " {
-		if { $thread_exited } {
-		    pass $gdb_test_name
-		} else {
-		    set prompt 1
-		    exp_continue
-		}
+	}
+	-re "\r\n$gdb_prompt " {
+	    if { $thread_exited } {
+		pass $gdb_test_name
+	    } else {
+		set prompt 1
+		exp_continue
 	    }
 	}
+    }
 
-	set test "thread-specific breakpoint was deleted"
-	gdb_test_multiple "info breakpoint" $test {
-	    -re "thread $start_thre\n$gdb_prompt $" {
-		fail $test
-	    }
-	    -re "$gdb_prompt $" {
-		pass $test
-	    }
+    set test "thread-specific breakpoint was deleted"
+    gdb_test_multiple "info breakpoint" $test {
+	-re "thread $start_thre\n$gdb_prompt $" {
+	    fail $test
+	}
+	-re "$gdb_prompt $" {
+	    pass $test
 	}
     }
 }
 
-# Test all-stop mode.
-check_thread_specific_breakpoint "all-stop"
-
-clean_restart ${binfile}
+foreach_with_prefix non_stop {on off} {
+    save_vars { GDBFLAGS } {
+	append GDBFLAGS " -ex \"set non-stop $non_stop\""
+	clean_restart $binfile
+    }
 
-# Test non-stop mode.
-gdb_test_no_output "set non-stop on" "set non-stop mode"
-check_thread_specific_breakpoint "non-stop"
+    check_thread_specific_breakpoint $non_stop
+}


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 4/6] gdb: error if 'thread' or 'task' keywords are overused
  2023-02-02 18:21     ` Pedro Alves
  2023-02-03 16:41       ` Andrew Burgess
@ 2023-02-06 11:06       ` Andrew Burgess
  1 sibling, 0 replies; 54+ messages in thread
From: Andrew Burgess @ 2023-02-06 11:06 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> On 2023-01-20 9:46 a.m., Andrew Burgess via Gdb-patches wrote:
>> When creating a breakpoint or watchpoint, the 'thread' and 'task'
>> keywords can be used to create a thread or task specific breakpoint or
>> watchpoint.
>> 
>> Currently, a thread or task specific breakpoint can only apply for a
>> single thread or task, if multiple threads or tasks are specified when
>> creating the breakpoint (or watchpoint), then the last specified id
>> will be used.
>> 
>> The exception to the above is that when the 'thread' keyword is used
>> during the creation of a watchpoint, GDB will give an error if
>> 'thread' is given more than once.
>> 
>> In this commit I propose making this behaviour consistent, if the
>> 'thread' or 'task' keywords are used more than once when creating
>> either a breakpoint or watchpoint, then GDB will give an error.
>
> The patch looks fine, but does it make sense to allow both thread and task
> at the same time?

Thanks for the review.  I've made the fix Eli suggested, and pushed this
patch.

Given Joel's feedback I'll take a look at a follow up patch the prevents
thread and task being used together.

Thanks,
Andrew

>
> For instance, with gdb.ada/tasks.exp :
>
> (gdb) b foo thread 1 task 2
> Breakpoint 1 at 0x555555557bd6: file /home/pedro/gdb/rocm/gdb/src/gdb/testsuite/gdb.ada/tasks/foo.adb, line 16.
> (gdb) info breakpoints 
> Num     Type           Disp Enb Address            What
> 1       breakpoint     keep y   0x0000555555557bd6 in foo at /home/pedro/gdb/rocm/gdb/src/gdb/testsuite/gdb.ada/tasks/foo.adb:16 thread 1
>         stop only in thread 1
>
> Pedro Alves
>
>> 
>> I haven't updated the manual, we don't explicitly say that these
>> keywords can be repeated, and (to me), given the keyword takes a
>> single id, I don't think it makes much sense to repeat the keyword.
>> As such, I see this more as adding a missing error to GDB, rather than
>> making some big change.  However, I have added an entry to the NEWS
>> file as I guess it is possible that some people might hit this new
>> error with an existing (I claim, badly written) GDB script.
>> 
>> I've added some new tests to check for the new error.
>> 
>> Just one test needed updating, gdb.linespec/keywords.exp, this test
>> did use the 'thread' keyword twice, and expected the breakpoint to be
>> created.  Looking at what this test was for though, it was checking
>> the use of '-force-condition', and I don't think that being able to
>> repeat 'thread' was actually a critical part of this test.
>> 
>> As such, I've updated this test to expect the error when 'thread' is
>> repeated.
>> ---
>>  gdb/NEWS                                         |  9 +++++++++
>>  gdb/breakpoint.c                                 |  9 +++++++++
>>  gdb/testsuite/gdb.ada/tasks.exp                  |  4 ++++
>>  gdb/testsuite/gdb.linespec/keywords.exp          | 10 ++++++++--
>>  gdb/testsuite/gdb.threads/thread-specific-bp.exp |  4 ++++
>>  gdb/testsuite/gdb.threads/watchthreads2.exp      |  3 +++
>>  6 files changed, 37 insertions(+), 2 deletions(-)
>> 
>> diff --git a/gdb/NEWS b/gdb/NEWS
>> index c0aac212e30..fb49f62f7e6 100644
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -9,6 +9,15 @@
>>    This support requires that GDB be built with Python scripting
>>    enabled.
>>  
>> +* For the break command, multiple uses of the 'thread' or 'task'
>> +  keywords will now give an error instead of just using the thread or
>> +  task id from the last instance of the keyword.
>> +
>> +* For the watch command, multiple uses of the 'task' keyword will now
>> +  give an error instead of just using the task id from the last
>> +  instance of the keyword.  The 'thread' keyword already gave an error
>> +  when used multiple times with the watch command, this remains unchanged.
>> +
>>  * New commands
>>  
>>  maintenance print record-instruction [ N ]
>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>> index b2cd89511fb..1400fd49642 100644
>> --- a/gdb/breakpoint.c
>> +++ b/gdb/breakpoint.c
>> @@ -8801,6 +8801,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
>>  	  const char *tmptok;
>>  	  struct thread_info *thr;
>>  
>> +	  if (*thread != -1)
>> +	    error(_("You can specify only one thread."));
>> +
>>  	  tok = end_tok + 1;
>>  	  thr = parse_thread_id (tok, &tmptok);
>>  	  if (tok == tmptok)
>> @@ -8812,6 +8815,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
>>  	{
>>  	  char *tmptok;
>>  
>> +	  if (*task != 0)
>> +	    error(_("You can specify only one task."));
>> +
>>  	  tok = end_tok + 1;
>>  	  *task = strtol (tok, &tmptok, 0);
>>  	  if (tok == tmptok)
>> @@ -10094,6 +10100,9 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
>>  	    {
>>  	      char *tmp;
>>  
>> +	      if (task != 0)
>> +		error(_("You can specify only one task."));
>> +
>>  	      task = strtol (value_start, &tmp, 0);
>>  	      if (tmp == value_start)
>>  		error (_("Junk after task keyword."));
>> diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp
>> index 23bf3937a20..4441d92719c 100644
>> --- a/gdb/testsuite/gdb.ada/tasks.exp
>> +++ b/gdb/testsuite/gdb.ada/tasks.exp
>> @@ -39,6 +39,10 @@ gdb_test "info tasks" \
>>                 "\r\n"] \
>>           "info tasks before inserting breakpoint"
>>  
>> +# Check that multiple uses of the 'task' keyword will give an error.
>> +gdb_test "break break_me task 1 task 3" "You can specify only one task\\."
>> +gdb_test "watch j task 1 task 3" "You can specify only one task\\."
>> +
>>  # Insert a breakpoint that should stop only if task 1 stops.  Since
>>  # task 1 never calls break_me, this shouldn't actually ever trigger.
>>  # The fact that this breakpoint is created _before_ the next one
>> diff --git a/gdb/testsuite/gdb.linespec/keywords.exp b/gdb/testsuite/gdb.linespec/keywords.exp
>> index bff64249542..dc66e32237c 100644
>> --- a/gdb/testsuite/gdb.linespec/keywords.exp
>> +++ b/gdb/testsuite/gdb.linespec/keywords.exp
>> @@ -80,8 +80,14 @@ foreach prefix {"" "thread 1 "} {
>>      foreach suffix {"" " " " thread 1"} {
>>  	foreach cond {"" " if 1"} {
>>  	    with_test_prefix "prefix: '$prefix', suffix: '$suffix', cond: '$cond'" {
>> -		gdb_breakpoint "main ${prefix}-force-condition${suffix}${cond}"\
>> -		    "message"
>> +
>> +		if { [regexp thread $prefix] && [regexp thread $suffix] } {
>> +		    gdb_test "break main ${prefix}-force-condition${suffix}${cond}" \
>> +			"You can specify only one thread\\."
>> +		} else {
>> +		    gdb_breakpoint "main ${prefix}-force-condition${suffix}${cond}"\
>> +			"message"
>> +		}
>>  	    }
>>  	}
>>      }
>> diff --git a/gdb/testsuite/gdb.threads/thread-specific-bp.exp b/gdb/testsuite/gdb.threads/thread-specific-bp.exp
>> index d33b4f47258..008ae5ed05e 100644
>> --- a/gdb/testsuite/gdb.threads/thread-specific-bp.exp
>> +++ b/gdb/testsuite/gdb.threads/thread-specific-bp.exp
>> @@ -63,6 +63,10 @@ proc check_thread_specific_breakpoint {non_stop} {
>>  	return -1
>>      }
>>  
>> +    # Check that multiple uses of 'thread' keyword give an error.
>> +    gdb_test "break main thread $start_thre thread $main_thre" \
>> +	"You can specify only one thread\\."
>> +
>>      # Set a thread-specific breakpoint at "main".  This can't ever
>>      # be hit, but that's OK.
>>      gdb_breakpoint "main thread $start_thre"
>> diff --git a/gdb/testsuite/gdb.threads/watchthreads2.exp b/gdb/testsuite/gdb.threads/watchthreads2.exp
>> index 09858aee486..a1398d668a4 100644
>> --- a/gdb/testsuite/gdb.threads/watchthreads2.exp
>> +++ b/gdb/testsuite/gdb.threads/watchthreads2.exp
>> @@ -71,6 +71,9 @@ if { $nr_started == $NR_THREADS } {
>>      return -1
>>  }
>>  
>> +# Check that multiple uses of the 'thread' keyword will give an error.
>> +gdb_test "watch x thread 1 thread 2" "You can specify only one thread\\."
>> +
>>  # Watch X, it will be modified by all threads.
>>  # We want this watchpoint to be set *after* all threads are running.
>>  gdb_test "watch x" "Hardware watchpoint 3: x"
>> 


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 3/6] gdb: fix display of thread condition for multi-location breakpoints
  2023-02-02 18:13     ` Pedro Alves
@ 2023-02-06 14:48       ` Andrew Burgess
  2023-02-06 17:01         ` Pedro Alves
  0 siblings, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2023-02-06 14:48 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> On 2023-01-20 9:46 a.m., Andrew Burgess via Gdb-patches wrote:
>> If a breakpoint with multiple locations has a thread condition, then
>> the 'info breakpoints' output is a little messed up, here's an example
>> of the current output:
>> 
>>   (gdb) break foo thread 1
>>   Breakpoint 2 at 0x401114: foo. (3 locations)
>>   (gdb) break bar thread 1
>>   Breakpoint 3 at 0x40110a: file /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c, line 32.
>>   (gdb) info breakpoints
>>   Num     Type           Disp Enb Address            What
>>   2       breakpoint     keep y   <MULTIPLE>          thread 1
>>           stop only in thread 1
>>   2.1                         y   0x0000000000401114 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>>   2.2                         y   0x0000000000401146 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>>   2.3                         y   0x0000000000401168 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>>   3       breakpoint     keep y   0x000000000040110a in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32 thread 1
>>           stop only in thread 1
>> 
>> Notice that, at the end of the location for breakpoint 3, the 'thread
>> 1' condition is printed, but this is then repeated on the next line
>> with 'stop only in thread 1'.
>> 
>> In contrast, for breakpoint 2, the 'thread 1' appears randomly, in the
>> "What" column, though slightly offset, non of the separate locations
>> have the 'thread 1' information.  Additionally for breakpoint 2 we
>> also get a 'stop only in thread 1' line.
>> 
>> There's two things going on here.  First the randomly placed 'thread
>> 1' for breakpoint 2 is due to a bug in print_one_breakpoint_location,
>> where we check the variable part_of_multiple instead of
>> header_of_multiple.
>> 
>> If I fix this oversight, then the output is now:
>> 
>>   (gdb) break foo thread 1
>>   Breakpoint 2 at 0x401114: foo. (3 locations)
>>   (gdb) break bar thread 1
>>   Breakpoint 3 at 0x40110a: file /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c, line 32.
>>   (gdb) info breakpoints
>>   Num     Type           Disp Enb Address            What
>>   2       breakpoint     keep y   <MULTIPLE>
>>           stop only in thread 1
>>   2.1                         y   0x0000000000401114 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
>>   2.2                         y   0x0000000000401146 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
>>   2.3                         y   0x0000000000401168 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
>>   3       breakpoint     keep y   0x000000000040110a in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32 thread 1
>>           stop only in thread 1
>> 
>> The 'thread 1' condition is now displayed at the end of each location,
>> which makes the output the same for single location breakpoints and
>> multi-location breakpoints.
>> 
>> However, there's still some duplication here.  Both breakpoints 2 and
>> 3 include a 'stop only in thread 1' line, and it feels like the
>> additional 'thread 1' is redundant.  In fact, there's a comment to
>> this very effect in the code:
>> 
>>   /* FIXME: This seems to be redundant and lost here; see the
>>      "stop only in" line a little further down.  */
>> 
>> So, lets fix this FIXME.  The new plan is to remove all the trailing
>> 'thread 1' markers from the CLI output, we now get this:
>> 
>>   (gdb) break foo thread 1
>>   Breakpoint 2 at 0x401114: foo. (3 locations)
>>   (gdb) break bar thread 1
>>   Breakpoint 3 at 0x40110a: file /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c, line 32.
>>   (gdb) info breakpoints
>>   Num     Type           Disp Enb Address            What
>>   2       breakpoint     keep y   <MULTIPLE>
>>           stop only in thread 1
>>   2.1                         y   0x0000000000401114 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>>   2.2                         y   0x0000000000401146 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>>   2.3                         y   0x0000000000401168 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>>   3       breakpoint     keep y   0x000000000040110a in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32
>>           stop only in thread 1
>> 
>
> This is the right direction, IMO.  The "thread 1" string is at best part of the breakpoint's
> location spec, not of the code locations that spec resolved to.
>
> If we went forward with my proposal to always show breakpoints using the multi-locations mode,
> and, to include the location spec in the "What" column of the breakpoint header, we could show:
>
>    (gdb) info breakpoints
>    Num     Type           Disp Enb Address            What
>    2       breakpoint     keep y                      foo thread 1
>            stop only in thread 1   
>     2.1                        y   0x0000000000401114  in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>     2.2                        y   0x0000000000401146  in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>     2.3                        y   0x0000000000401168  in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>    3       breakpoint     keep y                      bar thread 1
>            stop only in thread 1
>     3.1    breakpoint     keep y   0x000000000040110a  in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32
>
> ... for instance.  Or not include the "thread 1".  But my point is that if we were to show it, that's where we would
> show it, not in the breapoint locations.

I dug out your multi-location proposal, and it looks good.  It's a shame
it got bogged down as it did....

>
>> All of the above points are also true for the Ada 'task' breakpoint
>> condition, and the changes I've made also update how the task
>> information is printed, though in the case of the Ada task there was
>> no 'stop only in task XXX' line printed, so I've added one of those.
>> 
>> Obviously it can't be quite that easy.  For MI backwards compatibility
>> I've retained the existing code (but now only for MI like outputs),
>> which ensures we should generate backwards compatible output.
>> 
>> I've extended an Ada test to cover the new task related output, and
>> updated all the tests I could find that checked for the old output.
>> ---
>>  gdb/breakpoint.c                              | 30 ++++----
>>  gdb/testsuite/gdb.ada/tasks.exp               | 15 ++--
>>  gdb/testsuite/gdb.base/save-bp.exp            |  2 +-
>>  gdb/testsuite/gdb.base/thread-bp-multi-loc.c  | 44 ++++++++++++
>>  .../gdb.base/thread-bp-multi-loc.exp          | 72 +++++++++++++++++++
>>  5 files changed, 141 insertions(+), 22 deletions(-)
>>  create mode 100644 gdb/testsuite/gdb.base/thread-bp-multi-loc.c
>>  create mode 100644 gdb/testsuite/gdb.base/thread-bp-multi-loc.exp
>> 
>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>> index 00cc2ab401c..b2cd89511fb 100644
>> --- a/gdb/breakpoint.c
>> +++ b/gdb/breakpoint.c
>> @@ -6468,20 +6468,19 @@ print_one_breakpoint_location (struct breakpoint *b,
>>        output_thread_groups (uiout, "thread-groups", inf_nums, mi_only);
>>      }
>>  
>> -  if (!part_of_multiple)
>> +  /* In the MI output, each location of a thread or task specific
>> +     breakpoint is with the relevant thread or task ID.  This is done for
>> +     backwards compatibility reasons.
>
> Is "is with" idiomatic English meaning "includes"?  I don't think I ever heard that.
> Or is some word missing somewhere?  I'd suggest using "includes" or some
> such instead.

Fixed.

>
>> +
>> +     For the CLI output, the thread/task information is printed on a
>> +     separate line, see the 'stop only in thread' and 'stop only in task'
>> +     output below.  */
>> +  if (!header_of_multiple && uiout->is_mi_like_p ())
>>      {
>>        if (b->thread != -1)
>> -	{
>> -	  /* FIXME: This seems to be redundant and lost here; see the
>> -	     "stop only in" line a little further down.  */
>> -	  uiout->text (" thread ");
>> -	  uiout->field_signed ("thread", b->thread);
>> -	}
>> +	uiout->field_signed ("thread", b->thread);
>>        else if (b->task != 0)
>> -	{
>> -	  uiout->text (" task ");
>> -	  uiout->field_signed ("task", b->task);
>> -	}
>> +	uiout->field_signed ("task", b->task);
>>      }
>>  
>>    uiout->text ("\n");
>> @@ -6536,7 +6535,14 @@ print_one_breakpoint_location (struct breakpoint *b,
>>  	}
>>        uiout->text ("\n");
>>      }
>> -  
>> +
>> +  if (!part_of_multiple && b->task != 0)
>> +    {
>> +      uiout->text ("\tstop only in task ");
>> +      uiout->field_signed ("task", b->task);
>> +      uiout->text ("\n");
>> +    }
>> +
>>    if (!part_of_multiple)
>>      {
>>        if (b->hit_count)
>> diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp
>> index a9b58f20cf6..23bf3937a20 100644
>> --- a/gdb/testsuite/gdb.ada/tasks.exp
>> +++ b/gdb/testsuite/gdb.ada/tasks.exp
>> @@ -46,21 +46,18 @@ gdb_test "info tasks" \
>>  # breakpoint in the list that matched the triggered-breakpoint's
>>  # address, no matter which task it was specific to.
>>  gdb_test "break break_me task 1" "Breakpoint .* at .*"
>> +gdb_test "info breakpoints" "foo.adb:${decimal}\r\n\\s+stop only in task 1" \
>> +    "check info breakpoints for task 1 breakpoint"
>>  
>>  # Now, insert a breakpoint that should stop only if task 3 stops, and
>>  # extract its number.
>> -set bp_number -1
>> -set test "break break_me task 3"
>> -gdb_test_multiple $test $test {
>> -    -re "Breakpoint (.*) at .*$gdb_prompt $" {
>> -	set bp_number $expect_out(1,string)
>> -	pass $test
>> -    }
>> -}
>> -
>> +gdb_breakpoint "break_me task 3"
>> +set bp_number [get_integer_valueof "\$bpnum" -1]
>>  if {$bp_number < 0} {
>>      return
>>  }
>> +gdb_test "info breakpoints" "foo.adb:${decimal}\r\n\\s+stop only in task 3" \
>> +    "check info breakpoints for task 3 breakpoint"
>>  
>>  # Continue to that breakpoint.  Task 2 should hit it first, and GDB
>>  # is expected to ignore that hit and resume the execution.  Only then
>> diff --git a/gdb/testsuite/gdb.base/save-bp.exp b/gdb/testsuite/gdb.base/save-bp.exp
>> index a39712c7f5c..41d71837fb6 100644
>> --- a/gdb/testsuite/gdb.base/save-bp.exp
>> +++ b/gdb/testsuite/gdb.base/save-bp.exp
>> @@ -79,7 +79,7 @@ gdb_test_sequence "info break" "info break" [list				\
>>    "\[\r\n\]+Num +Type +Disp +Enb +Address +What"				\
>>    "\[\r\n\]+$bp_row_start break_me at \[^\r\n\]*$srcfile:\[0-9\]+"		\
>>    "\[\r\n\]+$bp_row_start main at \[^\r\n\]*$srcfile:$loc_bp2"			\
>> -  "\[\r\n\]+$bp_row_start main at \[^\r\n\]*$srcfile:$loc_bp3 +thread 1"	\
>> +  "\[\r\n\]+$bp_row_start main at \[^\r\n\]*$srcfile:$loc_bp3"			\
>>    "\[\r\n\]+\[ \t]+stop only in thread 1"					\
>>    "\[\r\n\]+$bp_row_start main at \[^\r\n\]*$srcfile:$loc_bp4"			\
>>    "\[\r\n\]+\[ \t\]+stop only if i == 1( \\((host|target) evals\\))?"		\
>> diff --git a/gdb/testsuite/gdb.base/thread-bp-multi-loc.c b/gdb/testsuite/gdb.base/thread-bp-multi-loc.c
>> new file mode 100644
>> index 00000000000..cab009c39ec
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.base/thread-bp-multi-loc.c
>> @@ -0,0 +1,44 @@
>> +/* This testcase is part of GDB, the GNU debugger.
>> +
>> +   Copyright 2022 Free Software Foundation, Inc.
>
> Needs to include 2023 in range now.

Fixed.

>
>> +
>> +   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/>.  */
>> +
>> +volatile int global_var = 0;
>> +
>> +__attribute__((__always_inline__)) static inline void
>> +foo (void)
>> +{
>> +  int i;
>> +
>> +  for (i = 0; i < 10; ++i)
>> +    global_var = i;
>> +}
>> +
>> +static void
>> +bar (void)
>> +{
>> +  global_var = 0;
>> +  foo ();
>> +}
>> +
>> +int
>> +main (void)
>> +{
>> +  global_var = 0;
>> +  foo ();
>> +  bar ();
>> +  foo ();
>> +  return 0;
>> +}
>> diff --git a/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp b/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp
>> new file mode 100644
>> index 00000000000..9665fe9e21f
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp
>> @@ -0,0 +1,72 @@
>> +# Copyright 2022 Free Software Foundation, Inc.
>
> Ditto.
>
>> +
>> +# 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/>.
>> +
>> +# Create a multi-location breakpoint with a thread condition, then check the
>> +# output of 'info breakpoints' to ensure that the thread condition is
>> +# displayed correctly.
>> +
>> +standard_testfile
>> +
>> +if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
>> +    return -1
>> +}
>> +
>> +if {![runto_main]} {
>> +    return -1
>> +}
>> +
>> +delete_breakpoints
>> +
>> +gdb_breakpoint "foo thread 1"
>> +set bp_number [get_integer_valueof "\$bpnum" 0]
>> +if { $bp_number == 0 } {
>> +    unresolved "breakpoint not placed correctly"
>> +    return -1
>> +}
>> +
>> +set saw_header false
>> +set saw_cond false
>> +set loc_count 0
>> +gdb_test_multiple "info breakpoints" "" {
>> +    -re "\r\nNum\\s+\[^\r\n\]+\r\n" {
>> +	exp_continue
>> +    }
>> +
>> +    -re "^$bp_number\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
>> +	set saw_header true
>> +	exp_continue
>> +    }
>> +
>> +    -re "^\\s+stop only in thread 1\r\n" {
>> +	set saw_cond true
>> +	exp_continue
>> +    }
>> +
>> +    -re "^$bp_number\\.\[123\]\\s+\[^\r\n\]+:${decimal}\r\n" {
>> +	incr loc_count
>> +	exp_continue
>> +    }
>> +
>> +    -re "^$gdb_prompt $" {
>> +	with_test_prefix $gdb_test_name {
>> +	    gdb_assert { $saw_header } \
>> +		"saw header line"
>> +	    gdb_assert { $saw_cond } \
>> +		"saw b/p condition line"
>> +	    gdb_assert { $loc_count == 3 } \
>> +		"saw all three locations"
>
> It's better if the tests are written such that FAIL/PASSes match.  In this case,
> if gdb_test_multiple hits an internal pattern, then we'll have a single
>
>  FAIL: info breakpoints
>
> To fix that, we should merge those three gdb_asserts into a single gdb_assert,
> and we should $gdb_test_name for its test name, along with passing a meaningful
> test name to gdb_test_multiple, so internal FAILs get that meaningful name
> as well.  If we want to show the individual conditions, that can
> still be done by outputting them to the log.

Done.

I agree that, if/when your multi-location work is merged we might want
to once again adjust how this information is displayed, inline with your
suggestion above.

However... how would you feel if this patch (as shown below) was merged
now?  I think this fixes the stray "thread 1" text immediately, and the
multi-location display patch should be easily updated on top of this, if
that was something that you plan to continue developing?

Thanks,
Andrew

---

commit fb13c47255f0738644ac85f705f1dae4ec3890e7
Author: Andrew Burgess <aburgess@redhat.com>
Date:   Mon Nov 7 17:18:55 2022 +0000

    gdb: fix display of thread condition for multi-location breakpoints
    
    If a breakpoint with multiple locations has a thread condition, then
    the 'info breakpoints' output is a little messed up, here's an example
    of the current output:
    
      (gdb) break foo thread 1
      Breakpoint 2 at 0x401114: foo. (3 locations)
      (gdb) break bar thread 1
      Breakpoint 3 at 0x40110a: file /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c, line 32.
      (gdb) info breakpoints
      Num     Type           Disp Enb Address            What
      2       breakpoint     keep y   <MULTIPLE>          thread 1
              stop only in thread 1
      2.1                         y   0x0000000000401114 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
      2.2                         y   0x0000000000401146 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
      2.3                         y   0x0000000000401168 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
      3       breakpoint     keep y   0x000000000040110a in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32 thread 1
              stop only in thread 1
    
    Notice that, at the end of the location for breakpoint 3, the 'thread
    1' condition is printed, but this is then repeated on the next line
    with 'stop only in thread 1'.
    
    In contrast, for breakpoint 2, the 'thread 1' appears randomly, in the
    "What" column, though slightly offset, non of the separate locations
    have the 'thread 1' information.  Additionally for breakpoint 2 we
    also get a 'stop only in thread 1' line.
    
    There's two things going on here.  First the randomly placed 'thread
    1' for breakpoint 2 is due to a bug in print_one_breakpoint_location,
    where we check the variable part_of_multiple instead of
    header_of_multiple.
    
    If I fix this oversight, then the output is now:
    
      (gdb) break foo thread 1
      Breakpoint 2 at 0x401114: foo. (3 locations)
      (gdb) break bar thread 1
      Breakpoint 3 at 0x40110a: file /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c, line 32.
      (gdb) info breakpoints
      Num     Type           Disp Enb Address            What
      2       breakpoint     keep y   <MULTIPLE>
              stop only in thread 1
      2.1                         y   0x0000000000401114 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
      2.2                         y   0x0000000000401146 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
      2.3                         y   0x0000000000401168 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25 thread 1
      3       breakpoint     keep y   0x000000000040110a in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32 thread 1
              stop only in thread 1
    
    The 'thread 1' condition is now displayed at the end of each location,
    which makes the output the same for single location breakpoints and
    multi-location breakpoints.
    
    However, there's still some duplication here.  Both breakpoints 2 and
    3 include a 'stop only in thread 1' line, and it feels like the
    additional 'thread 1' is redundant.  In fact, there's a comment to
    this very effect in the code:
    
      /* FIXME: This seems to be redundant and lost here; see the
         "stop only in" line a little further down.  */
    
    So, lets fix this FIXME.  The new plan is to remove all the trailing
    'thread 1' markers from the CLI output, we now get this:
    
      (gdb) break foo thread 1
      Breakpoint 2 at 0x401114: foo. (3 locations)
      (gdb) break bar thread 1
      Breakpoint 3 at 0x40110a: file /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c, line 32.
      (gdb) info breakpoints
      Num     Type           Disp Enb Address            What
      2       breakpoint     keep y   <MULTIPLE>
              stop only in thread 1
      2.1                         y   0x0000000000401114 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
      2.2                         y   0x0000000000401146 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
      2.3                         y   0x0000000000401168 in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
      3       breakpoint     keep y   0x000000000040110a in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32
              stop only in thread 1
    
    All of the above points are also true for the Ada 'task' breakpoint
    condition, and the changes I've made also update how the task
    information is printed, though in the case of the Ada task there was
    no 'stop only in task XXX' line printed, so I've added one of those.
    
    Obviously it can't be quite that easy.  For MI backwards compatibility
    I've retained the existing code (but now only for MI like outputs),
    which ensures we should generate backwards compatible output.
    
    I've extended an Ada test to cover the new task related output, and
    updated all the tests I could find that checked for the old output.

diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 8bc62743bb5..adf38e7d722 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -6468,20 +6468,19 @@ print_one_breakpoint_location (struct breakpoint *b,
       output_thread_groups (uiout, "thread-groups", inf_nums, mi_only);
     }
 
-  if (!part_of_multiple)
+  /* In the MI output, each location of a thread or task specific
+     breakpoint includes the relevant thread or task ID.  This is done for
+     backwards compatibility reasons.
+
+     For the CLI output, the thread/task information is printed on a
+     separate line, see the 'stop only in thread' and 'stop only in task'
+     output below.  */
+  if (!header_of_multiple && uiout->is_mi_like_p ())
     {
       if (b->thread != -1)
-	{
-	  /* FIXME: This seems to be redundant and lost here; see the
-	     "stop only in" line a little further down.  */
-	  uiout->text (" thread ");
-	  uiout->field_signed ("thread", b->thread);
-	}
+	uiout->field_signed ("thread", b->thread);
       else if (b->task != 0)
-	{
-	  uiout->text (" task ");
-	  uiout->field_signed ("task", b->task);
-	}
+	uiout->field_signed ("task", b->task);
     }
 
   uiout->text ("\n");
@@ -6536,7 +6535,14 @@ print_one_breakpoint_location (struct breakpoint *b,
 	}
       uiout->text ("\n");
     }
-  
+
+  if (!part_of_multiple && b->task != 0)
+    {
+      uiout->text ("\tstop only in task ");
+      uiout->field_signed ("task", b->task);
+      uiout->text ("\n");
+    }
+
   if (!part_of_multiple)
     {
       if (b->hit_count)
diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp
index 3eea04b3911..4441d92719c 100644
--- a/gdb/testsuite/gdb.ada/tasks.exp
+++ b/gdb/testsuite/gdb.ada/tasks.exp
@@ -50,21 +50,18 @@ gdb_test "watch j task 1 task 3" "You can specify only one task\\."
 # breakpoint in the list that matched the triggered-breakpoint's
 # address, no matter which task it was specific to.
 gdb_test "break break_me task 1" "Breakpoint .* at .*"
+gdb_test "info breakpoints" "foo.adb:${decimal}\r\n\\s+stop only in task 1" \
+    "check info breakpoints for task 1 breakpoint"
 
 # Now, insert a breakpoint that should stop only if task 3 stops, and
 # extract its number.
-set bp_number -1
-set test "break break_me task 3"
-gdb_test_multiple $test $test {
-    -re "Breakpoint (.*) at .*$gdb_prompt $" {
-	set bp_number $expect_out(1,string)
-	pass $test
-    }
-}
-
+gdb_breakpoint "break_me task 3"
+set bp_number [get_integer_valueof "\$bpnum" -1]
 if {$bp_number < 0} {
     return
 }
+gdb_test "info breakpoints" "foo.adb:${decimal}\r\n\\s+stop only in task 3" \
+    "check info breakpoints for task 3 breakpoint"
 
 # Continue to that breakpoint.  Task 2 should hit it first, and GDB
 # is expected to ignore that hit and resume the execution.  Only then
diff --git a/gdb/testsuite/gdb.base/save-bp.exp b/gdb/testsuite/gdb.base/save-bp.exp
index a39712c7f5c..41d71837fb6 100644
--- a/gdb/testsuite/gdb.base/save-bp.exp
+++ b/gdb/testsuite/gdb.base/save-bp.exp
@@ -79,7 +79,7 @@ gdb_test_sequence "info break" "info break" [list				\
   "\[\r\n\]+Num +Type +Disp +Enb +Address +What"				\
   "\[\r\n\]+$bp_row_start break_me at \[^\r\n\]*$srcfile:\[0-9\]+"		\
   "\[\r\n\]+$bp_row_start main at \[^\r\n\]*$srcfile:$loc_bp2"			\
-  "\[\r\n\]+$bp_row_start main at \[^\r\n\]*$srcfile:$loc_bp3 +thread 1"	\
+  "\[\r\n\]+$bp_row_start main at \[^\r\n\]*$srcfile:$loc_bp3"			\
   "\[\r\n\]+\[ \t]+stop only in thread 1"					\
   "\[\r\n\]+$bp_row_start main at \[^\r\n\]*$srcfile:$loc_bp4"			\
   "\[\r\n\]+\[ \t\]+stop only if i == 1( \\((host|target) evals\\))?"		\
diff --git a/gdb/testsuite/gdb.base/thread-bp-multi-loc.c b/gdb/testsuite/gdb.base/thread-bp-multi-loc.c
new file mode 100644
index 00000000000..2adb179d93c
--- /dev/null
+++ b/gdb/testsuite/gdb.base/thread-bp-multi-loc.c
@@ -0,0 +1,44 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2023 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/>.  */
+
+volatile int global_var = 0;
+
+__attribute__((__always_inline__)) static inline void
+foo (void)
+{
+  int i;
+
+  for (i = 0; i < 10; ++i)
+    global_var = i;
+}
+
+static void
+bar (void)
+{
+  global_var = 0;
+  foo ();
+}
+
+int
+main (void)
+{
+  global_var = 0;
+  foo ();
+  bar ();
+  foo ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp b/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp
new file mode 100644
index 00000000000..6e1121e867a
--- /dev/null
+++ b/gdb/testsuite/gdb.base/thread-bp-multi-loc.exp
@@ -0,0 +1,67 @@
+# Copyright 2022-2023 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/>.
+
+# Create a multi-location breakpoint with a thread condition, then check the
+# output of 'info breakpoints' to ensure that the thread condition is
+# displayed correctly.
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
+    return -1
+}
+
+if {![runto_main]} {
+    return -1
+}
+
+delete_breakpoints
+
+gdb_breakpoint "foo thread 1"
+set bp_number [get_integer_valueof "\$bpnum" 0]
+if { $bp_number == 0 } {
+    unresolved "breakpoint not placed correctly"
+    return -1
+}
+
+set saw_header false
+set saw_cond false
+set loc_count 0
+gdb_test_multiple "info breakpoints" \
+    "check thread condition is displayed correctly" {
+    -re "\r\nNum\\s+\[^\r\n\]+\r\n" {
+	exp_continue
+    }
+
+    -re "^$bp_number\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
+	set saw_header true
+	exp_continue
+    }
+
+    -re "^\\s+stop only in thread 1\r\n" {
+	set saw_cond true
+	exp_continue
+    }
+
+    -re "^$bp_number\\.\[123\]\\s+\[^\r\n\]+:${decimal}\r\n" {
+	incr loc_count
+	exp_continue
+    }
+
+    -re "^$gdb_prompt $" {
+	gdb_assert { $saw_header && $saw_cond && $loc_count == 3} \
+	    $gdb_test_name
+    }
+}


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 3/6] gdb: fix display of thread condition for multi-location breakpoints
  2023-02-06 14:48       ` Andrew Burgess
@ 2023-02-06 17:01         ` Pedro Alves
  2023-02-07 14:42           ` Andrew Burgess
  0 siblings, 1 reply; 54+ messages in thread
From: Pedro Alves @ 2023-02-06 17:01 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 2023-02-06 2:48 p.m., Andrew Burgess wrote:
> Pedro Alves <pedro@palves.net> writes:
> 

>>    (gdb) info breakpoints
>>    Num     Type           Disp Enb Address            What
>>    2       breakpoint     keep y                      foo thread 1
>>            stop only in thread 1   
>>     2.1                        y   0x0000000000401114  in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>>     2.2                        y   0x0000000000401146  in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>>     2.3                        y   0x0000000000401168  in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>>    3       breakpoint     keep y                      bar thread 1
>>            stop only in thread 1
>>     3.1    breakpoint     keep y   0x000000000040110a  in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32
>>
>> ... for instance.  Or not include the "thread 1".  But my point is that if we were to show it, that's where we would
>> show it, not in the breapoint locations.
> 
> I dug out your multi-location proposal, and it looks good.  It's a shame
> it got bogged down as it did....

Thanks.  I still hope to get back to it at some point, but probably only after I manage to
be done with the step over thread clone/exit series, and the big ctrl-c rework.

>> To fix that, we should merge those three gdb_asserts into a single gdb_assert,
>> and we should $gdb_test_name for its test name, along with passing a meaningful
>> test name to gdb_test_multiple, so internal FAILs get that meaningful name
>> as well.  If we want to show the individual conditions, that can
>> still be done by outputting them to the log.
> 
> Done.

Thank you.

> 
> I agree that, if/when your multi-location work is merged we might want
> to once again adjust how this information is displayed, inline with your
> suggestion above.
> 
> However... how would you feel if this patch (as shown below) was merged
> now?  I think this fixes the stray "thread 1" text immediately, and the
> multi-location display patch should be easily updated on top of this, if
> that was something that you plan to continue developing?

Oh yes, I totally agree with merging your patch.  I only referenced the
multi-locations proposal to help with justifying why I think your patch
is good.  I did not mean to suggest that getting my proposal in would
remove the need for your patch.

In sum, in my view, code locations are not thread specific, so the "thread N" part
should not be displayed as if part of the location.

Approved-By: Pedro Alves <pedro@palves.net>

Pedro Alves

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 5/6] gdb: add inferior-specific breakpoints and watchpoints
  2023-02-03 16:55       ` Andrew Burgess
@ 2023-02-06 17:24         ` Pedro Alves
  0 siblings, 0 replies; 54+ messages in thread
From: Pedro Alves @ 2023-02-06 17:24 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 2023-02-03 4:55 p.m., Andrew Burgess wrote:
> Pedro Alves <pedro@palves.net> writes:
> 
>> On 2023-01-20 9:46 a.m., Andrew Burgess via Gdb-patches wrote:
>>> This commit extends the breakpoint mechanism to allow for inferior
>>> specific breakpoints (and watchpoints).
>>>
>>> As GDB gains better support for multiple connections, and so for
>>> running multiple (possibly unrelated) inferiors, then it is not hard
>>> to imagine that a user might wish to create breakpoints that apply to
>>> any thread in a single inferior.  To achieve this currently, the user
>>> would need to create a condition possibly making use of the $_inferior
>>> convenience variable, which, though functional, isn't the most user
>>> friendly.
>>
>> So IIUC, if you do:
>>
>> (gdb) break foo inferior 1
>>
>> and foo exists in other inferiors, the resulting breakpoint will still
>> have locations for the other inferiors.  Is that so?
>>
>> I think that instead, we should not get any location for other inferiors.
>> I.e., we should do location pre-filtering, simply by not looking for
>> locations in pspaces other than the inferior's.  I think that can be
>> done easily by simply passing the inferior's pspace as search_pspace argument
>> to decode_line_full.
> 
> Thanks for all your great feedback.  I'll work through the rest of your
> comments, but I want to follow up on this one.
> 
> Baris already raised the point of filtering per-inferior breakpoints so
> a to avoid adding extra locations.

Ah, missed that.  Apologies.

> 
> I 100% agree that this is something that we should do, and this is on my
> plan if/when I land this series.
> 
> But I don't see any particular difference between per-inferior and
> per-thread breakpoints in this regard.  Please correct me if I'm wrong,
> but a per-thread breakpoint will insert into every inferior despite only
> ever triggering for a single thread in a single inferior (per thread
> breakpoints are on the global thread-id).

That is a very good point.  Given that, I agree you don't need to change it
in this patch.

I filed it as a bug, here:
 https://sourceware.org/bugzilla/show_bug.cgi?id=30086

> 
> So, because this oddity already exists in GDB, my plan was to land this
> feature, then follow up with a patch to limit both per-thread and
> per-inferior breakpoints to only insert into the correct pspace.

Agreed.

^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv2 3/6] gdb: fix display of thread condition for multi-location breakpoints
  2023-02-06 17:01         ` Pedro Alves
@ 2023-02-07 14:42           ` Andrew Burgess
  0 siblings, 0 replies; 54+ messages in thread
From: Andrew Burgess @ 2023-02-07 14:42 UTC (permalink / raw)
  To: Pedro Alves, gdb-patches

Pedro Alves <pedro@palves.net> writes:

> On 2023-02-06 2:48 p.m., Andrew Burgess wrote:
>> Pedro Alves <pedro@palves.net> writes:
>> 
>
>>>    (gdb) info breakpoints
>>>    Num     Type           Disp Enb Address            What
>>>    2       breakpoint     keep y                      foo thread 1
>>>            stop only in thread 1   
>>>     2.1                        y   0x0000000000401114  in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>>>     2.2                        y   0x0000000000401146  in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>>>     2.3                        y   0x0000000000401168  in foo at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:25
>>>    3       breakpoint     keep y                      bar thread 1
>>>            stop only in thread 1
>>>     3.1    breakpoint     keep y   0x000000000040110a  in bar at /tmp/src/gdb/testsuite/gdb.base/thread-bp-multi-loc.c:32
>>>
>>> ... for instance.  Or not include the "thread 1".  But my point is that if we were to show it, that's where we would
>>> show it, not in the breapoint locations.
>> 
>> I dug out your multi-location proposal, and it looks good.  It's a shame
>> it got bogged down as it did....
>
> Thanks.  I still hope to get back to it at some point, but probably only after I manage to
> be done with the step over thread clone/exit series, and the big ctrl-c rework.
>
>>> To fix that, we should merge those three gdb_asserts into a single gdb_assert,
>>> and we should $gdb_test_name for its test name, along with passing a meaningful
>>> test name to gdb_test_multiple, so internal FAILs get that meaningful name
>>> as well.  If we want to show the individual conditions, that can
>>> still be done by outputting them to the log.
>> 
>> Done.
>
> Thank you.
>
>> 
>> I agree that, if/when your multi-location work is merged we might want
>> to once again adjust how this information is displayed, inline with your
>> suggestion above.
>> 
>> However... how would you feel if this patch (as shown below) was merged
>> now?  I think this fixes the stray "thread 1" text immediately, and the
>> multi-location display patch should be easily updated on top of this, if
>> that was something that you plan to continue developing?
>
> Oh yes, I totally agree with merging your patch.  I only referenced the
> multi-locations proposal to help with justifying why I think your patch
> is good.  I did not mean to suggest that getting my proposal in would
> remove the need for your patch.
>
> In sum, in my view, code locations are not thread specific, so the "thread N" part
> should not be displayed as if part of the location.
>
> Approved-By: Pedro Alves <pedro@palves.net>

I pushed this.

Thanks,
Andrew



^ permalink raw reply	[flat|nested] 54+ messages in thread

* RE: [PATCHv2 5/6] gdb: add inferior-specific breakpoints and watchpoints
  2023-01-20  9:46   ` [PATCHv2 5/6] gdb: add inferior-specific breakpoints and watchpoints Andrew Burgess
  2023-01-20 13:25     ` Eli Zaretskii
  2023-02-02 19:17     ` Pedro Alves
@ 2023-02-16 12:56     ` Aktemur, Tankut Baris
  2 siblings, 0 replies; 54+ messages in thread
From: Aktemur, Tankut Baris @ 2023-02-16 12:56 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On Friday, January 20, 2023 10:46 AM, Andrew Burgess wrote:
> This commit extends the breakpoint mechanism to allow for inferior
> specific breakpoints (and watchpoints).
> 
> As GDB gains better support for multiple connections, and so for
> running multiple (possibly unrelated) inferiors, then it is not hard
> to imagine that a user might wish to create breakpoints that apply to
> any thread in a single inferior.  To achieve this currently, the user
> would need to create a condition possibly making use of the $_inferior
> convenience variable, which, though functional, isn't the most user
> friendly.
> 
> This commit adds a new 'inferior' keyword that allows for the creation
> of inferior specific breakpoints.
> 
> Like thread specific breakpoints, the inferior specific breakpoints
> are automatically deleted once the inferior in question exits.
...
> diff --git a/gdb/guile/scm-breakpoint.c b/gdb/guile/scm-breakpoint.c
> index a7e043d847b..4c6fff070cf 100644
> --- a/gdb/guile/scm-breakpoint.c
> +++ b/gdb/guile/scm-breakpoint.c
> @@ -779,6 +779,11 @@ gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue)
>    else
>      SCM_ASSERT_TYPE (0, newvalue, SCM_ARG2, FUNC_NAME, _("integer or #f"));
> 
> +  if (bp_smob->bp->inferior != -1)
> +    scm_misc_error (FUNC_NAME,
> +		    _("Cannot have both thread and inferior conditions "
> +		      "on a breakpoint"), SCM_EOL);

The CLI error message used single quotes around "thread" and "inferior".
I think it was better to read.  It'd be nice to do the same here, too.
There are two more such messages below, in the Python code.

Regards,
-Baris


Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 54+ messages in thread

* RE: [PATCHv2 6/6] gdb: convert the 'start' breakpoint to use inferior keyword
  2023-01-20  9:46   ` [PATCHv2 6/6] gdb: convert the 'start' breakpoint to use inferior keyword Andrew Burgess
@ 2023-02-16 12:59     ` Aktemur, Tankut Baris
  0 siblings, 0 replies; 54+ messages in thread
From: Aktemur, Tankut Baris @ 2023-02-16 12:59 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On Friday, January 20, 2023 10:46 AM, Andrew Burgess wrote:
> Now that we have support for inferior specific breakpoints, the
> breakpoint created for the 'start' command can make use of this
> keyword.
> 
> In most cases the observed functionality should be unchanged from a
> user's point of view, though the code in GDB is a little cleaner now,
> we no longer need to change the expression used based on the
> language.
> 
> I do wonder if the current mechanism could run into problems if we had
> different inferiors of different languages.  For example, if we had an
> Ada inferior and a C inferior and followed a process a little like
> this:
> 
>   1. Start C inferior, the inferior stops before main for some reason,
> 
>   2. Start the Ada inferior, this runs to main,
> 
>   3. I think when we hit main, the condition for both breakpoints will
>   be evaluated, this will include evaluating the expression for the C
>   'start' breakpoint, which uses '==' and is, I guess, not valid for
>   Ada.
> 
> I haven't tried to create a testcase for this situation, but it's a
> possibly interesting edge case.
> 
> One place where the observed behaviour is different, is that inferior
> breakpoints, like thread breakpoints, will be auto-deleted when the
> contained inferior exits.
> 
> As a result, if an inferior exits before hitting the start breakpoint,
> then the 'start' breakpoint will be auto-deleted.
> 
> I have got a test that covers this situation.
> ---
>  gdb/infcmd.c                                  | 10 +---
>  .../gdb.base/start-inferior-specific-1.c      | 32 +++++++++++
>  .../gdb.base/start-inferior-specific-2.c      | 22 ++++++++
>  .../gdb.base/start-inferior-specific.exp      | 55 +++++++++++++++++++
>  4 files changed, 112 insertions(+), 7 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.base/start-inferior-specific-1.c
>  create mode 100644 gdb/testsuite/gdb.base/start-inferior-specific-2.c
>  create mode 100644 gdb/testsuite/gdb.base/start-inferior-specific.exp
> 
> diff --git a/gdb/infcmd.c b/gdb/infcmd.c
> index 7d5ec77ff57..7dfef4b5a23 100644
> --- a/gdb/infcmd.c
> +++ b/gdb/infcmd.c
> @@ -429,13 +429,9 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
>  	 have proper inferior-specific breakpoint support, in the breakpoint
>  	 machinery.  We could then avoid inserting a breakpoint in the program
>  	 spaces unrelated to this inferior.  */
> -      const char *op
> -	= ((current_language->la_language == language_ada
> -	    || current_language->la_language == language_pascal
> -	    || current_language->la_language == language_m2) ? "=" : "==");
> -      std::string arg = string_printf
> -	("-qualified %s if $_inferior %s %d", main_name (), op,
> -	 current_inferior ()->num);
> +      std::string arg = string_printf ("-qualified %s inferior %d",
> +				       main_name (),
> +				       current_inferior ()->num);
>        tbreak_command (arg.c_str (), 0);
>      }
> 
> diff --git a/gdb/testsuite/gdb.base/start-inferior-specific-1.c
> b/gdb/testsuite/gdb.base/start-inferior-specific-1.c
> new file mode 100644
> index 00000000000..1717a82b75d
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/start-inferior-specific-1.c
> @@ -0,0 +1,32 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2022 Free Software Foundation, Inc.

2022-2023

> +
> +   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 <unistd.h>
> +#include <stdlib.h>
> +
> +__attribute__((constructor))
> +static void
> +ctor (void)
> +{
> +  exit (1);
> +}
> +
> +int
> +main ()
> +{
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.base/start-inferior-specific-2.c
> b/gdb/testsuite/gdb.base/start-inferior-specific-2.c
> new file mode 100644
> index 00000000000..b69e218962a
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/start-inferior-specific-2.c
> @@ -0,0 +1,22 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2022 Free Software Foundation, Inc.

2022-2023

> +
> +   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/>.  */
> +
> +int
> +main ()
> +{
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.base/start-inferior-specific.exp
> b/gdb/testsuite/gdb.base/start-inferior-specific.exp
> new file mode 100644
> index 00000000000..7271f6d49d3
> --- /dev/null
> +++ b/gdb/testsuite/gdb.base/start-inferior-specific.exp
> @@ -0,0 +1,55 @@
> +# Copyright 2022 Free Software Foundation, Inc.

2022-2023

Regards,
-Baris


Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCHv3 0/2] Inferior specific breakpoints
  2023-01-20  9:46 ` [PATCHv2 " Andrew Burgess
                     ` (5 preceding siblings ...)
  2023-01-20  9:46   ` [PATCHv2 6/6] gdb: convert the 'start' breakpoint to use inferior keyword Andrew Burgess
@ 2023-03-16 17:03   ` Andrew Burgess
  2023-03-16 17:03     ` [PATCHv3 1/2] gdb: cleanup around some set_momentary_breakpoint_at_pc calls Andrew Burgess
                       ` (2 more replies)
  6 siblings, 3 replies; 54+ messages in thread
From: Andrew Burgess @ 2023-03-16 17:03 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Changes in v3:

  - Patches #2, #3, and #4 from the previous series have been merged.

  - Patch #1 is removed from this series in favour of a future patch
    from Pedro as mentioned here:

    https://sourceware.org/pipermail/gdb-patches/2023-February/196557.html

  - Patch #6 has been removed.  The core part of this patch (changing
    how the 'start' breakpoint is created) has been merged back into
    patch #5.  The extra tests added in the old #6 are no longer
    relevant and have been dropped.  The 'start' command is already
    tested in our testsuite, so this extra change should be well tested.

  - In patch #5:

    + The docs are unchanged (actually I fixed 'a' to 'an' in one place).

    + Inferior breakpoints are deleted when the inferior is removed,
      not when it exits.

    + We still don't filter breakpoint locations based on the inferior
      program space, but this is now discussed in the commitmessage.
      Bascially doing this opens a huge can of worms.  I am working on
      this, but see this as a future optimisation rather than a
      blocker for this initial change.

    + Pedro's feedback about how inferior specific breakpoints were
      deleted has been addressed.  Since Pedro's initial feedback I've
      fixed how thread-specific breakpoints are deleted (I had
      originally just copied this code), and I've now copied the fixed
      code.

    + The patch no longer adds inferior specific watchpoints.  As
      Pedro pointed out this feature would only have had a very
      limited niche use case, and I didn't see the value of trying to
      include that change in this commit.  If a user tries to create
      an inferior-specific watchpoint they'll now get an error.  We
      could change this in the future if we're keen.

Changes in v2:

  - Biggest changes are in patch #3, as suggested by Baris, I've now
    removed the 'thread' and 'inferior' tags from the end of each
    location in the 'info breakpoints' output.  The information was
    already available in the condition line, so this removes some
    duplication.  For Ada task conditions I've added a condition line
    as this was missing.

  - Doc updates in #4 and #5 to fix the issues Eli pointed out.  These
    all seemed pretty minor so I don't think there should be anything
    too surprising there.

  - Updated patch #5 based on feedback from Baris, this was all pretty
    minor stuff though, formatting, and rewording some error messages,

  - Updated patch #6 based on feedback from Baris, this is a minor
    test cleanup.

Thanks,
Andrew

---

Andrew Burgess (2):
  gdb: cleanup around some set_momentary_breakpoint_at_pc calls
  gdb: add inferior-specific breakpoints

 gdb/NEWS                                      |  15 ++
 gdb/breakpoint.c                              | 237 ++++++++++++++----
 gdb/breakpoint.h                              |  14 +-
 gdb/doc/gdb.texinfo                           |  72 +++++-
 gdb/doc/python.texi                           |  26 +-
 gdb/guile/scm-breakpoint.c                    |   7 +-
 gdb/infcmd.c                                  |  15 +-
 gdb/inferior.h                                |  11 +
 gdb/infrun.c                                  |  12 +-
 gdb/linespec.c                                |   4 +-
 gdb/mi/mi-cmd-break.c                         |  11 +-
 gdb/mi/mi-main.c                              |  18 +-
 gdb/mi/mi-main.h                              |   6 +
 gdb/python/py-breakpoint.c                    |  96 ++++++-
 gdb/python/py-finishbreakpoint.c              |   2 +-
 gdb/testsuite/gdb.ada/tasks.exp               |   2 +
 gdb/testsuite/gdb.linespec/cpcompletion.exp   |   4 +-
 gdb/testsuite/gdb.linespec/explicit.exp       |   1 +
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c      |  29 +++
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp    | 108 ++++++++
 .../gdb.multi/inferior-specific-bp-1.c        |  52 ++++
 .../gdb.multi/inferior-specific-bp-2.c        |  52 ++++
 .../gdb.multi/inferior-specific-bp.exp        | 179 +++++++++++++
 gdb/testsuite/gdb.python/py-breakpoint.exp    |  43 ++++
 gdb/testsuite/lib/completion-support.exp      |   2 +-
 gdb/testsuite/lib/mi-support.exp              |  20 +-
 26 files changed, 946 insertions(+), 92 deletions(-)
 create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
 create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp.exp


base-commit: 5a9affd7b875ac183a66ce41f3f226819f0790ed
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCHv3 1/2] gdb: cleanup around some set_momentary_breakpoint_at_pc calls
  2023-03-16 17:03   ` [PATCHv3 0/2] Inferior specific breakpoints Andrew Burgess
@ 2023-03-16 17:03     ` Andrew Burgess
  2023-04-03 14:12       ` Andrew Burgess
  2023-03-16 17:03     ` [PATCHv3 2/2] gdb: add inferior-specific breakpoints Andrew Burgess
  2023-04-03 14:14     ` [PATCHv4] " Andrew Burgess
  2 siblings, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2023-03-16 17:03 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

I noticed a couple of places in infrun.c where we call
set_momentary_breakpoint_at_pc, and then set the newly created
breakpoint's thread field, these are in:

  insert_exception_resume_breakpoint
  insert_exception_resume_from_probe

Function set_momentary_breakpoint_at_pc calls
set_momentary_breakpoint, which always creates the breakpoint as
thread-specific for the current inferior_thread().

The two insert_* functions mentioned above take an arbitrary
thread_info* as an argument and set the breakpoint::thread to hold the
thread number of that arbitrary thread.

However, the insert_* functions store the breakpoint pointer within
the current inferior_thread(), so we know that the thread being passed
in must be the currently selected thread.

What this means is that we can:

  1. Assert that the thread being passed in is the currently selected
  thread, and

  2. No longer adjust the breakpoint::thread field, this will already
  have been set correctly be calling set_momentary_breakpoint_at_pc.

There should be no user visible changes after this commit.
---
 gdb/infrun.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/gdb/infrun.c b/gdb/infrun.c
index 33aa0c8794b..35c3d2b8f58 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -8138,6 +8138,9 @@ insert_exception_resume_breakpoint (struct thread_info *tp,
 	  infrun_debug_printf ("exception resume at %lx",
 			       (unsigned long) handler);
 
+	  /* set_momentary_breakpoint_at_pc creates a thread-specific
+	     breakpoint for the current inferior thread.  */
+	  gdb_assert (tp == inferior_thread ());
 	  bp = set_momentary_breakpoint_at_pc (get_frame_arch (frame),
 					       handler,
 					       bp_exception_resume).release ();
@@ -8145,8 +8148,7 @@ insert_exception_resume_breakpoint (struct thread_info *tp,
 	  /* set_momentary_breakpoint_at_pc invalidates FRAME.  */
 	  frame = nullptr;
 
-	  bp->thread = tp->global_num;
-	  inferior_thread ()->control.exception_resume_breakpoint = bp;
+	  tp->control.exception_resume_breakpoint = bp;
 	}
     }
   catch (const gdb_exception_error &e)
@@ -8176,10 +8178,12 @@ insert_exception_resume_from_probe (struct thread_info *tp,
   infrun_debug_printf ("exception resume at %s",
 		       paddress (probe->objfile->arch (), handler));
 
+  /* set_momentary_breakpoint_at_pc creates a thread-specific breakpoint
+     for the current inferior thread.  */
+  gdb_assert (tp == inferior_thread ());
   bp = set_momentary_breakpoint_at_pc (get_frame_arch (frame),
 				       handler, bp_exception_resume).release ();
-  bp->thread = tp->global_num;
-  inferior_thread ()->control.exception_resume_breakpoint = bp;
+  tp->control.exception_resume_breakpoint = bp;
 }
 
 /* This is called when an exception has been intercepted.  Check to
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCHv3 2/2] gdb: add inferior-specific breakpoints
  2023-03-16 17:03   ` [PATCHv3 0/2] Inferior specific breakpoints Andrew Burgess
  2023-03-16 17:03     ` [PATCHv3 1/2] gdb: cleanup around some set_momentary_breakpoint_at_pc calls Andrew Burgess
@ 2023-03-16 17:03     ` Andrew Burgess
  2023-04-03 14:14     ` [PATCHv4] " Andrew Burgess
  2 siblings, 0 replies; 54+ messages in thread
From: Andrew Burgess @ 2023-03-16 17:03 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Eli,

You already reviwed these doc changes here:

  https://sourceware.org/pipermail/gdb-patches/2023-January/195986.html

I've made no significant changes to the docs part since then.

Thanks,
Andrew

---

This commit extends the breakpoint mechanism to allow for inferior
specific breakpoints (but not watchpoints in this commit).

As GDB gains better support for multiple connections, and so for
running multiple (possibly unrelated) inferiors, then it is not hard
to imagine that a user might wish to create breakpoints that apply to
any thread in a single inferior.  To achieve this currently, the user
would need to create a condition possibly making use of the $_inferior
convenience variable, which, though functional, isn't the most user
friendly.

This commit adds a new 'inferior' keyword that allows for the creation
of inferior specific breakpoints.

Inferior specific breakpoints are automatically deleted when the
associated inferior is removed from GDB, this is similar to how
thread-specific breakpoints when the associated thread is deleted.

Watchpoints are already per-program-space, which in most cases mean
watchpoints are already inferior specific.  There is a small window
where inferior-specific watchpoints might make sense, which is after a
vfork, when two processes are sharing the same address space.
However, I'm leaving that as an exercise for another day.  For now,
attempting to use the inferior keyword with a watchpoint will give an
error, like this:

  (gdb) watch a8 inferior 1
  Cannot use 'inferior' keyword with watchpoints

A final note on the implementation.  Currently, inferior specific
breakpoints, like thread-specific breakpoints, are inserted into every
inferior, GDB then checks once the inferior stops if we are in the
correct thread or inferior, and resumes automatically if we stopped in
the wrong thread/inferior.

An obvious optimisation here is to only insert breakpoint locations
into the specific program space (which mostly means inferior) that
contains either the inferior or thread we are interested in.  This
would reduce the number times GDB has to stop and then resume again in
a multi-inferior setup.

I have started investigating implementing this.  And though the
initial implementation is trivial to get going, like so many things,
the devil is in the detail.  While my hope is that I will bring this
optimisation to the list at some point in the near future I can't
guarantee it.  That said, we already have the issue of placing
breakpoints into inferiors that will never be hit (with thread
specific breakpoints), so I don't believe I'm adding anything worse
than we already have.  Personally, I'd be happy to see this new
feature merged even if the optimisation never happens.
---
 gdb/NEWS                                      |  15 ++
 gdb/breakpoint.c                              | 237 ++++++++++++++----
 gdb/breakpoint.h                              |  14 +-
 gdb/doc/gdb.texinfo                           |  72 +++++-
 gdb/doc/python.texi                           |  26 +-
 gdb/guile/scm-breakpoint.c                    |   7 +-
 gdb/infcmd.c                                  |  15 +-
 gdb/inferior.h                                |  11 +
 gdb/linespec.c                                |   4 +-
 gdb/mi/mi-cmd-break.c                         |  11 +-
 gdb/mi/mi-main.c                              |  18 +-
 gdb/mi/mi-main.h                              |   6 +
 gdb/python/py-breakpoint.c                    |  96 ++++++-
 gdb/python/py-finishbreakpoint.c              |   2 +-
 gdb/testsuite/gdb.ada/tasks.exp               |   2 +
 gdb/testsuite/gdb.linespec/cpcompletion.exp   |   4 +-
 gdb/testsuite/gdb.linespec/explicit.exp       |   1 +
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c      |  29 +++
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp    | 108 ++++++++
 .../gdb.multi/inferior-specific-bp-1.c        |  52 ++++
 .../gdb.multi/inferior-specific-bp-2.c        |  52 ++++
 .../gdb.multi/inferior-specific-bp.exp        | 179 +++++++++++++
 gdb/testsuite/gdb.python/py-breakpoint.exp    |  43 ++++
 gdb/testsuite/lib/completion-support.exp      |   2 +-
 gdb/testsuite/lib/mi-support.exp              |  20 +-
 25 files changed, 938 insertions(+), 88 deletions(-)
 create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
 create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index cc262f1f8a6..c963c0dd29c 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -61,6 +61,13 @@
     break foo thread 1 task 1
     watch var thread 2 task 3
 
+* Breakpoints can now be inferior-specific.  This is similar to the
+  existing thread-specific breakpoint support.  Breakpoint conditions
+  can include the 'inferior' keyword followed by an inferior id (as
+  displayed in the 'info inferiors' output).  It is invalid to use the
+  'inferior' keyword with either the 'thread' or 'task' keywords when
+  creating a breakpoint.
+
 * New commands
 
 maintenance print record-instruction [ N ]
@@ -104,6 +111,14 @@ show always-read-ctf
    without a thread restriction.  The same is also true for the 'task'
    field of an Ada task-specific breakpoint.
 
+** The -break-insert command now accepts a '-g thread-group-id' option
+   to allow for the creation of inferior-specific breakpoints.
+
+** The bkpt tuple, which appears in breakpoint-created notifications,
+   and in the result of the -break-insert command can now include an
+   optional 'inferior' field for both the main breakpoint, and each
+   location, when the breakpoint is inferior-specific.
+
 *** Changes in GDB 13
 
 * MI version 1 is deprecated, and will be removed in GDB 14.
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index abee22cd162..5e2065e31b8 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -96,7 +96,7 @@ static void create_breakpoints_sal (struct gdbarch *,
 				    gdb::unique_xmalloc_ptr<char>,
 				    gdb::unique_xmalloc_ptr<char>,
 				    enum bptype,
-				    enum bpdisp, int, int,
+				    enum bpdisp, int, int, int,
 				    int,
 				    int, int, int, unsigned);
 
@@ -320,6 +320,9 @@ struct momentary_breakpoint : public code_breakpoint
     disposition = disp_donttouch;
     frame_id = frame_id_;
     thread = thread_;
+
+    /* The inferior should have been set by the parent constructor.  */
+    gdb_assert (inferior == -1);
   }
 
   void re_set () override;
@@ -1459,13 +1462,15 @@ breakpoint_set_silent (struct breakpoint *b, int silent)
 void
 breakpoint_set_thread (struct breakpoint *b, int thread)
 {
-  /* It is invalid to set the thread field to anything other than -1 (which
-     means no thread restriction) if a task restriction is already in
-     place.  */
-  gdb_assert (thread == -1 || b->task == -1);
+  /* THREAD should be -1, meaning no thread restriction, or it should be a
+     valid global thread-id, which are greater than zero.  */
+  gdb_assert (thread == -1 || thread > 0);
 
-  int old_thread = b->thread;
+  /* It is not valid to set a thread restriction for a breakpoint that
+     already has task or inferior restriction.  */
+  gdb_assert (thread == -1 || (b->task == -1 && b->inferior == -1));
 
+  int old_thread = b->thread;
   b->thread = thread;
   if (old_thread != thread)
     gdb::observers::breakpoint_modified.notify (b);
@@ -1473,16 +1478,37 @@ breakpoint_set_thread (struct breakpoint *b, int thread)
 
 /* See breakpoint.h.  */
 
+void
+breakpoint_set_inferior (struct breakpoint *b, int inferior)
+{
+  /* INFERIOR should be -1, meaning no inferior restriction, or it should
+     be a valid inferior number, which are greater than zero.  */
+  gdb_assert (inferior == -1 || inferior > 0);
+
+  /* It is not valid to set an inferior restriction for a breakpoint that
+     already has a task or thread restriction.  */
+  gdb_assert (inferior == -1 || (b->task == -1 && b->thread == -1));
+
+  int old_inferior = b->inferior;
+  b->inferior = inferior;
+  if (old_inferior != inferior)
+    gdb::observers::breakpoint_modified.notify (b);
+}
+
+/* See breakpoint.h.  */
+
 void
 breakpoint_set_task (struct breakpoint *b, int task)
 {
-  /* It is invalid to set the task field to anything other than -1 (which
-     means no task restriction) if a thread restriction is already in
-     place.  */
-  gdb_assert (task == -1 || b->thread == -1);
+  /* TASK should be -1, meaning no task restriction, or it should be a
+     valid task-id, which are greater than zero.  */
+  gdb_assert (task == -1 || task > 0);
 
-  int old_task = b->task;
+  /* It is not valid to set a task restriction for a breakpoint that
+     already has a thread or inferior restriction.  */
+  gdb_assert (task == -1 || (b->thread == -1 && b->inferior == -1));
 
+  int old_task = b->task;
   b->task = task;
   if (old_task != task)
     gdb::observers::breakpoint_modified.notify (b);
@@ -3157,6 +3183,12 @@ insert_breakpoint_locations (void)
 	  && !valid_global_thread_id (bl->owner->thread))
 	continue;
 
+      /* Or inferior specific breakpoints if the inferior no longer
+	 exists.  */
+      if (bl->owner->inferior != -1
+	  && !valid_global_inferior_id (bl->owner->inferior))
+	continue;
+
       switch_to_program_space_and_thread (bl->pspace);
 
       /* For targets that support global breakpoints, there's no need
@@ -3257,6 +3289,29 @@ Thread-specific breakpoint %d deleted - thread %s no longer in the thread list.\
     }
 }
 
+/* Called when inferior INF has been removed from GDB.  Remove associated
+   per-inferior breakpoints.  */
+
+static void
+remove_inferior_breakpoints (struct inferior *inf)
+{
+  for (breakpoint *b : all_breakpoints_safe ())
+    {
+      if (b->inferior == inf->num && user_breakpoint_p (b))
+	{
+	  /* Tell the user the breakpoint has been deleted.  But only for
+	     breakpoints that would not normally have been deleted at the
+	     next stop anyway.  */
+	  if (b->disposition != disp_del
+	      && b->disposition != disp_del_at_next_stop)
+	    gdb_printf (_("\
+Inferior-specific breakpoint %d deleted - inferior %d has been removed.\n"),
+			b->number, inf->num);
+	  delete_breakpoint (b);
+       }
+    }
+}
+
 /* See breakpoint.h.  */
 
 void
@@ -5466,6 +5521,7 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread)
      evaluating the condition if this isn't the specified
      thread/task.  */
   if ((b->thread != -1 && b->thread != thread->global_num)
+      || (b->inferior != -1 && b->inferior != thread->inf->num)
       || (b->task != -1 && b->task != ada_get_task_number (thread)))
     {
       infrun_debug_printf ("incorrect thread or task, not stopping");
@@ -6482,6 +6538,8 @@ print_one_breakpoint_location (struct breakpoint *b,
 	uiout->field_signed ("thread", b->thread);
       else if (b->task != -1)
 	uiout->field_signed ("task", b->task);
+      else if (b->inferior != -1)
+	uiout->field_signed ("inferior", b->inferior);
     }
 
   uiout->text ("\n");
@@ -6544,6 +6602,13 @@ print_one_breakpoint_location (struct breakpoint *b,
       uiout->text ("\n");
     }
 
+  if (!part_of_multiple && b->inferior != -1)
+    {
+      uiout->text ("\tstop only in inferior ");
+      uiout->field_signed ("inferior", b->inferior);
+      uiout->text ("\n");
+    }
+
   if (!part_of_multiple)
     {
       if (b->hit_count)
@@ -7522,7 +7587,10 @@ delete_longjmp_breakpoint (int thread)
     if (b->type == bp_longjmp || b->type == bp_exception)
       {
 	if (b->thread == thread)
-	  delete_breakpoint (b);
+	  {
+	    gdb_assert (b->inferior == -1);
+	    delete_breakpoint (b);
+	  }
       }
 }
 
@@ -7533,7 +7601,10 @@ delete_longjmp_breakpoint_at_next_stop (int thread)
     if (b->type == bp_longjmp || b->type == bp_exception)
       {
 	if (b->thread == thread)
-	  b->disposition = disp_del_at_next_stop;
+	  {
+	    gdb_assert (b->inferior == -1);
+	    b->disposition = disp_del_at_next_stop;
+	  }
       }
 }
 
@@ -7586,6 +7657,7 @@ check_longjmp_breakpoint_for_call_dummy (struct thread_info *tp)
   ALL_BREAKPOINTS_SAFE (b, b_tmp)
     if (b->type == bp_longjmp_call_dummy && b->thread == tp->global_num)
       {
+	gdb_assert (b->inferior == -1);
 	struct breakpoint *dummy_b = b->related_breakpoint;
 
 	/* Find the bp_call_dummy breakpoint in the list of breakpoints
@@ -8420,7 +8492,8 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
 				  gdb::unique_xmalloc_ptr<char> cond_string_,
 				  gdb::unique_xmalloc_ptr<char> extra_string_,
 				  enum bpdisp disposition_,
-				  int thread_, int task_, int ignore_count_,
+				  int thread_, int task_, int inferior_,
+				  int ignore_count_,
 				  int from_tty,
 				  int enabled_, unsigned flags,
 				  int display_canonical_)
@@ -8444,10 +8517,14 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
 
   gdb_assert (!sals.empty ());
 
-  /* At most one of thread or task can be set on any breakpoint.  */
-  gdb_assert (thread == -1 || task == -1);
+  /* At most one of thread, task, or inferior can be set on any breakpoint.  */
+  gdb_assert (((thread == -1 ? 0 : 1)
+	       + (task == -1 ? 0 : 1)
+	       + (inferior == -1 ? 0 : 1)) <= 1);
+
   thread = thread_;
   task = task_;
+  inferior = inferior_;
 
   cond_string = std::move (cond_string_);
   extra_string = std::move (extra_string_);
@@ -8549,7 +8626,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
 		       gdb::unique_xmalloc_ptr<char> cond_string,
 		       gdb::unique_xmalloc_ptr<char> extra_string,
 		       enum bptype type, enum bpdisp disposition,
-		       int thread, int task, int ignore_count,
+		       int thread, int task, int inferior, int ignore_count,
 		       int from_tty,
 		       int enabled, int internal, unsigned flags,
 		       int display_canonical)
@@ -8563,7 +8640,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
 				std::move (cond_string),
 				std::move (extra_string),
 				disposition,
-				thread, task, ignore_count,
+				thread, task, inferior, ignore_count,
 				from_tty,
 				enabled, flags,
 				display_canonical);
@@ -8592,7 +8669,8 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
 			gdb::unique_xmalloc_ptr<char> cond_string,
 			gdb::unique_xmalloc_ptr<char> extra_string,
 			enum bptype type, enum bpdisp disposition,
-			int thread, int task, int ignore_count,
+			int thread, int task, int inferior,
+			int ignore_count,
 			int from_tty,
 			int enabled, int internal, unsigned flags)
 {
@@ -8616,7 +8694,7 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
 			     std::move (cond_string),
 			     std::move (extra_string),
 			     type, disposition,
-			     thread, task, ignore_count,
+			     thread, task, inferior, ignore_count,
 			     from_tty, enabled, internal, flags,
 			     canonical->special_display);
     }
@@ -8746,21 +8824,26 @@ check_fast_tracepoint_sals (struct gdbarch *gdbarch,
     }
 }
 
-/* Given TOK, a string specification of condition and thread, as
-   accepted by the 'break' command, extract the condition
-   string and thread number and set *COND_STRING and *THREAD.
-   PC identifies the context at which the condition should be parsed.
-   If no condition is found, *COND_STRING is set to NULL.
-   If no thread is found, *THREAD is set to -1.  */
+/* Given TOK, a string specification of condition and thread, as accepted
+   by the 'break' command, extract the condition string into *COND_STRING.
+   If no condition string is found then *COND_STRING is set to nullptr.
+
+   If the breakpoint specification has an associated thread, task, or
+   inferior, these are extracted into *THREAD, *TASK, and *INFERIOR
+   respectively, otherwise these arguments are set to -1 (for THREAD and
+   INFERIOR) or 0 (for TASK).
+
+   PC identifies the context at which the condition should be parsed.  */
 
 static void
 find_condition_and_thread (const char *tok, CORE_ADDR pc,
 			   gdb::unique_xmalloc_ptr<char> *cond_string,
-			   int *thread, int *task,
+			   int *thread, int *inferior, int *task,
 			   gdb::unique_xmalloc_ptr<char> *rest)
 {
   cond_string->reset ();
   *thread = -1;
+  *inferior = -1;
   *task = -1;
   rest->reset ();
   bool force = false;
@@ -8777,7 +8860,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
       if ((*tok == '"' || *tok == ',') && rest)
 	{
 	  rest->reset (savestring (tok, strlen (tok)));
-	  return;
+	  break;
 	}
 
       end_tok = skip_to_space (tok);
@@ -8817,6 +8900,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  if (*task != -1)
 	    error (_("You can specify only one of thread or task."));
 
+	  if (*inferior != -1)
+	    error (_("You can specify only one of inferior or thread."));
+
 	  tok = end_tok + 1;
 	  thr = parse_thread_id (tok, &tmptok);
 	  if (tok == tmptok)
@@ -8824,6 +8910,26 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  *thread = thr->global_num;
 	  tok = tmptok;
 	}
+      else if (toklen >= 1 && strncmp (tok, "inferior", toklen) == 0)
+	{
+	  if (*inferior != -1)
+	    error(_("You can specify only one inferior."));
+
+	  if (*task != -1)
+	    error (_("You can specify only one of inferior or task."));
+
+	  if (*thread != -1)
+	    error (_("You can specify only one of inferior or thread."));
+
+	  char *tmptok;
+	  tok = end_tok + 1;
+	  *inferior = strtol (tok, &tmptok, 0);
+	  if (tok == tmptok)
+	    error (_("Junk after inferior keyword."));
+	  if (!valid_global_inferior_id (*inferior))
+	    error (_("Unknown inferior number %d."), *inferior);
+	  tok = tmptok;
+	}
       else if (toklen >= 1 && strncmp (tok, "task", toklen) == 0)
 	{
 	  char *tmptok;
@@ -8834,6 +8940,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  if (*thread != -1)
 	    error (_("You can specify only one of thread or task."));
 
+	  if (*inferior != -1)
+	    error (_("You can specify only one of inferior or task."));
+
 	  tok = end_tok + 1;
 	  *task = strtol (tok, &tmptok, 0);
 	  if (tok == tmptok)
@@ -8845,7 +8954,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
       else if (rest)
 	{
 	  rest->reset (savestring (tok, strlen (tok)));
-	  return;
+	  break;
 	}
       else
 	error (_("Junk at end of arguments."));
@@ -8861,7 +8970,7 @@ static void
 find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
 				    const char *input,
 				    gdb::unique_xmalloc_ptr<char> *cond_string,
-				    int *thread, int *task,
+				    int *thread, int *inferior, int *task,
 				    gdb::unique_xmalloc_ptr<char> *rest)
 {
   int num_failures = 0;
@@ -8869,6 +8978,7 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
     {
       gdb::unique_xmalloc_ptr<char> cond;
       int thread_id = -1;
+      int inferior_id = -1;
       int task_id = -1;
       gdb::unique_xmalloc_ptr<char> remaining;
 
@@ -8881,11 +8991,16 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
       try
 	{
 	  find_condition_and_thread (input, sal.pc, &cond, &thread_id,
-				     &task_id, &remaining);
+				     &inferior_id, &task_id, &remaining);
 	  *cond_string = std::move (cond);
-	  /* At most one of thread or task can be set.  */
-	  gdb_assert (thread_id == -1 || task_id == -1);
+	  /* A value of -1 indicates that these fields are unset.  At most
+	     one of these fields should be set (to a value other than -1)
+	     at this point.  */
+	  gdb_assert (((thread_id == -1 ? 1 : 0)
+		       + (task_id == -1 ? 1 : 0)
+		       + (inferior_id == -1 ? 1 : 0)) >= 2);
 	  *thread = thread_id;
+	  *inferior = inferior_id;
 	  *task = task_id;
 	  *rest = std::move (remaining);
 	  break;
@@ -8975,7 +9090,8 @@ int
 create_breakpoint (struct gdbarch *gdbarch,
 		   location_spec *locspec,
 		   const char *cond_string,
-		   int thread, const char *extra_string,
+		   int thread, int inferior,
+		   const char *extra_string,
 		   bool force_condition, int parse_extra,
 		   int tempflag, enum bptype type_wanted,
 		   int ignore_count,
@@ -8989,6 +9105,10 @@ create_breakpoint (struct gdbarch *gdbarch,
   int task = -1;
   int prev_bkpt_count = breakpoint_count;
 
+  gdb_assert (thread == -1 || thread > 0);
+  gdb_assert (inferior == -1 || inferior > 0);
+  gdb_assert (thread == -1 || inferior == -1);
+
   gdb_assert (ops != NULL);
 
   /* If extra_string isn't useful, set it to NULL.  */
@@ -9064,7 +9184,8 @@ create_breakpoint (struct gdbarch *gdbarch,
 	  const linespec_sals &lsal = canonical.lsals[0];
 
 	  find_condition_and_thread_for_sals (lsal.sals, extra_string,
-					      &cond, &thread, &task, &rest);
+					      &cond, &thread, &inferior,
+					      &task, &rest);
 	  cond_string_copy = std::move (cond);
 	  extra_string_copy = std::move (rest);
 	}
@@ -9114,7 +9235,7 @@ create_breakpoint (struct gdbarch *gdbarch,
 				   std::move (extra_string_copy),
 				   type_wanted,
 				   tempflag ? disp_del : disp_donttouch,
-				   thread, task, ignore_count,
+				   thread, task, inferior, ignore_count,
 				   from_tty, enabled, internal, flags);
     }
   else
@@ -9183,7 +9304,9 @@ break_command_1 (const char *arg, int flag, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL,
+		     -1 /* thread */, -1 /* inferior */,
+		     arg, false, 1 /* parse arg */,
 		     tempflag, type_wanted,
 		     0 /* Ignore count */,
 		     pending_break_support,
@@ -9295,7 +9418,8 @@ dprintf_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1,
+		     arg, false, 1 /* parse arg */,
 		     0, bp_dprintf,
 		     0 /* Ignore count */,
 		     pending_break_support,
@@ -10051,6 +10175,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
   const char *cond_end = NULL;
   enum bptype bp_type;
   int thread = -1;
+  int inferior = -1;
   /* Flag to indicate whether we are going to use masks for
      the hardware watchpoint.  */
   bool use_mask = false;
@@ -10105,12 +10230,13 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	      if (task != -1)
 		error (_("You can specify only one of thread or task."));
 
+	      if (inferior != -1)
+		error (_("You can specify only one of inferior or thread."));
+
 	      /* Extract the thread ID from the next token.  */
 	      thr = parse_thread_id (value_start, &endp);
-
-	      /* Check if the user provided a valid thread ID.  */
-	      if (*endp != ' ' && *endp != '\t' && *endp != '\0')
-		invalid_thread_id_error (value_start);
+	      if (value_start == endp)
+		error (_("Junk after thread keyword."));
 
 	      thread = thr->global_num;
 	    }
@@ -10124,12 +10250,20 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	      if (thread != -1)
 		error (_("You can specify only one of thread or task."));
 
+	      if (inferior != -1)
+		error (_("You can specify only one of inferior or task."));
+
 	      task = strtol (value_start, &tmp, 0);
 	      if (tmp == value_start)
 		error (_("Junk after task keyword."));
 	      if (!valid_task_id (task))
 		error (_("Unknown task %d."), task);
 	    }
+	  else if (toklen == 8 && startswith (tok, "inferior"))
+	    {
+	      /* Support for watchpoints will be added in a later commit.  */
+	      error (_("Cannot use 'inferior' keyword with watchpoints"));
+	    }
 	  else if (toklen == 4 && startswith (tok, "mask"))
 	    {
 	      /* We've found a "mask" token, which means the user wants to
@@ -10302,6 +10436,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
   /* At most one of thread or task can be set on a watchpoint.  */
   gdb_assert (thread == -1 || task == -1);
   w->thread = thread;
+  w->inferior = inferior;
   w->task = task;
   w->disposition = disp_donttouch;
   w->pspace = current_program_space;
@@ -12211,7 +12346,8 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
 				      enum bptype type_wanted,
 				      enum bpdisp disposition,
 				      int thread,
-				      int task, int ignore_count,
+				      int task, int inferior,
+				      int ignore_count,
 				      int from_tty, int enabled,
 				      int internal, unsigned flags)
 {
@@ -12237,7 +12373,7 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
 			 std::move (cond_string),
 			 std::move (extra_string),
 			 disposition,
-			 thread, task, ignore_count,
+			 thread, task, inferior, ignore_count,
 			 from_tty, enabled, flags,
 			 canonical->special_display));
 
@@ -12870,10 +13006,11 @@ code_breakpoint::location_spec_to_sals (location_spec *locspec,
       if (condition_not_parsed && extra_string != NULL)
 	{
 	  gdb::unique_xmalloc_ptr<char> local_cond, local_extra;
-	  int local_thread, local_task;
+	  int local_thread, local_task, local_inferior;
 
 	  find_condition_and_thread_for_sals (sals, extra_string.get (),
 					      &local_cond, &local_thread,
+					      &local_inferior,
 					      &local_task, &local_extra);
 	  gdb_assert (cond_string == nullptr);
 	  if (local_cond != nullptr)
@@ -13747,7 +13884,7 @@ trace_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     bp_tracepoint /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13765,7 +13902,7 @@ ftrace_command (const char *arg, int from_tty)
 						      current_language);
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     bp_fast_tracepoint /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13803,7 +13940,7 @@ strace_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     type /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13872,7 +14009,7 @@ create_tracepoint_from_upload (struct uploaded_tp *utp)
 						      current_language);
   if (!create_breakpoint (get_current_arch (),
 			  locspec.get (),
-			  utp->cond_string.get (), -1, addr_str,
+			  utp->cond_string.get (), -1, -1, addr_str,
 			  false /* force_condition */,
 			  0 /* parse cond/thread */,
 			  0 /* tempflag */,
@@ -14959,4 +15096,6 @@ This is useful for formatted output in user-defined commands."));
 					   "breakpoint");
   gdb::observers::thread_exit.attach (remove_threaded_breakpoints,
 				      "breakpoint");
+  gdb::observers::inferior_removed.attach (remove_inferior_breakpoints,
+					   "breakpoint");
 }
diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
index 03aecd15eff..d88bf8f5f89 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -584,7 +584,7 @@ struct breakpoint_ops
 				  struct linespec_result *,
 				  gdb::unique_xmalloc_ptr<char>,
 				  gdb::unique_xmalloc_ptr<char>,
-				  enum bptype, enum bpdisp, int, int,
+				  enum bptype, enum bpdisp, int, int, int,
 				  int, int, int, int, unsigned);
 };
 
@@ -802,6 +802,10 @@ struct breakpoint
      care.  */
   int thread = -1;
 
+  /* Inferior number for inferior-specific breakpoint, or -1 if this
+     breakpoint is for all inferiors.  */
+  int inferior = -1;
+
   /* Ada task number for task-specific breakpoint, or -1 if don't
      care.  */
   int task = -1;
@@ -857,7 +861,7 @@ struct code_breakpoint : public breakpoint
 		   gdb::unique_xmalloc_ptr<char> cond_string,
 		   gdb::unique_xmalloc_ptr<char> extra_string,
 		   enum bpdisp disposition,
-		   int thread, int task, int ignore_count,
+		   int thread, int task, int inferior, int ignore_count,
 		   int from_tty,
 		   int enabled, unsigned flags,
 		   int display_canonical);
@@ -1537,6 +1541,7 @@ enum breakpoint_create_flags
 extern int create_breakpoint (struct gdbarch *gdbarch,
 			      struct location_spec *locspec,
 			      const char *cond_string, int thread,
+			      int inferior,
 			      const char *extra_string,
 			      bool force_condition,
 			      int parse_extra,
@@ -1680,6 +1685,11 @@ extern void breakpoint_set_silent (struct breakpoint *b, int silent);
 
 extern void breakpoint_set_thread (struct breakpoint *b, int thread);
 
+/* Set the inferior for breakpoint B to INFERIOR.  If INFERIOR is -1, make
+   the breakpoint work for any inferior.  */
+
+extern void breakpoint_set_inferior (struct breakpoint *b, int inferior);
+
 /* Set the task for this breakpoint.  If TASK is -1, make the breakpoint
    work for any task.  Passing a value other than -1 for TASK should only
    be done if b->thread is -1; it is not valid to try and set both a thread
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 98b7c984aa7..83687bf6eaf 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3509,6 +3509,57 @@
 space as a result of inferior 1 having executed a @code{vfork} call.
 @end table
 
+@menu
+* Inferior-Specific Breakpoints::	Controlling breakpoints
+@end menu
+
+@node Inferior-Specific Breakpoints
+@subsection Inferior-Specific Breakpoints
+
+When debugging multiple inferiors, you can choose whether to set
+breakpoints for all inferiors, or for a particular inferior.
+
+@table @code
+@cindex breakpoints and inferiors
+@cindex inferior-specific breakpoints
+@kindex break @dots{} inferior @var{inferior-id}
+@item break @var{locspec} inferior @var{inferior-id}
+@itemx break @var{locspec} inferior @var{inferior-id} if @dots{}
+@var{locspec} specifies a code location or locations in your program.
+@xref{Location Specifications}, for details.
+
+Use the qualifier @samp{inferior @var{inferior-id}} with a breakpoint
+command to specify that you only want @value{GDBN} to stop when a
+particular inferior reaches this breakpoint.  The @var{inferior-id}
+specifier is one of the inferior identifiers assigned by @value{GDBN},
+shown in the first column of the @samp{info inferiors} output.
+
+If you do not specify @samp{inferior @var{inferior-id}} when you set a
+breakpoint, the breakpoint applies to @emph{all} inferiors of your
+program.
+
+You can use the @code{inferior} qualifier on conditional breakpoints as
+well; in this case, place @samp{inferior @var{inferior-id}} before or
+after the breakpoint condition, like this:
+
+@smallexample
+(@value{GDBP}) break frik.c:13 inferior 2 if bartab > lim
+@end smallexample
+@end table
+
+Inferior-specific breakpoints are automatically deleted when the
+corresponding inferior is removed from @value{GDBN}.  For example:
+
+@smallexample
+(@value{GDBP}) remove-inferiors 2
+Inferior-specific breakpoint 3 deleted - inferior 2 has been removed.
+@end smallexample
+
+A breakpoint can't be both inferior-specific and thread-specific
+(@pxref{Thread-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{inferior}, @code{thread}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
 @node Threads
 @section Debugging Programs with Multiple Threads
 
@@ -4471,8 +4522,9 @@
 situation.
 
 It is also possible to insert a breakpoint that will stop the program
-only if a specific thread (@pxref{Thread-Specific Breakpoints})
-or a specific task (@pxref{Ada Tasks}) hits that breakpoint.
+only if a specific thread (@pxref{Thread-Specific Breakpoints}),
+specific inferior (@pxref{Inferior-Specific Breakpoints}), or a
+specific task (@pxref{Ada Tasks}) hits that breakpoint.
 
 @item break
 When called without any arguments, @code{break} sets a breakpoint at
@@ -7323,9 +7375,14 @@
 Process}), or if @value{GDBN} loses the remote connection
 (@pxref{Remote Debugging}), etc.  Note that with some targets,
 @value{GDBN} is only able to detect a thread has exited when the user
-explictly asks for the thread list with the @code{info threads}
+explicitly asks for the thread list with the @code{info threads}
 command.
 
+A breakpoint can't be both thread-specific and inferior-specific
+(@pxref{Inferior-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{thread}, @code{inferior}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
 @node Interrupted System Calls
 @subsection Interrupted System Calls 
 
@@ -31508,6 +31565,10 @@
 If this is a thread-specific breakpoint, then this identifies the
 thread in which the breakpoint can trigger.
 
+@item inferior
+If this is an inferior-specific breakpoint, this this identifies the
+inferior in which the breakpoint can trigger.
+
 @item task
 If this breakpoint is restricted to a particular Ada task, then this
 field will hold the task identifier.
@@ -32099,7 +32160,7 @@
 @smallexample
  -break-insert [ -t ] [ -h ] [ -f ] [ -d ] [ -a ] [ --qualified ]
     [ -c @var{condition} ] [ --force-condition ] [ -i @var{ignore-count} ]
-    [ -p @var{thread-id} ] [ @var{locspec} ]
+    [ -p @var{thread-id} ] [ -g @var{thread-group-id} ] [ @var{locspec} ]
 @end smallexample
 
 @noindent
@@ -32162,6 +32223,9 @@
 @item -p @var{thread-id}
 Restrict the breakpoint to the thread with the specified global
 @var{thread-id}.
+@item -g @var{thread-group-id}
+Restrict the breakpoint to the thread group with the specified
+@var{thread-group-id}.
 @item --qualified
 This option makes @value{GDBN} interpret a function name specified as
 a complete fully-qualified name.
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 54d5660543a..59c229819b1 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -3290,7 +3290,10 @@
 A @code{gdb.Inferior} object has the following attributes:
 
 @defvar Inferior.num
-ID of inferior, as assigned by GDB.
+ID of inferior, as assigned by @value{GDBN}.  You can use this to make
+Python breakpoints inferior-specific, for example
+(@pxref{python_breakpoint_inferior,,The Breakpoint.inferior
+attribute}).
 @end defvar
 
 @anchor{gdbpy_inferior_connection}
@@ -6092,9 +6095,24 @@
 
 @anchor{python_breakpoint_thread}
 @defvar Breakpoint.thread
-If the breakpoint is thread-specific, this attribute holds the
-thread's global id.  If the breakpoint is not thread-specific, this
-attribute is @code{None}.  This attribute is writable.
+If the breakpoint is thread-specific (@pxref{Thread-Specific
+Breakpoints}), this attribute holds the thread's global id.  If the
+breakpoint is not thread-specific, this attribute is @code{None}.
+This attribute is writable.
+
+Only one of @code{Breakpoint.thread} or @code{Breakpoint.inferior} can
+be set to a valid id at any time, that is, a breakpoint can be thread
+specific, or inferior specific, but not both.
+@end defvar
+
+@anchor{python_breakpoint_inferior}
+@defvar Breakpoint.inferior
+If the breakpoint is inferior-specific (@pxref{Inferior-Specific
+Breakpoints}), this attribute holds the inferior's id.  If the
+breakpoint is not inferior-specific, this attribute is @code{None}.
+
+This attribute can be written for breakpoints of type
+@code{gdb.BP_BREAKPOINT} and @code{gdb.BP_HARDWARE_BREAKPOINT}.
 @end defvar
 
 @defvar Breakpoint.task
diff --git a/gdb/guile/scm-breakpoint.c b/gdb/guile/scm-breakpoint.c
index 2931df265d7..4e343b85656 100644
--- a/gdb/guile/scm-breakpoint.c
+++ b/gdb/guile/scm-breakpoint.c
@@ -465,7 +465,7 @@ gdbscm_register_breakpoint_x (SCM self)
 	    const breakpoint_ops *ops =
 	      breakpoint_ops_for_location_spec (locspec.get (), false);
 	    create_breakpoint (get_current_arch (),
-			       locspec.get (), NULL, -1, NULL, false,
+			       locspec.get (), NULL, -1, -1, NULL, false,
 			       0,
 			       temporary, bp_breakpoint,
 			       0,
@@ -784,6 +784,11 @@ gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue)
   else
     SCM_ASSERT_TYPE (0, newvalue, SCM_ARG2, FUNC_NAME, _("integer or #f"));
 
+  if (bp_smob->bp->inferior != -1 && id != -1)
+    scm_misc_error (FUNC_NAME,
+		    _("Cannot have both 'thread' and 'inferior' "
+		      "conditions on a breakpoint"), SCM_EOL);
+
   breakpoint_set_thread (bp_smob->bp, id);
 
   return SCM_UNSPECIFIED;
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index c369b795757..acd1eb081f1 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -425,17 +425,10 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
   if (run_how == RUN_STOP_AT_MAIN)
     {
       /* To avoid other inferiors hitting this breakpoint, make it
-	 inferior-specific using a condition.  A better solution would be to
-	 have proper inferior-specific breakpoint support, in the breakpoint
-	 machinery.  We could then avoid inserting a breakpoint in the program
-	 spaces unrelated to this inferior.  */
-      const char *op
-	= ((current_language->la_language == language_ada
-	    || current_language->la_language == language_pascal
-	    || current_language->la_language == language_m2) ? "=" : "==");
-      std::string arg = string_printf
-	("-qualified %s if $_inferior %s %d", main_name (), op,
-	 current_inferior ()->num);
+	 inferior-specific.  */
+      std::string arg = string_printf ("-qualified %s inferior %d",
+				       main_name (),
+				       current_inferior ()->num);
       tbreak_command (arg.c_str (), 0);
     }
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 72034cc4ffb..55df57666f9 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -815,4 +815,15 @@ extern void print_selected_inferior (struct ui_out *uiout);
 extern void switch_to_inferior_and_push_target
   (inferior *new_inf, bool no_connection, inferior *org_inf);
 
+/* Return true if ID is a valid global inferior number.  */
+
+inline bool
+valid_global_inferior_id (int id)
+{
+  for (inferior *inf : all_inferiors ())
+    if (inf->num == id)
+      return true;
+  return false;
+}
+
 #endif /* !defined (INFERIOR_H) */
diff --git a/gdb/linespec.c b/gdb/linespec.c
index 7d969f37fbf..695dc6515ee 100644
--- a/gdb/linespec.c
+++ b/gdb/linespec.c
@@ -254,9 +254,9 @@ enum linespec_token_type
 
 /* List of keywords.  This is NULL-terminated so that it can be used
    as enum completer.  */
-const char * const linespec_keywords[] = { "if", "thread", "task", "-force-condition", NULL };
+const char * const linespec_keywords[] = { "if", "thread", "task", "inferior", "-force-condition", NULL };
 #define IF_KEYWORD_INDEX 0
-#define FORCE_KEYWORD_INDEX 3
+#define FORCE_KEYWORD_INDEX 4
 
 /* A token of the linespec lexer  */
 
diff --git a/gdb/mi/mi-cmd-break.c b/gdb/mi/mi-cmd-break.c
index 75957b75bad..fc1c4205311 100644
--- a/gdb/mi/mi-cmd-break.c
+++ b/gdb/mi/mi-cmd-break.c
@@ -172,6 +172,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
   int hardware = 0;
   int temp_p = 0;
   int thread = -1;
+  int thread_group = -1;
   int ignore_count = 0;
   char *condition = NULL;
   int pending = 0;
@@ -190,7 +191,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
   enum opt
     {
       HARDWARE_OPT, TEMP_OPT, CONDITION_OPT,
-      IGNORE_COUNT_OPT, THREAD_OPT, PENDING_OPT, DISABLE_OPT,
+      IGNORE_COUNT_OPT, THREAD_OPT, THREAD_GROUP_OPT,
+      PENDING_OPT, DISABLE_OPT,
       TRACEPOINT_OPT,
       FORCE_CONDITION_OPT,
       QUALIFIED_OPT,
@@ -204,6 +206,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
     {"c", CONDITION_OPT, 1},
     {"i", IGNORE_COUNT_OPT, 1},
     {"p", THREAD_OPT, 1},
+    {"g", THREAD_GROUP_OPT, 1},
     {"f", PENDING_OPT, 0},
     {"d", DISABLE_OPT, 0},
     {"a", TRACEPOINT_OPT, 0},
@@ -244,6 +247,9 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
 	case THREAD_OPT:
 	  thread = atol (oarg);
 	  break;
+	case THREAD_GROUP_OPT:
+	  thread_group = mi_parse_thread_group_id (oarg);
+	  break;
 	case PENDING_OPT:
 	  pending = 1;
 	  break;
@@ -357,7 +363,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
 	error (_("Garbage '%s' at end of location"), address);
     }
 
-  create_breakpoint (get_current_arch (), locspec.get (), condition, thread,
+  create_breakpoint (get_current_arch (), locspec.get (), condition,
+		     thread, thread_group,
 		     extra_string.c_str (),
 		     force_condition,
 		     0 /* condition and thread are valid.  */,
diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c
index 0013e5dfafd..aaa13d1d65b 100644
--- a/gdb/mi/mi-main.c
+++ b/gdb/mi/mi-main.c
@@ -1745,8 +1745,7 @@ mi_cmd_remove_inferior (const char *command, char **argv, int argc)
   if (argc != 1)
     error (_("-remove-inferior should be passed a single argument"));
 
-  if (sscanf (argv[0], "i%d", &id) != 1)
-    error (_("the thread group id is syntactically invalid"));
+  id = mi_parse_thread_group_id (argv[0]);
 
   inf_to_remove = find_inferior_id (id);
   if (inf_to_remove == NULL)
@@ -2757,6 +2756,21 @@ mi_cmd_complete (const char *command, char **argv, int argc)
 		       result.number_matches == max_completions ? "1" : "0");
 }
 
+/* See mi-main.h.  */
+int
+mi_parse_thread_group_id (const char *id)
+{
+  if (*id != 'i')
+    error (_("thread group id should start with an 'i'"));
+
+  char *end;
+  long num = strtol (id + 1, &end, 10);
+
+  if (*end != '\0' || num > INT_MAX)
+    error (_("invalid thread group id '%s'"), id);
+
+  return (int) num;
+}
 
 void _initialize_mi_main ();
 void
diff --git a/gdb/mi/mi-main.h b/gdb/mi/mi-main.h
index ff1d4ed84bc..0389a141cf5 100644
--- a/gdb/mi/mi-main.h
+++ b/gdb/mi/mi-main.h
@@ -76,4 +76,10 @@ extern void mi_cmd_fix_multi_location_breakpoint_output (const char *command,
 extern void mi_cmd_fix_breakpoint_script_output (const char *command,
 						 char **argv, int argc);
 
+/* Parse a thread-group-id from ID, and return the integer part of the
+   ID.  A valid thread-group-id is the character 'i' followed by an
+   integer that is greater than zero.  */
+
+extern int mi_parse_thread_group_id (const char *id);
+
 #endif /* MI_MI_MAIN_H */
diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
index 880f1b5c1e2..2ac4d959a29 100644
--- a/gdb/python/py-breakpoint.c
+++ b/gdb/python/py-breakpoint.c
@@ -287,11 +287,86 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure)
       return -1;
     }
 
+  if (self_bp->bp->inferior != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'thread' and 'inferior' "
+			 "conditions on a breakpoint"));
+      return -1;
+    }
+
   breakpoint_set_thread (self_bp->bp, id);
 
   return 0;
 }
 
+/* Python function to set the inferior of a breakpoint.  */
+
+static int
+bppy_set_inferior (PyObject *self, PyObject *newvalue, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+  long id;
+
+  BPPY_SET_REQUIRE_VALID (self_bp);
+
+  if (newvalue == NULL)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("Cannot delete 'inferior' attribute."));
+      return -1;
+    }
+  else if (PyLong_Check (newvalue))
+    {
+      if (!gdb_py_int_as_long (newvalue, &id))
+	return -1;
+
+      if (!valid_global_inferior_id (id))
+	{
+	  PyErr_SetString (PyExc_RuntimeError,
+			   _("Invalid inferior ID."));
+	  return -1;
+	}
+    }
+  else if (newvalue == Py_None)
+    id = -1;
+  else
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("The value of 'inferior' must be an integer or None."));
+      return -1;
+    }
+
+  if (self_bp->bp->type != bp_breakpoint
+      && self_bp->bp->type != bp_hardware_breakpoint)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot set 'inferior' attribute on a gdb.Breakpoint "
+			 "of this type"));
+      return -1;
+    }
+
+  if (self_bp->bp->thread != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'thread' and 'inferior' conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
+  if (self_bp->bp->task != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'task' and 'inferior' conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
+  breakpoint_set_inferior (self_bp->bp, id);
+
+  return 0;
+}
+
 /* Python function to set the (Ada) task of a breakpoint.  */
 static int
 bppy_set_task (PyObject *self, PyObject *newvalue, void *closure)
@@ -703,6 +778,20 @@ bppy_get_thread (PyObject *self, void *closure)
   return gdb_py_object_from_longest (self_bp->bp->thread).release ();
 }
 
+/* Python function to get the breakpoint's inferior ID.  */
+static PyObject *
+bppy_get_inferior (PyObject *self, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+
+  BPPY_REQUIRE_VALID (self_bp);
+
+  if (self_bp->bp->inferior == -1)
+    Py_RETURN_NONE;
+
+  return gdb_py_object_from_longest (self_bp->bp->inferior).release ();
+}
+
 /* Python function to get the breakpoint's task ID (in Ada).  */
 static PyObject *
 bppy_get_task (PyObject *self, void *closure)
@@ -941,7 +1030,7 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs)
 	      = breakpoint_ops_for_location_spec (locspec.get (), false);
 
 	    create_breakpoint (gdbpy_enter::get_gdbarch (),
-			       locspec.get (), NULL, -1, NULL, false,
+			       locspec.get (), NULL, -1, -1, NULL, false,
 			       0,
 			       temporary_bp, type,
 			       0,
@@ -1350,6 +1439,11 @@ static gdb_PyGetSetDef breakpoint_object_getset[] = {
 If the value is a thread ID (integer), then this is a thread-specific breakpoint.\n\
 If the value is None, then this breakpoint is not thread-specific.\n\
 No other type of value can be used.", NULL },
+  { "inferior", bppy_get_inferior, bppy_set_inferior,
+    "Inferior ID for the breakpoint.\n\
+If the value is an inferior ID (integer), then this is an inferior-specific\n\
+breakpoint.  If the value is None, then this breakpoint is not\n\
+inferior-specific.  No other type of value can be used.", NULL },
   { "task", bppy_get_task, bppy_set_task,
     "Thread ID for the breakpoint.\n\
 If the value is a task ID (integer), then this is an Ada task-specific breakpoint.\n\
diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c
index 7122fa820f6..ba77b1d0a16 100644
--- a/gdb/python/py-finishbreakpoint.c
+++ b/gdb/python/py-finishbreakpoint.c
@@ -307,7 +307,7 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
       location_spec_up locspec
 	= new_address_location_spec (get_frame_pc (prev_frame), NULL, 0);
       create_breakpoint (gdbpy_enter::get_gdbarch (),
-			 locspec.get (), NULL, thread, NULL, false,
+			 locspec.get (), NULL, thread, -1, NULL, false,
 			 0,
 			 1 /*temp_flag*/,
 			 bp_breakpoint,
diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp
index eb7ee5c9951..603d43f7c36 100644
--- a/gdb/testsuite/gdb.ada/tasks.exp
+++ b/gdb/testsuite/gdb.ada/tasks.exp
@@ -58,6 +58,8 @@ gdb_test "break break_me task 1 thread 1" \
     "You can specify only one of thread or task\\."
 gdb_test "break break_me thread 1 task 1" \
     "You can specify only one of thread or task\\."
+gdb_test "break break_me inferior 1 task 1" \
+    "You can specify only one of inferior or task\\."
 gdb_test "watch j task 1 thread 1" \
     "You can specify only one of thread or task\\."
 gdb_test "watch j thread 1 task 1" \
diff --git a/gdb/testsuite/gdb.linespec/cpcompletion.exp b/gdb/testsuite/gdb.linespec/cpcompletion.exp
index fbe19b49d43..f005707b9da 100644
--- a/gdb/testsuite/gdb.linespec/cpcompletion.exp
+++ b/gdb/testsuite/gdb.linespec/cpcompletion.exp
@@ -1259,8 +1259,8 @@ proc_with_prefix function-labels {} {
 }
 
 # Test that completion after a function name offers keyword
-# (if/task/thread/-force-condition) matches in linespec mode, and also
-# the explicit location options in explicit locations mode.
+# (if/inferior/task/thread/-force-condition) matches in linespec mode,
+# and also the explicit location options in explicit locations mode.
 
 proc_with_prefix keywords-after-function {} {
     set explicit_list \
diff --git a/gdb/testsuite/gdb.linespec/explicit.exp b/gdb/testsuite/gdb.linespec/explicit.exp
index b08d65953d2..668002d9038 100644
--- a/gdb/testsuite/gdb.linespec/explicit.exp
+++ b/gdb/testsuite/gdb.linespec/explicit.exp
@@ -412,6 +412,7 @@ namespace eval $testfile {
 	    "-qualified"
 	    "-source"
 	    "if"
+	    "inferior"
 	    "task"
 	    "thread"
 	}
diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
new file mode 100644
index 00000000000..c171ef71b9d
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
@@ -0,0 +1,29 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2023 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/>.  */
+
+int
+foo (void)
+{
+  return 0;
+}
+
+int
+main (void)
+{
+  int res = foo ();
+  return res;
+}
diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
new file mode 100644
index 00000000000..93a0eb3de49
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
@@ -0,0 +1,108 @@
+# Copyright 2023 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 for the delivery of '=breakpoint-deleted' notifications when
+# breakpoints are deleted.  Right now this test only covers
+# inferior-specific breakpoints, but it could be extended to cover
+# other cases too.
+
+# Multiple inferiors are needed, therefore only native gdb and
+# extended gdbserver modes are supported.
+require !use_gdb_stub
+
+# Separate UI doesn't work with GDB debug.
+require !gdb_debug_enabled
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+standard_testfile
+
+if { [build_executable "failed to prepare" $testfile $srcfile] } {
+    return -1
+}
+
+# Helper proc to create a breakpoint location regexp.  NUM is the
+# regexp to match the number field of this location.
+proc make_bp_loc { num } {
+    return [mi_make_breakpoint_loc \
+		-number "$num" \
+		-enabled "y" \
+		-func "foo" \
+		-inferior "2"]
+}
+
+foreach_mi_ui_mode mode {
+    mi_gdb_exit
+
+    if {$mode eq "separate"} {
+	set start_ops "separate-mi-tty"
+    } else {
+	set start_ops ""
+    }
+
+    if [mi_gdb_start $start_ops] {
+	return
+    }
+
+    # Load a test binary into inferior 1.
+    mi_gdb_load ${binfile}
+
+    # Setup inferior 2, including loading an exec file.
+    mi_gdb_test "-add-inferior" \
+	[multi_line "=thread-group-added,id=\"\[^\"\]+\"" \
+	     "~\"\\\[New inferior 2\\\]\\\\n\"" \
+	     "\~\"Added inferior 2\\\\n\"" \
+	     "\\^done,inferior=\"\[^\"\]+\"" ] \
+	"mi add inferior 2"
+    mi_gdb_test "-file-exec-and-symbols --thread-group i2 $::binfile" \
+	"\\^done" \
+	"set executable of inferior 2"
+
+    # Build regexp for the two locations.
+    set loc1 [make_bp_loc "$::decimal\\.1"]
+    set loc2 [make_bp_loc "$::decimal\\.2"]
+
+    # Create the inferior-specific breakpoint.
+    mi_create_breakpoint_multi "-g i2 foo" "create breakpoint in inferior 2" \
+	-inferior "2" -locations "\\\[$loc1,$loc2\\\]"
+    set bpnum [mi_get_valueof "/d" "\$bpnum" "INVALID"]
+
+    if {$mode eq "separate"} {
+	# In 'separate' mode we delete the inferior from the CLI, and
+	# then look for the breakpoint-deleted notification on the MI.
+	with_spawn_id $gdb_main_spawn_id {
+	    gdb_test "inferior 1" ".*"
+	    gdb_test "remove-inferiors 2" \
+		"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\."
+	}
+
+	gdb_test_multiple "" "check for b/p deleted notification on MI" {
+	    -re "=breakpoint-deleted,id=\"$bpnum\"" {
+		pass $gdb_test_name
+	    }
+	}
+    } else {
+	# In the non-separate mode we delete the inferior from the MI
+	# and expect to immediately see a breakpoint-deleted
+	# notification.
+	mi_gdb_test "-remove-inferior i2" \
+	    [multi_line \
+		 "~\"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\.\\\\n\"" \
+		 "=breakpoint-deleted,id=\"$bpnum\"" \
+		 "=thread-group-removed,id=\"i2\"" \
+		 "\\^done"]
+    }
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
new file mode 100644
index 00000000000..8f86d8cdcea
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
@@ -0,0 +1,52 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2023 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/>.  */
+
+volatile int global_var = 0;
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+static inline void __attribute__((__always_inline__))
+foo (void)
+{
+  int i;
+
+  for (i = 0; i < 10; ++i)
+    global_var = 0;
+}
+
+static void
+bar (void)
+{
+  global_var = 0;
+
+  foo ();
+}
+
+
+int
+main (void)
+{
+  global_var = 0;
+  foo ();
+  bar ();
+  stop_breakpt ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
new file mode 100644
index 00000000000..e5b20b6e7a8
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
@@ -0,0 +1,52 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2023 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/>.  */
+
+static int bar (void);
+static int baz (void);
+static int foo (void);
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+int
+main (void)
+{
+  int ret = baz ();
+  stop_breakpt ();
+  return ret;
+}
+
+static int
+bar (void)
+{
+  return baz ();
+}
+
+static int
+foo (void)
+{
+  return 0;
+}
+
+static int
+baz (void)
+{
+  return foo ();
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
new file mode 100644
index 00000000000..1f6573268da
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
@@ -0,0 +1,179 @@
+# Copyright 2022-2023 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/>.
+
+# Test inferior-specific breakpoints.
+
+standard_testfile -1.c -2.c
+
+if {[use_gdb_stub]} {
+    return
+}
+
+set srcfile1 ${srcfile}
+set binfile1 ${binfile}-1
+set binfile2 ${binfile}-2
+
+if {[build_executable ${testfile}.exp ${binfile1} "${srcfile1}"] != 0} {
+    return -1
+}
+
+if {[build_executable ${testfile}.exp ${binfile2} "${srcfile2}"] != 0} {
+    return -1
+}
+
+# Start the first inferior.
+clean_restart ${binfile1}
+if {![runto_main]} {
+    return
+}
+
+# Add a second inferior, and start this one too.
+gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2"
+gdb_load $binfile2
+if {![runto_main]} {
+    return
+}
+
+# Try to create a breakpoint using both the 'inferior' and 'thread' keywords,
+# this should fail.  Try with the keywords in both orders just in case the
+# parser has a bug.
+gdb_test "break foo thread 1.1 inferior 1" \
+    "You can specify only one of inferior or thread\\."
+gdb_test "break foo inferior 1 thread 1.1" \
+    "You can specify only one of inferior or thread\\."
+
+# Try to create a breakpoint using the 'inferior' keyword multiple times.
+gdb_test "break foo inferior 1 inferior 2" \
+    "You can specify only one inferior\\."
+
+# Clear out any other breakpoints.
+delete_breakpoints
+
+# Use 'info breakpoint' to check that the inferior specific breakpoint is
+# present in the breakpoint list.  TESTNAME is the name used for this test,
+# BP_NUMBER is the number for the breakpoint, and EXPECTED_LOC_COUNT is the
+# number of locations we expect for that breakpoint.
+proc check_info_breakpoints { testname bp_number expected_loc_count } {
+    gdb_test_multiple "info breakpoints $bp_number" $testname {
+	-re "\r\nNum\\s+\[^\r\n\]+\r\n" {
+	    exp_continue
+	}
+
+	-re "^$bp_number\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
+	    set saw_header true
+	    exp_continue
+	}
+
+	-re "^\\s+stop only in inferior 1\r\n" {
+	    set saw_inf_cond true
+	    exp_continue
+	}
+
+	-re "^\\s+breakpoint already hit $::decimal times\r\n" {
+	    exp_continue
+	}
+
+	-re "^$bp_number\\.\[123\]\\s+y\\s+ $::hex in foo at \[^\r\n\]+(?: inf \[12\])?\r\n" {
+	    incr location_count
+	    exp_continue
+	}
+
+	-re "^$::gdb_prompt $" {
+	    with_test_prefix $gdb_test_name {
+		gdb_assert { $saw_header \
+				 && $location_count == $expected_loc_count \
+				 && $saw_inf_cond } \
+		    $gdb_test_name
+	    }
+	}
+    }
+}
+
+# Create an inferior-specific breakpoint.  Use gdb_test instead of
+# gdb_breakpoint here as we want to check the breakpoint was placed in
+# multiple locations.
+#
+# Currently GDB still places inferior specific breakpoints into every
+# inferior, just like it does with thread specific breakpoints.
+# Hopefully this will change in the future, at which point, this test
+# will need updating.
+#
+# Two of these locations are in inferior 1, while the third is in
+# inferior 2.
+gdb_test "break foo inferior 1" \
+    "Breakpoint $decimal at $hex: foo\\. \\(3 locations\\)"
+set bp_number [get_integer_valueof "\$bpnum" "INVALID" \
+		  "get b/p number for inferior specific breakpoint"]
+
+set saw_header false
+set location_count 0
+set saw_inf_cond false
+
+check_info_breakpoints "first check for inferior specific breakpoint" \
+    $bp_number 3
+
+# Create a multi-inferior breakpoint to stop at.
+gdb_breakpoint "stop_breakpt" message
+set stop_bp_num [get_integer_valueof "\$bpnum" "INVALID" \
+		    "get b/p number for stop_breakpt"]
+
+# Now resume inferior 2, this should reach 'stop_breakpt'.
+gdb_test "continue" \
+    "hit Breakpoint $stop_bp_num\.$decimal, stop_breakpt \\(\\) .*" \
+    "continue in inferior 2"
+
+# Switch to inferior 1, and try there.
+gdb_test "inferior 1" ".*" \
+    "select inferior 1 to check the inferior-specific b/p works"
+gdb_test "continue " \
+    "Thread 1\\.${decimal}\[^\r\n\]* hit Breakpoint\
+     $bp_number\.$decimal, foo \\(\\) .*" \
+    "first continue in inferior 1"
+
+# Now back to inferior 2, let the inferior exit, and then remove the
+# inferior, the inferior-specific breakpoint should not be deleted.
+gdb_test "inferior 2" ".*" \
+    "switch back to allow inferior 2 to exit"
+gdb_test "continue" "\\\[Inferior 2 \[^\r\n\]+ exited normally\\\]" \
+    "allow inferior 2 to exit"
+gdb_test "inferior 1" ".*" \
+    "back to inferior 1 so inferior 2 can be deleted"
+gdb_test_no_output "remove-inferiors 2"
+
+gdb_test "continue " "hit Breakpoint $bp_number\.$decimal, foo \\(\\) .*" \
+    "second continue in inferior 1"
+gdb_test "continue " "hit Breakpoint $stop_bp_num, stop_breakpt \\(\\) .*" \
+    "third continue in inferior 1"
+
+# Now allow inferior 1 to exit, the inferior specific breakpoint
+# should not be deleted.
+gdb_test "continue" \
+    "\\\[Inferior 1 \[^\r\n\]+ exited normally\\\]" \
+    "allow inferior 1 to exit"
+
+check_info_breakpoints "second check for inferior specific breakpoint" \
+    $bp_number 2
+
+# Now create another new inferior, then remove inferior 1.  As a result of
+# this removal, the inferior specific breakpoint should be deleted.
+gdb_test "add-inferior" "Added inferior 3.*" "add empty inferior 3"
+gdb_test "inferior 3" "Switching to inferior 3.*" "switch to inferior 3"
+gdb_test "remove-inferiors 1" \
+    "Inferior-specific breakpoint $bp_number deleted - inferior 1 has been removed\\."
+
+# Now check 'info breakpoints' to ensure the breakpoint is gone.
+gdb_test "info breakpoints $bp_number" \
+    "No breakpoint or watchpoint matching '$bp_number'\\."
diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp
index ab81b7ade85..e0e76109eb3 100644
--- a/gdb/testsuite/gdb.python/py-breakpoint.exp
+++ b/gdb/testsuite/gdb.python/py-breakpoint.exp
@@ -113,6 +113,8 @@ proc_with_prefix test_bkpt_basic { } {
 	"Get Breakpoint List" 0
     gdb_test "python print (blist\[1\].thread)" \
 	"None" "Check breakpoint thread"
+    gdb_test "python print (blist\[1\].inferior)" \
+	"None" "Check breakpoint inferior"
     gdb_test "python print (blist\[1\].type == gdb.BP_BREAKPOINT)" \
 	"True" "Check breakpoint type"
     gdb_test "python print (blist\[0\].number)" \
@@ -215,6 +217,46 @@ proc_with_prefix test_bkpt_cond_and_cmds { } {
 	"check number of lines in commands"
 }
 
+# Test breakpoint thread and inferior attributes.
+proc_with_prefix test_bkpt_thread_and_inferior { } {
+    global srcfile testfile hex decimal
+
+    # Start with a fresh gdb.
+    clean_restart ${testfile}
+
+    if {![runto_main]} {
+	return 0
+    }
+
+    with_test_prefix "thread" {
+	delete_breakpoints
+	gdb_test "break multiply thread 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "1"
+	gdb_test "python print(bp.inferior)" "None"
+	gdb_test "python bp.inferior = 1" \
+	    "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.thread = None"
+	gdb_test_no_output "python bp.inferior = 1" \
+	    "set the inferior now the thread has been cleared"
+	gdb_test "info breakpoints" "stop only in inferior 1\r\n.*"
+    }
+
+    with_test_prefix "inferior" {
+	delete_breakpoints
+	gdb_test "break multiply inferior 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "None"
+	gdb_test "python print(bp.inferior)" "1"
+	gdb_test "python bp.thread = 1" \
+	    "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.inferior = None"
+	gdb_test_no_output "python bp.thread = 1" \
+	    "set the thread now the inferior has been cleared"
+	gdb_test "info breakpoints" "stop only in thread 1\r\n.*"
+    }
+}
+
 proc_with_prefix test_bkpt_invisible { } {
     global srcfile testfile hex decimal
 
@@ -851,6 +893,7 @@ proc_with_prefix test_bkpt_auto_disable { } {
 test_bkpt_basic
 test_bkpt_deletion
 test_bkpt_cond_and_cmds
+test_bkpt_thread_and_inferior
 test_bkpt_invisible
 test_hardware_breakpoints
 test_catchpoints
diff --git a/gdb/testsuite/lib/completion-support.exp b/gdb/testsuite/lib/completion-support.exp
index 275f8874f15..85768deab94 100644
--- a/gdb/testsuite/lib/completion-support.exp
+++ b/gdb/testsuite/lib/completion-support.exp
@@ -27,7 +27,7 @@ namespace eval completion {
     # List of all quote chars, including no-quote at all.
     variable maybe_quoted_list {"" "'" "\""}
 
-    variable keyword_list {"-force-condition" "if" "task" "thread"}
+    variable keyword_list {"-force-condition" "if" "inferior" "task" "thread"}
 
     variable explicit_opts_list \
 	{"-function" "-label" "-line" "-qualified" "-source"}
diff --git a/gdb/testsuite/lib/mi-support.exp b/gdb/testsuite/lib/mi-support.exp
index 3c2dd2fab2b..ee76062de42 100644
--- a/gdb/testsuite/lib/mi-support.exp
+++ b/gdb/testsuite/lib/mi-support.exp
@@ -2524,7 +2524,7 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} {
 # locations.
 #
 # All arguments for the breakpoint location may be specified using the
-# options: number, enabled, addr, func, file, fullname, line,
+# options: number, enabled, addr, func, file, fullname, line, inferior
 # thread-groups, and thread.
 #
 # For the option -thread the corresponding output field is only added
@@ -2538,12 +2538,14 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} {
 proc mi_make_breakpoint_loc {args} {
     parse_args {{number .*} {enabled .*} {addr .*}
 	{func .*} {file .*} {fullname .*} {line .*}
-	{thread-groups \\\[.*\\\]} {thread ""}}
+	{thread-groups \\\[.*\\\]} {thread ""} {inferior ""}}
 
     set attr_list {}
     foreach attr [list number enabled addr func file \
-		      fullname line thread-groups] {
-	lappend attr_list $attr [set $attr]
+		      fullname line thread-groups inferior] {
+	if {$attr ne "inferior" || [set $attr] ne ""} {
+	    lappend attr_list $attr [set $attr]
+	}
     }
 
     set result [mi_build_kv_pairs $attr_list]
@@ -2617,7 +2619,7 @@ proc mi_make_breakpoint_1 {attr_list thread cond evaluated-by times \
 # locations.
 #
 # All arguments for the breakpoint may be specified using the options:
-# number, type, disp, enabled, times, ignore, script,
+# number, type, disp, enabled, times, ignore, script, inferior,
 # original-location, cond, evaluated-by, locations, and thread.
 #
 # Only if -script and -ignore are given will they appear in the output.
@@ -2638,7 +2640,7 @@ proc mi_make_breakpoint_multi {args} {
     parse_args {{number .*} {type .*} {disp .*} {enabled .*}
 	{times .*} {ignore 0}
 	{script ""} {original-location .*} {cond ""} {evaluated-by ""}
-	{locations .*} {thread ""}}
+	{locations .*} {thread ""} {inferior ""}}
 
     set attr_list {}
     foreach attr [list number type disp enabled] {
@@ -2647,6 +2649,12 @@ proc mi_make_breakpoint_multi {args} {
 
     lappend attr_list "addr" "<MULTIPLE>"
 
+    # Only include the inferior field if it was set.  This field is
+    # optional in the MI output.
+    if {$inferior ne ""} {
+	lappend attr_list "inferior" $inferior
+    }
+
     set result [mi_make_breakpoint_1 \
 		    $attr_list $thread $cond ${evaluated-by} $times \
 		    $ignore $script ${original-location}]
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* Re: [PATCHv3 1/2] gdb: cleanup around some set_momentary_breakpoint_at_pc calls
  2023-03-16 17:03     ` [PATCHv3 1/2] gdb: cleanup around some set_momentary_breakpoint_at_pc calls Andrew Burgess
@ 2023-04-03 14:12       ` Andrew Burgess
  0 siblings, 0 replies; 54+ messages in thread
From: Andrew Burgess @ 2023-04-03 14:12 UTC (permalink / raw)
  To: gdb-patches

Andrew Burgess <aburgess@redhat.com> writes:

> I noticed a couple of places in infrun.c where we call
> set_momentary_breakpoint_at_pc, and then set the newly created
> breakpoint's thread field, these are in:
>
>   insert_exception_resume_breakpoint
>   insert_exception_resume_from_probe
>
> Function set_momentary_breakpoint_at_pc calls
> set_momentary_breakpoint, which always creates the breakpoint as
> thread-specific for the current inferior_thread().
>
> The two insert_* functions mentioned above take an arbitrary
> thread_info* as an argument and set the breakpoint::thread to hold the
> thread number of that arbitrary thread.
>
> However, the insert_* functions store the breakpoint pointer within
> the current inferior_thread(), so we know that the thread being passed
> in must be the currently selected thread.
>
> What this means is that we can:
>
>   1. Assert that the thread being passed in is the currently selected
>   thread, and
>
>   2. No longer adjust the breakpoint::thread field, this will already
>   have been set correctly be calling set_momentary_breakpoint_at_pc.
>
> There should be no user visible changes after this commit.

I went ahead and pushed this patch.

Let me know if there are any objections.

Thanks,
Andrew



> ---
>  gdb/infrun.c | 12 ++++++++----
>  1 file changed, 8 insertions(+), 4 deletions(-)
>
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index 33aa0c8794b..35c3d2b8f58 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -8138,6 +8138,9 @@ insert_exception_resume_breakpoint (struct thread_info *tp,
>  	  infrun_debug_printf ("exception resume at %lx",
>  			       (unsigned long) handler);
>  
> +	  /* set_momentary_breakpoint_at_pc creates a thread-specific
> +	     breakpoint for the current inferior thread.  */
> +	  gdb_assert (tp == inferior_thread ());
>  	  bp = set_momentary_breakpoint_at_pc (get_frame_arch (frame),
>  					       handler,
>  					       bp_exception_resume).release ();
> @@ -8145,8 +8148,7 @@ insert_exception_resume_breakpoint (struct thread_info *tp,
>  	  /* set_momentary_breakpoint_at_pc invalidates FRAME.  */
>  	  frame = nullptr;
>  
> -	  bp->thread = tp->global_num;
> -	  inferior_thread ()->control.exception_resume_breakpoint = bp;
> +	  tp->control.exception_resume_breakpoint = bp;
>  	}
>      }
>    catch (const gdb_exception_error &e)
> @@ -8176,10 +8178,12 @@ insert_exception_resume_from_probe (struct thread_info *tp,
>    infrun_debug_printf ("exception resume at %s",
>  		       paddress (probe->objfile->arch (), handler));
>  
> +  /* set_momentary_breakpoint_at_pc creates a thread-specific breakpoint
> +     for the current inferior thread.  */
> +  gdb_assert (tp == inferior_thread ());
>    bp = set_momentary_breakpoint_at_pc (get_frame_arch (frame),
>  				       handler, bp_exception_resume).release ();
> -  bp->thread = tp->global_num;
> -  inferior_thread ()->control.exception_resume_breakpoint = bp;
> +  tp->control.exception_resume_breakpoint = bp;
>  }
>  
>  /* This is called when an exception has been intercepted.  Check to
> -- 
> 2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCHv4] gdb: add inferior-specific breakpoints
  2023-03-16 17:03   ` [PATCHv3 0/2] Inferior specific breakpoints Andrew Burgess
  2023-03-16 17:03     ` [PATCHv3 1/2] gdb: cleanup around some set_momentary_breakpoint_at_pc calls Andrew Burgess
  2023-03-16 17:03     ` [PATCHv3 2/2] gdb: add inferior-specific breakpoints Andrew Burgess
@ 2023-04-03 14:14     ` Andrew Burgess
  2023-05-15 19:15       ` [PATCHv5] " Andrew Burgess
  2 siblings, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2023-04-03 14:14 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Doc changes have already been approved here:
  https://sourceware.org/pipermail/gdb-patches/2023-January/195986.html

In v4:

  - Merged patch #1 from the previous series as this felt like a
    pretty obvious cleanup.

  - Rebased to HEAD of master.

  - No changes to code or docs.

--

This commit extends the breakpoint mechanism to allow for inferior
specific breakpoints (but not watchpoints in this commit).

As GDB gains better support for multiple connections, and so for
running multiple (possibly unrelated) inferiors, then it is not hard
to imagine that a user might wish to create breakpoints that apply to
any thread in a single inferior.  To achieve this currently, the user
would need to create a condition possibly making use of the $_inferior
convenience variable, which, though functional, isn't the most user
friendly.

This commit adds a new 'inferior' keyword that allows for the creation
of inferior specific breakpoints.

Inferior specific breakpoints are automatically deleted when the
associated inferior is removed from GDB, this is similar to how
thread-specific breakpoints when the associated thread is deleted.

Watchpoints are already per-program-space, which in most cases mean
watchpoints are already inferior specific.  There is a small window
where inferior-specific watchpoints might make sense, which is after a
vfork, when two processes are sharing the same address space.
However, I'm leaving that as an exercise for another day.  For now,
attempting to use the inferior keyword with a watchpoint will give an
error, like this:

  (gdb) watch a8 inferior 1
  Cannot use 'inferior' keyword with watchpoints

A final note on the implementation.  Currently, inferior specific
breakpoints, like thread-specific breakpoints, are inserted into every
inferior, GDB then checks once the inferior stops if we are in the
correct thread or inferior, and resumes automatically if we stopped in
the wrong thread/inferior.

An obvious optimisation here is to only insert breakpoint locations
into the specific program space (which mostly means inferior) that
contains either the inferior or thread we are interested in.  This
would reduce the number times GDB has to stop and then resume again in
a multi-inferior setup.

I have started investigating implementing this.  And though the
initial implementation is trivial to get going, like so many things,
the devil is in the detail.  While my hope is that I will bring this
optimisation to the list at some point in the near future I can't
guarantee it.  That said, we already have the issue of placing
breakpoints into inferiors that will never be hit (with thread
specific breakpoints), so I don't believe I'm adding anything worse
than we already have.  Personally, I'd be happy to see this new
feature merged even if the optimisation never happens.
---
 gdb/NEWS                                      |  15 ++
 gdb/breakpoint.c                              | 237 ++++++++++++++----
 gdb/breakpoint.h                              |  14 +-
 gdb/doc/gdb.texinfo                           |  72 +++++-
 gdb/doc/python.texi                           |  26 +-
 gdb/guile/scm-breakpoint.c                    |   7 +-
 gdb/infcmd.c                                  |  15 +-
 gdb/inferior.h                                |  11 +
 gdb/linespec.c                                |   4 +-
 gdb/mi/mi-cmd-break.c                         |  11 +-
 gdb/mi/mi-main.c                              |  18 +-
 gdb/mi/mi-main.h                              |   6 +
 gdb/python/py-breakpoint.c                    |  96 ++++++-
 gdb/python/py-finishbreakpoint.c              |   2 +-
 gdb/testsuite/gdb.ada/tasks.exp               |   2 +
 gdb/testsuite/gdb.linespec/cpcompletion.exp   |   4 +-
 gdb/testsuite/gdb.linespec/explicit.exp       |   1 +
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c      |  29 +++
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp    | 108 ++++++++
 .../gdb.multi/inferior-specific-bp-1.c        |  52 ++++
 .../gdb.multi/inferior-specific-bp-2.c        |  52 ++++
 .../gdb.multi/inferior-specific-bp.exp        | 179 +++++++++++++
 gdb/testsuite/gdb.python/py-breakpoint.exp    |  43 ++++
 gdb/testsuite/lib/completion-support.exp      |   2 +-
 gdb/testsuite/lib/mi-support.exp              |  20 +-
 25 files changed, 938 insertions(+), 88 deletions(-)
 create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
 create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index 10a1a70fa52..99520f815b9 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -63,6 +63,13 @@
     break foo thread 1 task 1
     watch var thread 2 task 3
 
+* Breakpoints can now be inferior-specific.  This is similar to the
+  existing thread-specific breakpoint support.  Breakpoint conditions
+  can include the 'inferior' keyword followed by an inferior id (as
+  displayed in the 'info inferiors' output).  It is invalid to use the
+  'inferior' keyword with either the 'thread' or 'task' keywords when
+  creating a breakpoint.
+
 * New commands
 
 maintenance print record-instruction [ N ]
@@ -109,6 +116,14 @@ info main
    without a thread restriction.  The same is also true for the 'task'
    field of an Ada task-specific breakpoint.
 
+** The -break-insert command now accepts a '-g thread-group-id' option
+   to allow for the creation of inferior-specific breakpoints.
+
+** The bkpt tuple, which appears in breakpoint-created notifications,
+   and in the result of the -break-insert command can now include an
+   optional 'inferior' field for both the main breakpoint, and each
+   location, when the breakpoint is inferior-specific.
+
 * Python API
 
   ** The gdb.unwinder.Unwinder.name attribute is now read-only.
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index ebe97940f54..674c84e07af 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -96,7 +96,7 @@ static void create_breakpoints_sal (struct gdbarch *,
 				    gdb::unique_xmalloc_ptr<char>,
 				    gdb::unique_xmalloc_ptr<char>,
 				    enum bptype,
-				    enum bpdisp, int, int,
+				    enum bpdisp, int, int, int,
 				    int,
 				    int, int, int, unsigned);
 
@@ -320,6 +320,9 @@ struct momentary_breakpoint : public code_breakpoint
     disposition = disp_donttouch;
     frame_id = frame_id_;
     thread = thread_;
+
+    /* The inferior should have been set by the parent constructor.  */
+    gdb_assert (inferior == -1);
   }
 
   void re_set () override;
@@ -1459,13 +1462,15 @@ breakpoint_set_silent (struct breakpoint *b, int silent)
 void
 breakpoint_set_thread (struct breakpoint *b, int thread)
 {
-  /* It is invalid to set the thread field to anything other than -1 (which
-     means no thread restriction) if a task restriction is already in
-     place.  */
-  gdb_assert (thread == -1 || b->task == -1);
+  /* THREAD should be -1, meaning no thread restriction, or it should be a
+     valid global thread-id, which are greater than zero.  */
+  gdb_assert (thread == -1 || thread > 0);
 
-  int old_thread = b->thread;
+  /* It is not valid to set a thread restriction for a breakpoint that
+     already has task or inferior restriction.  */
+  gdb_assert (thread == -1 || (b->task == -1 && b->inferior == -1));
 
+  int old_thread = b->thread;
   b->thread = thread;
   if (old_thread != thread)
     gdb::observers::breakpoint_modified.notify (b);
@@ -1473,16 +1478,37 @@ breakpoint_set_thread (struct breakpoint *b, int thread)
 
 /* See breakpoint.h.  */
 
+void
+breakpoint_set_inferior (struct breakpoint *b, int inferior)
+{
+  /* INFERIOR should be -1, meaning no inferior restriction, or it should
+     be a valid inferior number, which are greater than zero.  */
+  gdb_assert (inferior == -1 || inferior > 0);
+
+  /* It is not valid to set an inferior restriction for a breakpoint that
+     already has a task or thread restriction.  */
+  gdb_assert (inferior == -1 || (b->task == -1 && b->thread == -1));
+
+  int old_inferior = b->inferior;
+  b->inferior = inferior;
+  if (old_inferior != inferior)
+    gdb::observers::breakpoint_modified.notify (b);
+}
+
+/* See breakpoint.h.  */
+
 void
 breakpoint_set_task (struct breakpoint *b, int task)
 {
-  /* It is invalid to set the task field to anything other than -1 (which
-     means no task restriction) if a thread restriction is already in
-     place.  */
-  gdb_assert (task == -1 || b->thread == -1);
+  /* TASK should be -1, meaning no task restriction, or it should be a
+     valid task-id, which are greater than zero.  */
+  gdb_assert (task == -1 || task > 0);
 
-  int old_task = b->task;
+  /* It is not valid to set a task restriction for a breakpoint that
+     already has a thread or inferior restriction.  */
+  gdb_assert (task == -1 || (b->thread == -1 && b->inferior == -1));
 
+  int old_task = b->task;
   b->task = task;
   if (old_task != task)
     gdb::observers::breakpoint_modified.notify (b);
@@ -3157,6 +3183,12 @@ insert_breakpoint_locations (void)
 	  && !valid_global_thread_id (bl->owner->thread))
 	continue;
 
+      /* Or inferior specific breakpoints if the inferior no longer
+	 exists.  */
+      if (bl->owner->inferior != -1
+	  && !valid_global_inferior_id (bl->owner->inferior))
+	continue;
+
       switch_to_program_space_and_thread (bl->pspace);
 
       /* For targets that support global breakpoints, there's no need
@@ -3257,6 +3289,29 @@ Thread-specific breakpoint %d deleted - thread %s no longer in the thread list.\
     }
 }
 
+/* Called when inferior INF has been removed from GDB.  Remove associated
+   per-inferior breakpoints.  */
+
+static void
+remove_inferior_breakpoints (struct inferior *inf)
+{
+  for (breakpoint *b : all_breakpoints_safe ())
+    {
+      if (b->inferior == inf->num && user_breakpoint_p (b))
+	{
+	  /* Tell the user the breakpoint has been deleted.  But only for
+	     breakpoints that would not normally have been deleted at the
+	     next stop anyway.  */
+	  if (b->disposition != disp_del
+	      && b->disposition != disp_del_at_next_stop)
+	    gdb_printf (_("\
+Inferior-specific breakpoint %d deleted - inferior %d has been removed.\n"),
+			b->number, inf->num);
+	  delete_breakpoint (b);
+       }
+    }
+}
+
 /* See breakpoint.h.  */
 
 void
@@ -5466,6 +5521,7 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread)
      evaluating the condition if this isn't the specified
      thread/task.  */
   if ((b->thread != -1 && b->thread != thread->global_num)
+      || (b->inferior != -1 && b->inferior != thread->inf->num)
       || (b->task != -1 && b->task != ada_get_task_number (thread)))
     {
       infrun_debug_printf ("incorrect thread or task, not stopping");
@@ -6495,6 +6551,8 @@ print_one_breakpoint_location (struct breakpoint *b,
 	uiout->field_signed ("thread", b->thread);
       else if (b->task != -1)
 	uiout->field_signed ("task", b->task);
+      else if (b->inferior != -1)
+	uiout->field_signed ("inferior", b->inferior);
     }
 
   uiout->text ("\n");
@@ -6557,6 +6615,13 @@ print_one_breakpoint_location (struct breakpoint *b,
       uiout->text ("\n");
     }
 
+  if (!part_of_multiple && b->inferior != -1)
+    {
+      uiout->text ("\tstop only in inferior ");
+      uiout->field_signed ("inferior", b->inferior);
+      uiout->text ("\n");
+    }
+
   if (!part_of_multiple)
     {
       if (b->hit_count)
@@ -7542,7 +7607,10 @@ delete_longjmp_breakpoint (int thread)
     if (b->type == bp_longjmp || b->type == bp_exception)
       {
 	if (b->thread == thread)
-	  delete_breakpoint (b);
+	  {
+	    gdb_assert (b->inferior == -1);
+	    delete_breakpoint (b);
+	  }
       }
 }
 
@@ -7553,7 +7621,10 @@ delete_longjmp_breakpoint_at_next_stop (int thread)
     if (b->type == bp_longjmp || b->type == bp_exception)
       {
 	if (b->thread == thread)
-	  b->disposition = disp_del_at_next_stop;
+	  {
+	    gdb_assert (b->inferior == -1);
+	    b->disposition = disp_del_at_next_stop;
+	  }
       }
 }
 
@@ -7606,6 +7677,7 @@ check_longjmp_breakpoint_for_call_dummy (struct thread_info *tp)
   ALL_BREAKPOINTS_SAFE (b, b_tmp)
     if (b->type == bp_longjmp_call_dummy && b->thread == tp->global_num)
       {
+	gdb_assert (b->inferior == -1);
 	struct breakpoint *dummy_b = b->related_breakpoint;
 
 	/* Find the bp_call_dummy breakpoint in the list of breakpoints
@@ -8440,7 +8512,8 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
 				  gdb::unique_xmalloc_ptr<char> cond_string_,
 				  gdb::unique_xmalloc_ptr<char> extra_string_,
 				  enum bpdisp disposition_,
-				  int thread_, int task_, int ignore_count_,
+				  int thread_, int task_, int inferior_,
+				  int ignore_count_,
 				  int from_tty,
 				  int enabled_, unsigned flags,
 				  int display_canonical_)
@@ -8464,10 +8537,14 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
 
   gdb_assert (!sals.empty ());
 
-  /* At most one of thread or task can be set on any breakpoint.  */
-  gdb_assert (thread == -1 || task == -1);
+  /* At most one of thread, task, or inferior can be set on any breakpoint.  */
+  gdb_assert (((thread == -1 ? 0 : 1)
+	       + (task == -1 ? 0 : 1)
+	       + (inferior == -1 ? 0 : 1)) <= 1);
+
   thread = thread_;
   task = task_;
+  inferior = inferior_;
 
   cond_string = std::move (cond_string_);
   extra_string = std::move (extra_string_);
@@ -8569,7 +8646,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
 		       gdb::unique_xmalloc_ptr<char> cond_string,
 		       gdb::unique_xmalloc_ptr<char> extra_string,
 		       enum bptype type, enum bpdisp disposition,
-		       int thread, int task, int ignore_count,
+		       int thread, int task, int inferior, int ignore_count,
 		       int from_tty,
 		       int enabled, int internal, unsigned flags,
 		       int display_canonical)
@@ -8583,7 +8660,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
 				std::move (cond_string),
 				std::move (extra_string),
 				disposition,
-				thread, task, ignore_count,
+				thread, task, inferior, ignore_count,
 				from_tty,
 				enabled, flags,
 				display_canonical);
@@ -8612,7 +8689,8 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
 			gdb::unique_xmalloc_ptr<char> cond_string,
 			gdb::unique_xmalloc_ptr<char> extra_string,
 			enum bptype type, enum bpdisp disposition,
-			int thread, int task, int ignore_count,
+			int thread, int task, int inferior,
+			int ignore_count,
 			int from_tty,
 			int enabled, int internal, unsigned flags)
 {
@@ -8636,7 +8714,7 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
 			     std::move (cond_string),
 			     std::move (extra_string),
 			     type, disposition,
-			     thread, task, ignore_count,
+			     thread, task, inferior, ignore_count,
 			     from_tty, enabled, internal, flags,
 			     canonical->special_display);
     }
@@ -8766,21 +8844,26 @@ check_fast_tracepoint_sals (struct gdbarch *gdbarch,
     }
 }
 
-/* Given TOK, a string specification of condition and thread, as
-   accepted by the 'break' command, extract the condition
-   string and thread number and set *COND_STRING and *THREAD.
-   PC identifies the context at which the condition should be parsed.
-   If no condition is found, *COND_STRING is set to NULL.
-   If no thread is found, *THREAD is set to -1.  */
+/* Given TOK, a string specification of condition and thread, as accepted
+   by the 'break' command, extract the condition string into *COND_STRING.
+   If no condition string is found then *COND_STRING is set to nullptr.
+
+   If the breakpoint specification has an associated thread, task, or
+   inferior, these are extracted into *THREAD, *TASK, and *INFERIOR
+   respectively, otherwise these arguments are set to -1 (for THREAD and
+   INFERIOR) or 0 (for TASK).
+
+   PC identifies the context at which the condition should be parsed.  */
 
 static void
 find_condition_and_thread (const char *tok, CORE_ADDR pc,
 			   gdb::unique_xmalloc_ptr<char> *cond_string,
-			   int *thread, int *task,
+			   int *thread, int *inferior, int *task,
 			   gdb::unique_xmalloc_ptr<char> *rest)
 {
   cond_string->reset ();
   *thread = -1;
+  *inferior = -1;
   *task = -1;
   rest->reset ();
   bool force = false;
@@ -8797,7 +8880,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
       if ((*tok == '"' || *tok == ',') && rest)
 	{
 	  rest->reset (savestring (tok, strlen (tok)));
-	  return;
+	  break;
 	}
 
       end_tok = skip_to_space (tok);
@@ -8837,6 +8920,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  if (*task != -1)
 	    error (_("You can specify only one of thread or task."));
 
+	  if (*inferior != -1)
+	    error (_("You can specify only one of inferior or thread."));
+
 	  tok = end_tok + 1;
 	  thr = parse_thread_id (tok, &tmptok);
 	  if (tok == tmptok)
@@ -8844,6 +8930,26 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  *thread = thr->global_num;
 	  tok = tmptok;
 	}
+      else if (toklen >= 1 && strncmp (tok, "inferior", toklen) == 0)
+	{
+	  if (*inferior != -1)
+	    error(_("You can specify only one inferior."));
+
+	  if (*task != -1)
+	    error (_("You can specify only one of inferior or task."));
+
+	  if (*thread != -1)
+	    error (_("You can specify only one of inferior or thread."));
+
+	  char *tmptok;
+	  tok = end_tok + 1;
+	  *inferior = strtol (tok, &tmptok, 0);
+	  if (tok == tmptok)
+	    error (_("Junk after inferior keyword."));
+	  if (!valid_global_inferior_id (*inferior))
+	    error (_("Unknown inferior number %d."), *inferior);
+	  tok = tmptok;
+	}
       else if (toklen >= 1 && strncmp (tok, "task", toklen) == 0)
 	{
 	  char *tmptok;
@@ -8854,6 +8960,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  if (*thread != -1)
 	    error (_("You can specify only one of thread or task."));
 
+	  if (*inferior != -1)
+	    error (_("You can specify only one of inferior or task."));
+
 	  tok = end_tok + 1;
 	  *task = strtol (tok, &tmptok, 0);
 	  if (tok == tmptok)
@@ -8865,7 +8974,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
       else if (rest)
 	{
 	  rest->reset (savestring (tok, strlen (tok)));
-	  return;
+	  break;
 	}
       else
 	error (_("Junk at end of arguments."));
@@ -8881,7 +8990,7 @@ static void
 find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
 				    const char *input,
 				    gdb::unique_xmalloc_ptr<char> *cond_string,
-				    int *thread, int *task,
+				    int *thread, int *inferior, int *task,
 				    gdb::unique_xmalloc_ptr<char> *rest)
 {
   int num_failures = 0;
@@ -8889,6 +8998,7 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
     {
       gdb::unique_xmalloc_ptr<char> cond;
       int thread_id = -1;
+      int inferior_id = -1;
       int task_id = -1;
       gdb::unique_xmalloc_ptr<char> remaining;
 
@@ -8901,11 +9011,16 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
       try
 	{
 	  find_condition_and_thread (input, sal.pc, &cond, &thread_id,
-				     &task_id, &remaining);
+				     &inferior_id, &task_id, &remaining);
 	  *cond_string = std::move (cond);
-	  /* At most one of thread or task can be set.  */
-	  gdb_assert (thread_id == -1 || task_id == -1);
+	  /* A value of -1 indicates that these fields are unset.  At most
+	     one of these fields should be set (to a value other than -1)
+	     at this point.  */
+	  gdb_assert (((thread_id == -1 ? 1 : 0)
+		       + (task_id == -1 ? 1 : 0)
+		       + (inferior_id == -1 ? 1 : 0)) >= 2);
 	  *thread = thread_id;
+	  *inferior = inferior_id;
 	  *task = task_id;
 	  *rest = std::move (remaining);
 	  break;
@@ -8995,7 +9110,8 @@ int
 create_breakpoint (struct gdbarch *gdbarch,
 		   location_spec *locspec,
 		   const char *cond_string,
-		   int thread, const char *extra_string,
+		   int thread, int inferior,
+		   const char *extra_string,
 		   bool force_condition, int parse_extra,
 		   int tempflag, enum bptype type_wanted,
 		   int ignore_count,
@@ -9009,6 +9125,10 @@ create_breakpoint (struct gdbarch *gdbarch,
   int task = -1;
   int prev_bkpt_count = breakpoint_count;
 
+  gdb_assert (thread == -1 || thread > 0);
+  gdb_assert (inferior == -1 || inferior > 0);
+  gdb_assert (thread == -1 || inferior == -1);
+
   gdb_assert (ops != NULL);
 
   /* If extra_string isn't useful, set it to NULL.  */
@@ -9084,7 +9204,8 @@ create_breakpoint (struct gdbarch *gdbarch,
 	  const linespec_sals &lsal = canonical.lsals[0];
 
 	  find_condition_and_thread_for_sals (lsal.sals, extra_string,
-					      &cond, &thread, &task, &rest);
+					      &cond, &thread, &inferior,
+					      &task, &rest);
 	  cond_string_copy = std::move (cond);
 	  extra_string_copy = std::move (rest);
 	}
@@ -9134,7 +9255,7 @@ create_breakpoint (struct gdbarch *gdbarch,
 				   std::move (extra_string_copy),
 				   type_wanted,
 				   tempflag ? disp_del : disp_donttouch,
-				   thread, task, ignore_count,
+				   thread, task, inferior, ignore_count,
 				   from_tty, enabled, internal, flags);
     }
   else
@@ -9203,7 +9324,9 @@ break_command_1 (const char *arg, int flag, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL,
+		     -1 /* thread */, -1 /* inferior */,
+		     arg, false, 1 /* parse arg */,
 		     tempflag, type_wanted,
 		     0 /* Ignore count */,
 		     pending_break_support,
@@ -9315,7 +9438,8 @@ dprintf_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1,
+		     arg, false, 1 /* parse arg */,
 		     0, bp_dprintf,
 		     0 /* Ignore count */,
 		     pending_break_support,
@@ -10071,6 +10195,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
   const char *cond_end = NULL;
   enum bptype bp_type;
   int thread = -1;
+  int inferior = -1;
   /* Flag to indicate whether we are going to use masks for
      the hardware watchpoint.  */
   bool use_mask = false;
@@ -10125,12 +10250,13 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	      if (task != -1)
 		error (_("You can specify only one of thread or task."));
 
+	      if (inferior != -1)
+		error (_("You can specify only one of inferior or thread."));
+
 	      /* Extract the thread ID from the next token.  */
 	      thr = parse_thread_id (value_start, &endp);
-
-	      /* Check if the user provided a valid thread ID.  */
-	      if (*endp != ' ' && *endp != '\t' && *endp != '\0')
-		invalid_thread_id_error (value_start);
+	      if (value_start == endp)
+		error (_("Junk after thread keyword."));
 
 	      thread = thr->global_num;
 	    }
@@ -10144,12 +10270,20 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	      if (thread != -1)
 		error (_("You can specify only one of thread or task."));
 
+	      if (inferior != -1)
+		error (_("You can specify only one of inferior or task."));
+
 	      task = strtol (value_start, &tmp, 0);
 	      if (tmp == value_start)
 		error (_("Junk after task keyword."));
 	      if (!valid_task_id (task))
 		error (_("Unknown task %d."), task);
 	    }
+	  else if (toklen == 8 && startswith (tok, "inferior"))
+	    {
+	      /* Support for watchpoints will be added in a later commit.  */
+	      error (_("Cannot use 'inferior' keyword with watchpoints"));
+	    }
 	  else if (toklen == 4 && startswith (tok, "mask"))
 	    {
 	      /* We've found a "mask" token, which means the user wants to
@@ -10322,6 +10456,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
   /* At most one of thread or task can be set on a watchpoint.  */
   gdb_assert (thread == -1 || task == -1);
   w->thread = thread;
+  w->inferior = inferior;
   w->task = task;
   w->disposition = disp_donttouch;
   w->pspace = current_program_space;
@@ -12231,7 +12366,8 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
 				      enum bptype type_wanted,
 				      enum bpdisp disposition,
 				      int thread,
-				      int task, int ignore_count,
+				      int task, int inferior,
+				      int ignore_count,
 				      int from_tty, int enabled,
 				      int internal, unsigned flags)
 {
@@ -12257,7 +12393,7 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
 			 std::move (cond_string),
 			 std::move (extra_string),
 			 disposition,
-			 thread, task, ignore_count,
+			 thread, task, inferior, ignore_count,
 			 from_tty, enabled, flags,
 			 canonical->special_display));
 
@@ -12890,10 +13026,11 @@ code_breakpoint::location_spec_to_sals (location_spec *locspec,
       if (condition_not_parsed && extra_string != NULL)
 	{
 	  gdb::unique_xmalloc_ptr<char> local_cond, local_extra;
-	  int local_thread, local_task;
+	  int local_thread, local_task, local_inferior;
 
 	  find_condition_and_thread_for_sals (sals, extra_string.get (),
 					      &local_cond, &local_thread,
+					      &local_inferior,
 					      &local_task, &local_extra);
 	  gdb_assert (cond_string == nullptr);
 	  if (local_cond != nullptr)
@@ -13767,7 +13904,7 @@ trace_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     bp_tracepoint /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13785,7 +13922,7 @@ ftrace_command (const char *arg, int from_tty)
 						      current_language);
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     bp_fast_tracepoint /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13823,7 +13960,7 @@ strace_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     type /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13892,7 +14029,7 @@ create_tracepoint_from_upload (struct uploaded_tp *utp)
 						      current_language);
   if (!create_breakpoint (get_current_arch (),
 			  locspec.get (),
-			  utp->cond_string.get (), -1, addr_str,
+			  utp->cond_string.get (), -1, -1, addr_str,
 			  false /* force_condition */,
 			  0 /* parse cond/thread */,
 			  0 /* tempflag */,
@@ -14982,4 +15119,6 @@ This is useful for formatted output in user-defined commands."));
 					   "breakpoint");
   gdb::observers::thread_exit.attach (remove_threaded_breakpoints,
 				      "breakpoint");
+  gdb::observers::inferior_removed.attach (remove_inferior_breakpoints,
+					   "breakpoint");
 }
diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
index 7c5cf3f2bef..056299bcfd1 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -585,7 +585,7 @@ struct breakpoint_ops
 				  struct linespec_result *,
 				  gdb::unique_xmalloc_ptr<char>,
 				  gdb::unique_xmalloc_ptr<char>,
-				  enum bptype, enum bpdisp, int, int,
+				  enum bptype, enum bpdisp, int, int, int,
 				  int, int, int, int, unsigned);
 };
 
@@ -803,6 +803,10 @@ struct breakpoint
      care.  */
   int thread = -1;
 
+  /* Inferior number for inferior-specific breakpoint, or -1 if this
+     breakpoint is for all inferiors.  */
+  int inferior = -1;
+
   /* Ada task number for task-specific breakpoint, or -1 if don't
      care.  */
   int task = -1;
@@ -858,7 +862,7 @@ struct code_breakpoint : public breakpoint
 		   gdb::unique_xmalloc_ptr<char> cond_string,
 		   gdb::unique_xmalloc_ptr<char> extra_string,
 		   enum bpdisp disposition,
-		   int thread, int task, int ignore_count,
+		   int thread, int task, int inferior, int ignore_count,
 		   int from_tty,
 		   int enabled, unsigned flags,
 		   int display_canonical);
@@ -1538,6 +1542,7 @@ enum breakpoint_create_flags
 extern int create_breakpoint (struct gdbarch *gdbarch,
 			      struct location_spec *locspec,
 			      const char *cond_string, int thread,
+			      int inferior,
 			      const char *extra_string,
 			      bool force_condition,
 			      int parse_extra,
@@ -1681,6 +1686,11 @@ extern void breakpoint_set_silent (struct breakpoint *b, int silent);
 
 extern void breakpoint_set_thread (struct breakpoint *b, int thread);
 
+/* Set the inferior for breakpoint B to INFERIOR.  If INFERIOR is -1, make
+   the breakpoint work for any inferior.  */
+
+extern void breakpoint_set_inferior (struct breakpoint *b, int inferior);
+
 /* Set the task for this breakpoint.  If TASK is -1, make the breakpoint
    work for any task.  Passing a value other than -1 for TASK should only
    be done if b->thread is -1; it is not valid to try and set both a thread
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index fe76e5e0a0e..c3ed3989d77 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3509,6 +3509,57 @@
 space as a result of inferior 1 having executed a @code{vfork} call.
 @end table
 
+@menu
+* Inferior-Specific Breakpoints::	Controlling breakpoints
+@end menu
+
+@node Inferior-Specific Breakpoints
+@subsection Inferior-Specific Breakpoints
+
+When debugging multiple inferiors, you can choose whether to set
+breakpoints for all inferiors, or for a particular inferior.
+
+@table @code
+@cindex breakpoints and inferiors
+@cindex inferior-specific breakpoints
+@kindex break @dots{} inferior @var{inferior-id}
+@item break @var{locspec} inferior @var{inferior-id}
+@itemx break @var{locspec} inferior @var{inferior-id} if @dots{}
+@var{locspec} specifies a code location or locations in your program.
+@xref{Location Specifications}, for details.
+
+Use the qualifier @samp{inferior @var{inferior-id}} with a breakpoint
+command to specify that you only want @value{GDBN} to stop when a
+particular inferior reaches this breakpoint.  The @var{inferior-id}
+specifier is one of the inferior identifiers assigned by @value{GDBN},
+shown in the first column of the @samp{info inferiors} output.
+
+If you do not specify @samp{inferior @var{inferior-id}} when you set a
+breakpoint, the breakpoint applies to @emph{all} inferiors of your
+program.
+
+You can use the @code{inferior} qualifier on conditional breakpoints as
+well; in this case, place @samp{inferior @var{inferior-id}} before or
+after the breakpoint condition, like this:
+
+@smallexample
+(@value{GDBP}) break frik.c:13 inferior 2 if bartab > lim
+@end smallexample
+@end table
+
+Inferior-specific breakpoints are automatically deleted when the
+corresponding inferior is removed from @value{GDBN}.  For example:
+
+@smallexample
+(@value{GDBP}) remove-inferiors 2
+Inferior-specific breakpoint 3 deleted - inferior 2 has been removed.
+@end smallexample
+
+A breakpoint can't be both inferior-specific and thread-specific
+(@pxref{Thread-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{inferior}, @code{thread}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
 @node Threads
 @section Debugging Programs with Multiple Threads
 
@@ -4471,8 +4522,9 @@
 situation.
 
 It is also possible to insert a breakpoint that will stop the program
-only if a specific thread (@pxref{Thread-Specific Breakpoints})
-or a specific task (@pxref{Ada Tasks}) hits that breakpoint.
+only if a specific thread (@pxref{Thread-Specific Breakpoints}),
+specific inferior (@pxref{Inferior-Specific Breakpoints}), or a
+specific task (@pxref{Ada Tasks}) hits that breakpoint.
 
 @item break
 When called without any arguments, @code{break} sets a breakpoint at
@@ -7323,9 +7375,14 @@
 Process}), or if @value{GDBN} loses the remote connection
 (@pxref{Remote Debugging}), etc.  Note that with some targets,
 @value{GDBN} is only able to detect a thread has exited when the user
-explictly asks for the thread list with the @code{info threads}
+explicitly asks for the thread list with the @code{info threads}
 command.
 
+A breakpoint can't be both thread-specific and inferior-specific
+(@pxref{Inferior-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{thread}, @code{inferior}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
 @node Interrupted System Calls
 @subsection Interrupted System Calls 
 
@@ -31546,6 +31603,10 @@
 If this is a thread-specific breakpoint, then this identifies the
 thread in which the breakpoint can trigger.
 
+@item inferior
+If this is an inferior-specific breakpoint, this this identifies the
+inferior in which the breakpoint can trigger.
+
 @item task
 If this breakpoint is restricted to a particular Ada task, then this
 field will hold the task identifier.
@@ -32137,7 +32198,7 @@
 @smallexample
  -break-insert [ -t ] [ -h ] [ -f ] [ -d ] [ -a ] [ --qualified ]
     [ -c @var{condition} ] [ --force-condition ] [ -i @var{ignore-count} ]
-    [ -p @var{thread-id} ] [ @var{locspec} ]
+    [ -p @var{thread-id} ] [ -g @var{thread-group-id} ] [ @var{locspec} ]
 @end smallexample
 
 @noindent
@@ -32200,6 +32261,9 @@
 @item -p @var{thread-id}
 Restrict the breakpoint to the thread with the specified global
 @var{thread-id}.
+@item -g @var{thread-group-id}
+Restrict the breakpoint to the thread group with the specified
+@var{thread-group-id}.
 @item --qualified
 This option makes @value{GDBN} interpret a function name specified as
 a complete fully-qualified name.
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index c74d586ef39..27ccda01f75 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -3419,7 +3419,10 @@
 A @code{gdb.Inferior} object has the following attributes:
 
 @defvar Inferior.num
-ID of inferior, as assigned by GDB.
+ID of inferior, as assigned by @value{GDBN}.  You can use this to make
+Python breakpoints inferior-specific, for example
+(@pxref{python_breakpoint_inferior,,The Breakpoint.inferior
+attribute}).
 @end defvar
 
 @anchor{gdbpy_inferior_connection}
@@ -6221,9 +6224,24 @@
 
 @anchor{python_breakpoint_thread}
 @defvar Breakpoint.thread
-If the breakpoint is thread-specific, this attribute holds the
-thread's global id.  If the breakpoint is not thread-specific, this
-attribute is @code{None}.  This attribute is writable.
+If the breakpoint is thread-specific (@pxref{Thread-Specific
+Breakpoints}), this attribute holds the thread's global id.  If the
+breakpoint is not thread-specific, this attribute is @code{None}.
+This attribute is writable.
+
+Only one of @code{Breakpoint.thread} or @code{Breakpoint.inferior} can
+be set to a valid id at any time, that is, a breakpoint can be thread
+specific, or inferior specific, but not both.
+@end defvar
+
+@anchor{python_breakpoint_inferior}
+@defvar Breakpoint.inferior
+If the breakpoint is inferior-specific (@pxref{Inferior-Specific
+Breakpoints}), this attribute holds the inferior's id.  If the
+breakpoint is not inferior-specific, this attribute is @code{None}.
+
+This attribute can be written for breakpoints of type
+@code{gdb.BP_BREAKPOINT} and @code{gdb.BP_HARDWARE_BREAKPOINT}.
 @end defvar
 
 @defvar Breakpoint.task
diff --git a/gdb/guile/scm-breakpoint.c b/gdb/guile/scm-breakpoint.c
index 2931df265d7..4e343b85656 100644
--- a/gdb/guile/scm-breakpoint.c
+++ b/gdb/guile/scm-breakpoint.c
@@ -465,7 +465,7 @@ gdbscm_register_breakpoint_x (SCM self)
 	    const breakpoint_ops *ops =
 	      breakpoint_ops_for_location_spec (locspec.get (), false);
 	    create_breakpoint (get_current_arch (),
-			       locspec.get (), NULL, -1, NULL, false,
+			       locspec.get (), NULL, -1, -1, NULL, false,
 			       0,
 			       temporary, bp_breakpoint,
 			       0,
@@ -784,6 +784,11 @@ gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue)
   else
     SCM_ASSERT_TYPE (0, newvalue, SCM_ARG2, FUNC_NAME, _("integer or #f"));
 
+  if (bp_smob->bp->inferior != -1 && id != -1)
+    scm_misc_error (FUNC_NAME,
+		    _("Cannot have both 'thread' and 'inferior' "
+		      "conditions on a breakpoint"), SCM_EOL);
+
   breakpoint_set_thread (bp_smob->bp, id);
 
   return SCM_UNSPECIFIED;
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index a68611538f2..1dcc2ee0459 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -425,17 +425,10 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
   if (run_how == RUN_STOP_AT_MAIN)
     {
       /* To avoid other inferiors hitting this breakpoint, make it
-	 inferior-specific using a condition.  A better solution would be to
-	 have proper inferior-specific breakpoint support, in the breakpoint
-	 machinery.  We could then avoid inserting a breakpoint in the program
-	 spaces unrelated to this inferior.  */
-      const char *op
-	= ((current_language->la_language == language_ada
-	    || current_language->la_language == language_pascal
-	    || current_language->la_language == language_m2) ? "=" : "==");
-      std::string arg = string_printf
-	("-qualified %s if $_inferior %s %d", main_name (), op,
-	 current_inferior ()->num);
+	 inferior-specific.  */
+      std::string arg = string_printf ("-qualified %s inferior %d",
+				       main_name (),
+				       current_inferior ()->num);
       tbreak_command (arg.c_str (), 0);
     }
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 72034cc4ffb..55df57666f9 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -815,4 +815,15 @@ extern void print_selected_inferior (struct ui_out *uiout);
 extern void switch_to_inferior_and_push_target
   (inferior *new_inf, bool no_connection, inferior *org_inf);
 
+/* Return true if ID is a valid global inferior number.  */
+
+inline bool
+valid_global_inferior_id (int id)
+{
+  for (inferior *inf : all_inferiors ())
+    if (inf->num == id)
+      return true;
+  return false;
+}
+
 #endif /* !defined (INFERIOR_H) */
diff --git a/gdb/linespec.c b/gdb/linespec.c
index 7d969f37fbf..695dc6515ee 100644
--- a/gdb/linespec.c
+++ b/gdb/linespec.c
@@ -254,9 +254,9 @@ enum linespec_token_type
 
 /* List of keywords.  This is NULL-terminated so that it can be used
    as enum completer.  */
-const char * const linespec_keywords[] = { "if", "thread", "task", "-force-condition", NULL };
+const char * const linespec_keywords[] = { "if", "thread", "task", "inferior", "-force-condition", NULL };
 #define IF_KEYWORD_INDEX 0
-#define FORCE_KEYWORD_INDEX 3
+#define FORCE_KEYWORD_INDEX 4
 
 /* A token of the linespec lexer  */
 
diff --git a/gdb/mi/mi-cmd-break.c b/gdb/mi/mi-cmd-break.c
index 75957b75bad..fc1c4205311 100644
--- a/gdb/mi/mi-cmd-break.c
+++ b/gdb/mi/mi-cmd-break.c
@@ -172,6 +172,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
   int hardware = 0;
   int temp_p = 0;
   int thread = -1;
+  int thread_group = -1;
   int ignore_count = 0;
   char *condition = NULL;
   int pending = 0;
@@ -190,7 +191,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
   enum opt
     {
       HARDWARE_OPT, TEMP_OPT, CONDITION_OPT,
-      IGNORE_COUNT_OPT, THREAD_OPT, PENDING_OPT, DISABLE_OPT,
+      IGNORE_COUNT_OPT, THREAD_OPT, THREAD_GROUP_OPT,
+      PENDING_OPT, DISABLE_OPT,
       TRACEPOINT_OPT,
       FORCE_CONDITION_OPT,
       QUALIFIED_OPT,
@@ -204,6 +206,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
     {"c", CONDITION_OPT, 1},
     {"i", IGNORE_COUNT_OPT, 1},
     {"p", THREAD_OPT, 1},
+    {"g", THREAD_GROUP_OPT, 1},
     {"f", PENDING_OPT, 0},
     {"d", DISABLE_OPT, 0},
     {"a", TRACEPOINT_OPT, 0},
@@ -244,6 +247,9 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
 	case THREAD_OPT:
 	  thread = atol (oarg);
 	  break;
+	case THREAD_GROUP_OPT:
+	  thread_group = mi_parse_thread_group_id (oarg);
+	  break;
 	case PENDING_OPT:
 	  pending = 1;
 	  break;
@@ -357,7 +363,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
 	error (_("Garbage '%s' at end of location"), address);
     }
 
-  create_breakpoint (get_current_arch (), locspec.get (), condition, thread,
+  create_breakpoint (get_current_arch (), locspec.get (), condition,
+		     thread, thread_group,
 		     extra_string.c_str (),
 		     force_condition,
 		     0 /* condition and thread are valid.  */,
diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c
index 0013e5dfafd..aaa13d1d65b 100644
--- a/gdb/mi/mi-main.c
+++ b/gdb/mi/mi-main.c
@@ -1745,8 +1745,7 @@ mi_cmd_remove_inferior (const char *command, char **argv, int argc)
   if (argc != 1)
     error (_("-remove-inferior should be passed a single argument"));
 
-  if (sscanf (argv[0], "i%d", &id) != 1)
-    error (_("the thread group id is syntactically invalid"));
+  id = mi_parse_thread_group_id (argv[0]);
 
   inf_to_remove = find_inferior_id (id);
   if (inf_to_remove == NULL)
@@ -2757,6 +2756,21 @@ mi_cmd_complete (const char *command, char **argv, int argc)
 		       result.number_matches == max_completions ? "1" : "0");
 }
 
+/* See mi-main.h.  */
+int
+mi_parse_thread_group_id (const char *id)
+{
+  if (*id != 'i')
+    error (_("thread group id should start with an 'i'"));
+
+  char *end;
+  long num = strtol (id + 1, &end, 10);
+
+  if (*end != '\0' || num > INT_MAX)
+    error (_("invalid thread group id '%s'"), id);
+
+  return (int) num;
+}
 
 void _initialize_mi_main ();
 void
diff --git a/gdb/mi/mi-main.h b/gdb/mi/mi-main.h
index ff1d4ed84bc..0389a141cf5 100644
--- a/gdb/mi/mi-main.h
+++ b/gdb/mi/mi-main.h
@@ -76,4 +76,10 @@ extern void mi_cmd_fix_multi_location_breakpoint_output (const char *command,
 extern void mi_cmd_fix_breakpoint_script_output (const char *command,
 						 char **argv, int argc);
 
+/* Parse a thread-group-id from ID, and return the integer part of the
+   ID.  A valid thread-group-id is the character 'i' followed by an
+   integer that is greater than zero.  */
+
+extern int mi_parse_thread_group_id (const char *id);
+
 #endif /* MI_MI_MAIN_H */
diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
index 880f1b5c1e2..2ac4d959a29 100644
--- a/gdb/python/py-breakpoint.c
+++ b/gdb/python/py-breakpoint.c
@@ -287,11 +287,86 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure)
       return -1;
     }
 
+  if (self_bp->bp->inferior != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'thread' and 'inferior' "
+			 "conditions on a breakpoint"));
+      return -1;
+    }
+
   breakpoint_set_thread (self_bp->bp, id);
 
   return 0;
 }
 
+/* Python function to set the inferior of a breakpoint.  */
+
+static int
+bppy_set_inferior (PyObject *self, PyObject *newvalue, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+  long id;
+
+  BPPY_SET_REQUIRE_VALID (self_bp);
+
+  if (newvalue == NULL)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("Cannot delete 'inferior' attribute."));
+      return -1;
+    }
+  else if (PyLong_Check (newvalue))
+    {
+      if (!gdb_py_int_as_long (newvalue, &id))
+	return -1;
+
+      if (!valid_global_inferior_id (id))
+	{
+	  PyErr_SetString (PyExc_RuntimeError,
+			   _("Invalid inferior ID."));
+	  return -1;
+	}
+    }
+  else if (newvalue == Py_None)
+    id = -1;
+  else
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("The value of 'inferior' must be an integer or None."));
+      return -1;
+    }
+
+  if (self_bp->bp->type != bp_breakpoint
+      && self_bp->bp->type != bp_hardware_breakpoint)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot set 'inferior' attribute on a gdb.Breakpoint "
+			 "of this type"));
+      return -1;
+    }
+
+  if (self_bp->bp->thread != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'thread' and 'inferior' conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
+  if (self_bp->bp->task != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'task' and 'inferior' conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
+  breakpoint_set_inferior (self_bp->bp, id);
+
+  return 0;
+}
+
 /* Python function to set the (Ada) task of a breakpoint.  */
 static int
 bppy_set_task (PyObject *self, PyObject *newvalue, void *closure)
@@ -703,6 +778,20 @@ bppy_get_thread (PyObject *self, void *closure)
   return gdb_py_object_from_longest (self_bp->bp->thread).release ();
 }
 
+/* Python function to get the breakpoint's inferior ID.  */
+static PyObject *
+bppy_get_inferior (PyObject *self, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+
+  BPPY_REQUIRE_VALID (self_bp);
+
+  if (self_bp->bp->inferior == -1)
+    Py_RETURN_NONE;
+
+  return gdb_py_object_from_longest (self_bp->bp->inferior).release ();
+}
+
 /* Python function to get the breakpoint's task ID (in Ada).  */
 static PyObject *
 bppy_get_task (PyObject *self, void *closure)
@@ -941,7 +1030,7 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs)
 	      = breakpoint_ops_for_location_spec (locspec.get (), false);
 
 	    create_breakpoint (gdbpy_enter::get_gdbarch (),
-			       locspec.get (), NULL, -1, NULL, false,
+			       locspec.get (), NULL, -1, -1, NULL, false,
 			       0,
 			       temporary_bp, type,
 			       0,
@@ -1350,6 +1439,11 @@ static gdb_PyGetSetDef breakpoint_object_getset[] = {
 If the value is a thread ID (integer), then this is a thread-specific breakpoint.\n\
 If the value is None, then this breakpoint is not thread-specific.\n\
 No other type of value can be used.", NULL },
+  { "inferior", bppy_get_inferior, bppy_set_inferior,
+    "Inferior ID for the breakpoint.\n\
+If the value is an inferior ID (integer), then this is an inferior-specific\n\
+breakpoint.  If the value is None, then this breakpoint is not\n\
+inferior-specific.  No other type of value can be used.", NULL },
   { "task", bppy_get_task, bppy_set_task,
     "Thread ID for the breakpoint.\n\
 If the value is a task ID (integer), then this is an Ada task-specific breakpoint.\n\
diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c
index 7122fa820f6..ba77b1d0a16 100644
--- a/gdb/python/py-finishbreakpoint.c
+++ b/gdb/python/py-finishbreakpoint.c
@@ -307,7 +307,7 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
       location_spec_up locspec
 	= new_address_location_spec (get_frame_pc (prev_frame), NULL, 0);
       create_breakpoint (gdbpy_enter::get_gdbarch (),
-			 locspec.get (), NULL, thread, NULL, false,
+			 locspec.get (), NULL, thread, -1, NULL, false,
 			 0,
 			 1 /*temp_flag*/,
 			 bp_breakpoint,
diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp
index eb7ee5c9951..603d43f7c36 100644
--- a/gdb/testsuite/gdb.ada/tasks.exp
+++ b/gdb/testsuite/gdb.ada/tasks.exp
@@ -58,6 +58,8 @@ gdb_test "break break_me task 1 thread 1" \
     "You can specify only one of thread or task\\."
 gdb_test "break break_me thread 1 task 1" \
     "You can specify only one of thread or task\\."
+gdb_test "break break_me inferior 1 task 1" \
+    "You can specify only one of inferior or task\\."
 gdb_test "watch j task 1 thread 1" \
     "You can specify only one of thread or task\\."
 gdb_test "watch j thread 1 task 1" \
diff --git a/gdb/testsuite/gdb.linespec/cpcompletion.exp b/gdb/testsuite/gdb.linespec/cpcompletion.exp
index fbe19b49d43..f005707b9da 100644
--- a/gdb/testsuite/gdb.linespec/cpcompletion.exp
+++ b/gdb/testsuite/gdb.linespec/cpcompletion.exp
@@ -1259,8 +1259,8 @@ proc_with_prefix function-labels {} {
 }
 
 # Test that completion after a function name offers keyword
-# (if/task/thread/-force-condition) matches in linespec mode, and also
-# the explicit location options in explicit locations mode.
+# (if/inferior/task/thread/-force-condition) matches in linespec mode,
+# and also the explicit location options in explicit locations mode.
 
 proc_with_prefix keywords-after-function {} {
     set explicit_list \
diff --git a/gdb/testsuite/gdb.linespec/explicit.exp b/gdb/testsuite/gdb.linespec/explicit.exp
index b08d65953d2..668002d9038 100644
--- a/gdb/testsuite/gdb.linespec/explicit.exp
+++ b/gdb/testsuite/gdb.linespec/explicit.exp
@@ -412,6 +412,7 @@ namespace eval $testfile {
 	    "-qualified"
 	    "-source"
 	    "if"
+	    "inferior"
 	    "task"
 	    "thread"
 	}
diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
new file mode 100644
index 00000000000..c171ef71b9d
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
@@ -0,0 +1,29 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2023 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/>.  */
+
+int
+foo (void)
+{
+  return 0;
+}
+
+int
+main (void)
+{
+  int res = foo ();
+  return res;
+}
diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
new file mode 100644
index 00000000000..93a0eb3de49
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
@@ -0,0 +1,108 @@
+# Copyright 2023 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 for the delivery of '=breakpoint-deleted' notifications when
+# breakpoints are deleted.  Right now this test only covers
+# inferior-specific breakpoints, but it could be extended to cover
+# other cases too.
+
+# Multiple inferiors are needed, therefore only native gdb and
+# extended gdbserver modes are supported.
+require !use_gdb_stub
+
+# Separate UI doesn't work with GDB debug.
+require !gdb_debug_enabled
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+standard_testfile
+
+if { [build_executable "failed to prepare" $testfile $srcfile] } {
+    return -1
+}
+
+# Helper proc to create a breakpoint location regexp.  NUM is the
+# regexp to match the number field of this location.
+proc make_bp_loc { num } {
+    return [mi_make_breakpoint_loc \
+		-number "$num" \
+		-enabled "y" \
+		-func "foo" \
+		-inferior "2"]
+}
+
+foreach_mi_ui_mode mode {
+    mi_gdb_exit
+
+    if {$mode eq "separate"} {
+	set start_ops "separate-mi-tty"
+    } else {
+	set start_ops ""
+    }
+
+    if [mi_gdb_start $start_ops] {
+	return
+    }
+
+    # Load a test binary into inferior 1.
+    mi_gdb_load ${binfile}
+
+    # Setup inferior 2, including loading an exec file.
+    mi_gdb_test "-add-inferior" \
+	[multi_line "=thread-group-added,id=\"\[^\"\]+\"" \
+	     "~\"\\\[New inferior 2\\\]\\\\n\"" \
+	     "\~\"Added inferior 2\\\\n\"" \
+	     "\\^done,inferior=\"\[^\"\]+\"" ] \
+	"mi add inferior 2"
+    mi_gdb_test "-file-exec-and-symbols --thread-group i2 $::binfile" \
+	"\\^done" \
+	"set executable of inferior 2"
+
+    # Build regexp for the two locations.
+    set loc1 [make_bp_loc "$::decimal\\.1"]
+    set loc2 [make_bp_loc "$::decimal\\.2"]
+
+    # Create the inferior-specific breakpoint.
+    mi_create_breakpoint_multi "-g i2 foo" "create breakpoint in inferior 2" \
+	-inferior "2" -locations "\\\[$loc1,$loc2\\\]"
+    set bpnum [mi_get_valueof "/d" "\$bpnum" "INVALID"]
+
+    if {$mode eq "separate"} {
+	# In 'separate' mode we delete the inferior from the CLI, and
+	# then look for the breakpoint-deleted notification on the MI.
+	with_spawn_id $gdb_main_spawn_id {
+	    gdb_test "inferior 1" ".*"
+	    gdb_test "remove-inferiors 2" \
+		"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\."
+	}
+
+	gdb_test_multiple "" "check for b/p deleted notification on MI" {
+	    -re "=breakpoint-deleted,id=\"$bpnum\"" {
+		pass $gdb_test_name
+	    }
+	}
+    } else {
+	# In the non-separate mode we delete the inferior from the MI
+	# and expect to immediately see a breakpoint-deleted
+	# notification.
+	mi_gdb_test "-remove-inferior i2" \
+	    [multi_line \
+		 "~\"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\.\\\\n\"" \
+		 "=breakpoint-deleted,id=\"$bpnum\"" \
+		 "=thread-group-removed,id=\"i2\"" \
+		 "\\^done"]
+    }
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
new file mode 100644
index 00000000000..8f86d8cdcea
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
@@ -0,0 +1,52 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2023 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/>.  */
+
+volatile int global_var = 0;
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+static inline void __attribute__((__always_inline__))
+foo (void)
+{
+  int i;
+
+  for (i = 0; i < 10; ++i)
+    global_var = 0;
+}
+
+static void
+bar (void)
+{
+  global_var = 0;
+
+  foo ();
+}
+
+
+int
+main (void)
+{
+  global_var = 0;
+  foo ();
+  bar ();
+  stop_breakpt ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
new file mode 100644
index 00000000000..e5b20b6e7a8
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
@@ -0,0 +1,52 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2023 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/>.  */
+
+static int bar (void);
+static int baz (void);
+static int foo (void);
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+int
+main (void)
+{
+  int ret = baz ();
+  stop_breakpt ();
+  return ret;
+}
+
+static int
+bar (void)
+{
+  return baz ();
+}
+
+static int
+foo (void)
+{
+  return 0;
+}
+
+static int
+baz (void)
+{
+  return foo ();
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
new file mode 100644
index 00000000000..1f6573268da
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
@@ -0,0 +1,179 @@
+# Copyright 2022-2023 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/>.
+
+# Test inferior-specific breakpoints.
+
+standard_testfile -1.c -2.c
+
+if {[use_gdb_stub]} {
+    return
+}
+
+set srcfile1 ${srcfile}
+set binfile1 ${binfile}-1
+set binfile2 ${binfile}-2
+
+if {[build_executable ${testfile}.exp ${binfile1} "${srcfile1}"] != 0} {
+    return -1
+}
+
+if {[build_executable ${testfile}.exp ${binfile2} "${srcfile2}"] != 0} {
+    return -1
+}
+
+# Start the first inferior.
+clean_restart ${binfile1}
+if {![runto_main]} {
+    return
+}
+
+# Add a second inferior, and start this one too.
+gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2"
+gdb_load $binfile2
+if {![runto_main]} {
+    return
+}
+
+# Try to create a breakpoint using both the 'inferior' and 'thread' keywords,
+# this should fail.  Try with the keywords in both orders just in case the
+# parser has a bug.
+gdb_test "break foo thread 1.1 inferior 1" \
+    "You can specify only one of inferior or thread\\."
+gdb_test "break foo inferior 1 thread 1.1" \
+    "You can specify only one of inferior or thread\\."
+
+# Try to create a breakpoint using the 'inferior' keyword multiple times.
+gdb_test "break foo inferior 1 inferior 2" \
+    "You can specify only one inferior\\."
+
+# Clear out any other breakpoints.
+delete_breakpoints
+
+# Use 'info breakpoint' to check that the inferior specific breakpoint is
+# present in the breakpoint list.  TESTNAME is the name used for this test,
+# BP_NUMBER is the number for the breakpoint, and EXPECTED_LOC_COUNT is the
+# number of locations we expect for that breakpoint.
+proc check_info_breakpoints { testname bp_number expected_loc_count } {
+    gdb_test_multiple "info breakpoints $bp_number" $testname {
+	-re "\r\nNum\\s+\[^\r\n\]+\r\n" {
+	    exp_continue
+	}
+
+	-re "^$bp_number\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
+	    set saw_header true
+	    exp_continue
+	}
+
+	-re "^\\s+stop only in inferior 1\r\n" {
+	    set saw_inf_cond true
+	    exp_continue
+	}
+
+	-re "^\\s+breakpoint already hit $::decimal times\r\n" {
+	    exp_continue
+	}
+
+	-re "^$bp_number\\.\[123\]\\s+y\\s+ $::hex in foo at \[^\r\n\]+(?: inf \[12\])?\r\n" {
+	    incr location_count
+	    exp_continue
+	}
+
+	-re "^$::gdb_prompt $" {
+	    with_test_prefix $gdb_test_name {
+		gdb_assert { $saw_header \
+				 && $location_count == $expected_loc_count \
+				 && $saw_inf_cond } \
+		    $gdb_test_name
+	    }
+	}
+    }
+}
+
+# Create an inferior-specific breakpoint.  Use gdb_test instead of
+# gdb_breakpoint here as we want to check the breakpoint was placed in
+# multiple locations.
+#
+# Currently GDB still places inferior specific breakpoints into every
+# inferior, just like it does with thread specific breakpoints.
+# Hopefully this will change in the future, at which point, this test
+# will need updating.
+#
+# Two of these locations are in inferior 1, while the third is in
+# inferior 2.
+gdb_test "break foo inferior 1" \
+    "Breakpoint $decimal at $hex: foo\\. \\(3 locations\\)"
+set bp_number [get_integer_valueof "\$bpnum" "INVALID" \
+		  "get b/p number for inferior specific breakpoint"]
+
+set saw_header false
+set location_count 0
+set saw_inf_cond false
+
+check_info_breakpoints "first check for inferior specific breakpoint" \
+    $bp_number 3
+
+# Create a multi-inferior breakpoint to stop at.
+gdb_breakpoint "stop_breakpt" message
+set stop_bp_num [get_integer_valueof "\$bpnum" "INVALID" \
+		    "get b/p number for stop_breakpt"]
+
+# Now resume inferior 2, this should reach 'stop_breakpt'.
+gdb_test "continue" \
+    "hit Breakpoint $stop_bp_num\.$decimal, stop_breakpt \\(\\) .*" \
+    "continue in inferior 2"
+
+# Switch to inferior 1, and try there.
+gdb_test "inferior 1" ".*" \
+    "select inferior 1 to check the inferior-specific b/p works"
+gdb_test "continue " \
+    "Thread 1\\.${decimal}\[^\r\n\]* hit Breakpoint\
+     $bp_number\.$decimal, foo \\(\\) .*" \
+    "first continue in inferior 1"
+
+# Now back to inferior 2, let the inferior exit, and then remove the
+# inferior, the inferior-specific breakpoint should not be deleted.
+gdb_test "inferior 2" ".*" \
+    "switch back to allow inferior 2 to exit"
+gdb_test "continue" "\\\[Inferior 2 \[^\r\n\]+ exited normally\\\]" \
+    "allow inferior 2 to exit"
+gdb_test "inferior 1" ".*" \
+    "back to inferior 1 so inferior 2 can be deleted"
+gdb_test_no_output "remove-inferiors 2"
+
+gdb_test "continue " "hit Breakpoint $bp_number\.$decimal, foo \\(\\) .*" \
+    "second continue in inferior 1"
+gdb_test "continue " "hit Breakpoint $stop_bp_num, stop_breakpt \\(\\) .*" \
+    "third continue in inferior 1"
+
+# Now allow inferior 1 to exit, the inferior specific breakpoint
+# should not be deleted.
+gdb_test "continue" \
+    "\\\[Inferior 1 \[^\r\n\]+ exited normally\\\]" \
+    "allow inferior 1 to exit"
+
+check_info_breakpoints "second check for inferior specific breakpoint" \
+    $bp_number 2
+
+# Now create another new inferior, then remove inferior 1.  As a result of
+# this removal, the inferior specific breakpoint should be deleted.
+gdb_test "add-inferior" "Added inferior 3.*" "add empty inferior 3"
+gdb_test "inferior 3" "Switching to inferior 3.*" "switch to inferior 3"
+gdb_test "remove-inferiors 1" \
+    "Inferior-specific breakpoint $bp_number deleted - inferior 1 has been removed\\."
+
+# Now check 'info breakpoints' to ensure the breakpoint is gone.
+gdb_test "info breakpoints $bp_number" \
+    "No breakpoint or watchpoint matching '$bp_number'\\."
diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp
index ab81b7ade85..e0e76109eb3 100644
--- a/gdb/testsuite/gdb.python/py-breakpoint.exp
+++ b/gdb/testsuite/gdb.python/py-breakpoint.exp
@@ -113,6 +113,8 @@ proc_with_prefix test_bkpt_basic { } {
 	"Get Breakpoint List" 0
     gdb_test "python print (blist\[1\].thread)" \
 	"None" "Check breakpoint thread"
+    gdb_test "python print (blist\[1\].inferior)" \
+	"None" "Check breakpoint inferior"
     gdb_test "python print (blist\[1\].type == gdb.BP_BREAKPOINT)" \
 	"True" "Check breakpoint type"
     gdb_test "python print (blist\[0\].number)" \
@@ -215,6 +217,46 @@ proc_with_prefix test_bkpt_cond_and_cmds { } {
 	"check number of lines in commands"
 }
 
+# Test breakpoint thread and inferior attributes.
+proc_with_prefix test_bkpt_thread_and_inferior { } {
+    global srcfile testfile hex decimal
+
+    # Start with a fresh gdb.
+    clean_restart ${testfile}
+
+    if {![runto_main]} {
+	return 0
+    }
+
+    with_test_prefix "thread" {
+	delete_breakpoints
+	gdb_test "break multiply thread 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "1"
+	gdb_test "python print(bp.inferior)" "None"
+	gdb_test "python bp.inferior = 1" \
+	    "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.thread = None"
+	gdb_test_no_output "python bp.inferior = 1" \
+	    "set the inferior now the thread has been cleared"
+	gdb_test "info breakpoints" "stop only in inferior 1\r\n.*"
+    }
+
+    with_test_prefix "inferior" {
+	delete_breakpoints
+	gdb_test "break multiply inferior 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "None"
+	gdb_test "python print(bp.inferior)" "1"
+	gdb_test "python bp.thread = 1" \
+	    "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.inferior = None"
+	gdb_test_no_output "python bp.thread = 1" \
+	    "set the thread now the inferior has been cleared"
+	gdb_test "info breakpoints" "stop only in thread 1\r\n.*"
+    }
+}
+
 proc_with_prefix test_bkpt_invisible { } {
     global srcfile testfile hex decimal
 
@@ -851,6 +893,7 @@ proc_with_prefix test_bkpt_auto_disable { } {
 test_bkpt_basic
 test_bkpt_deletion
 test_bkpt_cond_and_cmds
+test_bkpt_thread_and_inferior
 test_bkpt_invisible
 test_hardware_breakpoints
 test_catchpoints
diff --git a/gdb/testsuite/lib/completion-support.exp b/gdb/testsuite/lib/completion-support.exp
index 275f8874f15..85768deab94 100644
--- a/gdb/testsuite/lib/completion-support.exp
+++ b/gdb/testsuite/lib/completion-support.exp
@@ -27,7 +27,7 @@ namespace eval completion {
     # List of all quote chars, including no-quote at all.
     variable maybe_quoted_list {"" "'" "\""}
 
-    variable keyword_list {"-force-condition" "if" "task" "thread"}
+    variable keyword_list {"-force-condition" "if" "inferior" "task" "thread"}
 
     variable explicit_opts_list \
 	{"-function" "-label" "-line" "-qualified" "-source"}
diff --git a/gdb/testsuite/lib/mi-support.exp b/gdb/testsuite/lib/mi-support.exp
index c914ed49150..75611233703 100644
--- a/gdb/testsuite/lib/mi-support.exp
+++ b/gdb/testsuite/lib/mi-support.exp
@@ -2528,7 +2528,7 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} {
 # locations.
 #
 # All arguments for the breakpoint location may be specified using the
-# options: number, enabled, addr, func, file, fullname, line,
+# options: number, enabled, addr, func, file, fullname, line, inferior
 # thread-groups, and thread.
 #
 # For the option -thread the corresponding output field is only added
@@ -2542,12 +2542,14 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} {
 proc mi_make_breakpoint_loc {args} {
     parse_args {{number .*} {enabled .*} {addr .*}
 	{func .*} {file .*} {fullname .*} {line .*}
-	{thread-groups \\\[.*\\\]} {thread ""}}
+	{thread-groups \\\[.*\\\]} {thread ""} {inferior ""}}
 
     set attr_list {}
     foreach attr [list number enabled addr func file \
-		      fullname line thread-groups] {
-	lappend attr_list $attr [set $attr]
+		      fullname line thread-groups inferior] {
+	if {$attr ne "inferior" || [set $attr] ne ""} {
+	    lappend attr_list $attr [set $attr]
+	}
     }
 
     set result [mi_build_kv_pairs $attr_list]
@@ -2621,7 +2623,7 @@ proc mi_make_breakpoint_1 {attr_list thread cond evaluated-by times \
 # locations.
 #
 # All arguments for the breakpoint may be specified using the options:
-# number, type, disp, enabled, times, ignore, script,
+# number, type, disp, enabled, times, ignore, script, inferior,
 # original-location, cond, evaluated-by, locations, and thread.
 #
 # Only if -script and -ignore are given will they appear in the output.
@@ -2642,7 +2644,7 @@ proc mi_make_breakpoint_multi {args} {
     parse_args {{number .*} {type .*} {disp .*} {enabled .*}
 	{times .*} {ignore 0}
 	{script ""} {original-location .*} {cond ""} {evaluated-by ""}
-	{locations .*} {thread ""}}
+	{locations .*} {thread ""} {inferior ""}}
 
     set attr_list {}
     foreach attr [list number type disp enabled] {
@@ -2651,6 +2653,12 @@ proc mi_make_breakpoint_multi {args} {
 
     lappend attr_list "addr" "<MULTIPLE>"
 
+    # Only include the inferior field if it was set.  This field is
+    # optional in the MI output.
+    if {$inferior ne ""} {
+	lappend attr_list "inferior" $inferior
+    }
+
     set result [mi_make_breakpoint_1 \
 		    $attr_list $thread $cond ${evaluated-by} $times \
 		    $ignore $script ${original-location}]

base-commit: 60a13bbcdfb0ce008a77563cea0c34c830d7b170
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCHv5] gdb: add inferior-specific breakpoints
  2023-04-03 14:14     ` [PATCHv4] " Andrew Burgess
@ 2023-05-15 19:15       ` Andrew Burgess
  2023-05-30 20:41         ` [PATCHv6] " Andrew Burgess
  0 siblings, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2023-05-15 19:15 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Doc changes have already been approved here:
  https://sourceware.org/pipermail/gdb-patches/2023-January/195986.html

In v5:

  - Rebased to current upstream master and retested,

  - No changes to code or docs.

In v4:

  - Merged patch #1 from the previous series as this felt like a
    pretty obvious cleanup.

  - Rebased to HEAD of master.

  - No changes to code or docs.

---

This commit extends the breakpoint mechanism to allow for inferior
specific breakpoints (but not watchpoints in this commit).

As GDB gains better support for multiple connections, and so for
running multiple (possibly unrelated) inferiors, then it is not hard
to imagine that a user might wish to create breakpoints that apply to
any thread in a single inferior.  To achieve this currently, the user
would need to create a condition possibly making use of the $_inferior
convenience variable, which, though functional, isn't the most user
friendly.

This commit adds a new 'inferior' keyword that allows for the creation
of inferior specific breakpoints.

Inferior specific breakpoints are automatically deleted when the
associated inferior is removed from GDB, this is similar to how
thread-specific breakpoints when the associated thread is deleted.

Watchpoints are already per-program-space, which in most cases mean
watchpoints are already inferior specific.  There is a small window
where inferior-specific watchpoints might make sense, which is after a
vfork, when two processes are sharing the same address space.
However, I'm leaving that as an exercise for another day.  For now,
attempting to use the inferior keyword with a watchpoint will give an
error, like this:

  (gdb) watch a8 inferior 1
  Cannot use 'inferior' keyword with watchpoints

A final note on the implementation.  Currently, inferior specific
breakpoints, like thread-specific breakpoints, are inserted into every
inferior, GDB then checks once the inferior stops if we are in the
correct thread or inferior, and resumes automatically if we stopped in
the wrong thread/inferior.

An obvious optimisation here is to only insert breakpoint locations
into the specific program space (which mostly means inferior) that
contains either the inferior or thread we are interested in.  This
would reduce the number times GDB has to stop and then resume again in
a multi-inferior setup.

I have started investigating implementing this.  And though the
initial implementation is trivial to get going, like so many things,
the devil is in the detail.  While my hope is that I will bring this
optimisation to the list at some point in the near future I can't
guarantee it.  That said, we already have the issue of placing
breakpoints into inferiors that will never be hit (with thread
specific breakpoints), so I don't believe I'm adding anything worse
than we already have.  Personally, I'd be happy to see this new
feature merged even if the optimisation never happens.
---
 gdb/NEWS                                      |  15 ++
 gdb/breakpoint.c                              | 237 ++++++++++++++----
 gdb/breakpoint.h                              |  14 +-
 gdb/doc/gdb.texinfo                           |  72 +++++-
 gdb/doc/python.texi                           |  26 +-
 gdb/guile/scm-breakpoint.c                    |   7 +-
 gdb/infcmd.c                                  |  15 +-
 gdb/inferior.h                                |  11 +
 gdb/linespec.c                                |   4 +-
 gdb/mi/mi-cmd-break.c                         |  11 +-
 gdb/mi/mi-main.c                              |  18 +-
 gdb/mi/mi-main.h                              |   6 +
 gdb/python/py-breakpoint.c                    |  96 ++++++-
 gdb/python/py-finishbreakpoint.c              |   2 +-
 gdb/testsuite/gdb.ada/tasks.exp               |   2 +
 gdb/testsuite/gdb.linespec/cpcompletion.exp   |   4 +-
 gdb/testsuite/gdb.linespec/explicit.exp       |   1 +
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c      |  29 +++
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp    | 108 ++++++++
 .../gdb.multi/inferior-specific-bp-1.c        |  52 ++++
 .../gdb.multi/inferior-specific-bp-2.c        |  52 ++++
 .../gdb.multi/inferior-specific-bp.exp        | 179 +++++++++++++
 gdb/testsuite/gdb.python/py-breakpoint.exp    |  43 ++++
 gdb/testsuite/lib/completion-support.exp      |   2 +-
 gdb/testsuite/lib/mi-support.exp              |  20 +-
 25 files changed, 938 insertions(+), 88 deletions(-)
 create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
 create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index 6aa0d5171f2..dde246e73f9 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -67,6 +67,13 @@
     break foo thread 1 task 1
     watch var thread 2 task 3
 
+* Breakpoints can now be inferior-specific.  This is similar to the
+  existing thread-specific breakpoint support.  Breakpoint conditions
+  can include the 'inferior' keyword followed by an inferior id (as
+  displayed in the 'info inferiors' output).  It is invalid to use the
+  'inferior' keyword with either the 'thread' or 'task' keywords when
+  creating a breakpoint.
+
 * New commands
 
 maintenance print record-instruction [ N ]
@@ -127,6 +134,14 @@ info main
    considered simple.)  Support for this feature can be verified by using the
    '-list-features' command, which should contain "simple-values-ref-types".
 
+** The -break-insert command now accepts a '-g thread-group-id' option
+   to allow for the creation of inferior-specific breakpoints.
+
+** The bkpt tuple, which appears in breakpoint-created notifications,
+   and in the result of the -break-insert command can now include an
+   optional 'inferior' field for both the main breakpoint, and each
+   location, when the breakpoint is inferior-specific.
+
 * Python API
 
   ** The gdb.unwinder.Unwinder.name attribute is now read-only.
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index fdb184ae81f..95ae476b3a2 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -98,7 +98,7 @@ static void create_breakpoints_sal (struct gdbarch *,
 				    gdb::unique_xmalloc_ptr<char>,
 				    gdb::unique_xmalloc_ptr<char>,
 				    enum bptype,
-				    enum bpdisp, int, int,
+				    enum bpdisp, int, int, int,
 				    int,
 				    int, int, int, unsigned);
 
@@ -322,6 +322,9 @@ struct momentary_breakpoint : public code_breakpoint
     disposition = disp_donttouch;
     frame_id = frame_id_;
     thread = thread_;
+
+    /* The inferior should have been set by the parent constructor.  */
+    gdb_assert (inferior == -1);
   }
 
   void re_set () override;
@@ -1452,13 +1455,15 @@ breakpoint_set_silent (struct breakpoint *b, int silent)
 void
 breakpoint_set_thread (struct breakpoint *b, int thread)
 {
-  /* It is invalid to set the thread field to anything other than -1 (which
-     means no thread restriction) if a task restriction is already in
-     place.  */
-  gdb_assert (thread == -1 || b->task == -1);
+  /* THREAD should be -1, meaning no thread restriction, or it should be a
+     valid global thread-id, which are greater than zero.  */
+  gdb_assert (thread == -1 || thread > 0);
 
-  int old_thread = b->thread;
+  /* It is not valid to set a thread restriction for a breakpoint that
+     already has task or inferior restriction.  */
+  gdb_assert (thread == -1 || (b->task == -1 && b->inferior == -1));
 
+  int old_thread = b->thread;
   b->thread = thread;
   if (old_thread != thread)
     gdb::observers::breakpoint_modified.notify (b);
@@ -1466,16 +1471,37 @@ breakpoint_set_thread (struct breakpoint *b, int thread)
 
 /* See breakpoint.h.  */
 
+void
+breakpoint_set_inferior (struct breakpoint *b, int inferior)
+{
+  /* INFERIOR should be -1, meaning no inferior restriction, or it should
+     be a valid inferior number, which are greater than zero.  */
+  gdb_assert (inferior == -1 || inferior > 0);
+
+  /* It is not valid to set an inferior restriction for a breakpoint that
+     already has a task or thread restriction.  */
+  gdb_assert (inferior == -1 || (b->task == -1 && b->thread == -1));
+
+  int old_inferior = b->inferior;
+  b->inferior = inferior;
+  if (old_inferior != inferior)
+    gdb::observers::breakpoint_modified.notify (b);
+}
+
+/* See breakpoint.h.  */
+
 void
 breakpoint_set_task (struct breakpoint *b, int task)
 {
-  /* It is invalid to set the task field to anything other than -1 (which
-     means no task restriction) if a thread restriction is already in
-     place.  */
-  gdb_assert (task == -1 || b->thread == -1);
+  /* TASK should be -1, meaning no task restriction, or it should be a
+     valid task-id, which are greater than zero.  */
+  gdb_assert (task == -1 || task > 0);
 
-  int old_task = b->task;
+  /* It is not valid to set a task restriction for a breakpoint that
+     already has a thread or inferior restriction.  */
+  gdb_assert (task == -1 || (b->thread == -1 && b->inferior == -1));
 
+  int old_task = b->task;
   b->task = task;
   if (old_task != task)
     gdb::observers::breakpoint_modified.notify (b);
@@ -3166,6 +3192,12 @@ insert_breakpoint_locations (void)
 	  && !valid_global_thread_id (bl->owner->thread))
 	continue;
 
+      /* Or inferior specific breakpoints if the inferior no longer
+	 exists.  */
+      if (bl->owner->inferior != -1
+	  && !valid_global_inferior_id (bl->owner->inferior))
+	continue;
+
       switch_to_program_space_and_thread (bl->pspace);
 
       /* For targets that support global breakpoints, there's no need
@@ -3266,6 +3298,29 @@ Thread-specific breakpoint %d deleted - thread %s no longer in the thread list.\
     }
 }
 
+/* Called when inferior INF has been removed from GDB.  Remove associated
+   per-inferior breakpoints.  */
+
+static void
+remove_inferior_breakpoints (struct inferior *inf)
+{
+  for (breakpoint *b : all_breakpoints_safe ())
+    {
+      if (b->inferior == inf->num && user_breakpoint_p (b))
+	{
+	  /* Tell the user the breakpoint has been deleted.  But only for
+	     breakpoints that would not normally have been deleted at the
+	     next stop anyway.  */
+	  if (b->disposition != disp_del
+	      && b->disposition != disp_del_at_next_stop)
+	    gdb_printf (_("\
+Inferior-specific breakpoint %d deleted - inferior %d has been removed.\n"),
+			b->number, inf->num);
+	  delete_breakpoint (b);
+       }
+    }
+}
+
 /* See breakpoint.h.  */
 
 void
@@ -5475,6 +5530,7 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread)
      evaluating the condition if this isn't the specified
      thread/task.  */
   if ((b->thread != -1 && b->thread != thread->global_num)
+      || (b->inferior != -1 && b->inferior != thread->inf->num)
       || (b->task != -1 && b->task != ada_get_task_number (thread)))
     {
       infrun_debug_printf ("incorrect thread or task, not stopping");
@@ -6504,6 +6560,8 @@ print_one_breakpoint_location (struct breakpoint *b,
 	uiout->field_signed ("thread", b->thread);
       else if (b->task != -1)
 	uiout->field_signed ("task", b->task);
+      else if (b->inferior != -1)
+	uiout->field_signed ("inferior", b->inferior);
     }
 
   uiout->text ("\n");
@@ -6566,6 +6624,13 @@ print_one_breakpoint_location (struct breakpoint *b,
       uiout->text ("\n");
     }
 
+  if (!part_of_multiple && b->inferior != -1)
+    {
+      uiout->text ("\tstop only in inferior ");
+      uiout->field_signed ("inferior", b->inferior);
+      uiout->text ("\n");
+    }
+
   if (!part_of_multiple)
     {
       if (b->hit_count)
@@ -7551,7 +7616,10 @@ delete_longjmp_breakpoint (int thread)
     if (b->type == bp_longjmp || b->type == bp_exception)
       {
 	if (b->thread == thread)
-	  delete_breakpoint (b);
+	  {
+	    gdb_assert (b->inferior == -1);
+	    delete_breakpoint (b);
+	  }
       }
 }
 
@@ -7562,7 +7630,10 @@ delete_longjmp_breakpoint_at_next_stop (int thread)
     if (b->type == bp_longjmp || b->type == bp_exception)
       {
 	if (b->thread == thread)
-	  b->disposition = disp_del_at_next_stop;
+	  {
+	    gdb_assert (b->inferior == -1);
+	    b->disposition = disp_del_at_next_stop;
+	  }
       }
 }
 
@@ -7619,6 +7690,7 @@ check_longjmp_breakpoint_for_call_dummy (struct thread_info *tp)
     {
       if (b->type == bp_longjmp_call_dummy && b->thread == tp->global_num)
 	{
+	  gdb_assert (b->inferior == -1);
 	  struct breakpoint *dummy_b = b->related_breakpoint;
 
 	  /* Find the bp_call_dummy breakpoint in the list of breakpoints
@@ -8456,7 +8528,8 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
 				  gdb::unique_xmalloc_ptr<char> cond_string_,
 				  gdb::unique_xmalloc_ptr<char> extra_string_,
 				  enum bpdisp disposition_,
-				  int thread_, int task_, int ignore_count_,
+				  int thread_, int task_, int inferior_,
+				  int ignore_count_,
 				  int from_tty,
 				  int enabled_, unsigned flags,
 				  int display_canonical_)
@@ -8480,10 +8553,14 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
 
   gdb_assert (!sals.empty ());
 
-  /* At most one of thread or task can be set on any breakpoint.  */
-  gdb_assert (thread == -1 || task == -1);
+  /* At most one of thread, task, or inferior can be set on any breakpoint.  */
+  gdb_assert (((thread == -1 ? 0 : 1)
+	       + (task == -1 ? 0 : 1)
+	       + (inferior == -1 ? 0 : 1)) <= 1);
+
   thread = thread_;
   task = task_;
+  inferior = inferior_;
 
   cond_string = std::move (cond_string_);
   extra_string = std::move (extra_string_);
@@ -8585,7 +8662,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
 		       gdb::unique_xmalloc_ptr<char> cond_string,
 		       gdb::unique_xmalloc_ptr<char> extra_string,
 		       enum bptype type, enum bpdisp disposition,
-		       int thread, int task, int ignore_count,
+		       int thread, int task, int inferior, int ignore_count,
 		       int from_tty,
 		       int enabled, int internal, unsigned flags,
 		       int display_canonical)
@@ -8599,7 +8676,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
 				std::move (cond_string),
 				std::move (extra_string),
 				disposition,
-				thread, task, ignore_count,
+				thread, task, inferior, ignore_count,
 				from_tty,
 				enabled, flags,
 				display_canonical);
@@ -8628,7 +8705,8 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
 			gdb::unique_xmalloc_ptr<char> cond_string,
 			gdb::unique_xmalloc_ptr<char> extra_string,
 			enum bptype type, enum bpdisp disposition,
-			int thread, int task, int ignore_count,
+			int thread, int task, int inferior,
+			int ignore_count,
 			int from_tty,
 			int enabled, int internal, unsigned flags)
 {
@@ -8652,7 +8730,7 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
 			     std::move (cond_string),
 			     std::move (extra_string),
 			     type, disposition,
-			     thread, task, ignore_count,
+			     thread, task, inferior, ignore_count,
 			     from_tty, enabled, internal, flags,
 			     canonical->special_display);
     }
@@ -8782,21 +8860,26 @@ check_fast_tracepoint_sals (struct gdbarch *gdbarch,
     }
 }
 
-/* Given TOK, a string specification of condition and thread, as
-   accepted by the 'break' command, extract the condition
-   string and thread number and set *COND_STRING and *THREAD.
-   PC identifies the context at which the condition should be parsed.
-   If no condition is found, *COND_STRING is set to NULL.
-   If no thread is found, *THREAD is set to -1.  */
+/* Given TOK, a string specification of condition and thread, as accepted
+   by the 'break' command, extract the condition string into *COND_STRING.
+   If no condition string is found then *COND_STRING is set to nullptr.
+
+   If the breakpoint specification has an associated thread, task, or
+   inferior, these are extracted into *THREAD, *TASK, and *INFERIOR
+   respectively, otherwise these arguments are set to -1 (for THREAD and
+   INFERIOR) or 0 (for TASK).
+
+   PC identifies the context at which the condition should be parsed.  */
 
 static void
 find_condition_and_thread (const char *tok, CORE_ADDR pc,
 			   gdb::unique_xmalloc_ptr<char> *cond_string,
-			   int *thread, int *task,
+			   int *thread, int *inferior, int *task,
 			   gdb::unique_xmalloc_ptr<char> *rest)
 {
   cond_string->reset ();
   *thread = -1;
+  *inferior = -1;
   *task = -1;
   rest->reset ();
   bool force = false;
@@ -8813,7 +8896,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
       if ((*tok == '"' || *tok == ',') && rest)
 	{
 	  rest->reset (savestring (tok, strlen (tok)));
-	  return;
+	  break;
 	}
 
       end_tok = skip_to_space (tok);
@@ -8853,6 +8936,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  if (*task != -1)
 	    error (_("You can specify only one of thread or task."));
 
+	  if (*inferior != -1)
+	    error (_("You can specify only one of inferior or thread."));
+
 	  tok = end_tok + 1;
 	  thr = parse_thread_id (tok, &tmptok);
 	  if (tok == tmptok)
@@ -8860,6 +8946,26 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  *thread = thr->global_num;
 	  tok = tmptok;
 	}
+      else if (toklen >= 1 && strncmp (tok, "inferior", toklen) == 0)
+	{
+	  if (*inferior != -1)
+	    error(_("You can specify only one inferior."));
+
+	  if (*task != -1)
+	    error (_("You can specify only one of inferior or task."));
+
+	  if (*thread != -1)
+	    error (_("You can specify only one of inferior or thread."));
+
+	  char *tmptok;
+	  tok = end_tok + 1;
+	  *inferior = strtol (tok, &tmptok, 0);
+	  if (tok == tmptok)
+	    error (_("Junk after inferior keyword."));
+	  if (!valid_global_inferior_id (*inferior))
+	    error (_("Unknown inferior number %d."), *inferior);
+	  tok = tmptok;
+	}
       else if (toklen >= 1 && strncmp (tok, "task", toklen) == 0)
 	{
 	  char *tmptok;
@@ -8870,6 +8976,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  if (*thread != -1)
 	    error (_("You can specify only one of thread or task."));
 
+	  if (*inferior != -1)
+	    error (_("You can specify only one of inferior or task."));
+
 	  tok = end_tok + 1;
 	  *task = strtol (tok, &tmptok, 0);
 	  if (tok == tmptok)
@@ -8881,7 +8990,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
       else if (rest)
 	{
 	  rest->reset (savestring (tok, strlen (tok)));
-	  return;
+	  break;
 	}
       else
 	error (_("Junk at end of arguments."));
@@ -8897,7 +9006,7 @@ static void
 find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
 				    const char *input,
 				    gdb::unique_xmalloc_ptr<char> *cond_string,
-				    int *thread, int *task,
+				    int *thread, int *inferior, int *task,
 				    gdb::unique_xmalloc_ptr<char> *rest)
 {
   int num_failures = 0;
@@ -8905,6 +9014,7 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
     {
       gdb::unique_xmalloc_ptr<char> cond;
       int thread_id = -1;
+      int inferior_id = -1;
       int task_id = -1;
       gdb::unique_xmalloc_ptr<char> remaining;
 
@@ -8917,11 +9027,16 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
       try
 	{
 	  find_condition_and_thread (input, sal.pc, &cond, &thread_id,
-				     &task_id, &remaining);
+				     &inferior_id, &task_id, &remaining);
 	  *cond_string = std::move (cond);
-	  /* At most one of thread or task can be set.  */
-	  gdb_assert (thread_id == -1 || task_id == -1);
+	  /* A value of -1 indicates that these fields are unset.  At most
+	     one of these fields should be set (to a value other than -1)
+	     at this point.  */
+	  gdb_assert (((thread_id == -1 ? 1 : 0)
+		       + (task_id == -1 ? 1 : 0)
+		       + (inferior_id == -1 ? 1 : 0)) >= 2);
 	  *thread = thread_id;
+	  *inferior = inferior_id;
 	  *task = task_id;
 	  *rest = std::move (remaining);
 	  break;
@@ -9011,7 +9126,8 @@ int
 create_breakpoint (struct gdbarch *gdbarch,
 		   location_spec *locspec,
 		   const char *cond_string,
-		   int thread, const char *extra_string,
+		   int thread, int inferior,
+		   const char *extra_string,
 		   bool force_condition, int parse_extra,
 		   int tempflag, enum bptype type_wanted,
 		   int ignore_count,
@@ -9025,6 +9141,10 @@ create_breakpoint (struct gdbarch *gdbarch,
   int task = -1;
   int prev_bkpt_count = breakpoint_count;
 
+  gdb_assert (thread == -1 || thread > 0);
+  gdb_assert (inferior == -1 || inferior > 0);
+  gdb_assert (thread == -1 || inferior == -1);
+
   gdb_assert (ops != NULL);
 
   /* If extra_string isn't useful, set it to NULL.  */
@@ -9100,7 +9220,8 @@ create_breakpoint (struct gdbarch *gdbarch,
 	  const linespec_sals &lsal = canonical.lsals[0];
 
 	  find_condition_and_thread_for_sals (lsal.sals, extra_string,
-					      &cond, &thread, &task, &rest);
+					      &cond, &thread, &inferior,
+					      &task, &rest);
 	  cond_string_copy = std::move (cond);
 	  extra_string_copy = std::move (rest);
 	}
@@ -9150,7 +9271,7 @@ create_breakpoint (struct gdbarch *gdbarch,
 				   std::move (extra_string_copy),
 				   type_wanted,
 				   tempflag ? disp_del : disp_donttouch,
-				   thread, task, ignore_count,
+				   thread, task, inferior, ignore_count,
 				   from_tty, enabled, internal, flags);
     }
   else
@@ -9219,7 +9340,9 @@ break_command_1 (const char *arg, int flag, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL,
+		     -1 /* thread */, -1 /* inferior */,
+		     arg, false, 1 /* parse arg */,
 		     tempflag, type_wanted,
 		     0 /* Ignore count */,
 		     pending_break_support,
@@ -9331,7 +9454,8 @@ dprintf_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1,
+		     arg, false, 1 /* parse arg */,
 		     0, bp_dprintf,
 		     0 /* Ignore count */,
 		     pending_break_support,
@@ -10087,6 +10211,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
   const char *cond_end = NULL;
   enum bptype bp_type;
   int thread = -1;
+  int inferior = -1;
   /* Flag to indicate whether we are going to use masks for
      the hardware watchpoint.  */
   bool use_mask = false;
@@ -10141,12 +10266,13 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	      if (task != -1)
 		error (_("You can specify only one of thread or task."));
 
+	      if (inferior != -1)
+		error (_("You can specify only one of inferior or thread."));
+
 	      /* Extract the thread ID from the next token.  */
 	      thr = parse_thread_id (value_start, &endp);
-
-	      /* Check if the user provided a valid thread ID.  */
-	      if (*endp != ' ' && *endp != '\t' && *endp != '\0')
-		invalid_thread_id_error (value_start);
+	      if (value_start == endp)
+		error (_("Junk after thread keyword."));
 
 	      thread = thr->global_num;
 	    }
@@ -10160,12 +10286,20 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	      if (thread != -1)
 		error (_("You can specify only one of thread or task."));
 
+	      if (inferior != -1)
+		error (_("You can specify only one of inferior or task."));
+
 	      task = strtol (value_start, &tmp, 0);
 	      if (tmp == value_start)
 		error (_("Junk after task keyword."));
 	      if (!valid_task_id (task))
 		error (_("Unknown task %d."), task);
 	    }
+	  else if (toklen == 8 && startswith (tok, "inferior"))
+	    {
+	      /* Support for watchpoints will be added in a later commit.  */
+	      error (_("Cannot use 'inferior' keyword with watchpoints"));
+	    }
 	  else if (toklen == 4 && startswith (tok, "mask"))
 	    {
 	      /* We've found a "mask" token, which means the user wants to
@@ -10338,6 +10472,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
   /* At most one of thread or task can be set on a watchpoint.  */
   gdb_assert (thread == -1 || task == -1);
   w->thread = thread;
+  w->inferior = inferior;
   w->task = task;
   w->disposition = disp_donttouch;
   w->pspace = current_program_space;
@@ -12247,7 +12382,8 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
 				      enum bptype type_wanted,
 				      enum bpdisp disposition,
 				      int thread,
-				      int task, int ignore_count,
+				      int task, int inferior,
+				      int ignore_count,
 				      int from_tty, int enabled,
 				      int internal, unsigned flags)
 {
@@ -12273,7 +12409,7 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
 			 std::move (cond_string),
 			 std::move (extra_string),
 			 disposition,
-			 thread, task, ignore_count,
+			 thread, task, inferior, ignore_count,
 			 from_tty, enabled, flags,
 			 canonical->special_display));
 
@@ -12906,10 +13042,11 @@ code_breakpoint::location_spec_to_sals (location_spec *locspec,
       if (condition_not_parsed && extra_string != NULL)
 	{
 	  gdb::unique_xmalloc_ptr<char> local_cond, local_extra;
-	  int local_thread, local_task;
+	  int local_thread, local_task, local_inferior;
 
 	  find_condition_and_thread_for_sals (sals, extra_string.get (),
 					      &local_cond, &local_thread,
+					      &local_inferior,
 					      &local_task, &local_extra);
 	  gdb_assert (cond_string == nullptr);
 	  if (local_cond != nullptr)
@@ -13783,7 +13920,7 @@ trace_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     bp_tracepoint /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13801,7 +13938,7 @@ ftrace_command (const char *arg, int from_tty)
 						      current_language);
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     bp_fast_tracepoint /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13839,7 +13976,7 @@ strace_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     type /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13908,7 +14045,7 @@ create_tracepoint_from_upload (struct uploaded_tp *utp)
 						      current_language);
   if (!create_breakpoint (get_current_arch (),
 			  locspec.get (),
-			  utp->cond_string.get (), -1, addr_str,
+			  utp->cond_string.get (), -1, -1, addr_str,
 			  false /* force_condition */,
 			  0 /* parse cond/thread */,
 			  0 /* tempflag */,
@@ -14998,4 +15135,6 @@ This is useful for formatted output in user-defined commands."));
 					   "breakpoint");
   gdb::observers::thread_exit.attach (remove_threaded_breakpoints,
 				      "breakpoint");
+  gdb::observers::inferior_removed.attach (remove_inferior_breakpoints,
+					   "breakpoint");
 }
diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
index 7c5cf3f2bef..056299bcfd1 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -585,7 +585,7 @@ struct breakpoint_ops
 				  struct linespec_result *,
 				  gdb::unique_xmalloc_ptr<char>,
 				  gdb::unique_xmalloc_ptr<char>,
-				  enum bptype, enum bpdisp, int, int,
+				  enum bptype, enum bpdisp, int, int, int,
 				  int, int, int, int, unsigned);
 };
 
@@ -803,6 +803,10 @@ struct breakpoint
      care.  */
   int thread = -1;
 
+  /* Inferior number for inferior-specific breakpoint, or -1 if this
+     breakpoint is for all inferiors.  */
+  int inferior = -1;
+
   /* Ada task number for task-specific breakpoint, or -1 if don't
      care.  */
   int task = -1;
@@ -858,7 +862,7 @@ struct code_breakpoint : public breakpoint
 		   gdb::unique_xmalloc_ptr<char> cond_string,
 		   gdb::unique_xmalloc_ptr<char> extra_string,
 		   enum bpdisp disposition,
-		   int thread, int task, int ignore_count,
+		   int thread, int task, int inferior, int ignore_count,
 		   int from_tty,
 		   int enabled, unsigned flags,
 		   int display_canonical);
@@ -1538,6 +1542,7 @@ enum breakpoint_create_flags
 extern int create_breakpoint (struct gdbarch *gdbarch,
 			      struct location_spec *locspec,
 			      const char *cond_string, int thread,
+			      int inferior,
 			      const char *extra_string,
 			      bool force_condition,
 			      int parse_extra,
@@ -1681,6 +1686,11 @@ extern void breakpoint_set_silent (struct breakpoint *b, int silent);
 
 extern void breakpoint_set_thread (struct breakpoint *b, int thread);
 
+/* Set the inferior for breakpoint B to INFERIOR.  If INFERIOR is -1, make
+   the breakpoint work for any inferior.  */
+
+extern void breakpoint_set_inferior (struct breakpoint *b, int inferior);
+
 /* Set the task for this breakpoint.  If TASK is -1, make the breakpoint
    work for any task.  Passing a value other than -1 for TASK should only
    be done if b->thread is -1; it is not valid to try and set both a thread
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 531147f6e6b..9cabd88f27e 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3517,6 +3517,57 @@
 space as a result of inferior 1 having executed a @code{vfork} call.
 @end table
 
+@menu
+* Inferior-Specific Breakpoints::	Controlling breakpoints
+@end menu
+
+@node Inferior-Specific Breakpoints
+@subsection Inferior-Specific Breakpoints
+
+When debugging multiple inferiors, you can choose whether to set
+breakpoints for all inferiors, or for a particular inferior.
+
+@table @code
+@cindex breakpoints and inferiors
+@cindex inferior-specific breakpoints
+@kindex break @dots{} inferior @var{inferior-id}
+@item break @var{locspec} inferior @var{inferior-id}
+@itemx break @var{locspec} inferior @var{inferior-id} if @dots{}
+@var{locspec} specifies a code location or locations in your program.
+@xref{Location Specifications}, for details.
+
+Use the qualifier @samp{inferior @var{inferior-id}} with a breakpoint
+command to specify that you only want @value{GDBN} to stop when a
+particular inferior reaches this breakpoint.  The @var{inferior-id}
+specifier is one of the inferior identifiers assigned by @value{GDBN},
+shown in the first column of the @samp{info inferiors} output.
+
+If you do not specify @samp{inferior @var{inferior-id}} when you set a
+breakpoint, the breakpoint applies to @emph{all} inferiors of your
+program.
+
+You can use the @code{inferior} qualifier on conditional breakpoints as
+well; in this case, place @samp{inferior @var{inferior-id}} before or
+after the breakpoint condition, like this:
+
+@smallexample
+(@value{GDBP}) break frik.c:13 inferior 2 if bartab > lim
+@end smallexample
+@end table
+
+Inferior-specific breakpoints are automatically deleted when the
+corresponding inferior is removed from @value{GDBN}.  For example:
+
+@smallexample
+(@value{GDBP}) remove-inferiors 2
+Inferior-specific breakpoint 3 deleted - inferior 2 has been removed.
+@end smallexample
+
+A breakpoint can't be both inferior-specific and thread-specific
+(@pxref{Thread-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{inferior}, @code{thread}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
 @node Threads
 @section Debugging Programs with Multiple Threads
 
@@ -4480,8 +4531,9 @@
 situation.
 
 It is also possible to insert a breakpoint that will stop the program
-only if a specific thread (@pxref{Thread-Specific Breakpoints})
-or a specific task (@pxref{Ada Tasks}) hits that breakpoint.
+only if a specific thread (@pxref{Thread-Specific Breakpoints}),
+specific inferior (@pxref{Inferior-Specific Breakpoints}), or a
+specific task (@pxref{Ada Tasks}) hits that breakpoint.
 
 @item break
 When called without any arguments, @code{break} sets a breakpoint at
@@ -7333,9 +7385,14 @@
 Process}), or if @value{GDBN} loses the remote connection
 (@pxref{Remote Debugging}), etc.  Note that with some targets,
 @value{GDBN} is only able to detect a thread has exited when the user
-explictly asks for the thread list with the @code{info threads}
+explicitly asks for the thread list with the @code{info threads}
 command.
 
+A breakpoint can't be both thread-specific and inferior-specific
+(@pxref{Inferior-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{thread}, @code{inferior}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
 @node Interrupted System Calls
 @subsection Interrupted System Calls 
 
@@ -31565,6 +31622,10 @@
 If this is a thread-specific breakpoint, then this identifies the
 thread in which the breakpoint can trigger.
 
+@item inferior
+If this is an inferior-specific breakpoint, this this identifies the
+inferior in which the breakpoint can trigger.
+
 @item task
 If this breakpoint is restricted to a particular Ada task, then this
 field will hold the task identifier.
@@ -32156,7 +32217,7 @@
 @smallexample
  -break-insert [ -t ] [ -h ] [ -f ] [ -d ] [ -a ] [ --qualified ]
     [ -c @var{condition} ] [ --force-condition ] [ -i @var{ignore-count} ]
-    [ -p @var{thread-id} ] [ @var{locspec} ]
+    [ -p @var{thread-id} ] [ -g @var{thread-group-id} ] [ @var{locspec} ]
 @end smallexample
 
 @noindent
@@ -32222,6 +32283,9 @@
 time the breakpoint is requested.  Breakpoints created with a
 @var{thread-id} will automatically be deleted when the corresponding
 thread exits.
+@item -g @var{thread-group-id}
+Restrict the breakpoint to the thread group with the specified
+@var{thread-group-id}.
 @item --qualified
 This option makes @value{GDBN} interpret a function name specified as
 a complete fully-qualified name.
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 11135910656..99c76ac9c7c 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -3419,7 +3419,10 @@
 A @code{gdb.Inferior} object has the following attributes:
 
 @defvar Inferior.num
-ID of inferior, as assigned by GDB.
+ID of inferior, as assigned by @value{GDBN}.  You can use this to make
+Python breakpoints inferior-specific, for example
+(@pxref{python_breakpoint_inferior,,The Breakpoint.inferior
+attribute}).
 @end defvar
 
 @anchor{gdbpy_inferior_connection}
@@ -6221,9 +6224,24 @@
 
 @anchor{python_breakpoint_thread}
 @defvar Breakpoint.thread
-If the breakpoint is thread-specific, this attribute holds the
-thread's global id.  If the breakpoint is not thread-specific, this
-attribute is @code{None}.  This attribute is writable.
+If the breakpoint is thread-specific (@pxref{Thread-Specific
+Breakpoints}), this attribute holds the thread's global id.  If the
+breakpoint is not thread-specific, this attribute is @code{None}.
+This attribute is writable.
+
+Only one of @code{Breakpoint.thread} or @code{Breakpoint.inferior} can
+be set to a valid id at any time, that is, a breakpoint can be thread
+specific, or inferior specific, but not both.
+@end defvar
+
+@anchor{python_breakpoint_inferior}
+@defvar Breakpoint.inferior
+If the breakpoint is inferior-specific (@pxref{Inferior-Specific
+Breakpoints}), this attribute holds the inferior's id.  If the
+breakpoint is not inferior-specific, this attribute is @code{None}.
+
+This attribute can be written for breakpoints of type
+@code{gdb.BP_BREAKPOINT} and @code{gdb.BP_HARDWARE_BREAKPOINT}.
 @end defvar
 
 @defvar Breakpoint.task
diff --git a/gdb/guile/scm-breakpoint.c b/gdb/guile/scm-breakpoint.c
index 2931df265d7..4e343b85656 100644
--- a/gdb/guile/scm-breakpoint.c
+++ b/gdb/guile/scm-breakpoint.c
@@ -465,7 +465,7 @@ gdbscm_register_breakpoint_x (SCM self)
 	    const breakpoint_ops *ops =
 	      breakpoint_ops_for_location_spec (locspec.get (), false);
 	    create_breakpoint (get_current_arch (),
-			       locspec.get (), NULL, -1, NULL, false,
+			       locspec.get (), NULL, -1, -1, NULL, false,
 			       0,
 			       temporary, bp_breakpoint,
 			       0,
@@ -784,6 +784,11 @@ gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue)
   else
     SCM_ASSERT_TYPE (0, newvalue, SCM_ARG2, FUNC_NAME, _("integer or #f"));
 
+  if (bp_smob->bp->inferior != -1 && id != -1)
+    scm_misc_error (FUNC_NAME,
+		    _("Cannot have both 'thread' and 'inferior' "
+		      "conditions on a breakpoint"), SCM_EOL);
+
   breakpoint_set_thread (bp_smob->bp, id);
 
   return SCM_UNSPECIFIED;
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index b12b58db9cb..9d4fffdd769 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -416,17 +416,10 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
   if (run_how == RUN_STOP_AT_MAIN)
     {
       /* To avoid other inferiors hitting this breakpoint, make it
-	 inferior-specific using a condition.  A better solution would be to
-	 have proper inferior-specific breakpoint support, in the breakpoint
-	 machinery.  We could then avoid inserting a breakpoint in the program
-	 spaces unrelated to this inferior.  */
-      const char *op
-	= ((current_language->la_language == language_ada
-	    || current_language->la_language == language_pascal
-	    || current_language->la_language == language_m2) ? "=" : "==");
-      std::string arg = string_printf
-	("-qualified %s if $_inferior %s %d", main_name (), op,
-	 current_inferior ()->num);
+	 inferior-specific.  */
+      std::string arg = string_printf ("-qualified %s inferior %d",
+				       main_name (),
+				       current_inferior ()->num);
       tbreak_command (arg.c_str (), 0);
     }
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index caa8e4d494a..9af0e367c6f 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -826,4 +826,15 @@ extern void print_selected_inferior (struct ui_out *uiout);
 extern void switch_to_inferior_and_push_target
   (inferior *new_inf, bool no_connection, inferior *org_inf);
 
+/* Return true if ID is a valid global inferior number.  */
+
+inline bool
+valid_global_inferior_id (int id)
+{
+  for (inferior *inf : all_inferiors ())
+    if (inf->num == id)
+      return true;
+  return false;
+}
+
 #endif /* !defined (INFERIOR_H) */
diff --git a/gdb/linespec.c b/gdb/linespec.c
index 7d969f37fbf..695dc6515ee 100644
--- a/gdb/linespec.c
+++ b/gdb/linespec.c
@@ -254,9 +254,9 @@ enum linespec_token_type
 
 /* List of keywords.  This is NULL-terminated so that it can be used
    as enum completer.  */
-const char * const linespec_keywords[] = { "if", "thread", "task", "-force-condition", NULL };
+const char * const linespec_keywords[] = { "if", "thread", "task", "inferior", "-force-condition", NULL };
 #define IF_KEYWORD_INDEX 0
-#define FORCE_KEYWORD_INDEX 3
+#define FORCE_KEYWORD_INDEX 4
 
 /* A token of the linespec lexer  */
 
diff --git a/gdb/mi/mi-cmd-break.c b/gdb/mi/mi-cmd-break.c
index e5432d58990..f5e1061ac07 100644
--- a/gdb/mi/mi-cmd-break.c
+++ b/gdb/mi/mi-cmd-break.c
@@ -172,6 +172,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
   int hardware = 0;
   int temp_p = 0;
   int thread = -1;
+  int thread_group = -1;
   int ignore_count = 0;
   char *condition = NULL;
   int pending = 0;
@@ -190,7 +191,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
   enum opt
     {
       HARDWARE_OPT, TEMP_OPT, CONDITION_OPT,
-      IGNORE_COUNT_OPT, THREAD_OPT, PENDING_OPT, DISABLE_OPT,
+      IGNORE_COUNT_OPT, THREAD_OPT, THREAD_GROUP_OPT,
+      PENDING_OPT, DISABLE_OPT,
       TRACEPOINT_OPT,
       FORCE_CONDITION_OPT,
       QUALIFIED_OPT,
@@ -204,6 +206,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
     {"c", CONDITION_OPT, 1},
     {"i", IGNORE_COUNT_OPT, 1},
     {"p", THREAD_OPT, 1},
+    {"g", THREAD_GROUP_OPT, 1},
     {"f", PENDING_OPT, 0},
     {"d", DISABLE_OPT, 0},
     {"a", TRACEPOINT_OPT, 0},
@@ -246,6 +249,9 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
 	  if (!valid_global_thread_id (thread))
 	    error (_("Unknown thread %d."), thread);
 	  break;
+	case THREAD_GROUP_OPT:
+	  thread_group = mi_parse_thread_group_id (oarg);
+	  break;
 	case PENDING_OPT:
 	  pending = 1;
 	  break;
@@ -359,7 +365,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command, char **argv, int argc)
 	error (_("Garbage '%s' at end of location"), address);
     }
 
-  create_breakpoint (get_current_arch (), locspec.get (), condition, thread,
+  create_breakpoint (get_current_arch (), locspec.get (), condition,
+		     thread, thread_group,
 		     extra_string.c_str (),
 		     force_condition,
 		     0 /* condition and thread are valid.  */,
diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c
index 840663e0fb4..9b023f07efa 100644
--- a/gdb/mi/mi-main.c
+++ b/gdb/mi/mi-main.c
@@ -1747,8 +1747,7 @@ mi_cmd_remove_inferior (const char *command, char **argv, int argc)
   if (argc != 1)
     error (_("-remove-inferior should be passed a single argument"));
 
-  if (sscanf (argv[0], "i%d", &id) != 1)
-    error (_("the thread group id is syntactically invalid"));
+  id = mi_parse_thread_group_id (argv[0]);
 
   inf_to_remove = find_inferior_id (id);
   if (inf_to_remove == NULL)
@@ -2755,6 +2754,21 @@ mi_cmd_complete (const char *command, char **argv, int argc)
 		       result.number_matches == max_completions ? "1" : "0");
 }
 
+/* See mi-main.h.  */
+int
+mi_parse_thread_group_id (const char *id)
+{
+  if (*id != 'i')
+    error (_("thread group id should start with an 'i'"));
+
+  char *end;
+  long num = strtol (id + 1, &end, 10);
+
+  if (*end != '\0' || num > INT_MAX)
+    error (_("invalid thread group id '%s'"), id);
+
+  return (int) num;
+}
 
 void _initialize_mi_main ();
 void
diff --git a/gdb/mi/mi-main.h b/gdb/mi/mi-main.h
index ff1d4ed84bc..0389a141cf5 100644
--- a/gdb/mi/mi-main.h
+++ b/gdb/mi/mi-main.h
@@ -76,4 +76,10 @@ extern void mi_cmd_fix_multi_location_breakpoint_output (const char *command,
 extern void mi_cmd_fix_breakpoint_script_output (const char *command,
 						 char **argv, int argc);
 
+/* Parse a thread-group-id from ID, and return the integer part of the
+   ID.  A valid thread-group-id is the character 'i' followed by an
+   integer that is greater than zero.  */
+
+extern int mi_parse_thread_group_id (const char *id);
+
 #endif /* MI_MI_MAIN_H */
diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
index becb04c91c1..7b2033c07d0 100644
--- a/gdb/python/py-breakpoint.c
+++ b/gdb/python/py-breakpoint.c
@@ -287,11 +287,86 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure)
       return -1;
     }
 
+  if (self_bp->bp->inferior != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'thread' and 'inferior' "
+			 "conditions on a breakpoint"));
+      return -1;
+    }
+
   breakpoint_set_thread (self_bp->bp, id);
 
   return 0;
 }
 
+/* Python function to set the inferior of a breakpoint.  */
+
+static int
+bppy_set_inferior (PyObject *self, PyObject *newvalue, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+  long id;
+
+  BPPY_SET_REQUIRE_VALID (self_bp);
+
+  if (newvalue == NULL)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("Cannot delete 'inferior' attribute."));
+      return -1;
+    }
+  else if (PyLong_Check (newvalue))
+    {
+      if (!gdb_py_int_as_long (newvalue, &id))
+	return -1;
+
+      if (!valid_global_inferior_id (id))
+	{
+	  PyErr_SetString (PyExc_RuntimeError,
+			   _("Invalid inferior ID."));
+	  return -1;
+	}
+    }
+  else if (newvalue == Py_None)
+    id = -1;
+  else
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("The value of 'inferior' must be an integer or None."));
+      return -1;
+    }
+
+  if (self_bp->bp->type != bp_breakpoint
+      && self_bp->bp->type != bp_hardware_breakpoint)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot set 'inferior' attribute on a gdb.Breakpoint "
+			 "of this type"));
+      return -1;
+    }
+
+  if (self_bp->bp->thread != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'thread' and 'inferior' conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
+  if (self_bp->bp->task != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'task' and 'inferior' conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
+  breakpoint_set_inferior (self_bp->bp, id);
+
+  return 0;
+}
+
 /* Python function to set the (Ada) task of a breakpoint.  */
 static int
 bppy_set_task (PyObject *self, PyObject *newvalue, void *closure)
@@ -703,6 +778,20 @@ bppy_get_thread (PyObject *self, void *closure)
   return gdb_py_object_from_longest (self_bp->bp->thread).release ();
 }
 
+/* Python function to get the breakpoint's inferior ID.  */
+static PyObject *
+bppy_get_inferior (PyObject *self, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+
+  BPPY_REQUIRE_VALID (self_bp);
+
+  if (self_bp->bp->inferior == -1)
+    Py_RETURN_NONE;
+
+  return gdb_py_object_from_longest (self_bp->bp->inferior).release ();
+}
+
 /* Python function to get the breakpoint's task ID (in Ada).  */
 static PyObject *
 bppy_get_task (PyObject *self, void *closure)
@@ -941,7 +1030,7 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs)
 	      = breakpoint_ops_for_location_spec (locspec.get (), false);
 
 	    create_breakpoint (gdbpy_enter::get_gdbarch (),
-			       locspec.get (), NULL, -1, NULL, false,
+			       locspec.get (), NULL, -1, -1, NULL, false,
 			       0,
 			       temporary_bp, type,
 			       0,
@@ -1350,6 +1439,11 @@ static gdb_PyGetSetDef breakpoint_object_getset[] = {
 If the value is a thread ID (integer), then this is a thread-specific breakpoint.\n\
 If the value is None, then this breakpoint is not thread-specific.\n\
 No other type of value can be used.", NULL },
+  { "inferior", bppy_get_inferior, bppy_set_inferior,
+    "Inferior ID for the breakpoint.\n\
+If the value is an inferior ID (integer), then this is an inferior-specific\n\
+breakpoint.  If the value is None, then this breakpoint is not\n\
+inferior-specific.  No other type of value can be used.", NULL },
   { "task", bppy_get_task, bppy_set_task,
     "Thread ID for the breakpoint.\n\
 If the value is a task ID (integer), then this is an Ada task-specific breakpoint.\n\
diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c
index bb4591e3a6b..7033125b438 100644
--- a/gdb/python/py-finishbreakpoint.c
+++ b/gdb/python/py-finishbreakpoint.c
@@ -307,7 +307,7 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
       location_spec_up locspec
 	= new_address_location_spec (get_frame_pc (prev_frame), NULL, 0);
       create_breakpoint (gdbpy_enter::get_gdbarch (),
-			 locspec.get (), NULL, thread, NULL, false,
+			 locspec.get (), NULL, thread, -1, NULL, false,
 			 0,
 			 1 /*temp_flag*/,
 			 bp_breakpoint,
diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp
index eb7ee5c9951..603d43f7c36 100644
--- a/gdb/testsuite/gdb.ada/tasks.exp
+++ b/gdb/testsuite/gdb.ada/tasks.exp
@@ -58,6 +58,8 @@ gdb_test "break break_me task 1 thread 1" \
     "You can specify only one of thread or task\\."
 gdb_test "break break_me thread 1 task 1" \
     "You can specify only one of thread or task\\."
+gdb_test "break break_me inferior 1 task 1" \
+    "You can specify only one of inferior or task\\."
 gdb_test "watch j task 1 thread 1" \
     "You can specify only one of thread or task\\."
 gdb_test "watch j thread 1 task 1" \
diff --git a/gdb/testsuite/gdb.linespec/cpcompletion.exp b/gdb/testsuite/gdb.linespec/cpcompletion.exp
index fbe19b49d43..f005707b9da 100644
--- a/gdb/testsuite/gdb.linespec/cpcompletion.exp
+++ b/gdb/testsuite/gdb.linespec/cpcompletion.exp
@@ -1259,8 +1259,8 @@ proc_with_prefix function-labels {} {
 }
 
 # Test that completion after a function name offers keyword
-# (if/task/thread/-force-condition) matches in linespec mode, and also
-# the explicit location options in explicit locations mode.
+# (if/inferior/task/thread/-force-condition) matches in linespec mode,
+# and also the explicit location options in explicit locations mode.
 
 proc_with_prefix keywords-after-function {} {
     set explicit_list \
diff --git a/gdb/testsuite/gdb.linespec/explicit.exp b/gdb/testsuite/gdb.linespec/explicit.exp
index b08d65953d2..668002d9038 100644
--- a/gdb/testsuite/gdb.linespec/explicit.exp
+++ b/gdb/testsuite/gdb.linespec/explicit.exp
@@ -412,6 +412,7 @@ namespace eval $testfile {
 	    "-qualified"
 	    "-source"
 	    "if"
+	    "inferior"
 	    "task"
 	    "thread"
 	}
diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
new file mode 100644
index 00000000000..c171ef71b9d
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
@@ -0,0 +1,29 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2023 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/>.  */
+
+int
+foo (void)
+{
+  return 0;
+}
+
+int
+main (void)
+{
+  int res = foo ();
+  return res;
+}
diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
new file mode 100644
index 00000000000..93a0eb3de49
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
@@ -0,0 +1,108 @@
+# Copyright 2023 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 for the delivery of '=breakpoint-deleted' notifications when
+# breakpoints are deleted.  Right now this test only covers
+# inferior-specific breakpoints, but it could be extended to cover
+# other cases too.
+
+# Multiple inferiors are needed, therefore only native gdb and
+# extended gdbserver modes are supported.
+require !use_gdb_stub
+
+# Separate UI doesn't work with GDB debug.
+require !gdb_debug_enabled
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+standard_testfile
+
+if { [build_executable "failed to prepare" $testfile $srcfile] } {
+    return -1
+}
+
+# Helper proc to create a breakpoint location regexp.  NUM is the
+# regexp to match the number field of this location.
+proc make_bp_loc { num } {
+    return [mi_make_breakpoint_loc \
+		-number "$num" \
+		-enabled "y" \
+		-func "foo" \
+		-inferior "2"]
+}
+
+foreach_mi_ui_mode mode {
+    mi_gdb_exit
+
+    if {$mode eq "separate"} {
+	set start_ops "separate-mi-tty"
+    } else {
+	set start_ops ""
+    }
+
+    if [mi_gdb_start $start_ops] {
+	return
+    }
+
+    # Load a test binary into inferior 1.
+    mi_gdb_load ${binfile}
+
+    # Setup inferior 2, including loading an exec file.
+    mi_gdb_test "-add-inferior" \
+	[multi_line "=thread-group-added,id=\"\[^\"\]+\"" \
+	     "~\"\\\[New inferior 2\\\]\\\\n\"" \
+	     "\~\"Added inferior 2\\\\n\"" \
+	     "\\^done,inferior=\"\[^\"\]+\"" ] \
+	"mi add inferior 2"
+    mi_gdb_test "-file-exec-and-symbols --thread-group i2 $::binfile" \
+	"\\^done" \
+	"set executable of inferior 2"
+
+    # Build regexp for the two locations.
+    set loc1 [make_bp_loc "$::decimal\\.1"]
+    set loc2 [make_bp_loc "$::decimal\\.2"]
+
+    # Create the inferior-specific breakpoint.
+    mi_create_breakpoint_multi "-g i2 foo" "create breakpoint in inferior 2" \
+	-inferior "2" -locations "\\\[$loc1,$loc2\\\]"
+    set bpnum [mi_get_valueof "/d" "\$bpnum" "INVALID"]
+
+    if {$mode eq "separate"} {
+	# In 'separate' mode we delete the inferior from the CLI, and
+	# then look for the breakpoint-deleted notification on the MI.
+	with_spawn_id $gdb_main_spawn_id {
+	    gdb_test "inferior 1" ".*"
+	    gdb_test "remove-inferiors 2" \
+		"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\."
+	}
+
+	gdb_test_multiple "" "check for b/p deleted notification on MI" {
+	    -re "=breakpoint-deleted,id=\"$bpnum\"" {
+		pass $gdb_test_name
+	    }
+	}
+    } else {
+	# In the non-separate mode we delete the inferior from the MI
+	# and expect to immediately see a breakpoint-deleted
+	# notification.
+	mi_gdb_test "-remove-inferior i2" \
+	    [multi_line \
+		 "~\"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\.\\\\n\"" \
+		 "=breakpoint-deleted,id=\"$bpnum\"" \
+		 "=thread-group-removed,id=\"i2\"" \
+		 "\\^done"]
+    }
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
new file mode 100644
index 00000000000..8f86d8cdcea
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
@@ -0,0 +1,52 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2023 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/>.  */
+
+volatile int global_var = 0;
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+static inline void __attribute__((__always_inline__))
+foo (void)
+{
+  int i;
+
+  for (i = 0; i < 10; ++i)
+    global_var = 0;
+}
+
+static void
+bar (void)
+{
+  global_var = 0;
+
+  foo ();
+}
+
+
+int
+main (void)
+{
+  global_var = 0;
+  foo ();
+  bar ();
+  stop_breakpt ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
new file mode 100644
index 00000000000..e5b20b6e7a8
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
@@ -0,0 +1,52 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2023 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/>.  */
+
+static int bar (void);
+static int baz (void);
+static int foo (void);
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+int
+main (void)
+{
+  int ret = baz ();
+  stop_breakpt ();
+  return ret;
+}
+
+static int
+bar (void)
+{
+  return baz ();
+}
+
+static int
+foo (void)
+{
+  return 0;
+}
+
+static int
+baz (void)
+{
+  return foo ();
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
new file mode 100644
index 00000000000..1f6573268da
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
@@ -0,0 +1,179 @@
+# Copyright 2022-2023 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/>.
+
+# Test inferior-specific breakpoints.
+
+standard_testfile -1.c -2.c
+
+if {[use_gdb_stub]} {
+    return
+}
+
+set srcfile1 ${srcfile}
+set binfile1 ${binfile}-1
+set binfile2 ${binfile}-2
+
+if {[build_executable ${testfile}.exp ${binfile1} "${srcfile1}"] != 0} {
+    return -1
+}
+
+if {[build_executable ${testfile}.exp ${binfile2} "${srcfile2}"] != 0} {
+    return -1
+}
+
+# Start the first inferior.
+clean_restart ${binfile1}
+if {![runto_main]} {
+    return
+}
+
+# Add a second inferior, and start this one too.
+gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2"
+gdb_load $binfile2
+if {![runto_main]} {
+    return
+}
+
+# Try to create a breakpoint using both the 'inferior' and 'thread' keywords,
+# this should fail.  Try with the keywords in both orders just in case the
+# parser has a bug.
+gdb_test "break foo thread 1.1 inferior 1" \
+    "You can specify only one of inferior or thread\\."
+gdb_test "break foo inferior 1 thread 1.1" \
+    "You can specify only one of inferior or thread\\."
+
+# Try to create a breakpoint using the 'inferior' keyword multiple times.
+gdb_test "break foo inferior 1 inferior 2" \
+    "You can specify only one inferior\\."
+
+# Clear out any other breakpoints.
+delete_breakpoints
+
+# Use 'info breakpoint' to check that the inferior specific breakpoint is
+# present in the breakpoint list.  TESTNAME is the name used for this test,
+# BP_NUMBER is the number for the breakpoint, and EXPECTED_LOC_COUNT is the
+# number of locations we expect for that breakpoint.
+proc check_info_breakpoints { testname bp_number expected_loc_count } {
+    gdb_test_multiple "info breakpoints $bp_number" $testname {
+	-re "\r\nNum\\s+\[^\r\n\]+\r\n" {
+	    exp_continue
+	}
+
+	-re "^$bp_number\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
+	    set saw_header true
+	    exp_continue
+	}
+
+	-re "^\\s+stop only in inferior 1\r\n" {
+	    set saw_inf_cond true
+	    exp_continue
+	}
+
+	-re "^\\s+breakpoint already hit $::decimal times\r\n" {
+	    exp_continue
+	}
+
+	-re "^$bp_number\\.\[123\]\\s+y\\s+ $::hex in foo at \[^\r\n\]+(?: inf \[12\])?\r\n" {
+	    incr location_count
+	    exp_continue
+	}
+
+	-re "^$::gdb_prompt $" {
+	    with_test_prefix $gdb_test_name {
+		gdb_assert { $saw_header \
+				 && $location_count == $expected_loc_count \
+				 && $saw_inf_cond } \
+		    $gdb_test_name
+	    }
+	}
+    }
+}
+
+# Create an inferior-specific breakpoint.  Use gdb_test instead of
+# gdb_breakpoint here as we want to check the breakpoint was placed in
+# multiple locations.
+#
+# Currently GDB still places inferior specific breakpoints into every
+# inferior, just like it does with thread specific breakpoints.
+# Hopefully this will change in the future, at which point, this test
+# will need updating.
+#
+# Two of these locations are in inferior 1, while the third is in
+# inferior 2.
+gdb_test "break foo inferior 1" \
+    "Breakpoint $decimal at $hex: foo\\. \\(3 locations\\)"
+set bp_number [get_integer_valueof "\$bpnum" "INVALID" \
+		  "get b/p number for inferior specific breakpoint"]
+
+set saw_header false
+set location_count 0
+set saw_inf_cond false
+
+check_info_breakpoints "first check for inferior specific breakpoint" \
+    $bp_number 3
+
+# Create a multi-inferior breakpoint to stop at.
+gdb_breakpoint "stop_breakpt" message
+set stop_bp_num [get_integer_valueof "\$bpnum" "INVALID" \
+		    "get b/p number for stop_breakpt"]
+
+# Now resume inferior 2, this should reach 'stop_breakpt'.
+gdb_test "continue" \
+    "hit Breakpoint $stop_bp_num\.$decimal, stop_breakpt \\(\\) .*" \
+    "continue in inferior 2"
+
+# Switch to inferior 1, and try there.
+gdb_test "inferior 1" ".*" \
+    "select inferior 1 to check the inferior-specific b/p works"
+gdb_test "continue " \
+    "Thread 1\\.${decimal}\[^\r\n\]* hit Breakpoint\
+     $bp_number\.$decimal, foo \\(\\) .*" \
+    "first continue in inferior 1"
+
+# Now back to inferior 2, let the inferior exit, and then remove the
+# inferior, the inferior-specific breakpoint should not be deleted.
+gdb_test "inferior 2" ".*" \
+    "switch back to allow inferior 2 to exit"
+gdb_test "continue" "\\\[Inferior 2 \[^\r\n\]+ exited normally\\\]" \
+    "allow inferior 2 to exit"
+gdb_test "inferior 1" ".*" \
+    "back to inferior 1 so inferior 2 can be deleted"
+gdb_test_no_output "remove-inferiors 2"
+
+gdb_test "continue " "hit Breakpoint $bp_number\.$decimal, foo \\(\\) .*" \
+    "second continue in inferior 1"
+gdb_test "continue " "hit Breakpoint $stop_bp_num, stop_breakpt \\(\\) .*" \
+    "third continue in inferior 1"
+
+# Now allow inferior 1 to exit, the inferior specific breakpoint
+# should not be deleted.
+gdb_test "continue" \
+    "\\\[Inferior 1 \[^\r\n\]+ exited normally\\\]" \
+    "allow inferior 1 to exit"
+
+check_info_breakpoints "second check for inferior specific breakpoint" \
+    $bp_number 2
+
+# Now create another new inferior, then remove inferior 1.  As a result of
+# this removal, the inferior specific breakpoint should be deleted.
+gdb_test "add-inferior" "Added inferior 3.*" "add empty inferior 3"
+gdb_test "inferior 3" "Switching to inferior 3.*" "switch to inferior 3"
+gdb_test "remove-inferiors 1" \
+    "Inferior-specific breakpoint $bp_number deleted - inferior 1 has been removed\\."
+
+# Now check 'info breakpoints' to ensure the breakpoint is gone.
+gdb_test "info breakpoints $bp_number" \
+    "No breakpoint or watchpoint matching '$bp_number'\\."
diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp
index 76094c95d10..6278e84e4be 100644
--- a/gdb/testsuite/gdb.python/py-breakpoint.exp
+++ b/gdb/testsuite/gdb.python/py-breakpoint.exp
@@ -113,6 +113,8 @@ proc_with_prefix test_bkpt_basic { } {
 	"Get Breakpoint List" 0
     gdb_test "python print (blist\[1\].thread)" \
 	"None" "Check breakpoint thread"
+    gdb_test "python print (blist\[1\].inferior)" \
+	"None" "Check breakpoint inferior"
     gdb_test "python print (blist\[1\].type == gdb.BP_BREAKPOINT)" \
 	"True" "Check breakpoint type"
     gdb_test "python print (blist\[0\].number)" \
@@ -215,6 +217,46 @@ proc_with_prefix test_bkpt_cond_and_cmds { } {
 	"check number of lines in commands"
 }
 
+# Test breakpoint thread and inferior attributes.
+proc_with_prefix test_bkpt_thread_and_inferior { } {
+    global srcfile testfile hex decimal
+
+    # Start with a fresh gdb.
+    clean_restart ${testfile}
+
+    if {![runto_main]} {
+	return 0
+    }
+
+    with_test_prefix "thread" {
+	delete_breakpoints
+	gdb_test "break multiply thread 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "1"
+	gdb_test "python print(bp.inferior)" "None"
+	gdb_test "python bp.inferior = 1" \
+	    "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.thread = None"
+	gdb_test_no_output "python bp.inferior = 1" \
+	    "set the inferior now the thread has been cleared"
+	gdb_test "info breakpoints" "stop only in inferior 1\r\n.*"
+    }
+
+    with_test_prefix "inferior" {
+	delete_breakpoints
+	gdb_test "break multiply inferior 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "None"
+	gdb_test "python print(bp.inferior)" "1"
+	gdb_test "python bp.thread = 1" \
+	    "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.inferior = None"
+	gdb_test_no_output "python bp.thread = 1" \
+	    "set the thread now the inferior has been cleared"
+	gdb_test "info breakpoints" "stop only in thread 1\r\n.*"
+    }
+}
+
 proc_with_prefix test_bkpt_invisible { } {
     global srcfile testfile hex decimal
 
@@ -851,6 +893,7 @@ proc_with_prefix test_bkpt_auto_disable { } {
 test_bkpt_basic
 test_bkpt_deletion
 test_bkpt_cond_and_cmds
+test_bkpt_thread_and_inferior
 test_bkpt_invisible
 test_hardware_breakpoints
 test_catchpoints
diff --git a/gdb/testsuite/lib/completion-support.exp b/gdb/testsuite/lib/completion-support.exp
index 275f8874f15..85768deab94 100644
--- a/gdb/testsuite/lib/completion-support.exp
+++ b/gdb/testsuite/lib/completion-support.exp
@@ -27,7 +27,7 @@ namespace eval completion {
     # List of all quote chars, including no-quote at all.
     variable maybe_quoted_list {"" "'" "\""}
 
-    variable keyword_list {"-force-condition" "if" "task" "thread"}
+    variable keyword_list {"-force-condition" "if" "inferior" "task" "thread"}
 
     variable explicit_opts_list \
 	{"-function" "-label" "-line" "-qualified" "-source"}
diff --git a/gdb/testsuite/lib/mi-support.exp b/gdb/testsuite/lib/mi-support.exp
index 0d830d8e4ae..c9dfa4ff793 100644
--- a/gdb/testsuite/lib/mi-support.exp
+++ b/gdb/testsuite/lib/mi-support.exp
@@ -2532,7 +2532,7 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} {
 # locations.
 #
 # All arguments for the breakpoint location may be specified using the
-# options: number, enabled, addr, func, file, fullname, line,
+# options: number, enabled, addr, func, file, fullname, line, inferior
 # thread-groups, and thread.
 #
 # For the option -thread the corresponding output field is only added
@@ -2546,12 +2546,14 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} {
 proc mi_make_breakpoint_loc {args} {
     parse_args {{number .*} {enabled .*} {addr .*}
 	{func .*} {file .*} {fullname .*} {line .*}
-	{thread-groups \\\[.*\\\]} {thread ""}}
+	{thread-groups \\\[.*\\\]} {thread ""} {inferior ""}}
 
     set attr_list {}
     foreach attr [list number enabled addr func file \
-		      fullname line thread-groups] {
-	lappend attr_list $attr [set $attr]
+		      fullname line thread-groups inferior] {
+	if {$attr ne "inferior" || [set $attr] ne ""} {
+	    lappend attr_list $attr [set $attr]
+	}
     }
 
     set result [mi_build_kv_pairs $attr_list]
@@ -2625,7 +2627,7 @@ proc mi_make_breakpoint_1 {attr_list thread cond evaluated-by times \
 # locations.
 #
 # All arguments for the breakpoint may be specified using the options:
-# number, type, disp, enabled, times, ignore, script,
+# number, type, disp, enabled, times, ignore, script, inferior,
 # original-location, cond, evaluated-by, locations, and thread.
 #
 # Only if -script and -ignore are given will they appear in the output.
@@ -2646,7 +2648,7 @@ proc mi_make_breakpoint_multi {args} {
     parse_args {{number .*} {type .*} {disp .*} {enabled .*}
 	{times .*} {ignore 0}
 	{script ""} {original-location .*} {cond ""} {evaluated-by ""}
-	{locations .*} {thread ""}}
+	{locations .*} {thread ""} {inferior ""}}
 
     set attr_list {}
     foreach attr [list number type disp enabled] {
@@ -2655,6 +2657,12 @@ proc mi_make_breakpoint_multi {args} {
 
     lappend attr_list "addr" "<MULTIPLE>"
 
+    # Only include the inferior field if it was set.  This field is
+    # optional in the MI output.
+    if {$inferior ne ""} {
+	lappend attr_list "inferior" $inferior
+    }
+
     set result [mi_make_breakpoint_1 \
 		    $attr_list $thread $cond ${evaluated-by} $times \
 		    $ignore $script ${original-location}]

base-commit: 6a1cf1bfedbcdb977d9ead3bf6a228360d78cc1b
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCHv6] gdb: add inferior-specific breakpoints
  2023-05-15 19:15       ` [PATCHv5] " Andrew Burgess
@ 2023-05-30 20:41         ` Andrew Burgess
  2023-07-07 10:23           ` [PATCHv7] " Andrew Burgess
  0 siblings, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2023-05-30 20:41 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Doc changes have already been approved here:
  https://sourceware.org/pipermail/gdb-patches/2023-January/195986.html

In v6:

  - Rebased to current upstream master, this includes all Simon's
    recent breakpoint changes.  Retested with no regressions seen.

In v5:

  - Rebased to current upstream master and retested,

  - No changes to code or docs.

In v4:

  - Merged patch #1 from the previous series as this felt like a
    pretty obvious cleanup.

  - Rebased to HEAD of master.

  - No changes to code or docs.

---

This commit extends the breakpoint mechanism to allow for inferior
specific breakpoints (but not watchpoints in this commit).

As GDB gains better support for multiple connections, and so for
running multiple (possibly unrelated) inferiors, then it is not hard
to imagine that a user might wish to create breakpoints that apply to
any thread in a single inferior.  To achieve this currently, the user
would need to create a condition possibly making use of the $_inferior
convenience variable, which, though functional, isn't the most user
friendly.

This commit adds a new 'inferior' keyword that allows for the creation
of inferior specific breakpoints.

Inferior specific breakpoints are automatically deleted when the
associated inferior is removed from GDB, this is similar to how
thread-specific breakpoints when the associated thread is deleted.

Watchpoints are already per-program-space, which in most cases mean
watchpoints are already inferior specific.  There is a small window
where inferior-specific watchpoints might make sense, which is after a
vfork, when two processes are sharing the same address space.
However, I'm leaving that as an exercise for another day.  For now,
attempting to use the inferior keyword with a watchpoint will give an
error, like this:

  (gdb) watch a8 inferior 1
  Cannot use 'inferior' keyword with watchpoints

A final note on the implementation.  Currently, inferior specific
breakpoints, like thread-specific breakpoints, are inserted into every
inferior, GDB then checks once the inferior stops if we are in the
correct thread or inferior, and resumes automatically if we stopped in
the wrong thread/inferior.

An obvious optimisation here is to only insert breakpoint locations
into the specific program space (which mostly means inferior) that
contains either the inferior or thread we are interested in.  This
would reduce the number times GDB has to stop and then resume again in
a multi-inferior setup.

I have started investigating implementing this.  And though the
initial implementation is trivial to get going, like so many things,
the devil is in the detail.  While my hope is that I will bring this
optimisation to the list at some point in the near future I can't
guarantee it.  That said, we already have the issue of placing
breakpoints into inferiors that will never be hit (with thread
specific breakpoints), so I don't believe I'm adding anything worse
than we already have.  Personally, I'd be happy to see this new
feature merged even if the optimisation never happens.
---
 gdb/NEWS                                      |  15 ++
 gdb/breakpoint.c                              | 237 ++++++++++++++----
 gdb/breakpoint.h                              |  14 +-
 gdb/doc/gdb.texinfo                           |  72 +++++-
 gdb/doc/python.texi                           |  26 +-
 gdb/guile/scm-breakpoint.c                    |   7 +-
 gdb/infcmd.c                                  |  15 +-
 gdb/inferior.h                                |  11 +
 gdb/linespec.c                                |   4 +-
 gdb/mi/mi-cmd-break.c                         |  11 +-
 gdb/mi/mi-main.c                              |  18 +-
 gdb/mi/mi-main.h                              |   6 +
 gdb/python/py-breakpoint.c                    |  96 ++++++-
 gdb/python/py-finishbreakpoint.c              |   2 +-
 gdb/testsuite/gdb.ada/tasks.exp               |   2 +
 gdb/testsuite/gdb.linespec/cpcompletion.exp   |   4 +-
 gdb/testsuite/gdb.linespec/explicit.exp       |   1 +
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c      |  29 +++
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp    | 108 ++++++++
 .../gdb.multi/inferior-specific-bp-1.c        |  52 ++++
 .../gdb.multi/inferior-specific-bp-2.c        |  52 ++++
 .../gdb.multi/inferior-specific-bp.exp        | 179 +++++++++++++
 gdb/testsuite/gdb.python/py-breakpoint.exp    |  43 ++++
 gdb/testsuite/lib/completion-support.exp      |   2 +-
 gdb/testsuite/lib/mi-support.exp              |  20 +-
 25 files changed, 938 insertions(+), 88 deletions(-)
 create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
 create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index d97e3c15a87..e16e93cb6cd 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -67,6 +67,13 @@
     break foo thread 1 task 1
     watch var thread 2 task 3
 
+* Breakpoints can now be inferior-specific.  This is similar to the
+  existing thread-specific breakpoint support.  Breakpoint conditions
+  can include the 'inferior' keyword followed by an inferior id (as
+  displayed in the 'info inferiors' output).  It is invalid to use the
+  'inferior' keyword with either the 'thread' or 'task' keywords when
+  creating a breakpoint.
+
 * New commands
 
 maintenance print record-instruction [ N ]
@@ -127,6 +134,14 @@ info main
    considered simple.)  Support for this feature can be verified by using the
    '-list-features' command, which should contain "simple-values-ref-types".
 
+** The -break-insert command now accepts a '-g thread-group-id' option
+   to allow for the creation of inferior-specific breakpoints.
+
+** The bkpt tuple, which appears in breakpoint-created notifications,
+   and in the result of the -break-insert command can now include an
+   optional 'inferior' field for both the main breakpoint, and each
+   location, when the breakpoint is inferior-specific.
+
 * Python API
 
   ** The gdb.unwinder.Unwinder.name attribute is now read-only.
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index a94277f0c6c..a838706eba4 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -98,7 +98,7 @@ static void create_breakpoints_sal (struct gdbarch *,
 				    gdb::unique_xmalloc_ptr<char>,
 				    gdb::unique_xmalloc_ptr<char>,
 				    enum bptype,
-				    enum bpdisp, int, int,
+				    enum bpdisp, int, int, int,
 				    int,
 				    int, int, int, unsigned);
 
@@ -322,6 +322,9 @@ struct momentary_breakpoint : public code_breakpoint
     disposition = disp_donttouch;
     frame_id = frame_id_;
     thread = thread_;
+
+    /* The inferior should have been set by the parent constructor.  */
+    gdb_assert (inferior == -1);
   }
 
   void re_set () override;
@@ -1453,13 +1456,15 @@ breakpoint_set_silent (struct breakpoint *b, int silent)
 void
 breakpoint_set_thread (struct breakpoint *b, int thread)
 {
-  /* It is invalid to set the thread field to anything other than -1 (which
-     means no thread restriction) if a task restriction is already in
-     place.  */
-  gdb_assert (thread == -1 || b->task == -1);
+  /* THREAD should be -1, meaning no thread restriction, or it should be a
+     valid global thread-id, which are greater than zero.  */
+  gdb_assert (thread == -1 || thread > 0);
 
-  int old_thread = b->thread;
+  /* It is not valid to set a thread restriction for a breakpoint that
+     already has task or inferior restriction.  */
+  gdb_assert (thread == -1 || (b->task == -1 && b->inferior == -1));
 
+  int old_thread = b->thread;
   b->thread = thread;
   if (old_thread != thread)
     gdb::observers::breakpoint_modified.notify (b);
@@ -1467,16 +1472,37 @@ breakpoint_set_thread (struct breakpoint *b, int thread)
 
 /* See breakpoint.h.  */
 
+void
+breakpoint_set_inferior (struct breakpoint *b, int inferior)
+{
+  /* INFERIOR should be -1, meaning no inferior restriction, or it should
+     be a valid inferior number, which are greater than zero.  */
+  gdb_assert (inferior == -1 || inferior > 0);
+
+  /* It is not valid to set an inferior restriction for a breakpoint that
+     already has a task or thread restriction.  */
+  gdb_assert (inferior == -1 || (b->task == -1 && b->thread == -1));
+
+  int old_inferior = b->inferior;
+  b->inferior = inferior;
+  if (old_inferior != inferior)
+    gdb::observers::breakpoint_modified.notify (b);
+}
+
+/* See breakpoint.h.  */
+
 void
 breakpoint_set_task (struct breakpoint *b, int task)
 {
-  /* It is invalid to set the task field to anything other than -1 (which
-     means no task restriction) if a thread restriction is already in
-     place.  */
-  gdb_assert (task == -1 || b->thread == -1);
+  /* TASK should be -1, meaning no task restriction, or it should be a
+     valid task-id, which are greater than zero.  */
+  gdb_assert (task == -1 || task > 0);
 
-  int old_task = b->task;
+  /* It is not valid to set a task restriction for a breakpoint that
+     already has a thread or inferior restriction.  */
+  gdb_assert (task == -1 || (b->thread == -1 && b->inferior == -1));
 
+  int old_task = b->task;
   b->task = task;
   if (old_task != task)
     gdb::observers::breakpoint_modified.notify (b);
@@ -3154,6 +3180,12 @@ insert_breakpoint_locations (void)
 	  && !valid_global_thread_id (bl->owner->thread))
 	continue;
 
+      /* Or inferior specific breakpoints if the inferior no longer
+	 exists.  */
+      if (bl->owner->inferior != -1
+	  && !valid_global_inferior_id (bl->owner->inferior))
+	continue;
+
       switch_to_program_space_and_thread (bl->pspace);
 
       /* For targets that support global breakpoints, there's no need
@@ -3254,6 +3286,29 @@ Thread-specific breakpoint %d deleted - thread %s no longer in the thread list.\
     }
 }
 
+/* Called when inferior INF has been removed from GDB.  Remove associated
+   per-inferior breakpoints.  */
+
+static void
+remove_inferior_breakpoints (struct inferior *inf)
+{
+  for (breakpoint &b : all_breakpoints_safe ())
+    {
+      if (b.inferior == inf->num && user_breakpoint_p (&b))
+	{
+	  /* Tell the user the breakpoint has been deleted.  But only for
+	     breakpoints that would not normally have been deleted at the
+	     next stop anyway.  */
+	  if (b.disposition != disp_del
+	      && b.disposition != disp_del_at_next_stop)
+	    gdb_printf (_("\
+Inferior-specific breakpoint %d deleted - inferior %d has been removed.\n"),
+			b.number, inf->num);
+	  delete_breakpoint (&b);
+       }
+    }
+}
+
 /* See breakpoint.h.  */
 
 void
@@ -5458,6 +5513,7 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread)
      evaluating the condition if this isn't the specified
      thread/task.  */
   if ((b->thread != -1 && b->thread != thread->global_num)
+      || (b->inferior != -1 && b->inferior != thread->inf->num)
       || (b->task != -1 && b->task != ada_get_task_number (thread)))
     {
       infrun_debug_printf ("incorrect thread or task, not stopping");
@@ -6489,6 +6545,8 @@ print_one_breakpoint_location (struct breakpoint *b,
 	uiout->field_signed ("thread", b->thread);
       else if (b->task != -1)
 	uiout->field_signed ("task", b->task);
+      else if (b->inferior != -1)
+	uiout->field_signed ("inferior", b->inferior);
     }
 
   uiout->text ("\n");
@@ -6551,6 +6609,13 @@ print_one_breakpoint_location (struct breakpoint *b,
       uiout->text ("\n");
     }
 
+  if (!part_of_multiple && b->inferior != -1)
+    {
+      uiout->text ("\tstop only in inferior ");
+      uiout->field_signed ("inferior", b->inferior);
+      uiout->text ("\n");
+    }
+
   if (!part_of_multiple)
     {
       if (b->hit_count)
@@ -7526,7 +7591,10 @@ delete_longjmp_breakpoint (int thread)
     if (b.type == bp_longjmp || b.type == bp_exception)
       {
 	if (b.thread == thread)
-	  delete_breakpoint (&b);
+	  {
+	    gdb_assert (b.inferior == -1);
+	    delete_breakpoint (&b);
+	  }
       }
 }
 
@@ -7537,7 +7605,10 @@ delete_longjmp_breakpoint_at_next_stop (int thread)
     if (b.type == bp_longjmp || b.type == bp_exception)
       {
 	if (b.thread == thread)
-	  b.disposition = disp_del_at_next_stop;
+	  {
+	    gdb_assert (b.inferior == -1);
+	    b.disposition = disp_del_at_next_stop;
+	  }
       }
 }
 
@@ -7594,6 +7665,7 @@ check_longjmp_breakpoint_for_call_dummy (struct thread_info *tp)
     {
       if (b.type == bp_longjmp_call_dummy && b.thread == tp->global_num)
 	{
+	  gdb_assert (b.inferior == -1);
 	  struct breakpoint *dummy_b = b.related_breakpoint;
 
 	  /* Find the bp_call_dummy breakpoint in the list of breakpoints
@@ -8429,7 +8501,8 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
 				  gdb::unique_xmalloc_ptr<char> cond_string_,
 				  gdb::unique_xmalloc_ptr<char> extra_string_,
 				  enum bpdisp disposition_,
-				  int thread_, int task_, int ignore_count_,
+				  int thread_, int task_, int inferior_,
+				  int ignore_count_,
 				  int from_tty,
 				  int enabled_, unsigned flags,
 				  int display_canonical_)
@@ -8453,10 +8526,14 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
 
   gdb_assert (!sals.empty ());
 
-  /* At most one of thread or task can be set on any breakpoint.  */
-  gdb_assert (thread == -1 || task == -1);
+  /* At most one of thread, task, or inferior can be set on any breakpoint.  */
+  gdb_assert (((thread == -1 ? 0 : 1)
+	       + (task == -1 ? 0 : 1)
+	       + (inferior == -1 ? 0 : 1)) <= 1);
+
   thread = thread_;
   task = task_;
+  inferior = inferior_;
 
   cond_string = std::move (cond_string_);
   extra_string = std::move (extra_string_);
@@ -8558,7 +8635,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
 		       gdb::unique_xmalloc_ptr<char> cond_string,
 		       gdb::unique_xmalloc_ptr<char> extra_string,
 		       enum bptype type, enum bpdisp disposition,
-		       int thread, int task, int ignore_count,
+		       int thread, int task, int inferior, int ignore_count,
 		       int from_tty,
 		       int enabled, int internal, unsigned flags,
 		       int display_canonical)
@@ -8572,7 +8649,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
 				std::move (cond_string),
 				std::move (extra_string),
 				disposition,
-				thread, task, ignore_count,
+				thread, task, inferior, ignore_count,
 				from_tty,
 				enabled, flags,
 				display_canonical);
@@ -8601,7 +8678,8 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
 			gdb::unique_xmalloc_ptr<char> cond_string,
 			gdb::unique_xmalloc_ptr<char> extra_string,
 			enum bptype type, enum bpdisp disposition,
-			int thread, int task, int ignore_count,
+			int thread, int task, int inferior,
+			int ignore_count,
 			int from_tty,
 			int enabled, int internal, unsigned flags)
 {
@@ -8625,7 +8703,7 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
 			     std::move (cond_string),
 			     std::move (extra_string),
 			     type, disposition,
-			     thread, task, ignore_count,
+			     thread, task, inferior, ignore_count,
 			     from_tty, enabled, internal, flags,
 			     canonical->special_display);
     }
@@ -8755,21 +8833,26 @@ check_fast_tracepoint_sals (struct gdbarch *gdbarch,
     }
 }
 
-/* Given TOK, a string specification of condition and thread, as
-   accepted by the 'break' command, extract the condition
-   string and thread number and set *COND_STRING and *THREAD.
-   PC identifies the context at which the condition should be parsed.
-   If no condition is found, *COND_STRING is set to NULL.
-   If no thread is found, *THREAD is set to -1.  */
+/* Given TOK, a string specification of condition and thread, as accepted
+   by the 'break' command, extract the condition string into *COND_STRING.
+   If no condition string is found then *COND_STRING is set to nullptr.
+
+   If the breakpoint specification has an associated thread, task, or
+   inferior, these are extracted into *THREAD, *TASK, and *INFERIOR
+   respectively, otherwise these arguments are set to -1 (for THREAD and
+   INFERIOR) or 0 (for TASK).
+
+   PC identifies the context at which the condition should be parsed.  */
 
 static void
 find_condition_and_thread (const char *tok, CORE_ADDR pc,
 			   gdb::unique_xmalloc_ptr<char> *cond_string,
-			   int *thread, int *task,
+			   int *thread, int *inferior, int *task,
 			   gdb::unique_xmalloc_ptr<char> *rest)
 {
   cond_string->reset ();
   *thread = -1;
+  *inferior = -1;
   *task = -1;
   rest->reset ();
   bool force = false;
@@ -8786,7 +8869,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
       if ((*tok == '"' || *tok == ',') && rest)
 	{
 	  rest->reset (savestring (tok, strlen (tok)));
-	  return;
+	  break;
 	}
 
       end_tok = skip_to_space (tok);
@@ -8826,6 +8909,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  if (*task != -1)
 	    error (_("You can specify only one of thread or task."));
 
+	  if (*inferior != -1)
+	    error (_("You can specify only one of inferior or thread."));
+
 	  tok = end_tok + 1;
 	  thr = parse_thread_id (tok, &tmptok);
 	  if (tok == tmptok)
@@ -8833,6 +8919,26 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  *thread = thr->global_num;
 	  tok = tmptok;
 	}
+      else if (toklen >= 1 && strncmp (tok, "inferior", toklen) == 0)
+	{
+	  if (*inferior != -1)
+	    error(_("You can specify only one inferior."));
+
+	  if (*task != -1)
+	    error (_("You can specify only one of inferior or task."));
+
+	  if (*thread != -1)
+	    error (_("You can specify only one of inferior or thread."));
+
+	  char *tmptok;
+	  tok = end_tok + 1;
+	  *inferior = strtol (tok, &tmptok, 0);
+	  if (tok == tmptok)
+	    error (_("Junk after inferior keyword."));
+	  if (!valid_global_inferior_id (*inferior))
+	    error (_("Unknown inferior number %d."), *inferior);
+	  tok = tmptok;
+	}
       else if (toklen >= 1 && strncmp (tok, "task", toklen) == 0)
 	{
 	  char *tmptok;
@@ -8843,6 +8949,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  if (*thread != -1)
 	    error (_("You can specify only one of thread or task."));
 
+	  if (*inferior != -1)
+	    error (_("You can specify only one of inferior or task."));
+
 	  tok = end_tok + 1;
 	  *task = strtol (tok, &tmptok, 0);
 	  if (tok == tmptok)
@@ -8854,7 +8963,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
       else if (rest)
 	{
 	  rest->reset (savestring (tok, strlen (tok)));
-	  return;
+	  break;
 	}
       else
 	error (_("Junk at end of arguments."));
@@ -8870,7 +8979,7 @@ static void
 find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
 				    const char *input,
 				    gdb::unique_xmalloc_ptr<char> *cond_string,
-				    int *thread, int *task,
+				    int *thread, int *inferior, int *task,
 				    gdb::unique_xmalloc_ptr<char> *rest)
 {
   int num_failures = 0;
@@ -8878,6 +8987,7 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
     {
       gdb::unique_xmalloc_ptr<char> cond;
       int thread_id = -1;
+      int inferior_id = -1;
       int task_id = -1;
       gdb::unique_xmalloc_ptr<char> remaining;
 
@@ -8890,11 +9000,16 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
       try
 	{
 	  find_condition_and_thread (input, sal.pc, &cond, &thread_id,
-				     &task_id, &remaining);
+				     &inferior_id, &task_id, &remaining);
 	  *cond_string = std::move (cond);
-	  /* At most one of thread or task can be set.  */
-	  gdb_assert (thread_id == -1 || task_id == -1);
+	  /* A value of -1 indicates that these fields are unset.  At most
+	     one of these fields should be set (to a value other than -1)
+	     at this point.  */
+	  gdb_assert (((thread_id == -1 ? 1 : 0)
+		       + (task_id == -1 ? 1 : 0)
+		       + (inferior_id == -1 ? 1 : 0)) >= 2);
 	  *thread = thread_id;
+	  *inferior = inferior_id;
 	  *task = task_id;
 	  *rest = std::move (remaining);
 	  break;
@@ -8984,7 +9099,8 @@ int
 create_breakpoint (struct gdbarch *gdbarch,
 		   location_spec *locspec,
 		   const char *cond_string,
-		   int thread, const char *extra_string,
+		   int thread, int inferior,
+		   const char *extra_string,
 		   bool force_condition, int parse_extra,
 		   int tempflag, enum bptype type_wanted,
 		   int ignore_count,
@@ -8998,6 +9114,10 @@ create_breakpoint (struct gdbarch *gdbarch,
   int task = -1;
   int prev_bkpt_count = breakpoint_count;
 
+  gdb_assert (thread == -1 || thread > 0);
+  gdb_assert (inferior == -1 || inferior > 0);
+  gdb_assert (thread == -1 || inferior == -1);
+
   gdb_assert (ops != NULL);
 
   /* If extra_string isn't useful, set it to NULL.  */
@@ -9073,7 +9193,8 @@ create_breakpoint (struct gdbarch *gdbarch,
 	  const linespec_sals &lsal = canonical.lsals[0];
 
 	  find_condition_and_thread_for_sals (lsal.sals, extra_string,
-					      &cond, &thread, &task, &rest);
+					      &cond, &thread, &inferior,
+					      &task, &rest);
 	  cond_string_copy = std::move (cond);
 	  extra_string_copy = std::move (rest);
 	}
@@ -9123,7 +9244,7 @@ create_breakpoint (struct gdbarch *gdbarch,
 				   std::move (extra_string_copy),
 				   type_wanted,
 				   tempflag ? disp_del : disp_donttouch,
-				   thread, task, ignore_count,
+				   thread, task, inferior, ignore_count,
 				   from_tty, enabled, internal, flags);
     }
   else
@@ -9192,7 +9313,9 @@ break_command_1 (const char *arg, int flag, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL,
+		     -1 /* thread */, -1 /* inferior */,
+		     arg, false, 1 /* parse arg */,
 		     tempflag, type_wanted,
 		     0 /* Ignore count */,
 		     pending_break_support,
@@ -9304,7 +9427,8 @@ dprintf_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1,
+		     arg, false, 1 /* parse arg */,
 		     0, bp_dprintf,
 		     0 /* Ignore count */,
 		     pending_break_support,
@@ -10049,6 +10173,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
   const char *cond_end = NULL;
   enum bptype bp_type;
   int thread = -1;
+  int inferior = -1;
   /* Flag to indicate whether we are going to use masks for
      the hardware watchpoint.  */
   bool use_mask = false;
@@ -10103,12 +10228,13 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	      if (task != -1)
 		error (_("You can specify only one of thread or task."));
 
+	      if (inferior != -1)
+		error (_("You can specify only one of inferior or thread."));
+
 	      /* Extract the thread ID from the next token.  */
 	      thr = parse_thread_id (value_start, &endp);
-
-	      /* Check if the user provided a valid thread ID.  */
-	      if (*endp != ' ' && *endp != '\t' && *endp != '\0')
-		invalid_thread_id_error (value_start);
+	      if (value_start == endp)
+		error (_("Junk after thread keyword."));
 
 	      thread = thr->global_num;
 	    }
@@ -10122,12 +10248,20 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	      if (thread != -1)
 		error (_("You can specify only one of thread or task."));
 
+	      if (inferior != -1)
+		error (_("You can specify only one of inferior or task."));
+
 	      task = strtol (value_start, &tmp, 0);
 	      if (tmp == value_start)
 		error (_("Junk after task keyword."));
 	      if (!valid_task_id (task))
 		error (_("Unknown task %d."), task);
 	    }
+	  else if (toklen == 8 && startswith (tok, "inferior"))
+	    {
+	      /* Support for watchpoints will be added in a later commit.  */
+	      error (_("Cannot use 'inferior' keyword with watchpoints"));
+	    }
 	  else if (toklen == 4 && startswith (tok, "mask"))
 	    {
 	      /* We've found a "mask" token, which means the user wants to
@@ -10300,6 +10434,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
   /* At most one of thread or task can be set on a watchpoint.  */
   gdb_assert (thread == -1 || task == -1);
   w->thread = thread;
+  w->inferior = inferior;
   w->task = task;
   w->disposition = disp_donttouch;
   w->pspace = current_program_space;
@@ -12234,7 +12369,8 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
 				      enum bptype type_wanted,
 				      enum bpdisp disposition,
 				      int thread,
-				      int task, int ignore_count,
+				      int task, int inferior,
+				      int ignore_count,
 				      int from_tty, int enabled,
 				      int internal, unsigned flags)
 {
@@ -12260,7 +12396,7 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
 			 std::move (cond_string),
 			 std::move (extra_string),
 			 disposition,
-			 thread, task, ignore_count,
+			 thread, task, inferior, ignore_count,
 			 from_tty, enabled, flags,
 			 canonical->special_display));
 
@@ -12870,10 +13006,11 @@ code_breakpoint::location_spec_to_sals (location_spec *locspec,
       if (condition_not_parsed && extra_string != NULL)
 	{
 	  gdb::unique_xmalloc_ptr<char> local_cond, local_extra;
-	  int local_thread, local_task;
+	  int local_thread, local_task, local_inferior;
 
 	  find_condition_and_thread_for_sals (sals, extra_string.get (),
 					      &local_cond, &local_thread,
+					      &local_inferior,
 					      &local_task, &local_extra);
 	  gdb_assert (cond_string == nullptr);
 	  if (local_cond != nullptr)
@@ -13747,7 +13884,7 @@ trace_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     bp_tracepoint /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13765,7 +13902,7 @@ ftrace_command (const char *arg, int from_tty)
 						      current_language);
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     bp_fast_tracepoint /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13803,7 +13940,7 @@ strace_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     type /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13872,7 +14009,7 @@ create_tracepoint_from_upload (struct uploaded_tp *utp)
 						      current_language);
   if (!create_breakpoint (get_current_arch (),
 			  locspec.get (),
-			  utp->cond_string.get (), -1, addr_str,
+			  utp->cond_string.get (), -1, -1, addr_str,
 			  false /* force_condition */,
 			  0 /* parse cond/thread */,
 			  0 /* tempflag */,
@@ -14960,4 +15097,6 @@ This is useful for formatted output in user-defined commands."));
 					   "breakpoint");
   gdb::observers::thread_exit.attach (remove_threaded_breakpoints,
 				      "breakpoint");
+  gdb::observers::inferior_removed.attach (remove_inferior_breakpoints,
+					   "breakpoint");
 }
diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
index 709d27fa4db..3465b2b4492 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -579,7 +579,7 @@ struct breakpoint_ops
 				  struct linespec_result *,
 				  gdb::unique_xmalloc_ptr<char>,
 				  gdb::unique_xmalloc_ptr<char>,
-				  enum bptype, enum bpdisp, int, int,
+				  enum bptype, enum bpdisp, int, int, int,
 				  int, int, int, int, unsigned);
 };
 
@@ -859,6 +859,10 @@ struct breakpoint : public intrusive_list_node<breakpoint>
      care.  */
   int thread = -1;
 
+  /* Inferior number for inferior-specific breakpoint, or -1 if this
+     breakpoint is for all inferiors.  */
+  int inferior = -1;
+
   /* Ada task number for task-specific breakpoint, or -1 if don't
      care.  */
   int task = -1;
@@ -917,7 +921,7 @@ struct code_breakpoint : public breakpoint
 		   gdb::unique_xmalloc_ptr<char> cond_string,
 		   gdb::unique_xmalloc_ptr<char> extra_string,
 		   enum bpdisp disposition,
-		   int thread, int task, int ignore_count,
+		   int thread, int task, int inferior, int ignore_count,
 		   int from_tty,
 		   int enabled, unsigned flags,
 		   int display_canonical);
@@ -1597,6 +1601,7 @@ enum breakpoint_create_flags
 extern int create_breakpoint (struct gdbarch *gdbarch,
 			      struct location_spec *locspec,
 			      const char *cond_string, int thread,
+			      int inferior,
 			      const char *extra_string,
 			      bool force_condition,
 			      int parse_extra,
@@ -1740,6 +1745,11 @@ extern void breakpoint_set_silent (struct breakpoint *b, int silent);
 
 extern void breakpoint_set_thread (struct breakpoint *b, int thread);
 
+/* Set the inferior for breakpoint B to INFERIOR.  If INFERIOR is -1, make
+   the breakpoint work for any inferior.  */
+
+extern void breakpoint_set_inferior (struct breakpoint *b, int inferior);
+
 /* Set the task for this breakpoint.  If TASK is -1, make the breakpoint
    work for any task.  Passing a value other than -1 for TASK should only
    be done if b->thread is -1; it is not valid to try and set both a thread
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index d1059e0cb7f..58d91f253b1 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3517,6 +3517,57 @@
 space as a result of inferior 1 having executed a @code{vfork} call.
 @end table
 
+@menu
+* Inferior-Specific Breakpoints::	Controlling breakpoints
+@end menu
+
+@node Inferior-Specific Breakpoints
+@subsection Inferior-Specific Breakpoints
+
+When debugging multiple inferiors, you can choose whether to set
+breakpoints for all inferiors, or for a particular inferior.
+
+@table @code
+@cindex breakpoints and inferiors
+@cindex inferior-specific breakpoints
+@kindex break @dots{} inferior @var{inferior-id}
+@item break @var{locspec} inferior @var{inferior-id}
+@itemx break @var{locspec} inferior @var{inferior-id} if @dots{}
+@var{locspec} specifies a code location or locations in your program.
+@xref{Location Specifications}, for details.
+
+Use the qualifier @samp{inferior @var{inferior-id}} with a breakpoint
+command to specify that you only want @value{GDBN} to stop when a
+particular inferior reaches this breakpoint.  The @var{inferior-id}
+specifier is one of the inferior identifiers assigned by @value{GDBN},
+shown in the first column of the @samp{info inferiors} output.
+
+If you do not specify @samp{inferior @var{inferior-id}} when you set a
+breakpoint, the breakpoint applies to @emph{all} inferiors of your
+program.
+
+You can use the @code{inferior} qualifier on conditional breakpoints as
+well; in this case, place @samp{inferior @var{inferior-id}} before or
+after the breakpoint condition, like this:
+
+@smallexample
+(@value{GDBP}) break frik.c:13 inferior 2 if bartab > lim
+@end smallexample
+@end table
+
+Inferior-specific breakpoints are automatically deleted when the
+corresponding inferior is removed from @value{GDBN}.  For example:
+
+@smallexample
+(@value{GDBP}) remove-inferiors 2
+Inferior-specific breakpoint 3 deleted - inferior 2 has been removed.
+@end smallexample
+
+A breakpoint can't be both inferior-specific and thread-specific
+(@pxref{Thread-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{inferior}, @code{thread}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
 @node Threads
 @section Debugging Programs with Multiple Threads
 
@@ -4480,8 +4531,9 @@
 situation.
 
 It is also possible to insert a breakpoint that will stop the program
-only if a specific thread (@pxref{Thread-Specific Breakpoints})
-or a specific task (@pxref{Ada Tasks}) hits that breakpoint.
+only if a specific thread (@pxref{Thread-Specific Breakpoints}),
+specific inferior (@pxref{Inferior-Specific Breakpoints}), or a
+specific task (@pxref{Ada Tasks}) hits that breakpoint.
 
 @item break
 When called without any arguments, @code{break} sets a breakpoint at
@@ -7333,9 +7385,14 @@
 Process}), or if @value{GDBN} loses the remote connection
 (@pxref{Remote Debugging}), etc.  Note that with some targets,
 @value{GDBN} is only able to detect a thread has exited when the user
-explictly asks for the thread list with the @code{info threads}
+explicitly asks for the thread list with the @code{info threads}
 command.
 
+A breakpoint can't be both thread-specific and inferior-specific
+(@pxref{Inferior-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{thread}, @code{inferior}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
 @node Interrupted System Calls
 @subsection Interrupted System Calls 
 
@@ -31567,6 +31624,10 @@
 If this is a thread-specific breakpoint, then this identifies the
 thread in which the breakpoint can trigger.
 
+@item inferior
+If this is an inferior-specific breakpoint, this this identifies the
+inferior in which the breakpoint can trigger.
+
 @item task
 If this breakpoint is restricted to a particular Ada task, then this
 field will hold the task identifier.
@@ -32158,7 +32219,7 @@
 @smallexample
  -break-insert [ -t ] [ -h ] [ -f ] [ -d ] [ -a ] [ --qualified ]
     [ -c @var{condition} ] [ --force-condition ] [ -i @var{ignore-count} ]
-    [ -p @var{thread-id} ] [ @var{locspec} ]
+    [ -p @var{thread-id} ] [ -g @var{thread-group-id} ] [ @var{locspec} ]
 @end smallexample
 
 @noindent
@@ -32224,6 +32285,9 @@
 time the breakpoint is requested.  Breakpoints created with a
 @var{thread-id} will automatically be deleted when the corresponding
 thread exits.
+@item -g @var{thread-group-id}
+Restrict the breakpoint to the thread group with the specified
+@var{thread-group-id}.
 @item --qualified
 This option makes @value{GDBN} interpret a function name specified as
 a complete fully-qualified name.
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 69755e96143..be03fa4dd5b 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -3424,7 +3424,10 @@
 A @code{gdb.Inferior} object has the following attributes:
 
 @defvar Inferior.num
-ID of inferior, as assigned by GDB.
+ID of inferior, as assigned by @value{GDBN}.  You can use this to make
+Python breakpoints inferior-specific, for example
+(@pxref{python_breakpoint_inferior,,The Breakpoint.inferior
+attribute}).
 @end defvar
 
 @anchor{gdbpy_inferior_connection}
@@ -6302,9 +6305,24 @@
 
 @anchor{python_breakpoint_thread}
 @defvar Breakpoint.thread
-If the breakpoint is thread-specific, this attribute holds the
-thread's global id.  If the breakpoint is not thread-specific, this
-attribute is @code{None}.  This attribute is writable.
+If the breakpoint is thread-specific (@pxref{Thread-Specific
+Breakpoints}), this attribute holds the thread's global id.  If the
+breakpoint is not thread-specific, this attribute is @code{None}.
+This attribute is writable.
+
+Only one of @code{Breakpoint.thread} or @code{Breakpoint.inferior} can
+be set to a valid id at any time, that is, a breakpoint can be thread
+specific, or inferior specific, but not both.
+@end defvar
+
+@anchor{python_breakpoint_inferior}
+@defvar Breakpoint.inferior
+If the breakpoint is inferior-specific (@pxref{Inferior-Specific
+Breakpoints}), this attribute holds the inferior's id.  If the
+breakpoint is not inferior-specific, this attribute is @code{None}.
+
+This attribute can be written for breakpoints of type
+@code{gdb.BP_BREAKPOINT} and @code{gdb.BP_HARDWARE_BREAKPOINT}.
 @end defvar
 
 @defvar Breakpoint.task
diff --git a/gdb/guile/scm-breakpoint.c b/gdb/guile/scm-breakpoint.c
index 6c6dacb3883..59254646bcc 100644
--- a/gdb/guile/scm-breakpoint.c
+++ b/gdb/guile/scm-breakpoint.c
@@ -465,7 +465,7 @@ gdbscm_register_breakpoint_x (SCM self)
 	    const breakpoint_ops *ops =
 	      breakpoint_ops_for_location_spec (locspec.get (), false);
 	    create_breakpoint (get_current_arch (),
-			       locspec.get (), NULL, -1, NULL, false,
+			       locspec.get (), NULL, -1, -1, NULL, false,
 			       0,
 			       temporary, bp_breakpoint,
 			       0,
@@ -784,6 +784,11 @@ gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue)
   else
     SCM_ASSERT_TYPE (0, newvalue, SCM_ARG2, FUNC_NAME, _("integer or #f"));
 
+  if (bp_smob->bp->inferior != -1 && id != -1)
+    scm_misc_error (FUNC_NAME,
+		    _("Cannot have both 'thread' and 'inferior' "
+		      "conditions on a breakpoint"), SCM_EOL);
+
   breakpoint_set_thread (bp_smob->bp, id);
 
   return SCM_UNSPECIFIED;
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 15702f84894..8144331bfe6 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -416,17 +416,10 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
   if (run_how == RUN_STOP_AT_MAIN)
     {
       /* To avoid other inferiors hitting this breakpoint, make it
-	 inferior-specific using a condition.  A better solution would be to
-	 have proper inferior-specific breakpoint support, in the breakpoint
-	 machinery.  We could then avoid inserting a breakpoint in the program
-	 spaces unrelated to this inferior.  */
-      const char *op
-	= ((current_language->la_language == language_ada
-	    || current_language->la_language == language_pascal
-	    || current_language->la_language == language_m2) ? "=" : "==");
-      std::string arg = string_printf
-	("-qualified %s if $_inferior %s %d", main_name (), op,
-	 current_inferior ()->num);
+	 inferior-specific.  */
+      std::string arg = string_printf ("-qualified %s inferior %d",
+				       main_name (),
+				       current_inferior ()->num);
       tbreak_command (arg.c_str (), 0);
     }
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index caa8e4d494a..9af0e367c6f 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -826,4 +826,15 @@ extern void print_selected_inferior (struct ui_out *uiout);
 extern void switch_to_inferior_and_push_target
   (inferior *new_inf, bool no_connection, inferior *org_inf);
 
+/* Return true if ID is a valid global inferior number.  */
+
+inline bool
+valid_global_inferior_id (int id)
+{
+  for (inferior *inf : all_inferiors ())
+    if (inf->num == id)
+      return true;
+  return false;
+}
+
 #endif /* !defined (INFERIOR_H) */
diff --git a/gdb/linespec.c b/gdb/linespec.c
index 7d969f37fbf..695dc6515ee 100644
--- a/gdb/linespec.c
+++ b/gdb/linespec.c
@@ -254,9 +254,9 @@ enum linespec_token_type
 
 /* List of keywords.  This is NULL-terminated so that it can be used
    as enum completer.  */
-const char * const linespec_keywords[] = { "if", "thread", "task", "-force-condition", NULL };
+const char * const linespec_keywords[] = { "if", "thread", "task", "inferior", "-force-condition", NULL };
 #define IF_KEYWORD_INDEX 0
-#define FORCE_KEYWORD_INDEX 3
+#define FORCE_KEYWORD_INDEX 4
 
 /* A token of the linespec lexer  */
 
diff --git a/gdb/mi/mi-cmd-break.c b/gdb/mi/mi-cmd-break.c
index 48b58587488..5fabe784417 100644
--- a/gdb/mi/mi-cmd-break.c
+++ b/gdb/mi/mi-cmd-break.c
@@ -173,6 +173,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
   int hardware = 0;
   int temp_p = 0;
   int thread = -1;
+  int thread_group = -1;
   int ignore_count = 0;
   const char *condition = NULL;
   int pending = 0;
@@ -191,7 +192,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
   enum opt
     {
       HARDWARE_OPT, TEMP_OPT, CONDITION_OPT,
-      IGNORE_COUNT_OPT, THREAD_OPT, PENDING_OPT, DISABLE_OPT,
+      IGNORE_COUNT_OPT, THREAD_OPT, THREAD_GROUP_OPT,
+      PENDING_OPT, DISABLE_OPT,
       TRACEPOINT_OPT,
       FORCE_CONDITION_OPT,
       QUALIFIED_OPT,
@@ -205,6 +207,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
     {"c", CONDITION_OPT, 1},
     {"i", IGNORE_COUNT_OPT, 1},
     {"p", THREAD_OPT, 1},
+    {"g", THREAD_GROUP_OPT, 1},
     {"f", PENDING_OPT, 0},
     {"d", DISABLE_OPT, 0},
     {"a", TRACEPOINT_OPT, 0},
@@ -247,6 +250,9 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
 	  if (!valid_global_thread_id (thread))
 	    error (_("Unknown thread %d."), thread);
 	  break;
+	case THREAD_GROUP_OPT:
+	  thread_group = mi_parse_thread_group_id (oarg);
+	  break;
 	case PENDING_OPT:
 	  pending = 1;
 	  break;
@@ -360,7 +366,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
 	error (_("Garbage '%s' at end of location"), address);
     }
 
-  create_breakpoint (get_current_arch (), locspec.get (), condition, thread,
+  create_breakpoint (get_current_arch (), locspec.get (), condition,
+		     thread, thread_group,
 		     extra_string.c_str (),
 		     force_condition,
 		     0 /* condition and thread are valid.  */,
diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c
index dc7d717ef60..809d7a0d8a0 100644
--- a/gdb/mi/mi-main.c
+++ b/gdb/mi/mi-main.c
@@ -1755,8 +1755,7 @@ mi_cmd_remove_inferior (const char *command, const char *const *argv, int argc)
   if (argc != 1)
     error (_("-remove-inferior should be passed a single argument"));
 
-  if (sscanf (argv[0], "i%d", &id) != 1)
-    error (_("the thread group id is syntactically invalid"));
+  id = mi_parse_thread_group_id (argv[0]);
 
   inf_to_remove = find_inferior_id (id);
   if (inf_to_remove == NULL)
@@ -2784,6 +2783,21 @@ mi_cmd_complete (const char *command, const char *const *argv, int argc)
 		       result.number_matches == max_completions ? "1" : "0");
 }
 
+/* See mi-main.h.  */
+int
+mi_parse_thread_group_id (const char *id)
+{
+  if (*id != 'i')
+    error (_("thread group id should start with an 'i'"));
+
+  char *end;
+  long num = strtol (id + 1, &end, 10);
+
+  if (*end != '\0' || num > INT_MAX)
+    error (_("invalid thread group id '%s'"), id);
+
+  return (int) num;
+}
 
 void _initialize_mi_main ();
 void
diff --git a/gdb/mi/mi-main.h b/gdb/mi/mi-main.h
index 1741f48b3ee..668a8186cdf 100644
--- a/gdb/mi/mi-main.h
+++ b/gdb/mi/mi-main.h
@@ -75,4 +75,10 @@ extern void mi_cmd_fix_breakpoint_script_output (const char *command,
 						 const char *const *argv,
 						 int argc);
 
+/* Parse a thread-group-id from ID, and return the integer part of the
+   ID.  A valid thread-group-id is the character 'i' followed by an
+   integer that is greater than zero.  */
+
+extern int mi_parse_thread_group_id (const char *id);
+
 #endif /* MI_MI_MAIN_H */
diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
index d11fc64df20..52fdb7c9a4e 100644
--- a/gdb/python/py-breakpoint.c
+++ b/gdb/python/py-breakpoint.c
@@ -287,11 +287,86 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure)
       return -1;
     }
 
+  if (self_bp->bp->inferior != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'thread' and 'inferior' "
+			 "conditions on a breakpoint"));
+      return -1;
+    }
+
   breakpoint_set_thread (self_bp->bp, id);
 
   return 0;
 }
 
+/* Python function to set the inferior of a breakpoint.  */
+
+static int
+bppy_set_inferior (PyObject *self, PyObject *newvalue, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+  long id;
+
+  BPPY_SET_REQUIRE_VALID (self_bp);
+
+  if (newvalue == NULL)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("Cannot delete 'inferior' attribute."));
+      return -1;
+    }
+  else if (PyLong_Check (newvalue))
+    {
+      if (!gdb_py_int_as_long (newvalue, &id))
+	return -1;
+
+      if (!valid_global_inferior_id (id))
+	{
+	  PyErr_SetString (PyExc_RuntimeError,
+			   _("Invalid inferior ID."));
+	  return -1;
+	}
+    }
+  else if (newvalue == Py_None)
+    id = -1;
+  else
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("The value of 'inferior' must be an integer or None."));
+      return -1;
+    }
+
+  if (self_bp->bp->type != bp_breakpoint
+      && self_bp->bp->type != bp_hardware_breakpoint)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot set 'inferior' attribute on a gdb.Breakpoint "
+			 "of this type"));
+      return -1;
+    }
+
+  if (self_bp->bp->thread != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'thread' and 'inferior' conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
+  if (self_bp->bp->task != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'task' and 'inferior' conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
+  breakpoint_set_inferior (self_bp->bp, id);
+
+  return 0;
+}
+
 /* Python function to set the (Ada) task of a breakpoint.  */
 static int
 bppy_set_task (PyObject *self, PyObject *newvalue, void *closure)
@@ -703,6 +778,20 @@ bppy_get_thread (PyObject *self, void *closure)
   return gdb_py_object_from_longest (self_bp->bp->thread).release ();
 }
 
+/* Python function to get the breakpoint's inferior ID.  */
+static PyObject *
+bppy_get_inferior (PyObject *self, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+
+  BPPY_REQUIRE_VALID (self_bp);
+
+  if (self_bp->bp->inferior == -1)
+    Py_RETURN_NONE;
+
+  return gdb_py_object_from_longest (self_bp->bp->inferior).release ();
+}
+
 /* Python function to get the breakpoint's task ID (in Ada).  */
 static PyObject *
 bppy_get_task (PyObject *self, void *closure)
@@ -941,7 +1030,7 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs)
 	      = breakpoint_ops_for_location_spec (locspec.get (), false);
 
 	    create_breakpoint (gdbpy_enter::get_gdbarch (),
-			       locspec.get (), NULL, -1, NULL, false,
+			       locspec.get (), NULL, -1, -1, NULL, false,
 			       0,
 			       temporary_bp, type,
 			       0,
@@ -1350,6 +1439,11 @@ static gdb_PyGetSetDef breakpoint_object_getset[] = {
 If the value is a thread ID (integer), then this is a thread-specific breakpoint.\n\
 If the value is None, then this breakpoint is not thread-specific.\n\
 No other type of value can be used.", NULL },
+  { "inferior", bppy_get_inferior, bppy_set_inferior,
+    "Inferior ID for the breakpoint.\n\
+If the value is an inferior ID (integer), then this is an inferior-specific\n\
+breakpoint.  If the value is None, then this breakpoint is not\n\
+inferior-specific.  No other type of value can be used.", NULL },
   { "task", bppy_get_task, bppy_set_task,
     "Thread ID for the breakpoint.\n\
 If the value is a task ID (integer), then this is an Ada task-specific breakpoint.\n\
diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c
index b71e5fafc46..fa2139ba5d2 100644
--- a/gdb/python/py-finishbreakpoint.c
+++ b/gdb/python/py-finishbreakpoint.c
@@ -307,7 +307,7 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
       location_spec_up locspec
 	= new_address_location_spec (get_frame_pc (prev_frame), NULL, 0);
       create_breakpoint (gdbpy_enter::get_gdbarch (),
-			 locspec.get (), NULL, thread, NULL, false,
+			 locspec.get (), NULL, thread, -1, NULL, false,
 			 0,
 			 1 /*temp_flag*/,
 			 bp_breakpoint,
diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp
index eb7ee5c9951..603d43f7c36 100644
--- a/gdb/testsuite/gdb.ada/tasks.exp
+++ b/gdb/testsuite/gdb.ada/tasks.exp
@@ -58,6 +58,8 @@ gdb_test "break break_me task 1 thread 1" \
     "You can specify only one of thread or task\\."
 gdb_test "break break_me thread 1 task 1" \
     "You can specify only one of thread or task\\."
+gdb_test "break break_me inferior 1 task 1" \
+    "You can specify only one of inferior or task\\."
 gdb_test "watch j task 1 thread 1" \
     "You can specify only one of thread or task\\."
 gdb_test "watch j thread 1 task 1" \
diff --git a/gdb/testsuite/gdb.linespec/cpcompletion.exp b/gdb/testsuite/gdb.linespec/cpcompletion.exp
index fbe19b49d43..f005707b9da 100644
--- a/gdb/testsuite/gdb.linespec/cpcompletion.exp
+++ b/gdb/testsuite/gdb.linespec/cpcompletion.exp
@@ -1259,8 +1259,8 @@ proc_with_prefix function-labels {} {
 }
 
 # Test that completion after a function name offers keyword
-# (if/task/thread/-force-condition) matches in linespec mode, and also
-# the explicit location options in explicit locations mode.
+# (if/inferior/task/thread/-force-condition) matches in linespec mode,
+# and also the explicit location options in explicit locations mode.
 
 proc_with_prefix keywords-after-function {} {
     set explicit_list \
diff --git a/gdb/testsuite/gdb.linespec/explicit.exp b/gdb/testsuite/gdb.linespec/explicit.exp
index b08d65953d2..668002d9038 100644
--- a/gdb/testsuite/gdb.linespec/explicit.exp
+++ b/gdb/testsuite/gdb.linespec/explicit.exp
@@ -412,6 +412,7 @@ namespace eval $testfile {
 	    "-qualified"
 	    "-source"
 	    "if"
+	    "inferior"
 	    "task"
 	    "thread"
 	}
diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
new file mode 100644
index 00000000000..c171ef71b9d
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
@@ -0,0 +1,29 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2023 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/>.  */
+
+int
+foo (void)
+{
+  return 0;
+}
+
+int
+main (void)
+{
+  int res = foo ();
+  return res;
+}
diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
new file mode 100644
index 00000000000..93a0eb3de49
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
@@ -0,0 +1,108 @@
+# Copyright 2023 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 for the delivery of '=breakpoint-deleted' notifications when
+# breakpoints are deleted.  Right now this test only covers
+# inferior-specific breakpoints, but it could be extended to cover
+# other cases too.
+
+# Multiple inferiors are needed, therefore only native gdb and
+# extended gdbserver modes are supported.
+require !use_gdb_stub
+
+# Separate UI doesn't work with GDB debug.
+require !gdb_debug_enabled
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+standard_testfile
+
+if { [build_executable "failed to prepare" $testfile $srcfile] } {
+    return -1
+}
+
+# Helper proc to create a breakpoint location regexp.  NUM is the
+# regexp to match the number field of this location.
+proc make_bp_loc { num } {
+    return [mi_make_breakpoint_loc \
+		-number "$num" \
+		-enabled "y" \
+		-func "foo" \
+		-inferior "2"]
+}
+
+foreach_mi_ui_mode mode {
+    mi_gdb_exit
+
+    if {$mode eq "separate"} {
+	set start_ops "separate-mi-tty"
+    } else {
+	set start_ops ""
+    }
+
+    if [mi_gdb_start $start_ops] {
+	return
+    }
+
+    # Load a test binary into inferior 1.
+    mi_gdb_load ${binfile}
+
+    # Setup inferior 2, including loading an exec file.
+    mi_gdb_test "-add-inferior" \
+	[multi_line "=thread-group-added,id=\"\[^\"\]+\"" \
+	     "~\"\\\[New inferior 2\\\]\\\\n\"" \
+	     "\~\"Added inferior 2\\\\n\"" \
+	     "\\^done,inferior=\"\[^\"\]+\"" ] \
+	"mi add inferior 2"
+    mi_gdb_test "-file-exec-and-symbols --thread-group i2 $::binfile" \
+	"\\^done" \
+	"set executable of inferior 2"
+
+    # Build regexp for the two locations.
+    set loc1 [make_bp_loc "$::decimal\\.1"]
+    set loc2 [make_bp_loc "$::decimal\\.2"]
+
+    # Create the inferior-specific breakpoint.
+    mi_create_breakpoint_multi "-g i2 foo" "create breakpoint in inferior 2" \
+	-inferior "2" -locations "\\\[$loc1,$loc2\\\]"
+    set bpnum [mi_get_valueof "/d" "\$bpnum" "INVALID"]
+
+    if {$mode eq "separate"} {
+	# In 'separate' mode we delete the inferior from the CLI, and
+	# then look for the breakpoint-deleted notification on the MI.
+	with_spawn_id $gdb_main_spawn_id {
+	    gdb_test "inferior 1" ".*"
+	    gdb_test "remove-inferiors 2" \
+		"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\."
+	}
+
+	gdb_test_multiple "" "check for b/p deleted notification on MI" {
+	    -re "=breakpoint-deleted,id=\"$bpnum\"" {
+		pass $gdb_test_name
+	    }
+	}
+    } else {
+	# In the non-separate mode we delete the inferior from the MI
+	# and expect to immediately see a breakpoint-deleted
+	# notification.
+	mi_gdb_test "-remove-inferior i2" \
+	    [multi_line \
+		 "~\"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\.\\\\n\"" \
+		 "=breakpoint-deleted,id=\"$bpnum\"" \
+		 "=thread-group-removed,id=\"i2\"" \
+		 "\\^done"]
+    }
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
new file mode 100644
index 00000000000..8f86d8cdcea
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
@@ -0,0 +1,52 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2023 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/>.  */
+
+volatile int global_var = 0;
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+static inline void __attribute__((__always_inline__))
+foo (void)
+{
+  int i;
+
+  for (i = 0; i < 10; ++i)
+    global_var = 0;
+}
+
+static void
+bar (void)
+{
+  global_var = 0;
+
+  foo ();
+}
+
+
+int
+main (void)
+{
+  global_var = 0;
+  foo ();
+  bar ();
+  stop_breakpt ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
new file mode 100644
index 00000000000..e5b20b6e7a8
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
@@ -0,0 +1,52 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2023 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/>.  */
+
+static int bar (void);
+static int baz (void);
+static int foo (void);
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+int
+main (void)
+{
+  int ret = baz ();
+  stop_breakpt ();
+  return ret;
+}
+
+static int
+bar (void)
+{
+  return baz ();
+}
+
+static int
+foo (void)
+{
+  return 0;
+}
+
+static int
+baz (void)
+{
+  return foo ();
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
new file mode 100644
index 00000000000..1f6573268da
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
@@ -0,0 +1,179 @@
+# Copyright 2022-2023 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/>.
+
+# Test inferior-specific breakpoints.
+
+standard_testfile -1.c -2.c
+
+if {[use_gdb_stub]} {
+    return
+}
+
+set srcfile1 ${srcfile}
+set binfile1 ${binfile}-1
+set binfile2 ${binfile}-2
+
+if {[build_executable ${testfile}.exp ${binfile1} "${srcfile1}"] != 0} {
+    return -1
+}
+
+if {[build_executable ${testfile}.exp ${binfile2} "${srcfile2}"] != 0} {
+    return -1
+}
+
+# Start the first inferior.
+clean_restart ${binfile1}
+if {![runto_main]} {
+    return
+}
+
+# Add a second inferior, and start this one too.
+gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2"
+gdb_load $binfile2
+if {![runto_main]} {
+    return
+}
+
+# Try to create a breakpoint using both the 'inferior' and 'thread' keywords,
+# this should fail.  Try with the keywords in both orders just in case the
+# parser has a bug.
+gdb_test "break foo thread 1.1 inferior 1" \
+    "You can specify only one of inferior or thread\\."
+gdb_test "break foo inferior 1 thread 1.1" \
+    "You can specify only one of inferior or thread\\."
+
+# Try to create a breakpoint using the 'inferior' keyword multiple times.
+gdb_test "break foo inferior 1 inferior 2" \
+    "You can specify only one inferior\\."
+
+# Clear out any other breakpoints.
+delete_breakpoints
+
+# Use 'info breakpoint' to check that the inferior specific breakpoint is
+# present in the breakpoint list.  TESTNAME is the name used for this test,
+# BP_NUMBER is the number for the breakpoint, and EXPECTED_LOC_COUNT is the
+# number of locations we expect for that breakpoint.
+proc check_info_breakpoints { testname bp_number expected_loc_count } {
+    gdb_test_multiple "info breakpoints $bp_number" $testname {
+	-re "\r\nNum\\s+\[^\r\n\]+\r\n" {
+	    exp_continue
+	}
+
+	-re "^$bp_number\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
+	    set saw_header true
+	    exp_continue
+	}
+
+	-re "^\\s+stop only in inferior 1\r\n" {
+	    set saw_inf_cond true
+	    exp_continue
+	}
+
+	-re "^\\s+breakpoint already hit $::decimal times\r\n" {
+	    exp_continue
+	}
+
+	-re "^$bp_number\\.\[123\]\\s+y\\s+ $::hex in foo at \[^\r\n\]+(?: inf \[12\])?\r\n" {
+	    incr location_count
+	    exp_continue
+	}
+
+	-re "^$::gdb_prompt $" {
+	    with_test_prefix $gdb_test_name {
+		gdb_assert { $saw_header \
+				 && $location_count == $expected_loc_count \
+				 && $saw_inf_cond } \
+		    $gdb_test_name
+	    }
+	}
+    }
+}
+
+# Create an inferior-specific breakpoint.  Use gdb_test instead of
+# gdb_breakpoint here as we want to check the breakpoint was placed in
+# multiple locations.
+#
+# Currently GDB still places inferior specific breakpoints into every
+# inferior, just like it does with thread specific breakpoints.
+# Hopefully this will change in the future, at which point, this test
+# will need updating.
+#
+# Two of these locations are in inferior 1, while the third is in
+# inferior 2.
+gdb_test "break foo inferior 1" \
+    "Breakpoint $decimal at $hex: foo\\. \\(3 locations\\)"
+set bp_number [get_integer_valueof "\$bpnum" "INVALID" \
+		  "get b/p number for inferior specific breakpoint"]
+
+set saw_header false
+set location_count 0
+set saw_inf_cond false
+
+check_info_breakpoints "first check for inferior specific breakpoint" \
+    $bp_number 3
+
+# Create a multi-inferior breakpoint to stop at.
+gdb_breakpoint "stop_breakpt" message
+set stop_bp_num [get_integer_valueof "\$bpnum" "INVALID" \
+		    "get b/p number for stop_breakpt"]
+
+# Now resume inferior 2, this should reach 'stop_breakpt'.
+gdb_test "continue" \
+    "hit Breakpoint $stop_bp_num\.$decimal, stop_breakpt \\(\\) .*" \
+    "continue in inferior 2"
+
+# Switch to inferior 1, and try there.
+gdb_test "inferior 1" ".*" \
+    "select inferior 1 to check the inferior-specific b/p works"
+gdb_test "continue " \
+    "Thread 1\\.${decimal}\[^\r\n\]* hit Breakpoint\
+     $bp_number\.$decimal, foo \\(\\) .*" \
+    "first continue in inferior 1"
+
+# Now back to inferior 2, let the inferior exit, and then remove the
+# inferior, the inferior-specific breakpoint should not be deleted.
+gdb_test "inferior 2" ".*" \
+    "switch back to allow inferior 2 to exit"
+gdb_test "continue" "\\\[Inferior 2 \[^\r\n\]+ exited normally\\\]" \
+    "allow inferior 2 to exit"
+gdb_test "inferior 1" ".*" \
+    "back to inferior 1 so inferior 2 can be deleted"
+gdb_test_no_output "remove-inferiors 2"
+
+gdb_test "continue " "hit Breakpoint $bp_number\.$decimal, foo \\(\\) .*" \
+    "second continue in inferior 1"
+gdb_test "continue " "hit Breakpoint $stop_bp_num, stop_breakpt \\(\\) .*" \
+    "third continue in inferior 1"
+
+# Now allow inferior 1 to exit, the inferior specific breakpoint
+# should not be deleted.
+gdb_test "continue" \
+    "\\\[Inferior 1 \[^\r\n\]+ exited normally\\\]" \
+    "allow inferior 1 to exit"
+
+check_info_breakpoints "second check for inferior specific breakpoint" \
+    $bp_number 2
+
+# Now create another new inferior, then remove inferior 1.  As a result of
+# this removal, the inferior specific breakpoint should be deleted.
+gdb_test "add-inferior" "Added inferior 3.*" "add empty inferior 3"
+gdb_test "inferior 3" "Switching to inferior 3.*" "switch to inferior 3"
+gdb_test "remove-inferiors 1" \
+    "Inferior-specific breakpoint $bp_number deleted - inferior 1 has been removed\\."
+
+# Now check 'info breakpoints' to ensure the breakpoint is gone.
+gdb_test "info breakpoints $bp_number" \
+    "No breakpoint or watchpoint matching '$bp_number'\\."
diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp
index 76094c95d10..6278e84e4be 100644
--- a/gdb/testsuite/gdb.python/py-breakpoint.exp
+++ b/gdb/testsuite/gdb.python/py-breakpoint.exp
@@ -113,6 +113,8 @@ proc_with_prefix test_bkpt_basic { } {
 	"Get Breakpoint List" 0
     gdb_test "python print (blist\[1\].thread)" \
 	"None" "Check breakpoint thread"
+    gdb_test "python print (blist\[1\].inferior)" \
+	"None" "Check breakpoint inferior"
     gdb_test "python print (blist\[1\].type == gdb.BP_BREAKPOINT)" \
 	"True" "Check breakpoint type"
     gdb_test "python print (blist\[0\].number)" \
@@ -215,6 +217,46 @@ proc_with_prefix test_bkpt_cond_and_cmds { } {
 	"check number of lines in commands"
 }
 
+# Test breakpoint thread and inferior attributes.
+proc_with_prefix test_bkpt_thread_and_inferior { } {
+    global srcfile testfile hex decimal
+
+    # Start with a fresh gdb.
+    clean_restart ${testfile}
+
+    if {![runto_main]} {
+	return 0
+    }
+
+    with_test_prefix "thread" {
+	delete_breakpoints
+	gdb_test "break multiply thread 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "1"
+	gdb_test "python print(bp.inferior)" "None"
+	gdb_test "python bp.inferior = 1" \
+	    "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.thread = None"
+	gdb_test_no_output "python bp.inferior = 1" \
+	    "set the inferior now the thread has been cleared"
+	gdb_test "info breakpoints" "stop only in inferior 1\r\n.*"
+    }
+
+    with_test_prefix "inferior" {
+	delete_breakpoints
+	gdb_test "break multiply inferior 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "None"
+	gdb_test "python print(bp.inferior)" "1"
+	gdb_test "python bp.thread = 1" \
+	    "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.inferior = None"
+	gdb_test_no_output "python bp.thread = 1" \
+	    "set the thread now the inferior has been cleared"
+	gdb_test "info breakpoints" "stop only in thread 1\r\n.*"
+    }
+}
+
 proc_with_prefix test_bkpt_invisible { } {
     global srcfile testfile hex decimal
 
@@ -851,6 +893,7 @@ proc_with_prefix test_bkpt_auto_disable { } {
 test_bkpt_basic
 test_bkpt_deletion
 test_bkpt_cond_and_cmds
+test_bkpt_thread_and_inferior
 test_bkpt_invisible
 test_hardware_breakpoints
 test_catchpoints
diff --git a/gdb/testsuite/lib/completion-support.exp b/gdb/testsuite/lib/completion-support.exp
index 275f8874f15..85768deab94 100644
--- a/gdb/testsuite/lib/completion-support.exp
+++ b/gdb/testsuite/lib/completion-support.exp
@@ -27,7 +27,7 @@ namespace eval completion {
     # List of all quote chars, including no-quote at all.
     variable maybe_quoted_list {"" "'" "\""}
 
-    variable keyword_list {"-force-condition" "if" "task" "thread"}
+    variable keyword_list {"-force-condition" "if" "inferior" "task" "thread"}
 
     variable explicit_opts_list \
 	{"-function" "-label" "-line" "-qualified" "-source"}
diff --git a/gdb/testsuite/lib/mi-support.exp b/gdb/testsuite/lib/mi-support.exp
index 2ff4ab93ea8..4af02655f21 100644
--- a/gdb/testsuite/lib/mi-support.exp
+++ b/gdb/testsuite/lib/mi-support.exp
@@ -2542,7 +2542,7 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} {
 # locations.
 #
 # All arguments for the breakpoint location may be specified using the
-# options: number, enabled, addr, func, file, fullname, line,
+# options: number, enabled, addr, func, file, fullname, line, inferior
 # thread-groups, and thread.
 #
 # For the option -thread the corresponding output field is only added
@@ -2556,12 +2556,14 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} {
 proc mi_make_breakpoint_loc {args} {
     parse_args {{number .*} {enabled .*} {addr .*}
 	{func .*} {file .*} {fullname .*} {line .*}
-	{thread-groups \\\[.*\\\]} {thread ""}}
+	{thread-groups \\\[.*\\\]} {thread ""} {inferior ""}}
 
     set attr_list {}
     foreach attr [list number enabled addr func file \
-		      fullname line thread-groups] {
-	lappend attr_list $attr [set $attr]
+		      fullname line thread-groups inferior] {
+	if {$attr ne "inferior" || [set $attr] ne ""} {
+	    lappend attr_list $attr [set $attr]
+	}
     }
 
     set result [mi_build_kv_pairs $attr_list]
@@ -2635,7 +2637,7 @@ proc mi_make_breakpoint_1 {attr_list thread cond evaluated-by times \
 # locations.
 #
 # All arguments for the breakpoint may be specified using the options:
-# number, type, disp, enabled, times, ignore, script,
+# number, type, disp, enabled, times, ignore, script, inferior,
 # original-location, cond, evaluated-by, locations, and thread.
 #
 # Only if -script and -ignore are given will they appear in the output.
@@ -2656,7 +2658,7 @@ proc mi_make_breakpoint_multi {args} {
     parse_args {{number .*} {type .*} {disp .*} {enabled .*}
 	{times .*} {ignore 0}
 	{script ""} {original-location .*} {cond ""} {evaluated-by ""}
-	{locations .*} {thread ""}}
+	{locations .*} {thread ""} {inferior ""}}
 
     set attr_list {}
     foreach attr [list number type disp enabled] {
@@ -2665,6 +2667,12 @@ proc mi_make_breakpoint_multi {args} {
 
     lappend attr_list "addr" "<MULTIPLE>"
 
+    # Only include the inferior field if it was set.  This field is
+    # optional in the MI output.
+    if {$inferior ne ""} {
+	lappend attr_list "inferior" $inferior
+    }
+
     set result [mi_make_breakpoint_1 \
 		    $attr_list $thread $cond ${evaluated-by} $times \
 		    $ignore $script ${original-location}]

base-commit: 2bd766d6245bf9db77c42da3537c949ffb814bfc
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PATCHv7] gdb: add inferior-specific breakpoints
  2023-05-30 20:41         ` [PATCHv6] " Andrew Burgess
@ 2023-07-07 10:23           ` Andrew Burgess
  2023-08-17 15:53             ` [PUSHEDv8] " Andrew Burgess
  0 siblings, 1 reply; 54+ messages in thread
From: Andrew Burgess @ 2023-07-07 10:23 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Though there's a lot of change in this patch, most of this is actually
just replicating code that already exists for thread-specific
breakpoints.

As far as actual "new" ideas in here, the only thing of note (I
believe) is that inferior-specific breakpoints are deleted when the
associated inferior is removed from GDB, I believe this was given a
review here[1].

As such, I'm planning to push this patch some time next week unless
anyone objects in the near future.

---

Doc changes have already been approved here:
  https://sourceware.org/pipermail/gdb-patches/2023-January/195986.html

In v7:

  - Rebased to current upstream master,

  - Minor fixes to gdb.mi/new-ui-bp-deleted.exp to fix this test when
    run with the native-extended-remote board.

In v6:

  - Rebased to current upstream master, this includes all Simon's
    recent breakpoint changes.  Retested with no regressions seen.

In v5:

  - Rebased to current upstream master and retested,

  - No changes to code or docs.

In v4:

  - Merged patch #1 from the previous series as this felt like a
    pretty obvious cleanup.

  - Rebased to HEAD of master.

  - No changes to code or docs.

[1] https://inbox.sourceware.org/gdb-patches/050da90b0b8c886983ec472a957b1075d4ecf7d6.1674207665.git.aburgess@redhat.com/

---

This commit extends the breakpoint mechanism to allow for inferior
specific breakpoints (but not watchpoints in this commit).

As GDB gains better support for multiple connections, and so for
running multiple (possibly unrelated) inferiors, then it is not hard
to imagine that a user might wish to create breakpoints that apply to
any thread in a single inferior.  To achieve this currently, the user
would need to create a condition possibly making use of the $_inferior
convenience variable, which, though functional, isn't the most user
friendly.

This commit adds a new 'inferior' keyword that allows for the creation
of inferior specific breakpoints.

Inferior specific breakpoints are automatically deleted when the
associated inferior is removed from GDB, this is similar to how
thread-specific breakpoints are deleted when the associated thread is
deleted.

Watchpoints are already per-program-space, which in most cases mean
watchpoints are already inferior specific.  There is a small window
where inferior-specific watchpoints might make sense, which is after a
vfork, when two processes are sharing the same address space.
However, I'm leaving that as an exercise for another day.  For now,
attempting to use the inferior keyword with a watchpoint will give an
error, like this:

  (gdb) watch a8 inferior 1
  Cannot use 'inferior' keyword with watchpoints

A final note on the implementation: currently, inferior specific
breakpoints, like thread-specific breakpoints, are inserted into every
inferior, GDB then checks once the inferior stops if we are in the
correct thread or inferior, and resumes automatically if we stopped in
the wrong thread/inferior.

An obvious optimisation here is to only insert breakpoint locations
into the specific program space (which mostly means inferior) that
contains either the inferior or thread we are interested in.  This
would reduce the number times GDB has to stop and then resume again in
a multi-inferior setup.

I have a series on the mailing list[1] that implements this
optimisation for thread-specific breakpoints.  Once this series has
landed I'll update that series to also handle inferior specific
breakpoints in the same way.  For now, inferior specific breakpoints
are just slightly less optimal, but this is no different to
thread-specific breakpoints in a multi-inferior debug session, so I
don't see this as a huge problem.

[1] https://inbox.sourceware.org/gdb-patches/cover.1685479504.git.aburgess@redhat.com/
---
 gdb/NEWS                                      |  15 ++
 gdb/breakpoint.c                              | 237 ++++++++++++++----
 gdb/breakpoint.h                              |  14 +-
 gdb/doc/gdb.texinfo                           |  72 +++++-
 gdb/doc/python.texi                           |  26 +-
 gdb/guile/scm-breakpoint.c                    |   7 +-
 gdb/infcmd.c                                  |  15 +-
 gdb/inferior.h                                |  11 +
 gdb/linespec.c                                |   4 +-
 gdb/mi/mi-cmd-break.c                         |  11 +-
 gdb/mi/mi-main.c                              |  18 +-
 gdb/mi/mi-main.h                              |   6 +
 gdb/python/py-breakpoint.c                    |  96 ++++++-
 gdb/python/py-finishbreakpoint.c              |   2 +-
 gdb/testsuite/gdb.ada/tasks.exp               |   2 +
 gdb/testsuite/gdb.linespec/cpcompletion.exp   |   4 +-
 gdb/testsuite/gdb.linespec/explicit.exp       |   1 +
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c      |  29 +++
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp    | 108 ++++++++
 .../gdb.multi/inferior-specific-bp-1.c        |  52 ++++
 .../gdb.multi/inferior-specific-bp-2.c        |  52 ++++
 .../gdb.multi/inferior-specific-bp.exp        | 179 +++++++++++++
 gdb/testsuite/gdb.python/py-breakpoint.exp    |  43 ++++
 gdb/testsuite/lib/completion-support.exp      |   2 +-
 gdb/testsuite/lib/mi-support.exp              |  20 +-
 25 files changed, 938 insertions(+), 88 deletions(-)
 create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
 create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index fd42864c692..9f3a8c783be 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -78,6 +78,13 @@
   functionality is also available for dprintf when dprintf-style is
   'gdb'.
 
+* Breakpoints can now be inferior-specific.  This is similar to the
+  existing thread-specific breakpoint support.  Breakpoint conditions
+  can include the 'inferior' keyword followed by an inferior id (as
+  displayed in the 'info inferiors' output).  It is invalid to use the
+  'inferior' keyword with either the 'thread' or 'task' keywords when
+  creating a breakpoint.
+
 * New commands
 
 maintenance print record-instruction [ N ]
@@ -138,6 +145,14 @@ info main
    considered simple.)  Support for this feature can be verified by using the
    '-list-features' command, which should contain "simple-values-ref-types".
 
+** The -break-insert command now accepts a '-g thread-group-id' option
+   to allow for the creation of inferior-specific breakpoints.
+
+** The bkpt tuple, which appears in breakpoint-created notifications,
+   and in the result of the -break-insert command can now include an
+   optional 'inferior' field for both the main breakpoint, and each
+   location, when the breakpoint is inferior-specific.
+
 * Python API
 
   ** gdb.ThreadExitedEvent added.  Emits a ThreadEvent.
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index da6c8de9d14..804871ac5c7 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -98,7 +98,7 @@ static void create_breakpoints_sal (struct gdbarch *,
 				    gdb::unique_xmalloc_ptr<char>,
 				    gdb::unique_xmalloc_ptr<char>,
 				    enum bptype,
-				    enum bpdisp, int, int,
+				    enum bpdisp, int, int, int,
 				    int,
 				    int, int, int, unsigned);
 
@@ -322,6 +322,9 @@ struct momentary_breakpoint : public code_breakpoint
     disposition = disp_donttouch;
     frame_id = frame_id_;
     thread = thread_;
+
+    /* The inferior should have been set by the parent constructor.  */
+    gdb_assert (inferior == -1);
   }
 
   void re_set () override;
@@ -1462,13 +1465,15 @@ breakpoint_set_silent (struct breakpoint *b, int silent)
 void
 breakpoint_set_thread (struct breakpoint *b, int thread)
 {
-  /* It is invalid to set the thread field to anything other than -1 (which
-     means no thread restriction) if a task restriction is already in
-     place.  */
-  gdb_assert (thread == -1 || b->task == -1);
+  /* THREAD should be -1, meaning no thread restriction, or it should be a
+     valid global thread-id, which are greater than zero.  */
+  gdb_assert (thread == -1 || thread > 0);
 
-  int old_thread = b->thread;
+  /* It is not valid to set a thread restriction for a breakpoint that
+     already has task or inferior restriction.  */
+  gdb_assert (thread == -1 || (b->task == -1 && b->inferior == -1));
 
+  int old_thread = b->thread;
   b->thread = thread;
   if (old_thread != thread)
     notify_breakpoint_modified (b);
@@ -1476,16 +1481,37 @@ breakpoint_set_thread (struct breakpoint *b, int thread)
 
 /* See breakpoint.h.  */
 
+void
+breakpoint_set_inferior (struct breakpoint *b, int inferior)
+{
+  /* INFERIOR should be -1, meaning no inferior restriction, or it should
+     be a valid inferior number, which are greater than zero.  */
+  gdb_assert (inferior == -1 || inferior > 0);
+
+  /* It is not valid to set an inferior restriction for a breakpoint that
+     already has a task or thread restriction.  */
+  gdb_assert (inferior == -1 || (b->task == -1 && b->thread == -1));
+
+  int old_inferior = b->inferior;
+  b->inferior = inferior;
+  if (old_inferior != inferior)
+    gdb::observers::breakpoint_modified.notify (b);
+}
+
+/* See breakpoint.h.  */
+
 void
 breakpoint_set_task (struct breakpoint *b, int task)
 {
-  /* It is invalid to set the task field to anything other than -1 (which
-     means no task restriction) if a thread restriction is already in
-     place.  */
-  gdb_assert (task == -1 || b->thread == -1);
+  /* TASK should be -1, meaning no task restriction, or it should be a
+     valid task-id, which are greater than zero.  */
+  gdb_assert (task == -1 || task > 0);
 
-  int old_task = b->task;
+  /* It is not valid to set a task restriction for a breakpoint that
+     already has a thread or inferior restriction.  */
+  gdb_assert (task == -1 || (b->thread == -1 && b->inferior == -1));
 
+  int old_task = b->task;
   b->task = task;
   if (old_task != task)
     notify_breakpoint_modified (b);
@@ -3163,6 +3189,12 @@ insert_breakpoint_locations (void)
 	  && !valid_global_thread_id (bl->owner->thread))
 	continue;
 
+      /* Or inferior specific breakpoints if the inferior no longer
+	 exists.  */
+      if (bl->owner->inferior != -1
+	  && !valid_global_inferior_id (bl->owner->inferior))
+	continue;
+
       switch_to_program_space_and_thread (bl->pspace);
 
       /* For targets that support global breakpoints, there's no need
@@ -3263,6 +3295,29 @@ Thread-specific breakpoint %d deleted - thread %s no longer in the thread list.\
     }
 }
 
+/* Called when inferior INF has been removed from GDB.  Remove associated
+   per-inferior breakpoints.  */
+
+static void
+remove_inferior_breakpoints (struct inferior *inf)
+{
+  for (breakpoint &b : all_breakpoints_safe ())
+    {
+      if (b.inferior == inf->num && user_breakpoint_p (&b))
+	{
+	  /* Tell the user the breakpoint has been deleted.  But only for
+	     breakpoints that would not normally have been deleted at the
+	     next stop anyway.  */
+	  if (b.disposition != disp_del
+	      && b.disposition != disp_del_at_next_stop)
+	    gdb_printf (_("\
+Inferior-specific breakpoint %d deleted - inferior %d has been removed.\n"),
+			b.number, inf->num);
+	  delete_breakpoint (&b);
+       }
+    }
+}
+
 /* See breakpoint.h.  */
 
 void
@@ -5467,6 +5522,7 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread)
      evaluating the condition if this isn't the specified
      thread/task.  */
   if ((b->thread != -1 && b->thread != thread->global_num)
+      || (b->inferior != -1 && b->inferior != thread->inf->num)
       || (b->task != -1 && b->task != ada_get_task_number (thread)))
     {
       infrun_debug_printf ("incorrect thread or task, not stopping");
@@ -6498,6 +6554,8 @@ print_one_breakpoint_location (struct breakpoint *b,
 	uiout->field_signed ("thread", b->thread);
       else if (b->task != -1)
 	uiout->field_signed ("task", b->task);
+      else if (b->inferior != -1)
+	uiout->field_signed ("inferior", b->inferior);
     }
 
   uiout->text ("\n");
@@ -6560,6 +6618,13 @@ print_one_breakpoint_location (struct breakpoint *b,
       uiout->text ("\n");
     }
 
+  if (!part_of_multiple && b->inferior != -1)
+    {
+      uiout->text ("\tstop only in inferior ");
+      uiout->field_signed ("inferior", b->inferior);
+      uiout->text ("\n");
+    }
+
   if (!part_of_multiple)
     {
       if (b->hit_count)
@@ -7535,7 +7600,10 @@ delete_longjmp_breakpoint (int thread)
     if (b.type == bp_longjmp || b.type == bp_exception)
       {
 	if (b.thread == thread)
-	  delete_breakpoint (&b);
+	  {
+	    gdb_assert (b.inferior == -1);
+	    delete_breakpoint (&b);
+	  }
       }
 }
 
@@ -7546,7 +7614,10 @@ delete_longjmp_breakpoint_at_next_stop (int thread)
     if (b.type == bp_longjmp || b.type == bp_exception)
       {
 	if (b.thread == thread)
-	  b.disposition = disp_del_at_next_stop;
+	  {
+	    gdb_assert (b.inferior == -1);
+	    b.disposition = disp_del_at_next_stop;
+	  }
       }
 }
 
@@ -7603,6 +7674,7 @@ check_longjmp_breakpoint_for_call_dummy (struct thread_info *tp)
     {
       if (b.type == bp_longjmp_call_dummy && b.thread == tp->global_num)
 	{
+	  gdb_assert (b.inferior == -1);
 	  struct breakpoint *dummy_b = b.related_breakpoint;
 
 	  /* Find the bp_call_dummy breakpoint in the list of breakpoints
@@ -8448,7 +8520,8 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
 				  gdb::unique_xmalloc_ptr<char> cond_string_,
 				  gdb::unique_xmalloc_ptr<char> extra_string_,
 				  enum bpdisp disposition_,
-				  int thread_, int task_, int ignore_count_,
+				  int thread_, int task_, int inferior_,
+				  int ignore_count_,
 				  int from_tty,
 				  int enabled_, unsigned flags,
 				  int display_canonical_)
@@ -8472,10 +8545,14 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
 
   gdb_assert (!sals.empty ());
 
-  /* At most one of thread or task can be set on any breakpoint.  */
-  gdb_assert (thread == -1 || task == -1);
+  /* At most one of thread, task, or inferior can be set on any breakpoint.  */
+  gdb_assert (((thread == -1 ? 0 : 1)
+	       + (task == -1 ? 0 : 1)
+	       + (inferior == -1 ? 0 : 1)) <= 1);
+
   thread = thread_;
   task = task_;
+  inferior = inferior_;
 
   cond_string = std::move (cond_string_);
   extra_string = std::move (extra_string_);
@@ -8577,7 +8654,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
 		       gdb::unique_xmalloc_ptr<char> cond_string,
 		       gdb::unique_xmalloc_ptr<char> extra_string,
 		       enum bptype type, enum bpdisp disposition,
-		       int thread, int task, int ignore_count,
+		       int thread, int task, int inferior, int ignore_count,
 		       int from_tty,
 		       int enabled, int internal, unsigned flags,
 		       int display_canonical)
@@ -8591,7 +8668,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
 				std::move (cond_string),
 				std::move (extra_string),
 				disposition,
-				thread, task, ignore_count,
+				thread, task, inferior, ignore_count,
 				from_tty,
 				enabled, flags,
 				display_canonical);
@@ -8620,7 +8697,8 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
 			gdb::unique_xmalloc_ptr<char> cond_string,
 			gdb::unique_xmalloc_ptr<char> extra_string,
 			enum bptype type, enum bpdisp disposition,
-			int thread, int task, int ignore_count,
+			int thread, int task, int inferior,
+			int ignore_count,
 			int from_tty,
 			int enabled, int internal, unsigned flags)
 {
@@ -8644,7 +8722,7 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
 			     std::move (cond_string),
 			     std::move (extra_string),
 			     type, disposition,
-			     thread, task, ignore_count,
+			     thread, task, inferior, ignore_count,
 			     from_tty, enabled, internal, flags,
 			     canonical->special_display);
     }
@@ -8774,21 +8852,26 @@ check_fast_tracepoint_sals (struct gdbarch *gdbarch,
     }
 }
 
-/* Given TOK, a string specification of condition and thread, as
-   accepted by the 'break' command, extract the condition
-   string and thread number and set *COND_STRING and *THREAD.
-   PC identifies the context at which the condition should be parsed.
-   If no condition is found, *COND_STRING is set to NULL.
-   If no thread is found, *THREAD is set to -1.  */
+/* Given TOK, a string specification of condition and thread, as accepted
+   by the 'break' command, extract the condition string into *COND_STRING.
+   If no condition string is found then *COND_STRING is set to nullptr.
+
+   If the breakpoint specification has an associated thread, task, or
+   inferior, these are extracted into *THREAD, *TASK, and *INFERIOR
+   respectively, otherwise these arguments are set to -1 (for THREAD and
+   INFERIOR) or 0 (for TASK).
+
+   PC identifies the context at which the condition should be parsed.  */
 
 static void
 find_condition_and_thread (const char *tok, CORE_ADDR pc,
 			   gdb::unique_xmalloc_ptr<char> *cond_string,
-			   int *thread, int *task,
+			   int *thread, int *inferior, int *task,
 			   gdb::unique_xmalloc_ptr<char> *rest)
 {
   cond_string->reset ();
   *thread = -1;
+  *inferior = -1;
   *task = -1;
   rest->reset ();
   bool force = false;
@@ -8805,7 +8888,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
       if ((*tok == '"' || *tok == ',') && rest)
 	{
 	  rest->reset (savestring (tok, strlen (tok)));
-	  return;
+	  break;
 	}
 
       end_tok = skip_to_space (tok);
@@ -8845,6 +8928,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  if (*task != -1)
 	    error (_("You can specify only one of thread or task."));
 
+	  if (*inferior != -1)
+	    error (_("You can specify only one of inferior or thread."));
+
 	  tok = end_tok + 1;
 	  thr = parse_thread_id (tok, &tmptok);
 	  if (tok == tmptok)
@@ -8852,6 +8938,26 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  *thread = thr->global_num;
 	  tok = tmptok;
 	}
+      else if (toklen >= 1 && strncmp (tok, "inferior", toklen) == 0)
+	{
+	  if (*inferior != -1)
+	    error(_("You can specify only one inferior."));
+
+	  if (*task != -1)
+	    error (_("You can specify only one of inferior or task."));
+
+	  if (*thread != -1)
+	    error (_("You can specify only one of inferior or thread."));
+
+	  char *tmptok;
+	  tok = end_tok + 1;
+	  *inferior = strtol (tok, &tmptok, 0);
+	  if (tok == tmptok)
+	    error (_("Junk after inferior keyword."));
+	  if (!valid_global_inferior_id (*inferior))
+	    error (_("Unknown inferior number %d."), *inferior);
+	  tok = tmptok;
+	}
       else if (toklen >= 1 && strncmp (tok, "task", toklen) == 0)
 	{
 	  char *tmptok;
@@ -8862,6 +8968,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  if (*thread != -1)
 	    error (_("You can specify only one of thread or task."));
 
+	  if (*inferior != -1)
+	    error (_("You can specify only one of inferior or task."));
+
 	  tok = end_tok + 1;
 	  *task = strtol (tok, &tmptok, 0);
 	  if (tok == tmptok)
@@ -8873,7 +8982,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
       else if (rest)
 	{
 	  rest->reset (savestring (tok, strlen (tok)));
-	  return;
+	  break;
 	}
       else
 	error (_("Junk at end of arguments."));
@@ -8889,7 +8998,7 @@ static void
 find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
 				    const char *input,
 				    gdb::unique_xmalloc_ptr<char> *cond_string,
-				    int *thread, int *task,
+				    int *thread, int *inferior, int *task,
 				    gdb::unique_xmalloc_ptr<char> *rest)
 {
   int num_failures = 0;
@@ -8897,6 +9006,7 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
     {
       gdb::unique_xmalloc_ptr<char> cond;
       int thread_id = -1;
+      int inferior_id = -1;
       int task_id = -1;
       gdb::unique_xmalloc_ptr<char> remaining;
 
@@ -8909,11 +9019,16 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
       try
 	{
 	  find_condition_and_thread (input, sal.pc, &cond, &thread_id,
-				     &task_id, &remaining);
+				     &inferior_id, &task_id, &remaining);
 	  *cond_string = std::move (cond);
-	  /* At most one of thread or task can be set.  */
-	  gdb_assert (thread_id == -1 || task_id == -1);
+	  /* A value of -1 indicates that these fields are unset.  At most
+	     one of these fields should be set (to a value other than -1)
+	     at this point.  */
+	  gdb_assert (((thread_id == -1 ? 1 : 0)
+		       + (task_id == -1 ? 1 : 0)
+		       + (inferior_id == -1 ? 1 : 0)) >= 2);
 	  *thread = thread_id;
+	  *inferior = inferior_id;
 	  *task = task_id;
 	  *rest = std::move (remaining);
 	  break;
@@ -9003,7 +9118,8 @@ int
 create_breakpoint (struct gdbarch *gdbarch,
 		   location_spec *locspec,
 		   const char *cond_string,
-		   int thread, const char *extra_string,
+		   int thread, int inferior,
+		   const char *extra_string,
 		   bool force_condition, int parse_extra,
 		   int tempflag, enum bptype type_wanted,
 		   int ignore_count,
@@ -9017,6 +9133,10 @@ create_breakpoint (struct gdbarch *gdbarch,
   int task = -1;
   int prev_bkpt_count = breakpoint_count;
 
+  gdb_assert (thread == -1 || thread > 0);
+  gdb_assert (inferior == -1 || inferior > 0);
+  gdb_assert (thread == -1 || inferior == -1);
+
   gdb_assert (ops != NULL);
 
   /* If extra_string isn't useful, set it to NULL.  */
@@ -9092,7 +9212,8 @@ create_breakpoint (struct gdbarch *gdbarch,
 	  const linespec_sals &lsal = canonical.lsals[0];
 
 	  find_condition_and_thread_for_sals (lsal.sals, extra_string,
-					      &cond, &thread, &task, &rest);
+					      &cond, &thread, &inferior,
+					      &task, &rest);
 	  cond_string_copy = std::move (cond);
 	  extra_string_copy = std::move (rest);
 	}
@@ -9142,7 +9263,7 @@ create_breakpoint (struct gdbarch *gdbarch,
 				   std::move (extra_string_copy),
 				   type_wanted,
 				   tempflag ? disp_del : disp_donttouch,
-				   thread, task, ignore_count,
+				   thread, task, inferior, ignore_count,
 				   from_tty, enabled, internal, flags);
     }
   else
@@ -9211,7 +9332,9 @@ break_command_1 (const char *arg, int flag, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL,
+		     -1 /* thread */, -1 /* inferior */,
+		     arg, false, 1 /* parse arg */,
 		     tempflag, type_wanted,
 		     0 /* Ignore count */,
 		     pending_break_support,
@@ -9323,7 +9446,8 @@ dprintf_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1,
+		     arg, false, 1 /* parse arg */,
 		     0, bp_dprintf,
 		     0 /* Ignore count */,
 		     pending_break_support,
@@ -10068,6 +10192,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
   const char *cond_end = NULL;
   enum bptype bp_type;
   int thread = -1;
+  int inferior = -1;
   /* Flag to indicate whether we are going to use masks for
      the hardware watchpoint.  */
   bool use_mask = false;
@@ -10122,12 +10247,13 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	      if (task != -1)
 		error (_("You can specify only one of thread or task."));
 
+	      if (inferior != -1)
+		error (_("You can specify only one of inferior or thread."));
+
 	      /* Extract the thread ID from the next token.  */
 	      thr = parse_thread_id (value_start, &endp);
-
-	      /* Check if the user provided a valid thread ID.  */
-	      if (*endp != ' ' && *endp != '\t' && *endp != '\0')
-		invalid_thread_id_error (value_start);
+	      if (value_start == endp)
+		error (_("Junk after thread keyword."));
 
 	      thread = thr->global_num;
 	    }
@@ -10141,12 +10267,20 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	      if (thread != -1)
 		error (_("You can specify only one of thread or task."));
 
+	      if (inferior != -1)
+		error (_("You can specify only one of inferior or task."));
+
 	      task = strtol (value_start, &tmp, 0);
 	      if (tmp == value_start)
 		error (_("Junk after task keyword."));
 	      if (!valid_task_id (task))
 		error (_("Unknown task %d."), task);
 	    }
+	  else if (toklen == 8 && startswith (tok, "inferior"))
+	    {
+	      /* Support for watchpoints will be added in a later commit.  */
+	      error (_("Cannot use 'inferior' keyword with watchpoints"));
+	    }
 	  else if (toklen == 4 && startswith (tok, "mask"))
 	    {
 	      /* We've found a "mask" token, which means the user wants to
@@ -10319,6 +10453,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
   /* At most one of thread or task can be set on a watchpoint.  */
   gdb_assert (thread == -1 || task == -1);
   w->thread = thread;
+  w->inferior = inferior;
   w->task = task;
   w->disposition = disp_donttouch;
   w->pspace = current_program_space;
@@ -12253,7 +12388,8 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
 				      enum bptype type_wanted,
 				      enum bpdisp disposition,
 				      int thread,
-				      int task, int ignore_count,
+				      int task, int inferior,
+				      int ignore_count,
 				      int from_tty, int enabled,
 				      int internal, unsigned flags)
 {
@@ -12279,7 +12415,7 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
 			 std::move (cond_string),
 			 std::move (extra_string),
 			 disposition,
-			 thread, task, ignore_count,
+			 thread, task, inferior, ignore_count,
 			 from_tty, enabled, flags,
 			 canonical->special_display));
 
@@ -12898,10 +13034,11 @@ code_breakpoint::location_spec_to_sals (location_spec *locspec,
       if (condition_not_parsed && extra_string != NULL)
 	{
 	  gdb::unique_xmalloc_ptr<char> local_cond, local_extra;
-	  int local_thread, local_task;
+	  int local_thread, local_task, local_inferior;
 
 	  find_condition_and_thread_for_sals (sals, extra_string.get (),
 					      &local_cond, &local_thread,
+					      &local_inferior,
 					      &local_task, &local_extra);
 	  gdb_assert (cond_string == nullptr);
 	  if (local_cond != nullptr)
@@ -13775,7 +13912,7 @@ trace_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     bp_tracepoint /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13793,7 +13930,7 @@ ftrace_command (const char *arg, int from_tty)
 						      current_language);
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     bp_fast_tracepoint /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13831,7 +13968,7 @@ strace_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     type /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13900,7 +14037,7 @@ create_tracepoint_from_upload (struct uploaded_tp *utp)
 						      current_language);
   if (!create_breakpoint (get_current_arch (),
 			  locspec.get (),
-			  utp->cond_string.get (), -1, addr_str,
+			  utp->cond_string.get (), -1, -1, addr_str,
 			  false /* force_condition */,
 			  0 /* parse cond/thread */,
 			  0 /* tempflag */,
@@ -14988,4 +15125,6 @@ This is useful for formatted output in user-defined commands."));
 					   "breakpoint");
   gdb::observers::thread_exit.attach (remove_threaded_breakpoints,
 				      "breakpoint");
+  gdb::observers::inferior_removed.attach (remove_inferior_breakpoints,
+					   "breakpoint");
 }
diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
index da150585f73..18fd90341c7 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -579,7 +579,7 @@ struct breakpoint_ops
 				  struct linespec_result *,
 				  gdb::unique_xmalloc_ptr<char>,
 				  gdb::unique_xmalloc_ptr<char>,
-				  enum bptype, enum bpdisp, int, int,
+				  enum bptype, enum bpdisp, int, int, int,
 				  int, int, int, int, unsigned);
 };
 
@@ -859,6 +859,10 @@ struct breakpoint : public intrusive_list_node<breakpoint>
      care.  */
   int thread = -1;
 
+  /* Inferior number for inferior-specific breakpoint, or -1 if this
+     breakpoint is for all inferiors.  */
+  int inferior = -1;
+
   /* Ada task number for task-specific breakpoint, or -1 if don't
      care.  */
   int task = -1;
@@ -917,7 +921,7 @@ struct code_breakpoint : public breakpoint
 		   gdb::unique_xmalloc_ptr<char> cond_string,
 		   gdb::unique_xmalloc_ptr<char> extra_string,
 		   enum bpdisp disposition,
-		   int thread, int task, int ignore_count,
+		   int thread, int task, int inferior, int ignore_count,
 		   int from_tty,
 		   int enabled, unsigned flags,
 		   int display_canonical);
@@ -1597,6 +1601,7 @@ enum breakpoint_create_flags
 extern int create_breakpoint (struct gdbarch *gdbarch,
 			      struct location_spec *locspec,
 			      const char *cond_string, int thread,
+			      int inferior,
 			      const char *extra_string,
 			      bool force_condition,
 			      int parse_extra,
@@ -1740,6 +1745,11 @@ extern void breakpoint_set_silent (struct breakpoint *b, int silent);
 
 extern void breakpoint_set_thread (struct breakpoint *b, int thread);
 
+/* Set the inferior for breakpoint B to INFERIOR.  If INFERIOR is -1, make
+   the breakpoint work for any inferior.  */
+
+extern void breakpoint_set_inferior (struct breakpoint *b, int inferior);
+
 /* Set the task for this breakpoint.  If TASK is -1, make the breakpoint
    work for any task.  Passing a value other than -1 for TASK should only
    be done if b->thread is -1; it is not valid to try and set both a thread
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index b10c06ae91f..d8679181215 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3517,6 +3517,57 @@
 space as a result of inferior 1 having executed a @code{vfork} call.
 @end table
 
+@menu
+* Inferior-Specific Breakpoints::	Controlling breakpoints
+@end menu
+
+@node Inferior-Specific Breakpoints
+@subsection Inferior-Specific Breakpoints
+
+When debugging multiple inferiors, you can choose whether to set
+breakpoints for all inferiors, or for a particular inferior.
+
+@table @code
+@cindex breakpoints and inferiors
+@cindex inferior-specific breakpoints
+@kindex break @dots{} inferior @var{inferior-id}
+@item break @var{locspec} inferior @var{inferior-id}
+@itemx break @var{locspec} inferior @var{inferior-id} if @dots{}
+@var{locspec} specifies a code location or locations in your program.
+@xref{Location Specifications}, for details.
+
+Use the qualifier @samp{inferior @var{inferior-id}} with a breakpoint
+command to specify that you only want @value{GDBN} to stop when a
+particular inferior reaches this breakpoint.  The @var{inferior-id}
+specifier is one of the inferior identifiers assigned by @value{GDBN},
+shown in the first column of the @samp{info inferiors} output.
+
+If you do not specify @samp{inferior @var{inferior-id}} when you set a
+breakpoint, the breakpoint applies to @emph{all} inferiors of your
+program.
+
+You can use the @code{inferior} qualifier on conditional breakpoints as
+well; in this case, place @samp{inferior @var{inferior-id}} before or
+after the breakpoint condition, like this:
+
+@smallexample
+(@value{GDBP}) break frik.c:13 inferior 2 if bartab > lim
+@end smallexample
+@end table
+
+Inferior-specific breakpoints are automatically deleted when the
+corresponding inferior is removed from @value{GDBN}.  For example:
+
+@smallexample
+(@value{GDBP}) remove-inferiors 2
+Inferior-specific breakpoint 3 deleted - inferior 2 has been removed.
+@end smallexample
+
+A breakpoint can't be both inferior-specific and thread-specific
+(@pxref{Thread-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{inferior}, @code{thread}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
 @node Threads
 @section Debugging Programs with Multiple Threads
 
@@ -4480,8 +4531,9 @@
 situation.
 
 It is also possible to insert a breakpoint that will stop the program
-only if a specific thread (@pxref{Thread-Specific Breakpoints})
-or a specific task (@pxref{Ada Tasks}) hits that breakpoint.
+only if a specific thread (@pxref{Thread-Specific Breakpoints}),
+specific inferior (@pxref{Inferior-Specific Breakpoints}), or a
+specific task (@pxref{Ada Tasks}) hits that breakpoint.
 
 @item break
 When called without any arguments, @code{break} sets a breakpoint at
@@ -7344,9 +7396,14 @@
 Process}), or if @value{GDBN} loses the remote connection
 (@pxref{Remote Debugging}), etc.  Note that with some targets,
 @value{GDBN} is only able to detect a thread has exited when the user
-explictly asks for the thread list with the @code{info threads}
+explicitly asks for the thread list with the @code{info threads}
 command.
 
+A breakpoint can't be both thread-specific and inferior-specific
+(@pxref{Inferior-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{thread}, @code{inferior}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
 @node Interrupted System Calls
 @subsection Interrupted System Calls 
 
@@ -31612,6 +31669,10 @@
 If this is a thread-specific breakpoint, then this identifies the
 thread in which the breakpoint can trigger.
 
+@item inferior
+If this is an inferior-specific breakpoint, this this identifies the
+inferior in which the breakpoint can trigger.
+
 @item task
 If this breakpoint is restricted to a particular Ada task, then this
 field will hold the task identifier.
@@ -32203,7 +32264,7 @@
 @smallexample
  -break-insert [ -t ] [ -h ] [ -f ] [ -d ] [ -a ] [ --qualified ]
     [ -c @var{condition} ] [ --force-condition ] [ -i @var{ignore-count} ]
-    [ -p @var{thread-id} ] [ @var{locspec} ]
+    [ -p @var{thread-id} ] [ -g @var{thread-group-id} ] [ @var{locspec} ]
 @end smallexample
 
 @noindent
@@ -32269,6 +32330,9 @@
 time the breakpoint is requested.  Breakpoints created with a
 @var{thread-id} will automatically be deleted when the corresponding
 thread exits.
+@item -g @var{thread-group-id}
+Restrict the breakpoint to the thread group with the specified
+@var{thread-group-id}.
 @item --qualified
 This option makes @value{GDBN} interpret a function name specified as
 a complete fully-qualified name.
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 9a342f34bf0..3cb93a69463 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -3430,7 +3430,10 @@
 A @code{gdb.Inferior} object has the following attributes:
 
 @defvar Inferior.num
-ID of inferior, as assigned by GDB.
+ID of inferior, as assigned by @value{GDBN}.  You can use this to make
+Python breakpoints inferior-specific, for example
+(@pxref{python_breakpoint_inferior,,The Breakpoint.inferior
+attribute}).
 @end defvar
 
 @anchor{gdbpy_inferior_connection}
@@ -6316,9 +6319,24 @@
 
 @anchor{python_breakpoint_thread}
 @defvar Breakpoint.thread
-If the breakpoint is thread-specific, this attribute holds the
-thread's global id.  If the breakpoint is not thread-specific, this
-attribute is @code{None}.  This attribute is writable.
+If the breakpoint is thread-specific (@pxref{Thread-Specific
+Breakpoints}), this attribute holds the thread's global id.  If the
+breakpoint is not thread-specific, this attribute is @code{None}.
+This attribute is writable.
+
+Only one of @code{Breakpoint.thread} or @code{Breakpoint.inferior} can
+be set to a valid id at any time, that is, a breakpoint can be thread
+specific, or inferior specific, but not both.
+@end defvar
+
+@anchor{python_breakpoint_inferior}
+@defvar Breakpoint.inferior
+If the breakpoint is inferior-specific (@pxref{Inferior-Specific
+Breakpoints}), this attribute holds the inferior's id.  If the
+breakpoint is not inferior-specific, this attribute is @code{None}.
+
+This attribute can be written for breakpoints of type
+@code{gdb.BP_BREAKPOINT} and @code{gdb.BP_HARDWARE_BREAKPOINT}.
 @end defvar
 
 @defvar Breakpoint.task
diff --git a/gdb/guile/scm-breakpoint.c b/gdb/guile/scm-breakpoint.c
index 6c6dacb3883..59254646bcc 100644
--- a/gdb/guile/scm-breakpoint.c
+++ b/gdb/guile/scm-breakpoint.c
@@ -465,7 +465,7 @@ gdbscm_register_breakpoint_x (SCM self)
 	    const breakpoint_ops *ops =
 	      breakpoint_ops_for_location_spec (locspec.get (), false);
 	    create_breakpoint (get_current_arch (),
-			       locspec.get (), NULL, -1, NULL, false,
+			       locspec.get (), NULL, -1, -1, NULL, false,
 			       0,
 			       temporary, bp_breakpoint,
 			       0,
@@ -784,6 +784,11 @@ gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue)
   else
     SCM_ASSERT_TYPE (0, newvalue, SCM_ARG2, FUNC_NAME, _("integer or #f"));
 
+  if (bp_smob->bp->inferior != -1 && id != -1)
+    scm_misc_error (FUNC_NAME,
+		    _("Cannot have both 'thread' and 'inferior' "
+		      "conditions on a breakpoint"), SCM_EOL);
+
   breakpoint_set_thread (bp_smob->bp, id);
 
   return SCM_UNSPECIFIED;
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 15702f84894..8144331bfe6 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -416,17 +416,10 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
   if (run_how == RUN_STOP_AT_MAIN)
     {
       /* To avoid other inferiors hitting this breakpoint, make it
-	 inferior-specific using a condition.  A better solution would be to
-	 have proper inferior-specific breakpoint support, in the breakpoint
-	 machinery.  We could then avoid inserting a breakpoint in the program
-	 spaces unrelated to this inferior.  */
-      const char *op
-	= ((current_language->la_language == language_ada
-	    || current_language->la_language == language_pascal
-	    || current_language->la_language == language_m2) ? "=" : "==");
-      std::string arg = string_printf
-	("-qualified %s if $_inferior %s %d", main_name (), op,
-	 current_inferior ()->num);
+	 inferior-specific.  */
+      std::string arg = string_printf ("-qualified %s inferior %d",
+				       main_name (),
+				       current_inferior ()->num);
       tbreak_command (arg.c_str (), 0);
     }
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index caa8e4d494a..9af0e367c6f 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -826,4 +826,15 @@ extern void print_selected_inferior (struct ui_out *uiout);
 extern void switch_to_inferior_and_push_target
   (inferior *new_inf, bool no_connection, inferior *org_inf);
 
+/* Return true if ID is a valid global inferior number.  */
+
+inline bool
+valid_global_inferior_id (int id)
+{
+  for (inferior *inf : all_inferiors ())
+    if (inf->num == id)
+      return true;
+  return false;
+}
+
 #endif /* !defined (INFERIOR_H) */
diff --git a/gdb/linespec.c b/gdb/linespec.c
index 7d969f37fbf..695dc6515ee 100644
--- a/gdb/linespec.c
+++ b/gdb/linespec.c
@@ -254,9 +254,9 @@ enum linespec_token_type
 
 /* List of keywords.  This is NULL-terminated so that it can be used
    as enum completer.  */
-const char * const linespec_keywords[] = { "if", "thread", "task", "-force-condition", NULL };
+const char * const linespec_keywords[] = { "if", "thread", "task", "inferior", "-force-condition", NULL };
 #define IF_KEYWORD_INDEX 0
-#define FORCE_KEYWORD_INDEX 3
+#define FORCE_KEYWORD_INDEX 4
 
 /* A token of the linespec lexer  */
 
diff --git a/gdb/mi/mi-cmd-break.c b/gdb/mi/mi-cmd-break.c
index 0777fcbd35e..975bc1c5dde 100644
--- a/gdb/mi/mi-cmd-break.c
+++ b/gdb/mi/mi-cmd-break.c
@@ -173,6 +173,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
   int hardware = 0;
   int temp_p = 0;
   int thread = -1;
+  int thread_group = -1;
   int ignore_count = 0;
   const char *condition = NULL;
   int pending = 0;
@@ -191,7 +192,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
   enum opt
     {
       HARDWARE_OPT, TEMP_OPT, CONDITION_OPT,
-      IGNORE_COUNT_OPT, THREAD_OPT, PENDING_OPT, DISABLE_OPT,
+      IGNORE_COUNT_OPT, THREAD_OPT, THREAD_GROUP_OPT,
+      PENDING_OPT, DISABLE_OPT,
       TRACEPOINT_OPT,
       FORCE_CONDITION_OPT,
       QUALIFIED_OPT,
@@ -205,6 +207,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
     {"c", CONDITION_OPT, 1},
     {"i", IGNORE_COUNT_OPT, 1},
     {"p", THREAD_OPT, 1},
+    {"g", THREAD_GROUP_OPT, 1},
     {"f", PENDING_OPT, 0},
     {"d", DISABLE_OPT, 0},
     {"a", TRACEPOINT_OPT, 0},
@@ -247,6 +250,9 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
 	  if (!valid_global_thread_id (thread))
 	    error (_("Unknown thread %d."), thread);
 	  break;
+	case THREAD_GROUP_OPT:
+	  thread_group = mi_parse_thread_group_id (oarg);
+	  break;
 	case PENDING_OPT:
 	  pending = 1;
 	  break;
@@ -360,7 +366,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
 	error (_("Garbage '%s' at end of location"), address);
     }
 
-  create_breakpoint (get_current_arch (), locspec.get (), condition, thread,
+  create_breakpoint (get_current_arch (), locspec.get (), condition,
+		     thread, thread_group,
 		     extra_string.c_str (),
 		     force_condition,
 		     0 /* condition and thread are valid.  */,
diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c
index 9108cf505c7..402b5a7212f 100644
--- a/gdb/mi/mi-main.c
+++ b/gdb/mi/mi-main.c
@@ -1755,8 +1755,7 @@ mi_cmd_remove_inferior (const char *command, const char *const *argv, int argc)
   if (argc != 1)
     error (_("-remove-inferior should be passed a single argument"));
 
-  if (sscanf (argv[0], "i%d", &id) != 1)
-    error (_("the thread group id is syntactically invalid"));
+  id = mi_parse_thread_group_id (argv[0]);
 
   inf_to_remove = find_inferior_id (id);
   if (inf_to_remove == NULL)
@@ -2784,6 +2783,21 @@ mi_cmd_complete (const char *command, const char *const *argv, int argc)
 		       result.number_matches == max_completions ? "1" : "0");
 }
 
+/* See mi-main.h.  */
+int
+mi_parse_thread_group_id (const char *id)
+{
+  if (*id != 'i')
+    error (_("thread group id should start with an 'i'"));
+
+  char *end;
+  long num = strtol (id + 1, &end, 10);
+
+  if (*end != '\0' || num > INT_MAX)
+    error (_("invalid thread group id '%s'"), id);
+
+  return (int) num;
+}
 
 void _initialize_mi_main ();
 void
diff --git a/gdb/mi/mi-main.h b/gdb/mi/mi-main.h
index cb17921c150..b35544baef1 100644
--- a/gdb/mi/mi-main.h
+++ b/gdb/mi/mi-main.h
@@ -75,4 +75,10 @@ extern void mi_cmd_fix_breakpoint_script_output (const char *command,
 						 const char *const *argv,
 						 int argc);
 
+/* Parse a thread-group-id from ID, and return the integer part of the
+   ID.  A valid thread-group-id is the character 'i' followed by an
+   integer that is greater than zero.  */
+
+extern int mi_parse_thread_group_id (const char *id);
+
 #endif /* MI_MI_MAIN_H */
diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
index fa1570e1a04..cb064515690 100644
--- a/gdb/python/py-breakpoint.c
+++ b/gdb/python/py-breakpoint.c
@@ -288,11 +288,86 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure)
       return -1;
     }
 
+  if (self_bp->bp->inferior != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'thread' and 'inferior' "
+			 "conditions on a breakpoint"));
+      return -1;
+    }
+
   breakpoint_set_thread (self_bp->bp, id);
 
   return 0;
 }
 
+/* Python function to set the inferior of a breakpoint.  */
+
+static int
+bppy_set_inferior (PyObject *self, PyObject *newvalue, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+  long id;
+
+  BPPY_SET_REQUIRE_VALID (self_bp);
+
+  if (newvalue == NULL)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("Cannot delete 'inferior' attribute."));
+      return -1;
+    }
+  else if (PyLong_Check (newvalue))
+    {
+      if (!gdb_py_int_as_long (newvalue, &id))
+	return -1;
+
+      if (!valid_global_inferior_id (id))
+	{
+	  PyErr_SetString (PyExc_RuntimeError,
+			   _("Invalid inferior ID."));
+	  return -1;
+	}
+    }
+  else if (newvalue == Py_None)
+    id = -1;
+  else
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("The value of 'inferior' must be an integer or None."));
+      return -1;
+    }
+
+  if (self_bp->bp->type != bp_breakpoint
+      && self_bp->bp->type != bp_hardware_breakpoint)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot set 'inferior' attribute on a gdb.Breakpoint "
+			 "of this type"));
+      return -1;
+    }
+
+  if (self_bp->bp->thread != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'thread' and 'inferior' conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
+  if (self_bp->bp->task != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'task' and 'inferior' conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
+  breakpoint_set_inferior (self_bp->bp, id);
+
+  return 0;
+}
+
 /* Python function to set the (Ada) task of a breakpoint.  */
 static int
 bppy_set_task (PyObject *self, PyObject *newvalue, void *closure)
@@ -704,6 +779,20 @@ bppy_get_thread (PyObject *self, void *closure)
   return gdb_py_object_from_longest (self_bp->bp->thread).release ();
 }
 
+/* Python function to get the breakpoint's inferior ID.  */
+static PyObject *
+bppy_get_inferior (PyObject *self, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+
+  BPPY_REQUIRE_VALID (self_bp);
+
+  if (self_bp->bp->inferior == -1)
+    Py_RETURN_NONE;
+
+  return gdb_py_object_from_longest (self_bp->bp->inferior).release ();
+}
+
 /* Python function to get the breakpoint's task ID (in Ada).  */
 static PyObject *
 bppy_get_task (PyObject *self, void *closure)
@@ -942,7 +1031,7 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs)
 	      = breakpoint_ops_for_location_spec (locspec.get (), false);
 
 	    create_breakpoint (gdbpy_enter::get_gdbarch (),
-			       locspec.get (), NULL, -1, NULL, false,
+			       locspec.get (), NULL, -1, -1, NULL, false,
 			       0,
 			       temporary_bp, type,
 			       0,
@@ -1376,6 +1465,11 @@ static gdb_PyGetSetDef breakpoint_object_getset[] = {
 If the value is a thread ID (integer), then this is a thread-specific breakpoint.\n\
 If the value is None, then this breakpoint is not thread-specific.\n\
 No other type of value can be used.", NULL },
+  { "inferior", bppy_get_inferior, bppy_set_inferior,
+    "Inferior ID for the breakpoint.\n\
+If the value is an inferior ID (integer), then this is an inferior-specific\n\
+breakpoint.  If the value is None, then this breakpoint is not\n\
+inferior-specific.  No other type of value can be used.", NULL },
   { "task", bppy_get_task, bppy_set_task,
     "Thread ID for the breakpoint.\n\
 If the value is a task ID (integer), then this is an Ada task-specific breakpoint.\n\
diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c
index b71e5fafc46..fa2139ba5d2 100644
--- a/gdb/python/py-finishbreakpoint.c
+++ b/gdb/python/py-finishbreakpoint.c
@@ -307,7 +307,7 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
       location_spec_up locspec
 	= new_address_location_spec (get_frame_pc (prev_frame), NULL, 0);
       create_breakpoint (gdbpy_enter::get_gdbarch (),
-			 locspec.get (), NULL, thread, NULL, false,
+			 locspec.get (), NULL, thread, -1, NULL, false,
 			 0,
 			 1 /*temp_flag*/,
 			 bp_breakpoint,
diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp
index eb7ee5c9951..603d43f7c36 100644
--- a/gdb/testsuite/gdb.ada/tasks.exp
+++ b/gdb/testsuite/gdb.ada/tasks.exp
@@ -58,6 +58,8 @@ gdb_test "break break_me task 1 thread 1" \
     "You can specify only one of thread or task\\."
 gdb_test "break break_me thread 1 task 1" \
     "You can specify only one of thread or task\\."
+gdb_test "break break_me inferior 1 task 1" \
+    "You can specify only one of inferior or task\\."
 gdb_test "watch j task 1 thread 1" \
     "You can specify only one of thread or task\\."
 gdb_test "watch j thread 1 task 1" \
diff --git a/gdb/testsuite/gdb.linespec/cpcompletion.exp b/gdb/testsuite/gdb.linespec/cpcompletion.exp
index fbe19b49d43..f005707b9da 100644
--- a/gdb/testsuite/gdb.linespec/cpcompletion.exp
+++ b/gdb/testsuite/gdb.linespec/cpcompletion.exp
@@ -1259,8 +1259,8 @@ proc_with_prefix function-labels {} {
 }
 
 # Test that completion after a function name offers keyword
-# (if/task/thread/-force-condition) matches in linespec mode, and also
-# the explicit location options in explicit locations mode.
+# (if/inferior/task/thread/-force-condition) matches in linespec mode,
+# and also the explicit location options in explicit locations mode.
 
 proc_with_prefix keywords-after-function {} {
     set explicit_list \
diff --git a/gdb/testsuite/gdb.linespec/explicit.exp b/gdb/testsuite/gdb.linespec/explicit.exp
index b08d65953d2..668002d9038 100644
--- a/gdb/testsuite/gdb.linespec/explicit.exp
+++ b/gdb/testsuite/gdb.linespec/explicit.exp
@@ -412,6 +412,7 @@ namespace eval $testfile {
 	    "-qualified"
 	    "-source"
 	    "if"
+	    "inferior"
 	    "task"
 	    "thread"
 	}
diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
new file mode 100644
index 00000000000..c171ef71b9d
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
@@ -0,0 +1,29 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2023 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/>.  */
+
+int
+foo (void)
+{
+  return 0;
+}
+
+int
+main (void)
+{
+  int res = foo ();
+  return res;
+}
diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
new file mode 100644
index 00000000000..57e69ef6240
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
@@ -0,0 +1,108 @@
+# Copyright 2023 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 for the delivery of '=breakpoint-deleted' notifications when
+# breakpoints are deleted.  Right now this test only covers
+# inferior-specific breakpoints, but it could be extended to cover
+# other cases too.
+
+# Multiple inferiors are needed, therefore only native gdb and
+# extended gdbserver modes are supported.
+require !use_gdb_stub
+
+# Separate UI doesn't work with GDB debug.
+require !gdb_debug_enabled
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+standard_testfile
+
+if { [build_executable "failed to prepare" $testfile $srcfile] } {
+    return -1
+}
+
+# Helper proc to create a breakpoint location regexp.  NUM is the
+# regexp to match the number field of this location.
+proc make_bp_loc { num } {
+    return [mi_make_breakpoint_loc \
+		-number "$num" \
+		-enabled "y" \
+		-func "foo" \
+		-inferior "2"]
+}
+
+foreach_mi_ui_mode mode {
+    mi_gdb_exit
+
+    if {$mode eq "separate"} {
+	set start_ops "separate-mi-tty"
+    } else {
+	set start_ops ""
+    }
+
+    if [mi_gdb_start $start_ops] {
+	return
+    }
+
+    # Load a test binary into inferior 1.
+    mi_gdb_load ${binfile}
+
+    # Setup inferior 2, including loading an exec file.
+    mi_gdb_test "-add-inferior" \
+	[multi_line "=thread-group-added,id=\"\[^\"\]+\"" \
+	     "~\"\\\[New inferior 2\\\]\\\\n\"" \
+	     "\~\"Added inferior 2\[^\r\n\]*\\\\n\"" \
+	     "\\^done,inferior=\"\[^\"\]+\"(?:,connection={.*})?" ] \
+	"mi add inferior 2"
+    mi_gdb_test "-file-exec-and-symbols --thread-group i2 $::binfile" \
+	"\\^done" \
+	"set executable of inferior 2"
+
+    # Build regexp for the two locations.
+    set loc1 [make_bp_loc "$::decimal\\.1"]
+    set loc2 [make_bp_loc "$::decimal\\.2"]
+
+    # Create the inferior-specific breakpoint.
+    mi_create_breakpoint_multi "-g i2 foo" "create breakpoint in inferior 2" \
+	-inferior "2" -locations "\\\[$loc1,$loc2\\\]"
+    set bpnum [mi_get_valueof "/d" "\$bpnum" "INVALID"]
+
+    if {$mode eq "separate"} {
+	# In 'separate' mode we delete the inferior from the CLI, and
+	# then look for the breakpoint-deleted notification on the MI.
+	with_spawn_id $gdb_main_spawn_id {
+	    gdb_test "inferior 1" ".*"
+	    gdb_test "remove-inferiors 2" \
+		"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\."
+	}
+
+	gdb_test_multiple "" "check for b/p deleted notification on MI" {
+	    -re "=breakpoint-deleted,id=\"$bpnum\"" {
+		pass $gdb_test_name
+	    }
+	}
+    } else {
+	# In the non-separate mode we delete the inferior from the MI
+	# and expect to immediately see a breakpoint-deleted
+	# notification.
+	mi_gdb_test "-remove-inferior i2" \
+	    [multi_line \
+		 "=thread-group-removed,id=\"i2\"" \
+		 "~\"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\.\\\\n\"" \
+		 "=breakpoint-deleted,id=\"$bpnum\"" \
+		 "\\^done"]
+    }
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
new file mode 100644
index 00000000000..8f86d8cdcea
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
@@ -0,0 +1,52 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2023 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/>.  */
+
+volatile int global_var = 0;
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+static inline void __attribute__((__always_inline__))
+foo (void)
+{
+  int i;
+
+  for (i = 0; i < 10; ++i)
+    global_var = 0;
+}
+
+static void
+bar (void)
+{
+  global_var = 0;
+
+  foo ();
+}
+
+
+int
+main (void)
+{
+  global_var = 0;
+  foo ();
+  bar ();
+  stop_breakpt ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
new file mode 100644
index 00000000000..e5b20b6e7a8
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
@@ -0,0 +1,52 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2023 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/>.  */
+
+static int bar (void);
+static int baz (void);
+static int foo (void);
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+int
+main (void)
+{
+  int ret = baz ();
+  stop_breakpt ();
+  return ret;
+}
+
+static int
+bar (void)
+{
+  return baz ();
+}
+
+static int
+foo (void)
+{
+  return 0;
+}
+
+static int
+baz (void)
+{
+  return foo ();
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
new file mode 100644
index 00000000000..1f6573268da
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
@@ -0,0 +1,179 @@
+# Copyright 2022-2023 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/>.
+
+# Test inferior-specific breakpoints.
+
+standard_testfile -1.c -2.c
+
+if {[use_gdb_stub]} {
+    return
+}
+
+set srcfile1 ${srcfile}
+set binfile1 ${binfile}-1
+set binfile2 ${binfile}-2
+
+if {[build_executable ${testfile}.exp ${binfile1} "${srcfile1}"] != 0} {
+    return -1
+}
+
+if {[build_executable ${testfile}.exp ${binfile2} "${srcfile2}"] != 0} {
+    return -1
+}
+
+# Start the first inferior.
+clean_restart ${binfile1}
+if {![runto_main]} {
+    return
+}
+
+# Add a second inferior, and start this one too.
+gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2"
+gdb_load $binfile2
+if {![runto_main]} {
+    return
+}
+
+# Try to create a breakpoint using both the 'inferior' and 'thread' keywords,
+# this should fail.  Try with the keywords in both orders just in case the
+# parser has a bug.
+gdb_test "break foo thread 1.1 inferior 1" \
+    "You can specify only one of inferior or thread\\."
+gdb_test "break foo inferior 1 thread 1.1" \
+    "You can specify only one of inferior or thread\\."
+
+# Try to create a breakpoint using the 'inferior' keyword multiple times.
+gdb_test "break foo inferior 1 inferior 2" \
+    "You can specify only one inferior\\."
+
+# Clear out any other breakpoints.
+delete_breakpoints
+
+# Use 'info breakpoint' to check that the inferior specific breakpoint is
+# present in the breakpoint list.  TESTNAME is the name used for this test,
+# BP_NUMBER is the number for the breakpoint, and EXPECTED_LOC_COUNT is the
+# number of locations we expect for that breakpoint.
+proc check_info_breakpoints { testname bp_number expected_loc_count } {
+    gdb_test_multiple "info breakpoints $bp_number" $testname {
+	-re "\r\nNum\\s+\[^\r\n\]+\r\n" {
+	    exp_continue
+	}
+
+	-re "^$bp_number\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
+	    set saw_header true
+	    exp_continue
+	}
+
+	-re "^\\s+stop only in inferior 1\r\n" {
+	    set saw_inf_cond true
+	    exp_continue
+	}
+
+	-re "^\\s+breakpoint already hit $::decimal times\r\n" {
+	    exp_continue
+	}
+
+	-re "^$bp_number\\.\[123\]\\s+y\\s+ $::hex in foo at \[^\r\n\]+(?: inf \[12\])?\r\n" {
+	    incr location_count
+	    exp_continue
+	}
+
+	-re "^$::gdb_prompt $" {
+	    with_test_prefix $gdb_test_name {
+		gdb_assert { $saw_header \
+				 && $location_count == $expected_loc_count \
+				 && $saw_inf_cond } \
+		    $gdb_test_name
+	    }
+	}
+    }
+}
+
+# Create an inferior-specific breakpoint.  Use gdb_test instead of
+# gdb_breakpoint here as we want to check the breakpoint was placed in
+# multiple locations.
+#
+# Currently GDB still places inferior specific breakpoints into every
+# inferior, just like it does with thread specific breakpoints.
+# Hopefully this will change in the future, at which point, this test
+# will need updating.
+#
+# Two of these locations are in inferior 1, while the third is in
+# inferior 2.
+gdb_test "break foo inferior 1" \
+    "Breakpoint $decimal at $hex: foo\\. \\(3 locations\\)"
+set bp_number [get_integer_valueof "\$bpnum" "INVALID" \
+		  "get b/p number for inferior specific breakpoint"]
+
+set saw_header false
+set location_count 0
+set saw_inf_cond false
+
+check_info_breakpoints "first check for inferior specific breakpoint" \
+    $bp_number 3
+
+# Create a multi-inferior breakpoint to stop at.
+gdb_breakpoint "stop_breakpt" message
+set stop_bp_num [get_integer_valueof "\$bpnum" "INVALID" \
+		    "get b/p number for stop_breakpt"]
+
+# Now resume inferior 2, this should reach 'stop_breakpt'.
+gdb_test "continue" \
+    "hit Breakpoint $stop_bp_num\.$decimal, stop_breakpt \\(\\) .*" \
+    "continue in inferior 2"
+
+# Switch to inferior 1, and try there.
+gdb_test "inferior 1" ".*" \
+    "select inferior 1 to check the inferior-specific b/p works"
+gdb_test "continue " \
+    "Thread 1\\.${decimal}\[^\r\n\]* hit Breakpoint\
+     $bp_number\.$decimal, foo \\(\\) .*" \
+    "first continue in inferior 1"
+
+# Now back to inferior 2, let the inferior exit, and then remove the
+# inferior, the inferior-specific breakpoint should not be deleted.
+gdb_test "inferior 2" ".*" \
+    "switch back to allow inferior 2 to exit"
+gdb_test "continue" "\\\[Inferior 2 \[^\r\n\]+ exited normally\\\]" \
+    "allow inferior 2 to exit"
+gdb_test "inferior 1" ".*" \
+    "back to inferior 1 so inferior 2 can be deleted"
+gdb_test_no_output "remove-inferiors 2"
+
+gdb_test "continue " "hit Breakpoint $bp_number\.$decimal, foo \\(\\) .*" \
+    "second continue in inferior 1"
+gdb_test "continue " "hit Breakpoint $stop_bp_num, stop_breakpt \\(\\) .*" \
+    "third continue in inferior 1"
+
+# Now allow inferior 1 to exit, the inferior specific breakpoint
+# should not be deleted.
+gdb_test "continue" \
+    "\\\[Inferior 1 \[^\r\n\]+ exited normally\\\]" \
+    "allow inferior 1 to exit"
+
+check_info_breakpoints "second check for inferior specific breakpoint" \
+    $bp_number 2
+
+# Now create another new inferior, then remove inferior 1.  As a result of
+# this removal, the inferior specific breakpoint should be deleted.
+gdb_test "add-inferior" "Added inferior 3.*" "add empty inferior 3"
+gdb_test "inferior 3" "Switching to inferior 3.*" "switch to inferior 3"
+gdb_test "remove-inferiors 1" \
+    "Inferior-specific breakpoint $bp_number deleted - inferior 1 has been removed\\."
+
+# Now check 'info breakpoints' to ensure the breakpoint is gone.
+gdb_test "info breakpoints $bp_number" \
+    "No breakpoint or watchpoint matching '$bp_number'\\."
diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp
index df17d646b28..b2c39d70c67 100644
--- a/gdb/testsuite/gdb.python/py-breakpoint.exp
+++ b/gdb/testsuite/gdb.python/py-breakpoint.exp
@@ -153,6 +153,8 @@ proc_with_prefix test_bkpt_basic { } {
 	"Check repr for a thread breakpoint"
     gdb_py_test_silent_cmd "python blist\[1\].thread = None" \
 	"clear breakpoint thread" 0
+    gdb_test "python print (blist\[1\].inferior)" \
+	"None" "Check breakpoint inferior"
     gdb_test "python print (blist\[1\].type == gdb.BP_BREAKPOINT)" \
 	"True" "Check breakpoint type"
     gdb_test "python print (blist\[0\].number)" \
@@ -255,6 +257,46 @@ proc_with_prefix test_bkpt_cond_and_cmds { } {
 	"check number of lines in commands"
 }
 
+# Test breakpoint thread and inferior attributes.
+proc_with_prefix test_bkpt_thread_and_inferior { } {
+    global srcfile testfile hex decimal
+
+    # Start with a fresh gdb.
+    clean_restart ${testfile}
+
+    if {![runto_main]} {
+	return 0
+    }
+
+    with_test_prefix "thread" {
+	delete_breakpoints
+	gdb_test "break multiply thread 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "1"
+	gdb_test "python print(bp.inferior)" "None"
+	gdb_test "python bp.inferior = 1" \
+	    "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.thread = None"
+	gdb_test_no_output "python bp.inferior = 1" \
+	    "set the inferior now the thread has been cleared"
+	gdb_test "info breakpoints" "stop only in inferior 1\r\n.*"
+    }
+
+    with_test_prefix "inferior" {
+	delete_breakpoints
+	gdb_test "break multiply inferior 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "None"
+	gdb_test "python print(bp.inferior)" "1"
+	gdb_test "python bp.thread = 1" \
+	    "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.inferior = None"
+	gdb_test_no_output "python bp.thread = 1" \
+	    "set the thread now the inferior has been cleared"
+	gdb_test "info breakpoints" "stop only in thread 1\r\n.*"
+    }
+}
+
 proc_with_prefix test_bkpt_invisible { } {
     global srcfile testfile hex decimal
 
@@ -900,6 +942,7 @@ proc_with_prefix test_bkpt_auto_disable { } {
 test_bkpt_basic
 test_bkpt_deletion
 test_bkpt_cond_and_cmds
+test_bkpt_thread_and_inferior
 test_bkpt_invisible
 test_hardware_breakpoints
 test_catchpoints
diff --git a/gdb/testsuite/lib/completion-support.exp b/gdb/testsuite/lib/completion-support.exp
index ea73c3bb367..fdc512838c3 100644
--- a/gdb/testsuite/lib/completion-support.exp
+++ b/gdb/testsuite/lib/completion-support.exp
@@ -27,7 +27,7 @@ namespace eval completion {
     # List of all quote chars, including no-quote at all.
     variable maybe_quoted_list {"" "'" "\""}
 
-    variable keyword_list {"-force-condition" "if" "task" "thread"}
+    variable keyword_list {"-force-condition" "if" "inferior" "task" "thread"}
 
     variable explicit_opts_list \
 	{"-function" "-label" "-line" "-qualified" "-source"}
diff --git a/gdb/testsuite/lib/mi-support.exp b/gdb/testsuite/lib/mi-support.exp
index 49d5e2ef272..3cd94b03c15 100644
--- a/gdb/testsuite/lib/mi-support.exp
+++ b/gdb/testsuite/lib/mi-support.exp
@@ -2542,7 +2542,7 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} {
 # locations.
 #
 # All arguments for the breakpoint location may be specified using the
-# options: number, enabled, addr, func, file, fullname, line,
+# options: number, enabled, addr, func, file, fullname, line, inferior
 # thread-groups, and thread.
 #
 # For the option -thread the corresponding output field is only added
@@ -2556,12 +2556,14 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} {
 proc mi_make_breakpoint_loc {args} {
     parse_args {{number .*} {enabled .*} {addr .*}
 	{func .*} {file .*} {fullname .*} {line .*}
-	{thread-groups \\\[.*\\\]} {thread ""}}
+	{thread-groups \\\[.*\\\]} {thread ""} {inferior ""}}
 
     set attr_list {}
     foreach attr [list number enabled addr func file \
-		      fullname line thread-groups] {
-	lappend attr_list $attr [set $attr]
+		      fullname line thread-groups inferior] {
+	if {$attr ne "inferior" || [set $attr] ne ""} {
+	    lappend attr_list $attr [set $attr]
+	}
     }
 
     set result [mi_build_kv_pairs $attr_list]
@@ -2635,7 +2637,7 @@ proc mi_make_breakpoint_1 {attr_list thread cond evaluated-by times \
 # locations.
 #
 # All arguments for the breakpoint may be specified using the options:
-# number, type, disp, enabled, times, ignore, script,
+# number, type, disp, enabled, times, ignore, script, inferior,
 # original-location, cond, evaluated-by, locations, and thread.
 #
 # Only if -script and -ignore are given will they appear in the output.
@@ -2656,7 +2658,7 @@ proc mi_make_breakpoint_multi {args} {
     parse_args {{number .*} {type .*} {disp .*} {enabled .*}
 	{times .*} {ignore 0}
 	{script ""} {original-location .*} {cond ""} {evaluated-by ""}
-	{locations .*} {thread ""}}
+	{locations .*} {thread ""} {inferior ""}}
 
     set attr_list {}
     foreach attr [list number type disp enabled] {
@@ -2665,6 +2667,12 @@ proc mi_make_breakpoint_multi {args} {
 
     lappend attr_list "addr" "<MULTIPLE>"
 
+    # Only include the inferior field if it was set.  This field is
+    # optional in the MI output.
+    if {$inferior ne ""} {
+	lappend attr_list "inferior" $inferior
+    }
+
     set result [mi_make_breakpoint_1 \
 		    $attr_list $thread $cond ${evaluated-by} $times \
 		    $ignore $script ${original-location}]

base-commit: 7c632c2a696fb68e5575db1e2c934788a831e578
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PUSHEDv8] gdb: add inferior-specific breakpoints
  2023-07-07 10:23           ` [PATCHv7] " Andrew Burgess
@ 2023-08-17 15:53             ` Andrew Burgess
  2023-08-23  8:06               ` [PUSHED] gdb: add missing notify_breakpoint_modified call Andrew Burgess
  2023-08-23  8:19               ` [PUSHED] gdb/testsuite: improve MI support for inferior specific breakpoints Andrew Burgess
  0 siblings, 2 replies; 54+ messages in thread
From: Andrew Burgess @ 2023-08-17 15:53 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Doc changes have already been approved here:
  https://sourceware.org/pipermail/gdb-patches/2023-January/195986.html

In v8:

  - Rebased and retested.

In v7:

  - Rebased to current upstream master,

  - Minor fixes to gdb.mi/new-ui-bp-deleted.exp to fix this test when
    run with the native-extended-remote board.

In v6:

  - Rebased to current upstream master, this includes all Simon's
    recent breakpoint changes.  Retested with no regressions seen.

In v5:

  - Rebased to current upstream master and retested,

  - No changes to code or docs.

In v4:

  - Merged patch #1 from the previous series as this felt like a
    pretty obvious cleanup.

  - Rebased to HEAD of master.

  - No changes to code or docs.

--

This commit extends the breakpoint mechanism to allow for inferior
specific breakpoints (but not watchpoints in this commit).

As GDB gains better support for multiple connections, and so for
running multiple (possibly unrelated) inferiors, then it is not hard
to imagine that a user might wish to create breakpoints that apply to
any thread in a single inferior.  To achieve this currently, the user
would need to create a condition possibly making use of the $_inferior
convenience variable, which, though functional, isn't the most user
friendly.

This commit adds a new 'inferior' keyword that allows for the creation
of inferior specific breakpoints.

Inferior specific breakpoints are automatically deleted when the
associated inferior is removed from GDB, this is similar to how
thread-specific breakpoints are deleted when the associated thread is
deleted.

Watchpoints are already per-program-space, which in most cases mean
watchpoints are already inferior specific.  There is a small window
where inferior-specific watchpoints might make sense, which is after a
vfork, when two processes are sharing the same address space.
However, I'm leaving that as an exercise for another day.  For now,
attempting to use the inferior keyword with a watchpoint will give an
error, like this:

  (gdb) watch a8 inferior 1
  Cannot use 'inferior' keyword with watchpoints

A final note on the implementation: currently, inferior specific
breakpoints, like thread-specific breakpoints, are inserted into every
inferior, GDB then checks once the inferior stops if we are in the
correct thread or inferior, and resumes automatically if we stopped in
the wrong thread/inferior.

An obvious optimisation here is to only insert breakpoint locations
into the specific program space (which mostly means inferior) that
contains either the inferior or thread we are interested in.  This
would reduce the number times GDB has to stop and then resume again in
a multi-inferior setup.

I have a series on the mailing list[1] that implements this
optimisation for thread-specific breakpoints.  Once this series has
landed I'll update that series to also handle inferior specific
breakpoints in the same way.  For now, inferior specific breakpoints
are just slightly less optimal, but this is no different to
thread-specific breakpoints in a multi-inferior debug session, so I
don't see this as a huge problem.

[1] https://inbox.sourceware.org/gdb-patches/cover.1685479504.git.aburgess@redhat.com/
---
 gdb/NEWS                                      |  21 ++
 gdb/breakpoint.c                              | 237 ++++++++++++++----
 gdb/breakpoint.h                              |  14 +-
 gdb/doc/gdb.texinfo                           |  72 +++++-
 gdb/doc/python.texi                           |  26 +-
 gdb/guile/scm-breakpoint.c                    |   7 +-
 gdb/infcmd.c                                  |  15 +-
 gdb/inferior.h                                |  11 +
 gdb/linespec.c                                |   4 +-
 gdb/mi/mi-cmd-break.c                         |  11 +-
 gdb/mi/mi-main.c                              |  18 +-
 gdb/mi/mi-main.h                              |   6 +
 gdb/python/py-breakpoint.c                    |  96 ++++++-
 gdb/python/py-finishbreakpoint.c              |   2 +-
 gdb/testsuite/gdb.ada/tasks.exp               |   2 +
 gdb/testsuite/gdb.linespec/cpcompletion.exp   |   4 +-
 gdb/testsuite/gdb.linespec/explicit.exp       |   1 +
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c      |  29 +++
 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp    | 108 ++++++++
 .../gdb.multi/inferior-specific-bp-1.c        |  52 ++++
 .../gdb.multi/inferior-specific-bp-2.c        |  52 ++++
 .../gdb.multi/inferior-specific-bp.exp        | 179 +++++++++++++
 gdb/testsuite/gdb.python/py-breakpoint.exp    |  43 ++++
 gdb/testsuite/lib/completion-support.exp      |   2 +-
 gdb/testsuite/lib/mi-support.exp              |  20 +-
 25 files changed, 944 insertions(+), 88 deletions(-)
 create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
 create mode 100644 gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
 create mode 100644 gdb/testsuite/gdb.multi/inferior-specific-bp.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index ef2b3b3a4f5..c4b1f7a7e3b 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -98,6 +98,13 @@
   user that the end of file has been reached, refers the user to the
   newly added '.' argument
 
+* Breakpoints can now be inferior-specific.  This is similar to the
+  existing thread-specific breakpoint support.  Breakpoint conditions
+  can include the 'inferior' keyword followed by an inferior id (as
+  displayed in the 'info inferiors' output).  It is invalid to use the
+  'inferior' keyword with either the 'thread' or 'task' keywords when
+  creating a breakpoint.
+
 * New commands
 
 set debug breakpoint on|off
@@ -162,6 +169,14 @@ info main
    considered simple.)  Support for this feature can be verified by using the
    '-list-features' command, which should contain "simple-values-ref-types".
 
+** The -break-insert command now accepts a '-g thread-group-id' option
+   to allow for the creation of inferior-specific breakpoints.
+
+** The bkpt tuple, which appears in breakpoint-created notifications,
+   and in the result of the -break-insert command can now include an
+   optional 'inferior' field for both the main breakpoint, and each
+   location, when the breakpoint is inferior-specific.
+
 * Python API
 
   ** gdb.ThreadExitedEvent added.  Emits a ThreadEvent.
@@ -257,6 +272,12 @@ info main
   ** gdb.Progspace now has the new method "objfile_for_address".  This
      returns the gdb.Objfile, if any, that covers a given address.
 
+  ** gdb.Breakpoint now has an "inferior" attribute.  If the
+     Breakpoint object is inferior specific then this attribute holds
+     the inferior-id (an integer).  If the Breakpoint object is not
+     inferior specific, then this field contains None.  This field can
+     be written too.
+
 *** Changes in GDB 13
 
 * MI version 1 is deprecated, and will be removed in GDB 14.
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index e08f3444a65..f88ca1c9b65 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -99,7 +99,7 @@ static void create_breakpoints_sal (struct gdbarch *,
 				    gdb::unique_xmalloc_ptr<char>,
 				    gdb::unique_xmalloc_ptr<char>,
 				    enum bptype,
-				    enum bpdisp, int, int,
+				    enum bpdisp, int, int, int,
 				    int,
 				    int, int, int, unsigned);
 
@@ -385,6 +385,9 @@ struct momentary_breakpoint : public code_breakpoint
     disposition = disp_donttouch;
     frame_id = frame_id_;
     thread = thread_;
+
+    /* The inferior should have been set by the parent constructor.  */
+    gdb_assert (inferior == -1);
   }
 
   void re_set () override;
@@ -1541,13 +1544,15 @@ breakpoint_set_silent (struct breakpoint *b, int silent)
 void
 breakpoint_set_thread (struct breakpoint *b, int thread)
 {
-  /* It is invalid to set the thread field to anything other than -1 (which
-     means no thread restriction) if a task restriction is already in
-     place.  */
-  gdb_assert (thread == -1 || b->task == -1);
+  /* THREAD should be -1, meaning no thread restriction, or it should be a
+     valid global thread-id, which are greater than zero.  */
+  gdb_assert (thread == -1 || thread > 0);
 
-  int old_thread = b->thread;
+  /* It is not valid to set a thread restriction for a breakpoint that
+     already has task or inferior restriction.  */
+  gdb_assert (thread == -1 || (b->task == -1 && b->inferior == -1));
 
+  int old_thread = b->thread;
   b->thread = thread;
   if (old_thread != thread)
     notify_breakpoint_modified (b);
@@ -1555,16 +1560,37 @@ breakpoint_set_thread (struct breakpoint *b, int thread)
 
 /* See breakpoint.h.  */
 
+void
+breakpoint_set_inferior (struct breakpoint *b, int inferior)
+{
+  /* INFERIOR should be -1, meaning no inferior restriction, or it should
+     be a valid inferior number, which are greater than zero.  */
+  gdb_assert (inferior == -1 || inferior > 0);
+
+  /* It is not valid to set an inferior restriction for a breakpoint that
+     already has a task or thread restriction.  */
+  gdb_assert (inferior == -1 || (b->task == -1 && b->thread == -1));
+
+  int old_inferior = b->inferior;
+  b->inferior = inferior;
+  if (old_inferior != inferior)
+    gdb::observers::breakpoint_modified.notify (b);
+}
+
+/* See breakpoint.h.  */
+
 void
 breakpoint_set_task (struct breakpoint *b, int task)
 {
-  /* It is invalid to set the task field to anything other than -1 (which
-     means no task restriction) if a thread restriction is already in
-     place.  */
-  gdb_assert (task == -1 || b->thread == -1);
+  /* TASK should be -1, meaning no task restriction, or it should be a
+     valid task-id, which are greater than zero.  */
+  gdb_assert (task == -1 || task > 0);
 
-  int old_task = b->task;
+  /* It is not valid to set a task restriction for a breakpoint that
+     already has a thread or inferior restriction.  */
+  gdb_assert (task == -1 || (b->thread == -1 && b->inferior == -1));
 
+  int old_task = b->task;
   b->task = task;
   if (old_task != task)
     notify_breakpoint_modified (b);
@@ -3244,6 +3270,12 @@ insert_breakpoint_locations (void)
 	  && !valid_global_thread_id (bl->owner->thread))
 	continue;
 
+      /* Or inferior specific breakpoints if the inferior no longer
+	 exists.  */
+      if (bl->owner->inferior != -1
+	  && !valid_global_inferior_id (bl->owner->inferior))
+	continue;
+
       switch_to_program_space_and_thread (bl->pspace);
 
       /* For targets that support global breakpoints, there's no need
@@ -3344,6 +3376,29 @@ Thread-specific breakpoint %d deleted - thread %s no longer in the thread list.\
     }
 }
 
+/* Called when inferior INF has been removed from GDB.  Remove associated
+   per-inferior breakpoints.  */
+
+static void
+remove_inferior_breakpoints (struct inferior *inf)
+{
+  for (breakpoint &b : all_breakpoints_safe ())
+    {
+      if (b.inferior == inf->num && user_breakpoint_p (&b))
+	{
+	  /* Tell the user the breakpoint has been deleted.  But only for
+	     breakpoints that would not normally have been deleted at the
+	     next stop anyway.  */
+	  if (b.disposition != disp_del
+	      && b.disposition != disp_del_at_next_stop)
+	    gdb_printf (_("\
+Inferior-specific breakpoint %d deleted - inferior %d has been removed.\n"),
+			b.number, inf->num);
+	  delete_breakpoint (&b);
+       }
+    }
+}
+
 /* See breakpoint.h.  */
 
 void
@@ -5554,6 +5609,7 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread)
      evaluating the condition if this isn't the specified
      thread/task.  */
   if ((b->thread != -1 && b->thread != thread->global_num)
+      || (b->inferior != -1 && b->inferior != thread->inf->num)
       || (b->task != -1 && b->task != ada_get_task_number (thread)))
     {
       infrun_debug_printf ("incorrect thread or task, not stopping");
@@ -6581,6 +6637,8 @@ print_one_breakpoint_location (struct breakpoint *b,
 	uiout->field_signed ("thread", b->thread);
       else if (b->task != -1)
 	uiout->field_signed ("task", b->task);
+      else if (b->inferior != -1)
+	uiout->field_signed ("inferior", b->inferior);
     }
 
   uiout->text ("\n");
@@ -6643,6 +6701,13 @@ print_one_breakpoint_location (struct breakpoint *b,
       uiout->text ("\n");
     }
 
+  if (!part_of_multiple && b->inferior != -1)
+    {
+      uiout->text ("\tstop only in inferior ");
+      uiout->field_signed ("inferior", b->inferior);
+      uiout->text ("\n");
+    }
+
   if (!part_of_multiple)
     {
       if (b->hit_count)
@@ -7629,7 +7694,10 @@ delete_longjmp_breakpoint (int thread)
     if (b.type == bp_longjmp || b.type == bp_exception)
       {
 	if (b.thread == thread)
-	  delete_breakpoint (&b);
+	  {
+	    gdb_assert (b.inferior == -1);
+	    delete_breakpoint (&b);
+	  }
       }
 }
 
@@ -7640,7 +7708,10 @@ delete_longjmp_breakpoint_at_next_stop (int thread)
     if (b.type == bp_longjmp || b.type == bp_exception)
       {
 	if (b.thread == thread)
-	  b.disposition = disp_del_at_next_stop;
+	  {
+	    gdb_assert (b.inferior == -1);
+	    b.disposition = disp_del_at_next_stop;
+	  }
       }
 }
 
@@ -7697,6 +7768,7 @@ check_longjmp_breakpoint_for_call_dummy (struct thread_info *tp)
     {
       if (b.type == bp_longjmp_call_dummy && b.thread == tp->global_num)
 	{
+	  gdb_assert (b.inferior == -1);
 	  struct breakpoint *dummy_b = b.related_breakpoint;
 
 	  /* Find the bp_call_dummy breakpoint in the list of breakpoints
@@ -8542,7 +8614,8 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
 				  gdb::unique_xmalloc_ptr<char> cond_string_,
 				  gdb::unique_xmalloc_ptr<char> extra_string_,
 				  enum bpdisp disposition_,
-				  int thread_, int task_, int ignore_count_,
+				  int thread_, int task_, int inferior_,
+				  int ignore_count_,
 				  int from_tty,
 				  int enabled_, unsigned flags,
 				  int display_canonical_)
@@ -8566,10 +8639,14 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
 
   gdb_assert (!sals.empty ());
 
-  /* At most one of thread or task can be set on any breakpoint.  */
-  gdb_assert (thread == -1 || task == -1);
+  /* At most one of thread, task, or inferior can be set on any breakpoint.  */
+  gdb_assert (((thread == -1 ? 0 : 1)
+	       + (task == -1 ? 0 : 1)
+	       + (inferior == -1 ? 0 : 1)) <= 1);
+
   thread = thread_;
   task = task_;
+  inferior = inferior_;
 
   cond_string = std::move (cond_string_);
   extra_string = std::move (extra_string_);
@@ -8671,7 +8748,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
 		       gdb::unique_xmalloc_ptr<char> cond_string,
 		       gdb::unique_xmalloc_ptr<char> extra_string,
 		       enum bptype type, enum bpdisp disposition,
-		       int thread, int task, int ignore_count,
+		       int thread, int task, int inferior, int ignore_count,
 		       int from_tty,
 		       int enabled, int internal, unsigned flags,
 		       int display_canonical)
@@ -8685,7 +8762,7 @@ create_breakpoint_sal (struct gdbarch *gdbarch,
 				std::move (cond_string),
 				std::move (extra_string),
 				disposition,
-				thread, task, ignore_count,
+				thread, task, inferior, ignore_count,
 				from_tty,
 				enabled, flags,
 				display_canonical);
@@ -8714,7 +8791,8 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
 			gdb::unique_xmalloc_ptr<char> cond_string,
 			gdb::unique_xmalloc_ptr<char> extra_string,
 			enum bptype type, enum bpdisp disposition,
-			int thread, int task, int ignore_count,
+			int thread, int task, int inferior,
+			int ignore_count,
 			int from_tty,
 			int enabled, int internal, unsigned flags)
 {
@@ -8738,7 +8816,7 @@ create_breakpoints_sal (struct gdbarch *gdbarch,
 			     std::move (cond_string),
 			     std::move (extra_string),
 			     type, disposition,
-			     thread, task, ignore_count,
+			     thread, task, inferior, ignore_count,
 			     from_tty, enabled, internal, flags,
 			     canonical->special_display);
     }
@@ -8868,21 +8946,26 @@ check_fast_tracepoint_sals (struct gdbarch *gdbarch,
     }
 }
 
-/* Given TOK, a string specification of condition and thread, as
-   accepted by the 'break' command, extract the condition
-   string and thread number and set *COND_STRING and *THREAD.
-   PC identifies the context at which the condition should be parsed.
-   If no condition is found, *COND_STRING is set to NULL.
-   If no thread is found, *THREAD is set to -1.  */
+/* Given TOK, a string specification of condition and thread, as accepted
+   by the 'break' command, extract the condition string into *COND_STRING.
+   If no condition string is found then *COND_STRING is set to nullptr.
+
+   If the breakpoint specification has an associated thread, task, or
+   inferior, these are extracted into *THREAD, *TASK, and *INFERIOR
+   respectively, otherwise these arguments are set to -1 (for THREAD and
+   INFERIOR) or 0 (for TASK).
+
+   PC identifies the context at which the condition should be parsed.  */
 
 static void
 find_condition_and_thread (const char *tok, CORE_ADDR pc,
 			   gdb::unique_xmalloc_ptr<char> *cond_string,
-			   int *thread, int *task,
+			   int *thread, int *inferior, int *task,
 			   gdb::unique_xmalloc_ptr<char> *rest)
 {
   cond_string->reset ();
   *thread = -1;
+  *inferior = -1;
   *task = -1;
   rest->reset ();
   bool force = false;
@@ -8899,7 +8982,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
       if ((*tok == '"' || *tok == ',') && rest)
 	{
 	  rest->reset (savestring (tok, strlen (tok)));
-	  return;
+	  break;
 	}
 
       end_tok = skip_to_space (tok);
@@ -8939,6 +9022,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  if (*task != -1)
 	    error (_("You can specify only one of thread or task."));
 
+	  if (*inferior != -1)
+	    error (_("You can specify only one of inferior or thread."));
+
 	  tok = end_tok + 1;
 	  thr = parse_thread_id (tok, &tmptok);
 	  if (tok == tmptok)
@@ -8946,6 +9032,26 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  *thread = thr->global_num;
 	  tok = tmptok;
 	}
+      else if (toklen >= 1 && strncmp (tok, "inferior", toklen) == 0)
+	{
+	  if (*inferior != -1)
+	    error(_("You can specify only one inferior."));
+
+	  if (*task != -1)
+	    error (_("You can specify only one of inferior or task."));
+
+	  if (*thread != -1)
+	    error (_("You can specify only one of inferior or thread."));
+
+	  char *tmptok;
+	  tok = end_tok + 1;
+	  *inferior = strtol (tok, &tmptok, 0);
+	  if (tok == tmptok)
+	    error (_("Junk after inferior keyword."));
+	  if (!valid_global_inferior_id (*inferior))
+	    error (_("Unknown inferior number %d."), *inferior);
+	  tok = tmptok;
+	}
       else if (toklen >= 1 && strncmp (tok, "task", toklen) == 0)
 	{
 	  char *tmptok;
@@ -8956,6 +9062,9 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
 	  if (*thread != -1)
 	    error (_("You can specify only one of thread or task."));
 
+	  if (*inferior != -1)
+	    error (_("You can specify only one of inferior or task."));
+
 	  tok = end_tok + 1;
 	  *task = strtol (tok, &tmptok, 0);
 	  if (tok == tmptok)
@@ -8967,7 +9076,7 @@ find_condition_and_thread (const char *tok, CORE_ADDR pc,
       else if (rest)
 	{
 	  rest->reset (savestring (tok, strlen (tok)));
-	  return;
+	  break;
 	}
       else
 	error (_("Junk at end of arguments."));
@@ -8983,7 +9092,7 @@ static void
 find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
 				    const char *input,
 				    gdb::unique_xmalloc_ptr<char> *cond_string,
-				    int *thread, int *task,
+				    int *thread, int *inferior, int *task,
 				    gdb::unique_xmalloc_ptr<char> *rest)
 {
   int num_failures = 0;
@@ -8991,6 +9100,7 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
     {
       gdb::unique_xmalloc_ptr<char> cond;
       int thread_id = -1;
+      int inferior_id = -1;
       int task_id = -1;
       gdb::unique_xmalloc_ptr<char> remaining;
 
@@ -9003,11 +9113,16 @@ find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
       try
 	{
 	  find_condition_and_thread (input, sal.pc, &cond, &thread_id,
-				     &task_id, &remaining);
+				     &inferior_id, &task_id, &remaining);
 	  *cond_string = std::move (cond);
-	  /* At most one of thread or task can be set.  */
-	  gdb_assert (thread_id == -1 || task_id == -1);
+	  /* A value of -1 indicates that these fields are unset.  At most
+	     one of these fields should be set (to a value other than -1)
+	     at this point.  */
+	  gdb_assert (((thread_id == -1 ? 1 : 0)
+		       + (task_id == -1 ? 1 : 0)
+		       + (inferior_id == -1 ? 1 : 0)) >= 2);
 	  *thread = thread_id;
+	  *inferior = inferior_id;
 	  *task = task_id;
 	  *rest = std::move (remaining);
 	  break;
@@ -9097,7 +9212,8 @@ int
 create_breakpoint (struct gdbarch *gdbarch,
 		   location_spec *locspec,
 		   const char *cond_string,
-		   int thread, const char *extra_string,
+		   int thread, int inferior,
+		   const char *extra_string,
 		   bool force_condition, int parse_extra,
 		   int tempflag, enum bptype type_wanted,
 		   int ignore_count,
@@ -9111,6 +9227,10 @@ create_breakpoint (struct gdbarch *gdbarch,
   int task = -1;
   int prev_bkpt_count = breakpoint_count;
 
+  gdb_assert (thread == -1 || thread > 0);
+  gdb_assert (inferior == -1 || inferior > 0);
+  gdb_assert (thread == -1 || inferior == -1);
+
   gdb_assert (ops != NULL);
 
   /* If extra_string isn't useful, set it to NULL.  */
@@ -9186,7 +9306,8 @@ create_breakpoint (struct gdbarch *gdbarch,
 	  const linespec_sals &lsal = canonical.lsals[0];
 
 	  find_condition_and_thread_for_sals (lsal.sals, extra_string,
-					      &cond, &thread, &task, &rest);
+					      &cond, &thread, &inferior,
+					      &task, &rest);
 	  cond_string_copy = std::move (cond);
 	  extra_string_copy = std::move (rest);
 	}
@@ -9236,7 +9357,7 @@ create_breakpoint (struct gdbarch *gdbarch,
 				   std::move (extra_string_copy),
 				   type_wanted,
 				   tempflag ? disp_del : disp_donttouch,
-				   thread, task, ignore_count,
+				   thread, task, inferior, ignore_count,
 				   from_tty, enabled, internal, flags);
     }
   else
@@ -9305,7 +9426,9 @@ break_command_1 (const char *arg, int flag, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL,
+		     -1 /* thread */, -1 /* inferior */,
+		     arg, false, 1 /* parse arg */,
 		     tempflag, type_wanted,
 		     0 /* Ignore count */,
 		     pending_break_support,
@@ -9417,7 +9540,8 @@ dprintf_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1,
+		     arg, false, 1 /* parse arg */,
 		     0, bp_dprintf,
 		     0 /* Ignore count */,
 		     pending_break_support,
@@ -10162,6 +10286,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
   const char *cond_end = NULL;
   enum bptype bp_type;
   int thread = -1;
+  int inferior = -1;
   /* Flag to indicate whether we are going to use masks for
      the hardware watchpoint.  */
   bool use_mask = false;
@@ -10216,12 +10341,13 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	      if (task != -1)
 		error (_("You can specify only one of thread or task."));
 
+	      if (inferior != -1)
+		error (_("You can specify only one of inferior or thread."));
+
 	      /* Extract the thread ID from the next token.  */
 	      thr = parse_thread_id (value_start, &endp);
-
-	      /* Check if the user provided a valid thread ID.  */
-	      if (*endp != ' ' && *endp != '\t' && *endp != '\0')
-		invalid_thread_id_error (value_start);
+	      if (value_start == endp)
+		error (_("Junk after thread keyword."));
 
 	      thread = thr->global_num;
 	    }
@@ -10235,12 +10361,20 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
 	      if (thread != -1)
 		error (_("You can specify only one of thread or task."));
 
+	      if (inferior != -1)
+		error (_("You can specify only one of inferior or task."));
+
 	      task = strtol (value_start, &tmp, 0);
 	      if (tmp == value_start)
 		error (_("Junk after task keyword."));
 	      if (!valid_task_id (task))
 		error (_("Unknown task %d."), task);
 	    }
+	  else if (toklen == 8 && startswith (tok, "inferior"))
+	    {
+	      /* Support for watchpoints will be added in a later commit.  */
+	      error (_("Cannot use 'inferior' keyword with watchpoints"));
+	    }
 	  else if (toklen == 4 && startswith (tok, "mask"))
 	    {
 	      /* We've found a "mask" token, which means the user wants to
@@ -10413,6 +10547,7 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
   /* At most one of thread or task can be set on a watchpoint.  */
   gdb_assert (thread == -1 || task == -1);
   w->thread = thread;
+  w->inferior = inferior;
   w->task = task;
   w->disposition = disp_donttouch;
   w->pspace = current_program_space;
@@ -12350,7 +12485,8 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
 				      enum bptype type_wanted,
 				      enum bpdisp disposition,
 				      int thread,
-				      int task, int ignore_count,
+				      int task, int inferior,
+				      int ignore_count,
 				      int from_tty, int enabled,
 				      int internal, unsigned flags)
 {
@@ -12376,7 +12512,7 @@ strace_marker_create_breakpoints_sal (struct gdbarch *gdbarch,
 			 std::move (cond_string),
 			 std::move (extra_string),
 			 disposition,
-			 thread, task, ignore_count,
+			 thread, task, inferior, ignore_count,
 			 from_tty, enabled, flags,
 			 canonical->special_display));
 
@@ -12995,10 +13131,11 @@ code_breakpoint::location_spec_to_sals (location_spec *locspec,
       if (condition_not_parsed && extra_string != NULL)
 	{
 	  gdb::unique_xmalloc_ptr<char> local_cond, local_extra;
-	  int local_thread, local_task;
+	  int local_thread, local_task, local_inferior;
 
 	  find_condition_and_thread_for_sals (sals, extra_string.get (),
 					      &local_cond, &local_thread,
+					      &local_inferior,
 					      &local_task, &local_extra);
 	  gdb_assert (cond_string == nullptr);
 	  if (local_cond != nullptr)
@@ -13872,7 +14009,7 @@ trace_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     bp_tracepoint /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13890,7 +14027,7 @@ ftrace_command (const char *arg, int from_tty)
 						      current_language);
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     bp_fast_tracepoint /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13928,7 +14065,7 @@ strace_command (const char *arg, int from_tty)
 
   create_breakpoint (get_current_arch (),
 		     locspec.get (),
-		     NULL, 0, arg, false, 1 /* parse arg */,
+		     NULL, -1, -1, arg, false, 1 /* parse arg */,
 		     0 /* tempflag */,
 		     type /* type_wanted */,
 		     0 /* Ignore count */,
@@ -13997,7 +14134,7 @@ create_tracepoint_from_upload (struct uploaded_tp *utp)
 						      current_language);
   if (!create_breakpoint (get_current_arch (),
 			  locspec.get (),
-			  utp->cond_string.get (), -1, addr_str,
+			  utp->cond_string.get (), -1, -1, addr_str,
 			  false /* force_condition */,
 			  0 /* parse cond/thread */,
 			  0 /* tempflag */,
@@ -15094,4 +15231,6 @@ This is useful for formatted output in user-defined commands."));
 					   "breakpoint");
   gdb::observers::thread_exit.attach (remove_threaded_breakpoints,
 				      "breakpoint");
+  gdb::observers::inferior_removed.attach (remove_inferior_breakpoints,
+					   "breakpoint");
 }
diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
index f4896293bb7..1a73d08a887 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -583,7 +583,7 @@ struct breakpoint_ops
 				  struct linespec_result *,
 				  gdb::unique_xmalloc_ptr<char>,
 				  gdb::unique_xmalloc_ptr<char>,
-				  enum bptype, enum bpdisp, int, int,
+				  enum bptype, enum bpdisp, int, int, int,
 				  int, int, int, int, unsigned);
 };
 
@@ -863,6 +863,10 @@ struct breakpoint : public intrusive_list_node<breakpoint>
      care.  */
   int thread = -1;
 
+  /* Inferior number for inferior-specific breakpoint, or -1 if this
+     breakpoint is for all inferiors.  */
+  int inferior = -1;
+
   /* Ada task number for task-specific breakpoint, or -1 if don't
      care.  */
   int task = -1;
@@ -921,7 +925,7 @@ struct code_breakpoint : public breakpoint
 		   gdb::unique_xmalloc_ptr<char> cond_string,
 		   gdb::unique_xmalloc_ptr<char> extra_string,
 		   enum bpdisp disposition,
-		   int thread, int task, int ignore_count,
+		   int thread, int task, int inferior, int ignore_count,
 		   int from_tty,
 		   int enabled, unsigned flags,
 		   int display_canonical);
@@ -1601,6 +1605,7 @@ enum breakpoint_create_flags
 extern int create_breakpoint (struct gdbarch *gdbarch,
 			      struct location_spec *locspec,
 			      const char *cond_string, int thread,
+			      int inferior,
 			      const char *extra_string,
 			      bool force_condition,
 			      int parse_extra,
@@ -1744,6 +1749,11 @@ extern void breakpoint_set_silent (struct breakpoint *b, int silent);
 
 extern void breakpoint_set_thread (struct breakpoint *b, int thread);
 
+/* Set the inferior for breakpoint B to INFERIOR.  If INFERIOR is -1, make
+   the breakpoint work for any inferior.  */
+
+extern void breakpoint_set_inferior (struct breakpoint *b, int inferior);
+
 /* Set the task for this breakpoint.  If TASK is -1, make the breakpoint
    work for any task.  Passing a value other than -1 for TASK should only
    be done if b->thread is -1; it is not valid to try and set both a thread
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 814cc6d714a..8be9725d1a2 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3517,6 +3517,57 @@
 space as a result of inferior 1 having executed a @code{vfork} call.
 @end table
 
+@menu
+* Inferior-Specific Breakpoints::	Controlling breakpoints
+@end menu
+
+@node Inferior-Specific Breakpoints
+@subsection Inferior-Specific Breakpoints
+
+When debugging multiple inferiors, you can choose whether to set
+breakpoints for all inferiors, or for a particular inferior.
+
+@table @code
+@cindex breakpoints and inferiors
+@cindex inferior-specific breakpoints
+@kindex break @dots{} inferior @var{inferior-id}
+@item break @var{locspec} inferior @var{inferior-id}
+@itemx break @var{locspec} inferior @var{inferior-id} if @dots{}
+@var{locspec} specifies a code location or locations in your program.
+@xref{Location Specifications}, for details.
+
+Use the qualifier @samp{inferior @var{inferior-id}} with a breakpoint
+command to specify that you only want @value{GDBN} to stop when a
+particular inferior reaches this breakpoint.  The @var{inferior-id}
+specifier is one of the inferior identifiers assigned by @value{GDBN},
+shown in the first column of the @samp{info inferiors} output.
+
+If you do not specify @samp{inferior @var{inferior-id}} when you set a
+breakpoint, the breakpoint applies to @emph{all} inferiors of your
+program.
+
+You can use the @code{inferior} qualifier on conditional breakpoints as
+well; in this case, place @samp{inferior @var{inferior-id}} before or
+after the breakpoint condition, like this:
+
+@smallexample
+(@value{GDBP}) break frik.c:13 inferior 2 if bartab > lim
+@end smallexample
+@end table
+
+Inferior-specific breakpoints are automatically deleted when the
+corresponding inferior is removed from @value{GDBN}.  For example:
+
+@smallexample
+(@value{GDBP}) remove-inferiors 2
+Inferior-specific breakpoint 3 deleted - inferior 2 has been removed.
+@end smallexample
+
+A breakpoint can't be both inferior-specific and thread-specific
+(@pxref{Thread-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{inferior}, @code{thread}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
 @node Threads
 @section Debugging Programs with Multiple Threads
 
@@ -4480,8 +4531,9 @@
 situation.
 
 It is also possible to insert a breakpoint that will stop the program
-only if a specific thread (@pxref{Thread-Specific Breakpoints})
-or a specific task (@pxref{Ada Tasks}) hits that breakpoint.
+only if a specific thread (@pxref{Thread-Specific Breakpoints}),
+specific inferior (@pxref{Inferior-Specific Breakpoints}), or a
+specific task (@pxref{Ada Tasks}) hits that breakpoint.
 
 @item break
 When called without any arguments, @code{break} sets a breakpoint at
@@ -7344,9 +7396,14 @@
 Process}), or if @value{GDBN} loses the remote connection
 (@pxref{Remote Debugging}), etc.  Note that with some targets,
 @value{GDBN} is only able to detect a thread has exited when the user
-explictly asks for the thread list with the @code{info threads}
+explicitly asks for the thread list with the @code{info threads}
 command.
 
+A breakpoint can't be both thread-specific and inferior-specific
+(@pxref{Inferior-Specific Breakpoints}), or task-specific (@pxref{Ada
+Tasks}); using more than one of the @code{thread}, @code{inferior}, or
+@code{task} keywords when creating a breakpoint will give an error.
+
 @node Interrupted System Calls
 @subsection Interrupted System Calls 
 
@@ -31630,6 +31687,10 @@
 If this is a thread-specific breakpoint, then this identifies the
 thread in which the breakpoint can trigger.
 
+@item inferior
+If this is an inferior-specific breakpoint, this this identifies the
+inferior in which the breakpoint can trigger.
+
 @item task
 If this breakpoint is restricted to a particular Ada task, then this
 field will hold the task identifier.
@@ -32221,7 +32282,7 @@
 @smallexample
  -break-insert [ -t ] [ -h ] [ -f ] [ -d ] [ -a ] [ --qualified ]
     [ -c @var{condition} ] [ --force-condition ] [ -i @var{ignore-count} ]
-    [ -p @var{thread-id} ] [ @var{locspec} ]
+    [ -p @var{thread-id} ] [ -g @var{thread-group-id} ] [ @var{locspec} ]
 @end smallexample
 
 @noindent
@@ -32287,6 +32348,9 @@
 time the breakpoint is requested.  Breakpoints created with a
 @var{thread-id} will automatically be deleted when the corresponding
 thread exits.
+@item -g @var{thread-group-id}
+Restrict the breakpoint to the thread group with the specified
+@var{thread-group-id}.
 @item --qualified
 This option makes @value{GDBN} interpret a function name specified as
 a complete fully-qualified name.
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 505d1102c20..7460d6c8e31 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -3439,7 +3439,10 @@
 A @code{gdb.Inferior} object has the following attributes:
 
 @defvar Inferior.num
-ID of inferior, as assigned by GDB.
+ID of inferior, as assigned by @value{GDBN}.  You can use this to make
+Python breakpoints inferior-specific, for example
+(@pxref{python_breakpoint_inferior,,The Breakpoint.inferior
+attribute}).
 @end defvar
 
 @anchor{gdbpy_inferior_connection}
@@ -6303,9 +6306,24 @@
 
 @anchor{python_breakpoint_thread}
 @defvar Breakpoint.thread
-If the breakpoint is thread-specific, this attribute holds the
-thread's global id.  If the breakpoint is not thread-specific, this
-attribute is @code{None}.  This attribute is writable.
+If the breakpoint is thread-specific (@pxref{Thread-Specific
+Breakpoints}), this attribute holds the thread's global id.  If the
+breakpoint is not thread-specific, this attribute is @code{None}.
+This attribute is writable.
+
+Only one of @code{Breakpoint.thread} or @code{Breakpoint.inferior} can
+be set to a valid id at any time, that is, a breakpoint can be thread
+specific, or inferior specific, but not both.
+@end defvar
+
+@anchor{python_breakpoint_inferior}
+@defvar Breakpoint.inferior
+If the breakpoint is inferior-specific (@pxref{Inferior-Specific
+Breakpoints}), this attribute holds the inferior's id.  If the
+breakpoint is not inferior-specific, this attribute is @code{None}.
+
+This attribute can be written for breakpoints of type
+@code{gdb.BP_BREAKPOINT} and @code{gdb.BP_HARDWARE_BREAKPOINT}.
 @end defvar
 
 @defvar Breakpoint.task
diff --git a/gdb/guile/scm-breakpoint.c b/gdb/guile/scm-breakpoint.c
index 6c6dacb3883..59254646bcc 100644
--- a/gdb/guile/scm-breakpoint.c
+++ b/gdb/guile/scm-breakpoint.c
@@ -465,7 +465,7 @@ gdbscm_register_breakpoint_x (SCM self)
 	    const breakpoint_ops *ops =
 	      breakpoint_ops_for_location_spec (locspec.get (), false);
 	    create_breakpoint (get_current_arch (),
-			       locspec.get (), NULL, -1, NULL, false,
+			       locspec.get (), NULL, -1, -1, NULL, false,
 			       0,
 			       temporary, bp_breakpoint,
 			       0,
@@ -784,6 +784,11 @@ gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue)
   else
     SCM_ASSERT_TYPE (0, newvalue, SCM_ARG2, FUNC_NAME, _("integer or #f"));
 
+  if (bp_smob->bp->inferior != -1 && id != -1)
+    scm_misc_error (FUNC_NAME,
+		    _("Cannot have both 'thread' and 'inferior' "
+		      "conditions on a breakpoint"), SCM_EOL);
+
   breakpoint_set_thread (bp_smob->bp, id);
 
   return SCM_UNSPECIFIED;
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 96c5feafb1a..fd85d27466a 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -416,17 +416,10 @@ run_command_1 (const char *args, int from_tty, enum run_how run_how)
   if (run_how == RUN_STOP_AT_MAIN)
     {
       /* To avoid other inferiors hitting this breakpoint, make it
-	 inferior-specific using a condition.  A better solution would be to
-	 have proper inferior-specific breakpoint support, in the breakpoint
-	 machinery.  We could then avoid inserting a breakpoint in the program
-	 spaces unrelated to this inferior.  */
-      const char *op
-	= ((current_language->la_language == language_ada
-	    || current_language->la_language == language_pascal
-	    || current_language->la_language == language_m2) ? "=" : "==");
-      std::string arg = string_printf
-	("-qualified %s if $_inferior %s %d", main_name (), op,
-	 current_inferior ()->num);
+	 inferior-specific.  */
+      std::string arg = string_printf ("-qualified %s inferior %d",
+				       main_name (),
+				       current_inferior ()->num);
       tbreak_command (arg.c_str (), 0);
     }
 
diff --git a/gdb/inferior.h b/gdb/inferior.h
index 8f300a5bcc5..74578353482 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -853,4 +853,15 @@ extern void print_selected_inferior (struct ui_out *uiout);
 extern void switch_to_inferior_and_push_target
   (inferior *new_inf, bool no_connection, inferior *org_inf);
 
+/* Return true if ID is a valid global inferior number.  */
+
+inline bool
+valid_global_inferior_id (int id)
+{
+  for (inferior *inf : all_inferiors ())
+    if (inf->num == id)
+      return true;
+  return false;
+}
+
 #endif /* !defined (INFERIOR_H) */
diff --git a/gdb/linespec.c b/gdb/linespec.c
index afa9eb4d3ac..fd9f54d4afd 100644
--- a/gdb/linespec.c
+++ b/gdb/linespec.c
@@ -254,9 +254,9 @@ enum linespec_token_type
 
 /* List of keywords.  This is NULL-terminated so that it can be used
    as enum completer.  */
-const char * const linespec_keywords[] = { "if", "thread", "task", "-force-condition", NULL };
+const char * const linespec_keywords[] = { "if", "thread", "task", "inferior", "-force-condition", NULL };
 #define IF_KEYWORD_INDEX 0
-#define FORCE_KEYWORD_INDEX 3
+#define FORCE_KEYWORD_INDEX 4
 
 /* A token of the linespec lexer  */
 
diff --git a/gdb/mi/mi-cmd-break.c b/gdb/mi/mi-cmd-break.c
index 0777fcbd35e..975bc1c5dde 100644
--- a/gdb/mi/mi-cmd-break.c
+++ b/gdb/mi/mi-cmd-break.c
@@ -173,6 +173,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
   int hardware = 0;
   int temp_p = 0;
   int thread = -1;
+  int thread_group = -1;
   int ignore_count = 0;
   const char *condition = NULL;
   int pending = 0;
@@ -191,7 +192,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
   enum opt
     {
       HARDWARE_OPT, TEMP_OPT, CONDITION_OPT,
-      IGNORE_COUNT_OPT, THREAD_OPT, PENDING_OPT, DISABLE_OPT,
+      IGNORE_COUNT_OPT, THREAD_OPT, THREAD_GROUP_OPT,
+      PENDING_OPT, DISABLE_OPT,
       TRACEPOINT_OPT,
       FORCE_CONDITION_OPT,
       QUALIFIED_OPT,
@@ -205,6 +207,7 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
     {"c", CONDITION_OPT, 1},
     {"i", IGNORE_COUNT_OPT, 1},
     {"p", THREAD_OPT, 1},
+    {"g", THREAD_GROUP_OPT, 1},
     {"f", PENDING_OPT, 0},
     {"d", DISABLE_OPT, 0},
     {"a", TRACEPOINT_OPT, 0},
@@ -247,6 +250,9 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
 	  if (!valid_global_thread_id (thread))
 	    error (_("Unknown thread %d."), thread);
 	  break;
+	case THREAD_GROUP_OPT:
+	  thread_group = mi_parse_thread_group_id (oarg);
+	  break;
 	case PENDING_OPT:
 	  pending = 1;
 	  break;
@@ -360,7 +366,8 @@ mi_cmd_break_insert_1 (int dprintf, const char *command,
 	error (_("Garbage '%s' at end of location"), address);
     }
 
-  create_breakpoint (get_current_arch (), locspec.get (), condition, thread,
+  create_breakpoint (get_current_arch (), locspec.get (), condition,
+		     thread, thread_group,
 		     extra_string.c_str (),
 		     force_condition,
 		     0 /* condition and thread are valid.  */,
diff --git a/gdb/mi/mi-main.c b/gdb/mi/mi-main.c
index 0ac2c74153d..b76940e7403 100644
--- a/gdb/mi/mi-main.c
+++ b/gdb/mi/mi-main.c
@@ -1762,8 +1762,7 @@ mi_cmd_remove_inferior (const char *command, const char *const *argv, int argc)
   if (argc != 1)
     error (_("-remove-inferior should be passed a single argument"));
 
-  if (sscanf (argv[0], "i%d", &id) != 1)
-    error (_("the thread group id is syntactically invalid"));
+  id = mi_parse_thread_group_id (argv[0]);
 
   inf_to_remove = find_inferior_id (id);
   if (inf_to_remove == NULL)
@@ -2796,6 +2795,21 @@ mi_cmd_complete (const char *command, const char *const *argv, int argc)
 		       result.number_matches == max_completions ? "1" : "0");
 }
 
+/* See mi-main.h.  */
+int
+mi_parse_thread_group_id (const char *id)
+{
+  if (*id != 'i')
+    error (_("thread group id should start with an 'i'"));
+
+  char *end;
+  long num = strtol (id + 1, &end, 10);
+
+  if (*end != '\0' || num > INT_MAX)
+    error (_("invalid thread group id '%s'"), id);
+
+  return (int) num;
+}
 
 void _initialize_mi_main ();
 void
diff --git a/gdb/mi/mi-main.h b/gdb/mi/mi-main.h
index cb17921c150..b35544baef1 100644
--- a/gdb/mi/mi-main.h
+++ b/gdb/mi/mi-main.h
@@ -75,4 +75,10 @@ extern void mi_cmd_fix_breakpoint_script_output (const char *command,
 						 const char *const *argv,
 						 int argc);
 
+/* Parse a thread-group-id from ID, and return the integer part of the
+   ID.  A valid thread-group-id is the character 'i' followed by an
+   integer that is greater than zero.  */
+
+extern int mi_parse_thread_group_id (const char *id);
+
 #endif /* MI_MI_MAIN_H */
diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
index fa1570e1a04..cb064515690 100644
--- a/gdb/python/py-breakpoint.c
+++ b/gdb/python/py-breakpoint.c
@@ -288,11 +288,86 @@ bppy_set_thread (PyObject *self, PyObject *newvalue, void *closure)
       return -1;
     }
 
+  if (self_bp->bp->inferior != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'thread' and 'inferior' "
+			 "conditions on a breakpoint"));
+      return -1;
+    }
+
   breakpoint_set_thread (self_bp->bp, id);
 
   return 0;
 }
 
+/* Python function to set the inferior of a breakpoint.  */
+
+static int
+bppy_set_inferior (PyObject *self, PyObject *newvalue, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+  long id;
+
+  BPPY_SET_REQUIRE_VALID (self_bp);
+
+  if (newvalue == NULL)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("Cannot delete 'inferior' attribute."));
+      return -1;
+    }
+  else if (PyLong_Check (newvalue))
+    {
+      if (!gdb_py_int_as_long (newvalue, &id))
+	return -1;
+
+      if (!valid_global_inferior_id (id))
+	{
+	  PyErr_SetString (PyExc_RuntimeError,
+			   _("Invalid inferior ID."));
+	  return -1;
+	}
+    }
+  else if (newvalue == Py_None)
+    id = -1;
+  else
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       _("The value of 'inferior' must be an integer or None."));
+      return -1;
+    }
+
+  if (self_bp->bp->type != bp_breakpoint
+      && self_bp->bp->type != bp_hardware_breakpoint)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot set 'inferior' attribute on a gdb.Breakpoint "
+			 "of this type"));
+      return -1;
+    }
+
+  if (self_bp->bp->thread != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'thread' and 'inferior' conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
+  if (self_bp->bp->task != -1 && id != -1)
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Cannot have both 'task' and 'inferior' conditions "
+			 "on a breakpoint"));
+      return -1;
+    }
+
+  breakpoint_set_inferior (self_bp->bp, id);
+
+  return 0;
+}
+
 /* Python function to set the (Ada) task of a breakpoint.  */
 static int
 bppy_set_task (PyObject *self, PyObject *newvalue, void *closure)
@@ -704,6 +779,20 @@ bppy_get_thread (PyObject *self, void *closure)
   return gdb_py_object_from_longest (self_bp->bp->thread).release ();
 }
 
+/* Python function to get the breakpoint's inferior ID.  */
+static PyObject *
+bppy_get_inferior (PyObject *self, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+
+  BPPY_REQUIRE_VALID (self_bp);
+
+  if (self_bp->bp->inferior == -1)
+    Py_RETURN_NONE;
+
+  return gdb_py_object_from_longest (self_bp->bp->inferior).release ();
+}
+
 /* Python function to get the breakpoint's task ID (in Ada).  */
 static PyObject *
 bppy_get_task (PyObject *self, void *closure)
@@ -942,7 +1031,7 @@ bppy_init (PyObject *self, PyObject *args, PyObject *kwargs)
 	      = breakpoint_ops_for_location_spec (locspec.get (), false);
 
 	    create_breakpoint (gdbpy_enter::get_gdbarch (),
-			       locspec.get (), NULL, -1, NULL, false,
+			       locspec.get (), NULL, -1, -1, NULL, false,
 			       0,
 			       temporary_bp, type,
 			       0,
@@ -1376,6 +1465,11 @@ static gdb_PyGetSetDef breakpoint_object_getset[] = {
 If the value is a thread ID (integer), then this is a thread-specific breakpoint.\n\
 If the value is None, then this breakpoint is not thread-specific.\n\
 No other type of value can be used.", NULL },
+  { "inferior", bppy_get_inferior, bppy_set_inferior,
+    "Inferior ID for the breakpoint.\n\
+If the value is an inferior ID (integer), then this is an inferior-specific\n\
+breakpoint.  If the value is None, then this breakpoint is not\n\
+inferior-specific.  No other type of value can be used.", NULL },
   { "task", bppy_get_task, bppy_set_task,
     "Thread ID for the breakpoint.\n\
 If the value is a task ID (integer), then this is an Ada task-specific breakpoint.\n\
diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c
index b71e5fafc46..fa2139ba5d2 100644
--- a/gdb/python/py-finishbreakpoint.c
+++ b/gdb/python/py-finishbreakpoint.c
@@ -307,7 +307,7 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
       location_spec_up locspec
 	= new_address_location_spec (get_frame_pc (prev_frame), NULL, 0);
       create_breakpoint (gdbpy_enter::get_gdbarch (),
-			 locspec.get (), NULL, thread, NULL, false,
+			 locspec.get (), NULL, thread, -1, NULL, false,
 			 0,
 			 1 /*temp_flag*/,
 			 bp_breakpoint,
diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp
index eb7ee5c9951..603d43f7c36 100644
--- a/gdb/testsuite/gdb.ada/tasks.exp
+++ b/gdb/testsuite/gdb.ada/tasks.exp
@@ -58,6 +58,8 @@ gdb_test "break break_me task 1 thread 1" \
     "You can specify only one of thread or task\\."
 gdb_test "break break_me thread 1 task 1" \
     "You can specify only one of thread or task\\."
+gdb_test "break break_me inferior 1 task 1" \
+    "You can specify only one of inferior or task\\."
 gdb_test "watch j task 1 thread 1" \
     "You can specify only one of thread or task\\."
 gdb_test "watch j thread 1 task 1" \
diff --git a/gdb/testsuite/gdb.linespec/cpcompletion.exp b/gdb/testsuite/gdb.linespec/cpcompletion.exp
index fbe19b49d43..f005707b9da 100644
--- a/gdb/testsuite/gdb.linespec/cpcompletion.exp
+++ b/gdb/testsuite/gdb.linespec/cpcompletion.exp
@@ -1259,8 +1259,8 @@ proc_with_prefix function-labels {} {
 }
 
 # Test that completion after a function name offers keyword
-# (if/task/thread/-force-condition) matches in linespec mode, and also
-# the explicit location options in explicit locations mode.
+# (if/inferior/task/thread/-force-condition) matches in linespec mode,
+# and also the explicit location options in explicit locations mode.
 
 proc_with_prefix keywords-after-function {} {
     set explicit_list \
diff --git a/gdb/testsuite/gdb.linespec/explicit.exp b/gdb/testsuite/gdb.linespec/explicit.exp
index b08d65953d2..668002d9038 100644
--- a/gdb/testsuite/gdb.linespec/explicit.exp
+++ b/gdb/testsuite/gdb.linespec/explicit.exp
@@ -412,6 +412,7 @@ namespace eval $testfile {
 	    "-qualified"
 	    "-source"
 	    "if"
+	    "inferior"
 	    "task"
 	    "thread"
 	}
diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
new file mode 100644
index 00000000000..c171ef71b9d
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.c
@@ -0,0 +1,29 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2023 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/>.  */
+
+int
+foo (void)
+{
+  return 0;
+}
+
+int
+main (void)
+{
+  int res = foo ();
+  return res;
+}
diff --git a/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
new file mode 100644
index 00000000000..57e69ef6240
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/new-ui-bp-deleted.exp
@@ -0,0 +1,108 @@
+# Copyright 2023 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 for the delivery of '=breakpoint-deleted' notifications when
+# breakpoints are deleted.  Right now this test only covers
+# inferior-specific breakpoints, but it could be extended to cover
+# other cases too.
+
+# Multiple inferiors are needed, therefore only native gdb and
+# extended gdbserver modes are supported.
+require !use_gdb_stub
+
+# Separate UI doesn't work with GDB debug.
+require !gdb_debug_enabled
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+standard_testfile
+
+if { [build_executable "failed to prepare" $testfile $srcfile] } {
+    return -1
+}
+
+# Helper proc to create a breakpoint location regexp.  NUM is the
+# regexp to match the number field of this location.
+proc make_bp_loc { num } {
+    return [mi_make_breakpoint_loc \
+		-number "$num" \
+		-enabled "y" \
+		-func "foo" \
+		-inferior "2"]
+}
+
+foreach_mi_ui_mode mode {
+    mi_gdb_exit
+
+    if {$mode eq "separate"} {
+	set start_ops "separate-mi-tty"
+    } else {
+	set start_ops ""
+    }
+
+    if [mi_gdb_start $start_ops] {
+	return
+    }
+
+    # Load a test binary into inferior 1.
+    mi_gdb_load ${binfile}
+
+    # Setup inferior 2, including loading an exec file.
+    mi_gdb_test "-add-inferior" \
+	[multi_line "=thread-group-added,id=\"\[^\"\]+\"" \
+	     "~\"\\\[New inferior 2\\\]\\\\n\"" \
+	     "\~\"Added inferior 2\[^\r\n\]*\\\\n\"" \
+	     "\\^done,inferior=\"\[^\"\]+\"(?:,connection={.*})?" ] \
+	"mi add inferior 2"
+    mi_gdb_test "-file-exec-and-symbols --thread-group i2 $::binfile" \
+	"\\^done" \
+	"set executable of inferior 2"
+
+    # Build regexp for the two locations.
+    set loc1 [make_bp_loc "$::decimal\\.1"]
+    set loc2 [make_bp_loc "$::decimal\\.2"]
+
+    # Create the inferior-specific breakpoint.
+    mi_create_breakpoint_multi "-g i2 foo" "create breakpoint in inferior 2" \
+	-inferior "2" -locations "\\\[$loc1,$loc2\\\]"
+    set bpnum [mi_get_valueof "/d" "\$bpnum" "INVALID"]
+
+    if {$mode eq "separate"} {
+	# In 'separate' mode we delete the inferior from the CLI, and
+	# then look for the breakpoint-deleted notification on the MI.
+	with_spawn_id $gdb_main_spawn_id {
+	    gdb_test "inferior 1" ".*"
+	    gdb_test "remove-inferiors 2" \
+		"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\."
+	}
+
+	gdb_test_multiple "" "check for b/p deleted notification on MI" {
+	    -re "=breakpoint-deleted,id=\"$bpnum\"" {
+		pass $gdb_test_name
+	    }
+	}
+    } else {
+	# In the non-separate mode we delete the inferior from the MI
+	# and expect to immediately see a breakpoint-deleted
+	# notification.
+	mi_gdb_test "-remove-inferior i2" \
+	    [multi_line \
+		 "=thread-group-removed,id=\"i2\"" \
+		 "~\"Inferior-specific breakpoint $bpnum deleted - inferior 2 has been removed\\.\\\\n\"" \
+		 "=breakpoint-deleted,id=\"$bpnum\"" \
+		 "\\^done"]
+    }
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
new file mode 100644
index 00000000000..8f86d8cdcea
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-1.c
@@ -0,0 +1,52 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2023 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/>.  */
+
+volatile int global_var = 0;
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+static inline void __attribute__((__always_inline__))
+foo (void)
+{
+  int i;
+
+  for (i = 0; i < 10; ++i)
+    global_var = 0;
+}
+
+static void
+bar (void)
+{
+  global_var = 0;
+
+  foo ();
+}
+
+
+int
+main (void)
+{
+  global_var = 0;
+  foo ();
+  bar ();
+  stop_breakpt ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
new file mode 100644
index 00000000000..e5b20b6e7a8
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp-2.c
@@ -0,0 +1,52 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022-2023 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/>.  */
+
+static int bar (void);
+static int baz (void);
+static int foo (void);
+
+static void
+stop_breakpt (void)
+{
+  /* Nothing.  */
+}
+
+int
+main (void)
+{
+  int ret = baz ();
+  stop_breakpt ();
+  return ret;
+}
+
+static int
+bar (void)
+{
+  return baz ();
+}
+
+static int
+foo (void)
+{
+  return 0;
+}
+
+static int
+baz (void)
+{
+  return foo ();
+}
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
new file mode 100644
index 00000000000..1f6573268da
--- /dev/null
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
@@ -0,0 +1,179 @@
+# Copyright 2022-2023 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/>.
+
+# Test inferior-specific breakpoints.
+
+standard_testfile -1.c -2.c
+
+if {[use_gdb_stub]} {
+    return
+}
+
+set srcfile1 ${srcfile}
+set binfile1 ${binfile}-1
+set binfile2 ${binfile}-2
+
+if {[build_executable ${testfile}.exp ${binfile1} "${srcfile1}"] != 0} {
+    return -1
+}
+
+if {[build_executable ${testfile}.exp ${binfile2} "${srcfile2}"] != 0} {
+    return -1
+}
+
+# Start the first inferior.
+clean_restart ${binfile1}
+if {![runto_main]} {
+    return
+}
+
+# Add a second inferior, and start this one too.
+gdb_test "add-inferior" "Added inferior 2.*" "add empty inferior 2"
+gdb_test "inferior 2" "Switching to inferior 2.*" "switch to inferior 2"
+gdb_load $binfile2
+if {![runto_main]} {
+    return
+}
+
+# Try to create a breakpoint using both the 'inferior' and 'thread' keywords,
+# this should fail.  Try with the keywords in both orders just in case the
+# parser has a bug.
+gdb_test "break foo thread 1.1 inferior 1" \
+    "You can specify only one of inferior or thread\\."
+gdb_test "break foo inferior 1 thread 1.1" \
+    "You can specify only one of inferior or thread\\."
+
+# Try to create a breakpoint using the 'inferior' keyword multiple times.
+gdb_test "break foo inferior 1 inferior 2" \
+    "You can specify only one inferior\\."
+
+# Clear out any other breakpoints.
+delete_breakpoints
+
+# Use 'info breakpoint' to check that the inferior specific breakpoint is
+# present in the breakpoint list.  TESTNAME is the name used for this test,
+# BP_NUMBER is the number for the breakpoint, and EXPECTED_LOC_COUNT is the
+# number of locations we expect for that breakpoint.
+proc check_info_breakpoints { testname bp_number expected_loc_count } {
+    gdb_test_multiple "info breakpoints $bp_number" $testname {
+	-re "\r\nNum\\s+\[^\r\n\]+\r\n" {
+	    exp_continue
+	}
+
+	-re "^$bp_number\\s+breakpoint\\s+keep\\s+y\\s+<MULTIPLE>\\s*\r\n" {
+	    set saw_header true
+	    exp_continue
+	}
+
+	-re "^\\s+stop only in inferior 1\r\n" {
+	    set saw_inf_cond true
+	    exp_continue
+	}
+
+	-re "^\\s+breakpoint already hit $::decimal times\r\n" {
+	    exp_continue
+	}
+
+	-re "^$bp_number\\.\[123\]\\s+y\\s+ $::hex in foo at \[^\r\n\]+(?: inf \[12\])?\r\n" {
+	    incr location_count
+	    exp_continue
+	}
+
+	-re "^$::gdb_prompt $" {
+	    with_test_prefix $gdb_test_name {
+		gdb_assert { $saw_header \
+				 && $location_count == $expected_loc_count \
+				 && $saw_inf_cond } \
+		    $gdb_test_name
+	    }
+	}
+    }
+}
+
+# Create an inferior-specific breakpoint.  Use gdb_test instead of
+# gdb_breakpoint here as we want to check the breakpoint was placed in
+# multiple locations.
+#
+# Currently GDB still places inferior specific breakpoints into every
+# inferior, just like it does with thread specific breakpoints.
+# Hopefully this will change in the future, at which point, this test
+# will need updating.
+#
+# Two of these locations are in inferior 1, while the third is in
+# inferior 2.
+gdb_test "break foo inferior 1" \
+    "Breakpoint $decimal at $hex: foo\\. \\(3 locations\\)"
+set bp_number [get_integer_valueof "\$bpnum" "INVALID" \
+		  "get b/p number for inferior specific breakpoint"]
+
+set saw_header false
+set location_count 0
+set saw_inf_cond false
+
+check_info_breakpoints "first check for inferior specific breakpoint" \
+    $bp_number 3
+
+# Create a multi-inferior breakpoint to stop at.
+gdb_breakpoint "stop_breakpt" message
+set stop_bp_num [get_integer_valueof "\$bpnum" "INVALID" \
+		    "get b/p number for stop_breakpt"]
+
+# Now resume inferior 2, this should reach 'stop_breakpt'.
+gdb_test "continue" \
+    "hit Breakpoint $stop_bp_num\.$decimal, stop_breakpt \\(\\) .*" \
+    "continue in inferior 2"
+
+# Switch to inferior 1, and try there.
+gdb_test "inferior 1" ".*" \
+    "select inferior 1 to check the inferior-specific b/p works"
+gdb_test "continue " \
+    "Thread 1\\.${decimal}\[^\r\n\]* hit Breakpoint\
+     $bp_number\.$decimal, foo \\(\\) .*" \
+    "first continue in inferior 1"
+
+# Now back to inferior 2, let the inferior exit, and then remove the
+# inferior, the inferior-specific breakpoint should not be deleted.
+gdb_test "inferior 2" ".*" \
+    "switch back to allow inferior 2 to exit"
+gdb_test "continue" "\\\[Inferior 2 \[^\r\n\]+ exited normally\\\]" \
+    "allow inferior 2 to exit"
+gdb_test "inferior 1" ".*" \
+    "back to inferior 1 so inferior 2 can be deleted"
+gdb_test_no_output "remove-inferiors 2"
+
+gdb_test "continue " "hit Breakpoint $bp_number\.$decimal, foo \\(\\) .*" \
+    "second continue in inferior 1"
+gdb_test "continue " "hit Breakpoint $stop_bp_num, stop_breakpt \\(\\) .*" \
+    "third continue in inferior 1"
+
+# Now allow inferior 1 to exit, the inferior specific breakpoint
+# should not be deleted.
+gdb_test "continue" \
+    "\\\[Inferior 1 \[^\r\n\]+ exited normally\\\]" \
+    "allow inferior 1 to exit"
+
+check_info_breakpoints "second check for inferior specific breakpoint" \
+    $bp_number 2
+
+# Now create another new inferior, then remove inferior 1.  As a result of
+# this removal, the inferior specific breakpoint should be deleted.
+gdb_test "add-inferior" "Added inferior 3.*" "add empty inferior 3"
+gdb_test "inferior 3" "Switching to inferior 3.*" "switch to inferior 3"
+gdb_test "remove-inferiors 1" \
+    "Inferior-specific breakpoint $bp_number deleted - inferior 1 has been removed\\."
+
+# Now check 'info breakpoints' to ensure the breakpoint is gone.
+gdb_test "info breakpoints $bp_number" \
+    "No breakpoint or watchpoint matching '$bp_number'\\."
diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp
index df17d646b28..b2c39d70c67 100644
--- a/gdb/testsuite/gdb.python/py-breakpoint.exp
+++ b/gdb/testsuite/gdb.python/py-breakpoint.exp
@@ -153,6 +153,8 @@ proc_with_prefix test_bkpt_basic { } {
 	"Check repr for a thread breakpoint"
     gdb_py_test_silent_cmd "python blist\[1\].thread = None" \
 	"clear breakpoint thread" 0
+    gdb_test "python print (blist\[1\].inferior)" \
+	"None" "Check breakpoint inferior"
     gdb_test "python print (blist\[1\].type == gdb.BP_BREAKPOINT)" \
 	"True" "Check breakpoint type"
     gdb_test "python print (blist\[0\].number)" \
@@ -255,6 +257,46 @@ proc_with_prefix test_bkpt_cond_and_cmds { } {
 	"check number of lines in commands"
 }
 
+# Test breakpoint thread and inferior attributes.
+proc_with_prefix test_bkpt_thread_and_inferior { } {
+    global srcfile testfile hex decimal
+
+    # Start with a fresh gdb.
+    clean_restart ${testfile}
+
+    if {![runto_main]} {
+	return 0
+    }
+
+    with_test_prefix "thread" {
+	delete_breakpoints
+	gdb_test "break multiply thread 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "1"
+	gdb_test "python print(bp.inferior)" "None"
+	gdb_test "python bp.inferior = 1" \
+	    "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.thread = None"
+	gdb_test_no_output "python bp.inferior = 1" \
+	    "set the inferior now the thread has been cleared"
+	gdb_test "info breakpoints" "stop only in inferior 1\r\n.*"
+    }
+
+    with_test_prefix "inferior" {
+	delete_breakpoints
+	gdb_test "break multiply inferior 1"
+	gdb_test "python bp = gdb.breakpoints ()\[0\]"
+	gdb_test "python print(bp.thread)" "None"
+	gdb_test "python print(bp.inferior)" "1"
+	gdb_test "python bp.thread = 1" \
+	    "RuntimeError: Cannot have both 'thread' and 'inferior' conditions on a breakpoint.*"
+	gdb_test_no_output "python bp.inferior = None"
+	gdb_test_no_output "python bp.thread = 1" \
+	    "set the thread now the inferior has been cleared"
+	gdb_test "info breakpoints" "stop only in thread 1\r\n.*"
+    }
+}
+
 proc_with_prefix test_bkpt_invisible { } {
     global srcfile testfile hex decimal
 
@@ -900,6 +942,7 @@ proc_with_prefix test_bkpt_auto_disable { } {
 test_bkpt_basic
 test_bkpt_deletion
 test_bkpt_cond_and_cmds
+test_bkpt_thread_and_inferior
 test_bkpt_invisible
 test_hardware_breakpoints
 test_catchpoints
diff --git a/gdb/testsuite/lib/completion-support.exp b/gdb/testsuite/lib/completion-support.exp
index ea73c3bb367..fdc512838c3 100644
--- a/gdb/testsuite/lib/completion-support.exp
+++ b/gdb/testsuite/lib/completion-support.exp
@@ -27,7 +27,7 @@ namespace eval completion {
     # List of all quote chars, including no-quote at all.
     variable maybe_quoted_list {"" "'" "\""}
 
-    variable keyword_list {"-force-condition" "if" "task" "thread"}
+    variable keyword_list {"-force-condition" "if" "inferior" "task" "thread"}
 
     variable explicit_opts_list \
 	{"-function" "-label" "-line" "-qualified" "-source"}
diff --git a/gdb/testsuite/lib/mi-support.exp b/gdb/testsuite/lib/mi-support.exp
index 49d5e2ef272..3cd94b03c15 100644
--- a/gdb/testsuite/lib/mi-support.exp
+++ b/gdb/testsuite/lib/mi-support.exp
@@ -2542,7 +2542,7 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} {
 # locations.
 #
 # All arguments for the breakpoint location may be specified using the
-# options: number, enabled, addr, func, file, fullname, line,
+# options: number, enabled, addr, func, file, fullname, line, inferior
 # thread-groups, and thread.
 #
 # For the option -thread the corresponding output field is only added
@@ -2556,12 +2556,14 @@ proc mi_build_kv_pairs {attr_list {joiner ,}} {
 proc mi_make_breakpoint_loc {args} {
     parse_args {{number .*} {enabled .*} {addr .*}
 	{func .*} {file .*} {fullname .*} {line .*}
-	{thread-groups \\\[.*\\\]} {thread ""}}
+	{thread-groups \\\[.*\\\]} {thread ""} {inferior ""}}
 
     set attr_list {}
     foreach attr [list number enabled addr func file \
-		      fullname line thread-groups] {
-	lappend attr_list $attr [set $attr]
+		      fullname line thread-groups inferior] {
+	if {$attr ne "inferior" || [set $attr] ne ""} {
+	    lappend attr_list $attr [set $attr]
+	}
     }
 
     set result [mi_build_kv_pairs $attr_list]
@@ -2635,7 +2637,7 @@ proc mi_make_breakpoint_1 {attr_list thread cond evaluated-by times \
 # locations.
 #
 # All arguments for the breakpoint may be specified using the options:
-# number, type, disp, enabled, times, ignore, script,
+# number, type, disp, enabled, times, ignore, script, inferior,
 # original-location, cond, evaluated-by, locations, and thread.
 #
 # Only if -script and -ignore are given will they appear in the output.
@@ -2656,7 +2658,7 @@ proc mi_make_breakpoint_multi {args} {
     parse_args {{number .*} {type .*} {disp .*} {enabled .*}
 	{times .*} {ignore 0}
 	{script ""} {original-location .*} {cond ""} {evaluated-by ""}
-	{locations .*} {thread ""}}
+	{locations .*} {thread ""} {inferior ""}}
 
     set attr_list {}
     foreach attr [list number type disp enabled] {
@@ -2665,6 +2667,12 @@ proc mi_make_breakpoint_multi {args} {
 
     lappend attr_list "addr" "<MULTIPLE>"
 
+    # Only include the inferior field if it was set.  This field is
+    # optional in the MI output.
+    if {$inferior ne ""} {
+	lappend attr_list "inferior" $inferior
+    }
+
     set result [mi_make_breakpoint_1 \
 		    $attr_list $thread $cond ${evaluated-by} $times \
 		    $ignore $script ${original-location}]

base-commit: 0c9546b152f6b01756475ce259c895d3fa446774
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PUSHED] gdb: add missing notify_breakpoint_modified call
  2023-08-17 15:53             ` [PUSHEDv8] " Andrew Burgess
@ 2023-08-23  8:06               ` Andrew Burgess
  2023-08-23  8:19               ` [PUSHED] gdb/testsuite: improve MI support for inferior specific breakpoints Andrew Burgess
  1 sibling, 0 replies; 54+ messages in thread
From: Andrew Burgess @ 2023-08-23  8:06 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

The commit:

  commit b080fe54fb3414b488b8ef323c6c50def061f918
  Date:   Tue Nov 8 12:32:51 2022 +0000

      gdb: add inferior-specific breakpoints

introduced a bug in the function breakpoint_set_inferior. The above
commit includes this line:

  gdb::observers::breakpoint_modified.notify (b);

when it should have instead used this line:

  notify_breakpoint_modified (b);

The change to use notify_breakpoint_modified was introduced to GDB
after commit b080fe54fb34 was written, but before it was merged, and I
failed to update this part of the code during the rebase.

The consequence of this error is that the MI interpreter will not emit
breakpoint-modified notifications when breakpoint_set_inferior is
called.

In this commit I update the code to call notify_breakpoint_modified,
and add a test that checks the MI events are being emitted correctly
in this case.
---
 gdb/breakpoint.c                         |  2 +-
 gdb/testsuite/gdb.mi/mi-py-modify-bp.c   | 28 +++++++++++
 gdb/testsuite/gdb.mi/mi-py-modify-bp.exp | 61 ++++++++++++++++++++++++
 gdb/testsuite/gdb.mi/mi-py-modify-bp.py  | 25 ++++++++++
 4 files changed, 115 insertions(+), 1 deletion(-)
 create mode 100644 gdb/testsuite/gdb.mi/mi-py-modify-bp.c
 create mode 100644 gdb/testsuite/gdb.mi/mi-py-modify-bp.exp
 create mode 100644 gdb/testsuite/gdb.mi/mi-py-modify-bp.py

diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index f88ca1c9b65..f0f5328fb5e 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -1574,7 +1574,7 @@ breakpoint_set_inferior (struct breakpoint *b, int inferior)
   int old_inferior = b->inferior;
   b->inferior = inferior;
   if (old_inferior != inferior)
-    gdb::observers::breakpoint_modified.notify (b);
+    notify_breakpoint_modified (b);
 }
 
 /* See breakpoint.h.  */
diff --git a/gdb/testsuite/gdb.mi/mi-py-modify-bp.c b/gdb/testsuite/gdb.mi/mi-py-modify-bp.c
new file mode 100644
index 00000000000..58546b33387
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/mi-py-modify-bp.c
@@ -0,0 +1,28 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright (C) 2023 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/>.  */
+
+int
+foo ()
+{
+  return 0;
+}
+
+int
+main ()
+{
+  return foo ();
+}
diff --git a/gdb/testsuite/gdb.mi/mi-py-modify-bp.exp b/gdb/testsuite/gdb.mi/mi-py-modify-bp.exp
new file mode 100644
index 00000000000..95a26eab672
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/mi-py-modify-bp.exp
@@ -0,0 +1,61 @@
+# Copyright (C) 2023 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 MI interpreter correctly emits breakpoint-modified
+# notifications when a breakpoint's thread or inferior is set from
+# Python code.
+
+load_lib mi-support.exp
+set MIFLAGS "-i=mi"
+
+standard_testfile
+
+require allow_python_tests
+
+if {[build_executable $testfile.exp $testfile $srcfile] == -1} {
+    return -1
+}
+
+set remote_python_file [gdb_remote_download host \
+			    ${srcdir}/${subdir}/${testfile}.py]
+
+mi_clean_restart $binfile
+mi_runto_main
+
+# Delete all breakpoints.
+mi_delete_breakpoints
+
+# Create a breakpoint.  At this point the breakpoint is global, but
+# this will be adjusted from Python code.
+mi_create_breakpoint "foo" "break in foo" -disp keep -func foo
+set bpnum [mi_get_valueof "/d" "\$bpnum" "INVALID" \
+	       "get number for thread-specific breakpoint"]
+
+# Some patterns used in the expected output below.
+set thr_group_re [string_to_regexp {thread-groups=["i1"]}]
+set times_re [string_to_regexp {times="0"}]
+
+# Source the Python script, the script changes the thread then
+# inferior field of the first breakpoint, we expect to see the
+# breakpoint modified four times.
+mi_gdb_test "source $remote_python_file" \
+    [multi_line \
+	 "&\"source \[^\r\n\]+\"" \
+	 "=breakpoint-modified,bkpt=\\{number=\"$bpnum\",\[^\r\n\]+\\,$thr_group_re,thread=\"1\",$times_re,\[^\r\n\]+}" \
+	 "=breakpoint-modified,bkpt=\\{number=\"$bpnum\",\[^\r\n\]+\\,$thr_group_re,$times_re,\[^\r\n\]+}" \
+	 "=breakpoint-modified,bkpt=\\{number=\"$bpnum\",\[^\r\n\]+\\,$thr_group_re,inferior=\"1\",$times_re,\[^\r\n\]+}" \
+	 "=breakpoint-modified,bkpt=\\{number=\"$bpnum\",\[^\r\n\]+\\,$thr_group_re,$times_re,\[^\r\n\]+}" \
+	 "\\^done"] \
+    "source python script"
diff --git a/gdb/testsuite/gdb.mi/mi-py-modify-bp.py b/gdb/testsuite/gdb.mi/mi-py-modify-bp.py
new file mode 100644
index 00000000000..6e1034c38bd
--- /dev/null
+++ b/gdb/testsuite/gdb.mi/mi-py-modify-bp.py
@@ -0,0 +1,25 @@
+# Copyright (C) 2023 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/>.
+
+import gdb
+
+bps = gdb.breakpoints()
+bp = bps[0]
+
+bp.thread = 1
+bp.thread = None
+
+bp.inferior = 1
+bp.inferior = None

base-commit: 835f16daa77952015d1a97ae6eab48cc2ea14fb8
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

* [PUSHED] gdb/testsuite: improve MI support for inferior specific breakpoints
  2023-08-17 15:53             ` [PUSHEDv8] " Andrew Burgess
  2023-08-23  8:06               ` [PUSHED] gdb: add missing notify_breakpoint_modified call Andrew Burgess
@ 2023-08-23  8:19               ` Andrew Burgess
  1 sibling, 0 replies; 54+ messages in thread
From: Andrew Burgess @ 2023-08-23  8:19 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

In this commit:

  commit b080fe54fb3414b488b8ef323c6c50def061f918
  Date:   Tue Nov 8 12:32:51 2022 +0000

      gdb: add inferior-specific breakpoints

limited support was added in lib/mi-support.exp to help with testing
of inferior specific breakpoints.

Though the changes that were added were not wrong, while working on a
later patch, I realised that I had added the support in the wrong
place -- I only added support to mi_make_breakpoint_multi, when really
I should have added the support to mi_make_breakpoint_1, which is used
by all of the MI procs that create breakpoints.

This commit moves the support to mi_make_breakpoint_1, and updates all
the procs that use mi_make_breakpoint_1 to accept, and then pass
through, and (optional) inferior argument.  This will make it much
easier to write MI tests for inferior specific breakpoints.

There's no change in what is tested after this commit.
---
 gdb/testsuite/lib/mi-support.exp | 34 ++++++++++++++++----------------
 1 file changed, 17 insertions(+), 17 deletions(-)

diff --git a/gdb/testsuite/lib/mi-support.exp b/gdb/testsuite/lib/mi-support.exp
index 3cd94b03c15..c9af88b9b1b 100644
--- a/gdb/testsuite/lib/mi-support.exp
+++ b/gdb/testsuite/lib/mi-support.exp
@@ -2578,8 +2578,8 @@ proc mi_make_breakpoint_loc {args} {
 
 # Bits shared between mi_make_breakpoint and mi_make_breakpoint_multi.
 
-proc mi_make_breakpoint_1 {attr_list thread cond evaluated-by times \
-			   ignore script original-location} {
+proc mi_make_breakpoint_1 {attr_list thread inferior cond evaluated-by \
+			   times ignore script original-location} {
     set result "bkpt=\\\{[mi_build_kv_pairs $attr_list]"
 
     # There are always exceptions.
@@ -2590,6 +2590,12 @@ proc mi_make_breakpoint_1 {attr_list thread cond evaluated-by times \
 	append result [mi_build_kv_pairs [list "thread" $thread]]
     }
 
+    # If INFERIOR is not present, do not output it.
+    if {[string length $inferior] > 0} {
+	append result ","
+	append result [mi_build_kv_pairs [list "inferior" $inferior]]
+    }
+
     # If COND is not present, do not output it.
     if {[string length $cond] > 0} {
 	append result ","
@@ -2667,15 +2673,9 @@ proc mi_make_breakpoint_multi {args} {
 
     lappend attr_list "addr" "<MULTIPLE>"
 
-    # Only include the inferior field if it was set.  This field is
-    # optional in the MI output.
-    if {$inferior ne ""} {
-	lappend attr_list "inferior" $inferior
-    }
-
     set result [mi_make_breakpoint_1 \
-		    $attr_list $thread $cond ${evaluated-by} $times \
-		    $ignore $script ${original-location}]
+		    $attr_list $thread $inferior $cond ${evaluated-by} \
+		    $times $ignore $script ${original-location}]
 
     append result ","
     append result [mi_build_kv_pairs [list "locations" $locations]]
@@ -2703,8 +2703,8 @@ proc mi_make_breakpoint_multi {args} {
 
 proc mi_make_breakpoint_pending {args} {
     parse_args {{number .*} {type .*} {disp .*} {enabled .*}
-	{pending .*} {original-location .*} {thread ""} {cond ""}
-	{script ""} {times .*}}
+	{pending .*} {original-location .*} {thread ""} {inferior ""}
+	{cond ""} {script ""} {times .*}}
 
     set attr_list {}
     foreach attr [list number type disp enabled] {
@@ -2721,8 +2721,8 @@ proc mi_make_breakpoint_pending {args} {
     set evaluated-by ""
 
     set result [mi_make_breakpoint_1 \
-		    $attr_list $thread $cond ${evaluated-by} $times \
-		    $ignore $script ${original-location}]
+		    $attr_list $thread $inferior $cond ${evaluated-by} \
+		    $times $ignore $script ${original-location}]
 
     append result "\\\}"
     return $result
@@ -2750,7 +2750,7 @@ proc mi_make_breakpoint {args} {
 	{func .*} {file .*} {fullname .*} {line .*}
 	{thread-groups \\\[.*\\\]} {times .*} {ignore 0}
 	{script ""} {original-location .*} {cond ""} {evaluated-by ""}
-	{thread ""}}
+	{thread ""} {inferior ""}}
 
     set attr_list {}
     foreach attr [list number type disp enabled addr func file \
@@ -2759,8 +2759,8 @@ proc mi_make_breakpoint {args} {
     }
 
     set result [mi_make_breakpoint_1 \
-		    $attr_list $thread $cond ${evaluated-by} $times \
-		    $ignore $script ${original-location}]
+		    $attr_list $thread $inferior $cond ${evaluated-by} \
+		    $times $ignore $script ${original-location}]
 
     append result "\\\}"
     return $result

base-commit: f29ab2e0e350a4b382a1e4eb1b41c28564d83e94
-- 
2.25.4


^ permalink raw reply	[flat|nested] 54+ messages in thread

end of thread, other threads:[~2023-08-23  8:19 UTC | newest]

Thread overview: 54+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-11-28 11:25 [PATCH 0/6] Inferior specific breakpoints Andrew Burgess
2022-11-28 11:25 ` [PATCH 1/6] gdb/remote: announce thread exit events for remote targets Andrew Burgess
2022-11-28 11:25 ` [PATCH 2/6] gdb/testsuite: don't try to set non-stop mode on a running target Andrew Burgess
2022-11-28 11:25 ` [PATCH 3/6] gdb: fix display of thread condition for multi-location breakpoints Andrew Burgess
2022-12-23  8:37   ` Aktemur, Tankut Baris
2022-11-28 11:25 ` [PATCH 4/6] gdb: error if 'thread' or 'task' keywords are overused Andrew Burgess
2022-11-28 13:10   ` Eli Zaretskii
2022-11-28 11:25 ` [PATCH 5/6] gdb: add inferior-specific breakpoints and watchpoints Andrew Burgess
2022-11-28 13:18   ` Eli Zaretskii
2022-12-23 10:05   ` Aktemur, Tankut Baris
2023-01-19 19:13     ` Andrew Burgess
2023-01-20 13:12       ` Aktemur, Tankut Baris
2022-11-28 11:25 ` [PATCH 6/6] gdb: convert the 'start' breakpoint to use inferior keyword Andrew Burgess
2022-12-23 10:17   ` Aktemur, Tankut Baris
2022-12-23 10:55 ` [PATCH 0/6] Inferior specific breakpoints Aktemur, Tankut Baris
2023-01-20  9:46 ` [PATCHv2 " Andrew Burgess
2023-01-20  9:46   ` [PATCHv2 1/6] gdb/remote: announce thread exit events for remote targets Andrew Burgess
2023-02-02 17:50     ` Pedro Alves
2023-02-04 15:46       ` Andrew Burgess
2023-01-20  9:46   ` [PATCHv2 2/6] gdb/testsuite: don't try to set non-stop mode on a running target Andrew Burgess
2023-02-04 16:22     ` Andrew Burgess
2023-01-20  9:46   ` [PATCHv2 3/6] gdb: fix display of thread condition for multi-location breakpoints Andrew Burgess
2023-02-02 18:13     ` Pedro Alves
2023-02-06 14:48       ` Andrew Burgess
2023-02-06 17:01         ` Pedro Alves
2023-02-07 14:42           ` Andrew Burgess
2023-01-20  9:46   ` [PATCHv2 4/6] gdb: error if 'thread' or 'task' keywords are overused Andrew Burgess
2023-01-20 13:22     ` Eli Zaretskii
2023-02-02 14:08       ` Andrew Burgess
2023-02-02 14:31         ` Eli Zaretskii
2023-02-02 18:21     ` Pedro Alves
2023-02-03 16:41       ` Andrew Burgess
2023-02-04  5:52         ` Joel Brobecker
2023-02-04 15:40           ` Andrew Burgess
2023-02-06 11:06       ` Andrew Burgess
2023-01-20  9:46   ` [PATCHv2 5/6] gdb: add inferior-specific breakpoints and watchpoints Andrew Burgess
2023-01-20 13:25     ` Eli Zaretskii
2023-02-02 19:17     ` Pedro Alves
2023-02-03 16:55       ` Andrew Burgess
2023-02-06 17:24         ` Pedro Alves
2023-02-16 12:56     ` Aktemur, Tankut Baris
2023-01-20  9:46   ` [PATCHv2 6/6] gdb: convert the 'start' breakpoint to use inferior keyword Andrew Burgess
2023-02-16 12:59     ` Aktemur, Tankut Baris
2023-03-16 17:03   ` [PATCHv3 0/2] Inferior specific breakpoints Andrew Burgess
2023-03-16 17:03     ` [PATCHv3 1/2] gdb: cleanup around some set_momentary_breakpoint_at_pc calls Andrew Burgess
2023-04-03 14:12       ` Andrew Burgess
2023-03-16 17:03     ` [PATCHv3 2/2] gdb: add inferior-specific breakpoints Andrew Burgess
2023-04-03 14:14     ` [PATCHv4] " Andrew Burgess
2023-05-15 19:15       ` [PATCHv5] " Andrew Burgess
2023-05-30 20:41         ` [PATCHv6] " Andrew Burgess
2023-07-07 10:23           ` [PATCHv7] " Andrew Burgess
2023-08-17 15:53             ` [PUSHEDv8] " Andrew Burgess
2023-08-23  8:06               ` [PUSHED] gdb: add missing notify_breakpoint_modified call Andrew Burgess
2023-08-23  8:19               ` [PUSHED] gdb/testsuite: improve MI support for inferior specific breakpoints Andrew Burgess

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).