aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/breakpoint.c2
-rw-r--r--gdb/gdbthread.h3
-rw-r--r--gdb/infcall.c6
-rw-r--r--gdb/infrun.c64
-rw-r--r--gdb/infrun.h3
-rw-r--r--gdb/testsuite/gdb.threads/infcall-from-bp-cond-other-thread-event.c135
-rw-r--r--gdb/testsuite/gdb.threads/infcall-from-bp-cond-other-thread-event.exp174
-rw-r--r--gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.c89
-rw-r--r--gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.exp235
-rw-r--r--gdb/testsuite/gdb.threads/infcall-from-bp-cond-single.c139
-rw-r--r--gdb/testsuite/gdb.threads/infcall-from-bp-cond-single.exp117
11 files changed, 952 insertions, 15 deletions
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 102bd7f..39c1310 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -5665,6 +5665,8 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread)
{
try
{
+ scoped_restore reset_in_cond_eval
+ = make_scoped_restore (&thread->control.in_cond_eval, true);
condition_result = breakpoint_cond_eval (cond);
}
catch (const gdb_exception_error &ex)
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 11c553a..73f6895 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -173,6 +173,9 @@ struct thread_control_state
command. This is used to decide whether "set scheduler-locking
step" behaves like "on" or "off". */
int stepping_command = 0;
+
+ /* True if the thread is evaluating a BP condition. */
+ bool in_cond_eval = false;
};
/* Inferior thread specific part of `struct infcall_suspend_state'. */
diff --git a/gdb/infcall.c b/gdb/infcall.c
index 145ce25..bfcac20 100644
--- a/gdb/infcall.c
+++ b/gdb/infcall.c
@@ -672,6 +672,12 @@ run_inferior_call (std::unique_ptr<call_thread_fsm> sm,
proceed (real_pc, GDB_SIGNAL_0);
+ /* Enable commit resume, but pass true for the force flag. This
+ ensures any thread we set running in proceed will actually be
+ committed to the target, even if some other thread in the current
+ target has a pending event. */
+ scoped_enable_commit_resumed enable ("infcall", true);
+
infrun_debug_show_threads ("non-exited threads after proceed for inferior-call",
all_non_exited_threads ());
diff --git a/gdb/infrun.c b/gdb/infrun.c
index e0e0ba3..b06972b 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -2406,6 +2406,14 @@ user_visible_resume_ptid (int step)
mode. */
resume_ptid = inferior_ptid;
}
+ else if (inferior_ptid != null_ptid
+ && inferior_thread ()->control.in_cond_eval)
+ {
+ /* The inferior thread is evaluating a BP condition. Other threads
+ might be stopped or running and we do not want to change their
+ state, thus, resume only the current thread. */
+ resume_ptid = inferior_ptid;
+ }
else if (!sched_multi && target_supports_multi_process ())
{
/* Resume all threads of the current process (and none of other
@@ -3201,12 +3209,24 @@ schedlock_applies (struct thread_info *tp)
execution_direction)));
}
-/* Set process_stratum_target::COMMIT_RESUMED_STATE in all target
- stacks that have threads executing and don't have threads with
- pending events. */
+/* When FORCE_P is false, set process_stratum_target::COMMIT_RESUMED_STATE
+ in all target stacks that have threads executing and don't have threads
+ with pending events.
+
+ When FORCE_P is true, set process_stratum_target::COMMIT_RESUMED_STATE
+ in all target stacks that have threads executing regardless of whether
+ there are pending events or not.
+
+ Passing FORCE_P as false makes sense when GDB is going to wait for
+ events from all threads and will therefore spot the pending events.
+ However, if GDB is only going to wait for events from select threads
+ (i.e. when performing an inferior call) then a pending event on some
+ other thread will not be spotted, and if we fail to commit the resume
+ state for the thread performing the inferior call, then the inferior
+ call will never complete (or even start). */
static void
-maybe_set_commit_resumed_all_targets ()
+maybe_set_commit_resumed_all_targets (bool force_p)
{
scoped_restore_current_thread restore_thread;
@@ -3235,7 +3255,7 @@ maybe_set_commit_resumed_all_targets ()
status to report, handle it before requiring the target to
commit its resumed threads: handling the status might lead to
resuming more threads. */
- if (proc_target->has_resumed_with_pending_wait_status ())
+ if (!force_p && proc_target->has_resumed_with_pending_wait_status ())
{
infrun_debug_printf ("not requesting commit-resumed for target %s, a"
" thread has a pending waitstatus",
@@ -3245,7 +3265,7 @@ maybe_set_commit_resumed_all_targets ()
switch_to_inferior_no_thread (inf);
- if (target_has_pending_events ())
+ if (!force_p && target_has_pending_events ())
{
infrun_debug_printf ("not requesting commit-resumed for target %s, "
"target has pending events",
@@ -3338,7 +3358,7 @@ scoped_disable_commit_resumed::reset ()
{
/* This is the outermost instance, re-enable
COMMIT_RESUMED_STATE on the targets where it's possible. */
- maybe_set_commit_resumed_all_targets ();
+ maybe_set_commit_resumed_all_targets (false);
}
else
{
@@ -3371,7 +3391,7 @@ scoped_disable_commit_resumed::reset_and_commit ()
/* See infrun.h. */
scoped_enable_commit_resumed::scoped_enable_commit_resumed
- (const char *reason)
+ (const char *reason, bool force_p)
: m_reason (reason),
m_prev_enable_commit_resumed (enable_commit_resumed)
{
@@ -3383,7 +3403,7 @@ scoped_enable_commit_resumed::scoped_enable_commit_resumed
/* Re-enable COMMIT_RESUMED_STATE on the targets where it's
possible. */
- maybe_set_commit_resumed_all_targets ();
+ maybe_set_commit_resumed_all_targets (force_p);
maybe_call_commit_resumed_all_targets ();
}
@@ -4136,10 +4156,11 @@ do_target_wait (ptid_t wait_ptid, execution_control_state *ecs,
polling the rest of the inferior list starting from that one in a
circular fashion until the whole list is polled once. */
- auto inferior_matches = [&wait_ptid] (inferior *inf)
+ ptid_t wait_ptid_pid {wait_ptid.pid ()};
+ auto inferior_matches = [&wait_ptid_pid] (inferior *inf)
{
return (inf->process_target () != nullptr
- && ptid_t (inf->pid).matches (wait_ptid));
+ && ptid_t (inf->pid).matches (wait_ptid_pid));
};
/* First see how many matching inferiors we have. */
@@ -4628,7 +4649,17 @@ fetch_inferior_event ()
the event. */
scoped_disable_commit_resumed disable_commit_resumed ("handling event");
- if (!do_target_wait (minus_one_ptid, &ecs, TARGET_WNOHANG))
+ /* Is the current thread performing an inferior function call as part
+ of a breakpoint condition evaluation? */
+ bool in_cond_eval = (inferior_ptid != null_ptid
+ && inferior_thread ()->control.in_cond_eval);
+
+ /* If the thread is in the middle of the condition evaluation, wait for
+ an event from the current thread. Otherwise, wait for an event from
+ any thread. */
+ ptid_t waiton_ptid = in_cond_eval ? inferior_ptid : minus_one_ptid;
+
+ if (!do_target_wait (waiton_ptid, &ecs, TARGET_WNOHANG))
{
infrun_debug_printf ("do_target_wait returned no event");
disable_commit_resumed.reset_and_commit ();
@@ -4686,7 +4717,12 @@ fetch_inferior_event ()
bool should_notify_stop = true;
bool proceeded = false;
- stop_all_threads_if_all_stop_mode ();
+ /* If the thread that stopped just completed an inferior
+ function call as part of a condition evaluation, then we
+ don't want to stop all the other threads. */
+ if (ecs.event_thread == nullptr
+ || !ecs.event_thread->control.in_cond_eval)
+ stop_all_threads_if_all_stop_mode ();
clean_up_just_stopped_threads_fsms (&ecs);
@@ -4713,7 +4749,7 @@ fetch_inferior_event ()
proceeded = normal_stop ();
}
- if (!proceeded)
+ if (!proceeded && !in_cond_eval)
{
inferior_event_handler (INF_EXEC_COMPLETE);
cmd_done = 1;
diff --git a/gdb/infrun.h b/gdb/infrun.h
index 6339fd9..5f83ca2 100644
--- a/gdb/infrun.h
+++ b/gdb/infrun.h
@@ -406,7 +406,8 @@ extern void maybe_call_commit_resumed_all_targets ();
struct scoped_enable_commit_resumed
{
- explicit scoped_enable_commit_resumed (const char *reason);
+ explicit scoped_enable_commit_resumed (const char *reason,
+ bool force_p = false);
~scoped_enable_commit_resumed ();
DISABLE_COPY_AND_ASSIGN (scoped_enable_commit_resumed);
diff --git a/gdb/testsuite/gdb.threads/infcall-from-bp-cond-other-thread-event.c b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-other-thread-event.c
new file mode 100644
index 0000000..38a5bc2
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-other-thread-event.c
@@ -0,0 +1,135 @@
+/* Copyright 2022-2024 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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 <pthread.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sched.h>
+
+#define NUM_THREADS 2
+
+pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* Some global variables to poke, just for something to do. */
+volatile int global_var_0 = 0;
+volatile int global_var_1 = 0;
+
+/* This flag is updated from GDB. */
+volatile int raise_signal = 0;
+
+/* Implement the breakpoint condition function. Release the other thread
+ and try to give the other thread a chance to run. Then return ANSWER. */
+int
+condition_core_func (int answer)
+{
+ /* This unlock should release the other thread. */
+ if (pthread_mutex_unlock (&mutex) != 0)
+ abort ();
+
+ /* And this yield and sleep should (hopefully) give the other thread a
+ chance to run. This isn't guaranteed of course, but once the other
+ thread does run it should hit a breakpoint, which GDB should
+ (temporarily) ignore, so there's no easy way for us to know the other
+ thread has done what it needs to, thus, yielding and sleeping is the
+ best we can do. */
+ sched_yield ();
+ sleep (2);
+
+ return answer;
+}
+
+void
+stop_marker ()
+{
+ int a = 100; /* Final breakpoint here. */
+}
+
+/* A breakpoint condition function that always returns true. */
+int
+condition_true_func ()
+{
+ return condition_core_func (1);
+}
+
+/* A breakpoint condition function that always returns false. */
+int
+condition_false_func ()
+{
+ return condition_core_func (0);
+}
+
+void *
+worker_func (void *arg)
+{
+ volatile int *ptr = 0;
+ int tid = *((int *) arg);
+
+ switch (tid)
+ {
+ case 0:
+ global_var_0 = 11; /* First thread breakpoint. */
+ break;
+
+ case 1:
+ if (pthread_mutex_lock (&mutex) != 0)
+ abort ();
+ if (raise_signal)
+ global_var_1 = *ptr; /* Signal here. */
+ else
+ global_var_1 = 99; /* Other thread breakpoint. */
+ break;
+
+ default:
+ abort ();
+ }
+
+ return NULL;
+}
+
+int
+main ()
+{
+ pthread_t threads[NUM_THREADS];
+ int args[NUM_THREADS];
+
+ /* Set an alarm, just in case the test deadlocks. */
+ alarm (300);
+
+ /* We want the mutex to start locked. */
+ if (pthread_mutex_lock (&mutex) != 0)
+ abort ();
+
+ for (int i = 0; i < NUM_THREADS; i++)
+ {
+ args[i] = i;
+ pthread_create (&threads[i], NULL, worker_func, &args[i]);
+ }
+
+ for (int i = 0; i < NUM_THREADS; i++)
+ {
+ void *retval;
+ pthread_join (threads[i], &retval);
+ }
+
+ /* Unlock once we're done, just for cleanliness. */
+ if (pthread_mutex_unlock (&mutex) != 0)
+ abort ();
+
+ stop_marker ();
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/infcall-from-bp-cond-other-thread-event.exp b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-other-thread-event.exp
new file mode 100644
index 0000000..b5c0d84
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-other-thread-event.exp
@@ -0,0 +1,174 @@
+# Copyright 2022-2024 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 for conditional breakpoints where the breakpoint condition includes
+# an inferior function call.
+#
+# The tests in this script are testing what happens when an event arrives in
+# another thread while GDB is waiting for the inferior function call (in the
+# breakpoint condition) to finish.
+#
+# The expectation is that GDB will queue events for other threads and wait
+# for the inferior function call to complete, if the condition is true, then
+# the conditional breakpoint should be reported first. The other thread
+# event should of course, not get lost, and should be reported as soon as
+# the user tries to continue the inferior.
+#
+# If the conditional breakpoint ends up not being taken (the condition is
+# false), then the other thread event should be reported immediately.
+#
+# This script tests what happens when the other thread event is (a) the
+# other thread hitting a breakpoint, and (b) the other thread taking a
+# signal (SIGSEGV in this case).
+
+standard_testfile
+
+if { [build_executable "failed to prepare" ${binfile} "${srcfile}" \
+ {debug pthreads}] == -1 } {
+ return
+}
+
+set cond_bp_line [gdb_get_line_number "First thread breakpoint"]
+set other_bp_line [gdb_get_line_number "Other thread breakpoint"]
+set final_bp_line [gdb_get_line_number "Final breakpoint here"]
+set signal_line [gdb_get_line_number "Signal here"]
+
+# Start GDB based on TARGET_ASYNC and TARGET_NON_STOP, and then runto main.
+proc start_gdb_and_runto_main { target_async target_non_stop } {
+ save_vars { ::GDBFLAGS } {
+ append ::GDBFLAGS \
+ " -ex \"maint set target-non-stop $target_non_stop\""
+ append ::GDBFLAGS \
+ " -ex \"maintenance set target-async ${target_async}\""
+
+ clean_restart ${::binfile}
+ }
+
+ if { ![runto_main] } {
+ return -1
+ }
+
+ return 0
+}
+
+# Run a test of GDB's conditional breakpoints, where the conditions include
+# inferior function calls. While the inferior function call is executing
+# another thread will hit a breakpoint (when OTHER_THREAD_SIGNAL is false),
+# or receive a signal (when OTHER_THREAD_SIGNAL is true). GDB should report
+# the conditional breakpoint first (if the condition is true), and then
+# report the second thread event once the inferior is continued again.
+#
+# When STOP_AT_COND is true then the conditional breakpoint will have a
+# condition that evaluates to true (and GDB will stop at the breakpoint),
+# otherwise, the condition will evaluate to false (and GDB will not stop at
+# the breakpoint).
+proc run_condition_test { stop_at_cond other_thread_signal \
+ target_async target_non_stop } {
+ if { [start_gdb_and_runto_main $target_async \
+ $target_non_stop] == -1 } {
+ return
+ }
+
+ # Setup the conditional breakpoint.
+ if { $stop_at_cond } {
+ set cond_func "condition_true_func"
+ } else {
+ set cond_func "condition_false_func"
+ }
+ gdb_breakpoint \
+ "${::srcfile}:${::cond_bp_line} if (${cond_func} ())"
+ set cond_bp_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \
+ "get number for conditional breakpoint"]
+
+ if { $other_thread_signal } {
+ # Arrange for the other thread to raise a signal while GDB is
+ # evaluating the breakpoint condition.
+ gdb_test_no_output "set raise_signal = 1"
+ } else {
+ # And a breakpoint that will be hit by another thread only once the
+ # breakpoint condition starts to be evaluated.
+ gdb_breakpoint "${::srcfile}:${::other_bp_line}"
+ set other_bp_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \
+ "get number for other breakpoint"]
+ }
+
+ # A final breakpoint once the test has completed.
+ gdb_breakpoint "${::srcfile}:${::final_bp_line}"
+ set final_bp_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \
+ "get number for final breakpoint"]
+
+ if { $stop_at_cond } {
+ # Continue. The first breakpoint we hit should be the conditional
+ # breakpoint. The other thread will have hit its breakpoint, but
+ # that will have been deferred until the conditional breakpoint is
+ # reported.
+ gdb_test "continue" \
+ [multi_line \
+ "Continuing\\." \
+ ".*" \
+ "" \
+ "Thread ${::decimal} \"\[^\"\r\n\]+\" hit Breakpoint ${cond_bp_num}, worker_func \[^\r\n\]+:${::cond_bp_line}" \
+ "${::decimal}\\s+\[^\r\n\]+First thread breakpoint\[^\r\n\]+"] \
+ "hit the conditional breakpoint"
+ }
+
+ if { $other_thread_signal } {
+ # Now continue again, the other thread will now report that it
+ # received a signal.
+ gdb_test "continue" \
+ [multi_line \
+ "Continuing\\." \
+ ".*" \
+ "Thread ${::decimal} \"\[^\"\r\n\]+\" received signal SIGSEGV, Segmentation fault\\." \
+ "\\\[Switching to Thread \[^\r\n\]+\\\]" \
+ "${::hex} in worker_func \[^\r\n\]+:${::signal_line}" \
+ "${::decimal}\\s+\[^\r\n\]+Signal here\[^\r\n\]+"] \
+ "received signal in other thread"
+ } else {
+ # Now continue again, the other thread will now report its
+ # breakpoint.
+ gdb_test "continue" \
+ [multi_line \
+ "Continuing\\." \
+ ".*" \
+ "" \
+ "Thread ${::decimal} \"\[^\"\r\n\]+\" hit Breakpoint ${other_bp_num}, worker_func \[^\r\n\]+:${::other_bp_line}" \
+ "${::decimal}\\s+\[^\r\n\]+Other thread breakpoint\[^\r\n\]+"] \
+ "hit the breakpoint in other thread"
+
+ # Run to the stop marker.
+ gdb_test "continue" \
+ [multi_line \
+ "Continuing\\." \
+ ".*" \
+ "" \
+ "Thread ${::decimal} \"\[^\"\r\n\]+\" hit Breakpoint ${final_bp_num}, stop_marker \[^\r\n\]+:${::final_bp_line}" \
+ "${::decimal}\\s+\[^\r\n\]+Final breakpoint here\[^\r\n\]+"] \
+ "hit the final breakpoint"
+ }
+
+ gdb_exit
+}
+
+foreach_with_prefix target_async { "on" "off" } {
+ foreach_with_prefix target_non_stop { "on" "off" } {
+ foreach_with_prefix other_thread_signal { true false } {
+ foreach_with_prefix stop_at_cond { true false } {
+ run_condition_test $stop_at_cond $other_thread_signal \
+ $target_async $target_non_stop
+ }
+ }
+ }
+}
diff --git a/gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.c b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.c
new file mode 100644
index 0000000..2e23f12
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.c
@@ -0,0 +1,89 @@
+/* Copyright 2022-2024 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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 <pthread.h>
+#include <unistd.h>
+
+#define NUM_THREADS 3
+
+int
+is_matching_tid (int *tid_ptr, int tid_value)
+{
+ return *tid_ptr == tid_value;
+}
+
+int
+return_true ()
+{
+ return 1;
+}
+
+int
+return_false ()
+{
+ return 0;
+}
+
+int
+function_that_segfaults ()
+{
+ int *p = 0;
+ *p = 1; /* Segfault happens here. */
+}
+
+int
+function_with_breakpoint ()
+{
+ return 1; /* Nested breakpoint. */
+}
+
+void *
+worker_func (void *arg)
+{
+ int a = 42; /* Breakpoint here. */
+}
+
+void
+stop_marker ()
+{
+ int b = 99; /* Stop marker. */
+}
+
+int
+main ()
+{
+ pthread_t threads[NUM_THREADS];
+ int args[NUM_THREADS];
+
+ alarm (300);
+
+ for (int i = 0; i < NUM_THREADS; i++)
+ {
+ args[i] = i;
+ pthread_create (&threads[i], NULL, worker_func, &args[i]);
+ }
+
+ for (int i = 0; i < NUM_THREADS; i++)
+ {
+ void *retval;
+ pthread_join (threads[i], &retval);
+ }
+
+ stop_marker ();
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.exp b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.exp
new file mode 100644
index 0000000..d6069eba
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-simple.exp
@@ -0,0 +1,235 @@
+# Copyright 2022-2024 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/>.
+
+# Some simple tests of inferior function calls from breakpoint
+# conditions, in multi-threaded inferiors.
+#
+# This test sets up a multi-threaded inferior, and places a breakpoint
+# at a location that many of the threads will reach. We repeat the
+# test with different conditions, sometimes a single thread should
+# stop at the breakpoint, sometimes multiple threads should stop, and
+# sometimes no threads should stop.
+
+standard_testfile
+
+if { [build_executable "failed to prepare" ${binfile} "${srcfile}" \
+ {debug pthreads}] == -1 } {
+ return
+}
+
+set cond_bp_line [gdb_get_line_number "Breakpoint here"]
+set stop_bp_line [gdb_get_line_number "Stop marker"]
+set nested_bp_line [gdb_get_line_number "Nested breakpoint"]
+set segv_line [gdb_get_line_number "Segfault happens here"]
+
+# Start GDB based on TARGET_ASYNC and TARGET_NON_STOP, and then runto main.
+proc start_gdb_and_runto_main { target_async target_non_stop } {
+ save_vars { ::GDBFLAGS } {
+ append ::GDBFLAGS \
+ " -ex \"maint set target-non-stop $target_non_stop\""
+ append ::GDBFLAGS \
+ " -ex \"maintenance set target-async ${target_async}\""
+
+ clean_restart ${::binfile}
+ }
+
+ if { ![runto_main] } {
+ return -1
+ }
+
+ return 0
+}
+
+# Run a test of GDB's conditional breakpoints, where the conditions include
+# inferior function calls.
+#
+# CONDITION is the expression to be used as the breakpoint condition.
+#
+# N_EXPECTED_HITS is the number of threads that we expect to stop due to
+# CONDITON.
+#
+# MESSAGE is used as a test name prefix.
+proc run_condition_test { message n_expected_hits condition \
+ target_async target_non_stop } {
+ with_test_prefix $message {
+
+ if { [start_gdb_and_runto_main $target_async \
+ $target_non_stop] == -1 } {
+ return
+ }
+
+ # Use this convenience variable to track how often the
+ # breakpoint condition has been evaluated. This should be
+ # once per thread.
+ gdb_test "set \$n_cond_eval = 0"
+
+ # Setup the conditional breakpoint.
+ gdb_breakpoint \
+ "${::srcfile}:${::cond_bp_line} if ((++\$n_cond_eval) && (${condition}))"
+
+ # And a breakpoint that we hit when the test is over, this one is
+ # not conditional. Only the main thread gets here once all the
+ # other threads have finished.
+ gdb_breakpoint "${::srcfile}:${::stop_bp_line}"
+
+ # The number of times we stop at the conditional breakpoint.
+ set n_hit_condition 0
+
+ # Now keep 'continue'-ing GDB until all the threads have finished
+ # and we reach the stop_marker breakpoint.
+ gdb_test_multiple "continue" "spot all breakpoint hits" {
+ -re " worker_func \[^\r\n\]+${::srcfile}:${::cond_bp_line}\r\n${::decimal}\\s+\[^\r\n\]+Breakpoint here\[^\r\n\]+\r\n${::gdb_prompt} $" {
+ incr n_hit_condition
+ send_gdb "continue\n"
+ exp_continue
+ }
+
+ -re " stop_marker \[^\r\n\]+${::srcfile}:${::stop_bp_line}\r\n${::decimal}\\s+\[^\r\n\]+Stop marker\[^\r\n\]+\r\n${::gdb_prompt} $" {
+ pass $gdb_test_name
+ }
+ }
+
+ gdb_assert { $n_hit_condition == $n_expected_hits } \
+ "stopped at breakpoint the expected number of times"
+
+ # Ensure the breakpoint condition was evaluated once per thread.
+ gdb_test "print \$n_cond_eval" "= 3" \
+ "condition was evaluated in each thread"
+ }
+}
+
+# Check that after handling a conditional breakpoint (where the condition
+# includes an inferior call), it is still possible to kill the running
+# inferior, and then restart the inferior.
+#
+# At once point doing this would result in GDB giving an assertion error.
+proc_with_prefix run_kill_and_restart_test { target_async target_non_stop } {
+ # This test relies on the 'start' command, which is not possible with
+ # the plain 'remote' target.
+ if { [target_info gdb_protocol] == "remote" } {
+ return
+ }
+
+ if { [start_gdb_and_runto_main $target_async \
+ $target_non_stop] == -1 } {
+ return
+ }
+
+ # Setup the conditional breakpoint.
+ gdb_breakpoint \
+ "${::srcfile}:${::cond_bp_line} if (is_matching_tid (arg, 1))"
+ gdb_continue_to_breakpoint "worker_func"
+
+ # Now kill the program being debugged.
+ gdb_test "kill" "" "kill process" \
+ "Kill the program being debugged.*y or n. $" "y"
+
+ # Check we can restart the inferior. At one point this would trigger an
+ # assertion.
+ gdb_start_cmd
+}
+
+# Create a conditional breakpoint which includes a call to a function that
+# segfaults. Run GDB and check what happens when the inferior segfaults
+# during the inferior call.
+proc_with_prefix run_bp_cond_segfaults { target_async target_non_stop } {
+ if { [start_gdb_and_runto_main $target_async \
+ $target_non_stop] == -1 } {
+ return
+ }
+
+ # This test relies on the inferior segfaulting when trying to
+ # access address zero.
+ if { [is_address_zero_readable] } {
+ return
+ }
+
+ # Setup the conditional breakpoint, include a call to
+ # 'function_that_segfaults', which triggers the segfault.
+ gdb_breakpoint \
+ "${::srcfile}:${::cond_bp_line} if (is_matching_tid (arg, 0) && function_that_segfaults ())"
+ set bp_1_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \
+ "get number of conditional breakpoint"]
+
+ gdb_test "continue" \
+ [multi_line \
+ "Continuing\\." \
+ ".*" \
+ "Thread ${::decimal} \"infcall-from-bp\" received signal SIGSEGV, Segmentation fault\\." \
+ "${::hex} in function_that_segfaults \\(\\) at \[^\r\n\]+:${::segv_line}" \
+ "${::decimal}\\s+\[^\r\n\]+Segfault happens here\[^\r\n\]+" \
+ "Error in testing condition for breakpoint ${bp_1_num}:" \
+ "The program being debugged was signaled while in a function called from GDB\\." \
+ "GDB remains in the frame where the signal was received\\." \
+ "To change this behavior use \"set unwindonsignal on\"\\." \
+ "Evaluation of the expression containing the function" \
+ "\\(function_that_segfaults\\) will be abandoned\\." \
+ "When the function is done executing, GDB will silently stop\\."]
+}
+
+# Create a conditional breakpoint which includes a call to a function that
+# itself has a breakpoint set within it. Run GDB and check what happens
+# when GDB hits the nested breakpoint.
+proc_with_prefix run_bp_cond_hits_breakpoint { target_async target_non_stop } {
+ if { [start_gdb_and_runto_main $target_async \
+ $target_non_stop] == -1 } {
+ return
+ }
+
+ # Setup the conditional breakpoint, include a call to
+ # 'function_with_breakpoint' in which we will shortly place a
+ # breakpoint.
+ gdb_breakpoint \
+ "${::srcfile}:${::cond_bp_line} if (is_matching_tid (arg, 0) && function_with_breakpoint ())"
+ set bp_1_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \
+ "get number of conditional breakpoint"]
+
+ gdb_breakpoint "${::srcfile}:${::nested_bp_line}"
+ set bp_2_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \
+ "get number of nested breakpoint"]
+
+ gdb_test "continue" \
+ [multi_line \
+ "Continuing\\." \
+ ".*" \
+ "Thread ${::decimal} \"infcall-from-bp\" hit Breakpoint ${bp_2_num}, function_with_breakpoint \\(\\) at \[^\r\n\]+:${::nested_bp_line}" \
+ "${::decimal}\\s+\[^\r\n\]+Nested breakpoint\[^\r\n\]+" \
+ "Error in testing condition for breakpoint ${bp_1_num}:" \
+ "The program being debugged stopped while in a function called from GDB\\." \
+ "Evaluation of the expression containing the function" \
+ "\\(function_with_breakpoint\\) will be abandoned\\." \
+ "When the function is done executing, GDB will silently stop\\."]
+}
+
+foreach_with_prefix target_async { "on" "off" } {
+ foreach_with_prefix target_non_stop { "on" "off" } {
+ run_condition_test "exactly one thread is hit" \
+ 1 "is_matching_tid (arg, 1)" \
+ $target_async $target_non_stop
+ run_condition_test "exactly two threads are hit" \
+ 2 "(is_matching_tid (arg, 0) || is_matching_tid (arg, 2))" \
+ $target_async $target_non_stop
+ run_condition_test "all three threads are hit" \
+ 3 "return_true ()" \
+ $target_async $target_non_stop
+ run_condition_test "no thread is hit" \
+ 0 "return_false ()" \
+ $target_async $target_non_stop
+
+ run_kill_and_restart_test $target_async $target_non_stop
+ run_bp_cond_segfaults $target_async $target_non_stop
+ run_bp_cond_hits_breakpoint $target_async $target_non_stop
+ }
+}
diff --git a/gdb/testsuite/gdb.threads/infcall-from-bp-cond-single.c b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-single.c
new file mode 100644
index 0000000..0b045f8
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-single.c
@@ -0,0 +1,139 @@
+/* Copyright 2022-2024 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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 <pthread.h>
+#include <unistd.h>
+#include <semaphore.h>
+#include <stdlib.h>
+
+#define NUM_THREADS 5
+
+/* Semaphores, used to track when threads have started, and to control
+ when the threads finish. */
+sem_t startup_semaphore;
+sem_t finish_semaphore;
+
+/* Mutex to control when the first worker thread hit a breakpoint
+ location. */
+pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/* Global variable to poke, just so threads have something to do. */
+volatile int global_var = 0;
+
+int
+return_true ()
+{
+ return 1;
+}
+
+int
+return_false ()
+{
+ return 0;
+}
+
+void *
+worker_func (void *arg)
+{
+ int tid = *((int *) arg);
+
+ switch (tid)
+ {
+ case 0:
+ /* Wait for MUTEX to become available, then pass through the
+ conditional breakpoint location. */
+ if (pthread_mutex_lock (&mutex) != 0)
+ abort ();
+ global_var = 99; /* Conditional breakpoint here. */
+ if (pthread_mutex_unlock (&mutex) != 0)
+ abort ();
+ break;
+
+ default:
+ /* Notify the main thread that the thread has started, then wait for
+ the main thread to tell us to finish. */
+ sem_post (&startup_semaphore);
+ if (sem_wait (&finish_semaphore) != 0)
+ abort ();
+ break;
+ }
+}
+
+void
+stop_marker ()
+{
+ global_var = 99; /* Stop marker. */
+}
+
+int
+main ()
+{
+ pthread_t threads[NUM_THREADS];
+ int args[NUM_THREADS];
+ void *retval;
+
+ /* An alarm, just in case the thread deadlocks. */
+ alarm (300);
+
+ /* Semaphore initialization. */
+ if (sem_init (&startup_semaphore, 0, 0) != 0)
+ abort ();
+ if (sem_init (&finish_semaphore, 0, 0) != 0)
+ abort ();
+
+ /* Lock MUTEX, this prevents the first worker thread from rushing ahead. */
+ if (pthread_mutex_lock (&mutex) != 0)
+ abort ();
+
+ /* Worker thread creation. */
+ for (int i = 0; i < NUM_THREADS; i++)
+ {
+ args[i] = i;
+ pthread_create (&threads[i], NULL, worker_func, &args[i]);
+ }
+
+ /* Wait for every thread (other than the first) to tell us it has started
+ up. */
+ for (int i = 1; i < NUM_THREADS; i++)
+ {
+ if (sem_wait (&startup_semaphore) != 0)
+ abort ();
+ }
+
+ /* Unlock the first thread so it can proceed. */
+ if (pthread_mutex_unlock (&mutex) != 0)
+ abort ();
+
+ /* Wait for the first thread only. */
+ pthread_join (threads[0], &retval);
+
+ /* Now post FINISH_SEMAPHORE to allow all the other threads to finish. */
+ for (int i = 1; i < NUM_THREADS; i++)
+ sem_post (&finish_semaphore);
+
+ /* Now wait for the remaining threads to complete. */
+ for (int i = 1; i < NUM_THREADS; i++)
+ pthread_join (threads[i], &retval);
+
+ /* Semaphore cleanup. */
+ sem_destroy (&finish_semaphore);
+ sem_destroy (&startup_semaphore);
+
+ stop_marker ();
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/infcall-from-bp-cond-single.exp b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-single.exp
new file mode 100644
index 0000000..2c3623e
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/infcall-from-bp-cond-single.exp
@@ -0,0 +1,117 @@
+# Copyright 2022-2024 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This test reprocuces bug gdb/28942, performing an inferior function
+# call from a breakpoint condition in a multi-threaded inferior.
+#
+# The important part of this test is that, when the conditional
+# breakpoint is hit, and the condition (which includes an inferior
+# function call) is evaluated, the other threads are running.
+
+standard_testfile
+
+if { [build_executable "failed to prepare" ${binfile} "${srcfile}" \
+ {debug pthreads}] == -1 } {
+ return
+}
+
+set cond_bp_line [gdb_get_line_number "Conditional breakpoint here"]
+set final_bp_line [gdb_get_line_number "Stop marker"]
+
+# Start GDB based on TARGET_ASYNC and TARGET_NON_STOP, and then runto main.
+proc start_gdb_and_runto_main { target_async target_non_stop } {
+ save_vars { ::GDBFLAGS } {
+ append ::GDBFLAGS \
+ " -ex \"maint set target-non-stop $target_non_stop\""
+ append ::GDBFLAGS \
+ " -ex \"maintenance set target-async ${target_async}\""
+
+ clean_restart ${::binfile}
+ }
+
+ if { ![runto_main] } {
+ return -1
+ }
+
+ return 0
+}
+
+# Run a test of GDB's conditional breakpoints, where the conditions include
+# inferior function calls.
+#
+# TARGET_ASYNC and TARGET_NON_STOP are used when starting up GDB.
+#
+# When STOP_AT_COND is true the breakpoint condtion will evaluate to
+# true, and GDB will stop at the breakpoint. Otherwise, the
+# breakpoint condition will evaluate to false and GDB will not stop at
+# the breakpoint.
+proc run_condition_test { stop_at_cond \
+ target_async target_non_stop } {
+ if { [start_gdb_and_runto_main $target_async \
+ $target_non_stop] == -1 } {
+ return
+ }
+
+ # Setup the conditional breakpoint.
+ if { $stop_at_cond } {
+ set cond_func "return_true"
+ } else {
+ set cond_func "return_false"
+ }
+ gdb_breakpoint \
+ "${::srcfile}:${::cond_bp_line} if (${cond_func} ())"
+ set cond_bp_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \
+ "get number for conditional breakpoint"]
+
+ # And a breakpoint that we hit when the test is over, this one is
+ # not conditional.
+ gdb_breakpoint "${::srcfile}:${::final_bp_line}"
+ set final_bp_num [get_integer_valueof "\$bpnum" "*UNKNOWN*" \
+ "get number for final breakpoint"]
+
+ if { $stop_at_cond } {
+ # Continue. The first breakpoint we hit should be the conditional
+ # breakpoint. The other thread will have hit its breakpoint, but
+ # that will have been deferred until the conditional breakpoint is
+ # reported.
+ gdb_test "continue" \
+ [multi_line \
+ "Continuing\\." \
+ ".*" \
+ "" \
+ "Thread ${::decimal} \"\[^\"\r\n\]+\" hit Breakpoint ${cond_bp_num}, worker_func \[^\r\n\]+:${::cond_bp_line}" \
+ "${::decimal}\\s+\[^\r\n\]+Conditional breakpoint here\[^\r\n\]+"] \
+ "hit the conditional breakpoint"
+ }
+
+ # Run to the stop marker.
+ gdb_test "continue" \
+ [multi_line \
+ "Continuing\\." \
+ ".*" \
+ "" \
+ "Thread ${::decimal} \"\[^\"\r\n\]+\" hit Breakpoint ${final_bp_num}, stop_marker \[^\r\n\]+:${::final_bp_line}" \
+ "${::decimal}\\s+\[^\r\n\]+Stop marker\[^\r\n\]+"] \
+ "hit the final breakpoint"
+}
+
+foreach_with_prefix target_async { "on" "off" } {
+ foreach_with_prefix target_non_stop { "on" "off" } {
+ foreach_with_prefix stop_at_cond { true false } {
+ run_condition_test $stop_at_cond \
+ $target_async $target_non_stop
+ }
+ }
+}