@@ -42,6 +42,9 @@ munmap-pthread: LDFLAGS+=-pthread
vma-pthread: CFLAGS+=-pthread
vma-pthread: LDFLAGS+=-pthread
+thread-breakpoint-stress: CFLAGS+=-pthread
+thread-breakpoint-stress: LDFLAGS+=-pthread
+
# The vma-pthread seems very sensitive on gitlab and we currently
# don't know if its exposing a real bug or the test is flaky.
ifneq ($(GITLAB_CI),)
@@ -127,6 +130,13 @@ run-gdbstub-follow-fork-mode-parent: follow-fork-mode
--bin $< --test $(MULTIARCH_SRC)/gdbstub/follow-fork-mode-parent.py, \
following parents on fork)
+run-gdbstub-thread-breakpoint-stress: thread-breakpoint-stress
+ $(call run-test, $@, $(GDB_SCRIPT) \
+ --gdb $(GDB) \
+ --qemu $(QEMU) --qargs "$(QEMU_OPTS)" \
+ --bin $< --test $(MULTIARCH_SRC)/gdbstub/test-thread-breakpoint-stress.py, \
+ hitting many breakpoints on different threads)
+
else
run-gdbstub-%:
$(call skip-test, "gdbstub test $*", "need working gdb with $(patsubst -%,,$(TARGET_NAME)) support")
@@ -136,7 +146,8 @@ EXTRA_RUNS += run-gdbstub-sha1 run-gdbstub-qxfer-auxv-read \
run-gdbstub-registers run-gdbstub-prot-none \
run-gdbstub-catch-syscalls run-gdbstub-follow-fork-mode-child \
run-gdbstub-follow-fork-mode-parent \
- run-gdbstub-qxfer-siginfo-read
+ run-gdbstub-qxfer-siginfo-read \
+ run-gdbstub-thread-breakpoint-stress
# ARM Compatible Semi Hosting Tests
#
new file mode 100644
@@ -0,0 +1,28 @@
+"""Test multiple threads hitting breakpoints.
+
+SPDX-License-Identifier: GPL-2.0-or-later
+"""
+from test_gdbstub import main, report
+
+
+N_BREAK_THREADS = 2
+N_BREAKS = 100
+
+
+def run_test():
+ """Run through the tests one by one"""
+ if gdb.selected_inferior().architecture().name() == "MicroBlaze":
+ print("SKIP: Atomics are broken on MicroBlaze")
+ exit(0)
+ gdb.execute("break break_here")
+ gdb.execute("continue")
+ for _ in range(N_BREAK_THREADS * N_BREAKS):
+ counter1 = int(gdb.parse_and_eval("s->counter"))
+ counter2 = int(gdb.parse_and_eval("s->counter"))
+ report(counter1 == counter2, "{} == {}".format(counter1, counter2))
+ gdb.execute("continue")
+ exitcode = int(gdb.parse_and_eval("$_exitcode"))
+ report(exitcode == 0, "{} == 0".format(exitcode))
+
+
+main(run_test)
new file mode 100644
@@ -0,0 +1,92 @@
+/*
+ * Test multiple threads hitting breakpoints.
+ *
+ * The main thread performs a lengthy syscall. The test verifies that this
+ * does not interfere with the ability to stop threads.
+ *
+ * The counter thread constantly increments a value by 1. The test verifies
+ * that it is stopped when another thread hits a breakpoint.
+ *
+ * The break threads constantly and simultaneously hit the same breakpoint.
+ * The test verifies that GDB and gdbstub do not lose any hits and do not
+ * deadlock.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include <assert.h>
+#include <pthread.h>
+#include <stdlib.h>
+
+struct state {
+ int counter;
+ int done;
+ pthread_barrier_t barrier;
+ int break_counter;
+};
+
+static void *counter_loop(void *arg)
+{
+ struct state *s = arg;
+
+ while (!__atomic_load_n(&s->done, __ATOMIC_SEQ_CST)) {
+ __atomic_add_fetch(&s->counter, 1, __ATOMIC_SEQ_CST);
+ }
+
+ return NULL;
+}
+
+#define N_BREAK_THREADS 2
+#define N_BREAKS 100
+
+/* Non-static to avoid inlining. */
+void break_here(struct state *s)
+{
+ __atomic_add_fetch(&s->break_counter, 1, __ATOMIC_SEQ_CST);
+}
+
+static void *break_loop(void *arg)
+{
+ struct state *s = arg;
+ int i;
+
+ pthread_barrier_wait(&s->barrier);
+ for (i = 0; i < N_BREAKS; i++) {
+ break_here(s);
+ }
+
+ return NULL;
+}
+
+int main(void)
+{
+ pthread_t break_threads[N_BREAK_THREADS], counter_thread;
+ struct state s = {};
+ int i, ret;
+
+#ifdef __MICROBLAZE__
+ /*
+ * Microblaze has broken atomics.
+ * See https://github.com/Xilinx/meta-xilinx/blob/xlnx-rel-v2024.1/meta-microblaze/recipes-devtools/gcc/gcc-12/0009-Patch-microblaze-Fix-atomic-boolean-return-value.patch
+ */
+ return EXIT_SUCCESS;
+#endif
+
+ ret = pthread_barrier_init(&s.barrier, NULL, N_BREAK_THREADS);
+ assert(ret == 0);
+ ret = pthread_create(&counter_thread, NULL, counter_loop, &s);
+ assert(ret == 0);
+ for (i = 0; i < N_BREAK_THREADS; i++) {
+ ret = pthread_create(&break_threads[i], NULL, break_loop, &s);
+ assert(ret == 0);
+ }
+ for (i = 0; i < N_BREAK_THREADS; i++) {
+ ret = pthread_join(break_threads[i], NULL);
+ assert(ret == 0);
+ }
+ __atomic_store_n(&s.done, 1, __ATOMIC_SEQ_CST);
+ ret = pthread_join(counter_thread, NULL);
+ assert(ret == 0);
+ assert(s.break_counter == N_BREAK_THREADS * N_BREAKS);
+
+ return EXIT_SUCCESS;
+}
Add a test to prevent regressions. Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com> --- tests/tcg/multiarch/Makefile.target | 13 ++- .../gdbstub/test-thread-breakpoint-stress.py | 28 ++++++ .../tcg/multiarch/thread-breakpoint-stress.c | 92 +++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 tests/tcg/multiarch/gdbstub/test-thread-breakpoint-stress.py create mode 100644 tests/tcg/multiarch/thread-breakpoint-stress.c