diff --git a/lldb/test/API/tools/lldb-server/vCont-threads/TestPartialResume.py b/lldb/test/API/tools/lldb-server/vCont-threads/TestPartialResume.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-server/vCont-threads/TestPartialResume.py @@ -0,0 +1,128 @@ +import re + +import gdbremote_testcase +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +class TestPartialResume(gdbremote_testcase.GdbRemoteTestCaseBase): + THREAD_MATCH_RE = re.compile(r"thread ([0-9a-f]+) running") + + def start_vCont_run_subset_of_threads_test(self): + self.build() + self.set_inferior_startup_launch() + + procs = self.prep_debug_monitor_and_inferior(inferior_args=["3"]) + # grab the main thread id + self.add_threadinfo_collection_packets() + main_thread = self.parse_threadinfo_packets( + self.expect_gdbremote_sequence()) + self.assertEqual(len(main_thread), 1) + self.reset_test_sequence() + + # run until threads start, then grab full thread list + self.test_sequence.add_log_lines([ + "read packet: $c#63", + {"direction": "send", "regex": "[$]T.*;reason:signal.*"}, + ], True) + self.add_threadinfo_collection_packets() + + all_threads = self.parse_threadinfo_packets( + self.expect_gdbremote_sequence()) + self.assertEqual(len(all_threads), 4) + self.assertIn(main_thread[0], all_threads) + self.reset_test_sequence() + + all_subthreads = set(all_threads) - set(main_thread) + self.assertEqual(len(all_subthreads), 3) + + return (main_thread[0], list(all_subthreads)) + + def continue_and_get_threads_running(self, main_thread, vCont_req): + self.test_sequence.add_log_lines( + ["read packet: $vCont;c:{:x};{}#00".format(main_thread, vCont_req), + "send packet: $W00#00", + ], True) + exp = self.expect_gdbremote_sequence() + self.reset_test_sequence() + found = set() + for line in exp["O_content"].decode().splitlines(): + m = self.THREAD_MATCH_RE.match(line) + if m is not None: + found.add(int(m.group(1), 16)) + return found + + @skipIfWindows + @add_test_categories(["llgs"]) + def test_vCont_cxcx(self): + main_thread, all_subthreads_list = ( + self.start_vCont_run_subset_of_threads_test()) + # resume two threads explicitly, stop the third one implicitly + self.assertEqual( + self.continue_and_get_threads_running( + main_thread, + "c:{:x};c:{:x}".format(*all_subthreads_list[:2])), + set(all_subthreads_list[:2])) + + @skipIfWindows + @add_test_categories(["llgs"]) + def test_vCont_cxcxt(self): + main_thread, all_subthreads_list = ( + self.start_vCont_run_subset_of_threads_test()) + # resume two threads explicitly, stop others explicitly + self.assertEqual( + self.continue_and_get_threads_running( + main_thread, + "c:{:x};c:{:x};t".format(*all_subthreads_list[:2])), + set(all_subthreads_list[:2])) + + @skipIfWindows + @add_test_categories(["llgs"]) + def test_vCont_txc(self): + main_thread, all_subthreads_list = ( + self.start_vCont_run_subset_of_threads_test()) + # stop one thread explicitly, resume others + self.assertEqual( + self.continue_and_get_threads_running( + main_thread, + "t:{:x};c".format(all_subthreads_list[-1])), + set(all_subthreads_list[:2])) + + @skipIfWindows + @add_test_categories(["llgs"]) + def test_vCont_cxtxc(self): + main_thread, all_subthreads_list = ( + self.start_vCont_run_subset_of_threads_test()) + # resume one thread explicitly, stop one explicitly, + # resume others + self.assertEqual( + self.continue_and_get_threads_running( + main_thread, + "c:{:x};t:{:x};c".format(*all_subthreads_list[-2:])), + set(all_subthreads_list[:2])) + + @skipIfWindows + @add_test_categories(["llgs"]) + def test_vCont_txcx(self): + main_thread, all_subthreads_list = ( + self.start_vCont_run_subset_of_threads_test()) + # resume one thread explicitly, stop one explicitly, + # stop others implicitly + self.assertEqual( + self.continue_and_get_threads_running( + main_thread, + "t:{:x};c:{:x}".format(*all_subthreads_list[:2])), + set(all_subthreads_list[1:2])) + + @skipIfWindows + @add_test_categories(["llgs"]) + def test_vCont_txcxt(self): + main_thread, all_subthreads_list = ( + self.start_vCont_run_subset_of_threads_test()) + # resume one thread explicitly, stop one explicitly, + # stop others explicitly + self.assertEqual( + self.continue_and_get_threads_running( + main_thread, + "t:{:x};c:{:x};t".format(*all_subthreads_list[:2])), + set(all_subthreads_list[1:2])) diff --git a/lldb/test/API/tools/lldb-server/vCont-threads/TestGdbRemote_vContThreads.py b/lldb/test/API/tools/lldb-server/vCont-threads/TestSignal.py rename from lldb/test/API/tools/lldb-server/vCont-threads/TestGdbRemote_vContThreads.py rename to lldb/test/API/tools/lldb-server/vCont-threads/TestSignal.py --- a/lldb/test/API/tools/lldb-server/vCont-threads/TestGdbRemote_vContThreads.py +++ b/lldb/test/API/tools/lldb-server/vCont-threads/TestSignal.py @@ -1,23 +1,18 @@ -import json import re -import time import gdbremote_testcase from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil -class TestGdbRemote_vContThreads(gdbremote_testcase.GdbRemoteTestCaseBase): +class TestSignal(gdbremote_testcase.GdbRemoteTestCaseBase): def start_threads(self, num): procs = self.prep_debug_monitor_and_inferior(inferior_args=[str(num)]) - # start the process and wait for output self.test_sequence.add_log_lines([ "read packet: $c#63", - {"type": "output_match", "regex": r".*@started\r\n.*"}, + {"direction": "send", "regex": "[$]T.*;reason:signal.*"}, ], True) - # then interrupt it - self.add_interrupt_packets() self.add_threadinfo_collection_packets() context = self.expect_gdbremote_sequence() @@ -29,22 +24,21 @@ self.reset_test_sequence() return threads + SIGNAL_MATCH_RE = re.compile(r"received SIGUSR1 on thread id: ([0-9a-f]+)") + def send_and_check_signal(self, vCont_data, threads): self.test_sequence.add_log_lines([ "read packet: $vCont;{0}#00".format(vCont_data), - {"type": "output_match", - "regex": len(threads) * - r".*received SIGUSR1 on thread id: ([0-9a-f]+)\r\n.*", - "capture": dict((i, "tid{0}".format(i)) for i - in range(1, len(threads)+1)), - }, + "send packet: $W00#00", ], True) - - context = self.expect_gdbremote_sequence() - self.assertIsNotNone(context) - tids = sorted(int(context["tid{0}".format(x)], 16) - for x in range(1, len(threads)+1)) - self.assertEqual(tids, sorted(threads)) + exp = self.expect_gdbremote_sequence() + self.reset_test_sequence() + tids = [] + for line in exp["O_content"].decode().splitlines(): + m = self.SIGNAL_MATCH_RE.match(line) + if m is not None: + tids.append(int(m.group(1), 16)) + self.assertEqual(sorted(tids), sorted(threads)) def get_pid(self): self.add_process_info_collection_packets() @@ -242,72 +236,3 @@ context = self.expect_gdbremote_sequence() self.assertIsNotNone(context) - - THREAD_MATCH_RE = re.compile(r"thread ([0-9a-f]+) running") - - def continue_and_get_threads_running(self, continue_packet): - self.test_sequence.add_log_lines( - ["read packet: ${}#00".format(continue_packet), - ], True) - self.expect_gdbremote_sequence() - self.reset_test_sequence() - time.sleep(1) - self.add_interrupt_packets() - exp = self.expect_gdbremote_sequence() - found = set() - for line in exp["O_content"].decode().splitlines(): - m = self.THREAD_MATCH_RE.match(line) - if m is not None: - found.add(int(m.group(1), 16)) - return found - - @skipIfWindows - @add_test_categories(["llgs"]) - def test_vCont_run_subset_of_threads(self): - self.build() - self.set_inferior_startup_launch() - - threads = set(self.start_threads(3)) - all_subthreads = self.continue_and_get_threads_running("c") - all_subthreads_list = list(all_subthreads) - self.assertEqual(len(all_subthreads), 3) - self.assertEqual(threads & all_subthreads, all_subthreads) - - # resume two threads explicitly, stop the third one implicitly - self.assertEqual( - self.continue_and_get_threads_running( - "vCont;c:{:x};c:{:x}".format(*all_subthreads_list[:2])), - set(all_subthreads_list[:2])) - - # resume two threads explicitly, stop others explicitly - self.assertEqual( - self.continue_and_get_threads_running( - "vCont;c:{:x};c:{:x};t".format(*all_subthreads_list[:2])), - set(all_subthreads_list[:2])) - - # stop one thread explicitly, resume others - self.assertEqual( - self.continue_and_get_threads_running( - "vCont;t:{:x};c".format(all_subthreads_list[-1])), - set(all_subthreads_list[:2])) - - # resume one thread explicitly, stop one explicitly, - # resume others - self.assertEqual( - self.continue_and_get_threads_running( - "vCont;c:{:x};t:{:x};c".format(*all_subthreads_list[-2:])), - set(all_subthreads_list[:2])) - - # resume one thread explicitly, stop one explicitly, - # stop others implicitly - self.assertEqual( - self.continue_and_get_threads_running( - "vCont;t:{:x};c:{:x}".format(*all_subthreads_list[:2])), - set(all_subthreads_list[1:2])) - - # resume one thread explicitly, stop one explicitly, - # stop others explicitly - self.assertEqual( - self.continue_and_get_threads_running( - "vCont;t:{:x};c:{:x};t".format(*all_subthreads_list[:2])), - set(all_subthreads_list[1:2])) diff --git a/lldb/test/API/tools/lldb-server/vCont-threads/main.cpp b/lldb/test/API/tools/lldb-server/vCont-threads/main.cpp --- a/lldb/test/API/tools/lldb-server/vCont-threads/main.cpp +++ b/lldb/test/API/tools/lldb-server/vCont-threads/main.cpp @@ -1,31 +1,56 @@ #include "pseudo_barrier.h" #include "thread.h" +#include #include #include #include #include #include #include +#include #include #include #include pseudo_barrier_t barrier; +std::mutex print_mutex; +std::atomic can_work = ATOMIC_VAR_INIT(false); +thread_local volatile sig_atomic_t can_exit_now = false; + +static void sigint_handler(int signo) {} static void sigusr1_handler(int signo) { - char buf[100]; - std::snprintf(buf, sizeof(buf), - "received SIGUSR1 on thread id: %" PRIx64 "\n", - get_thread_id()); - write(STDOUT_FILENO, buf, strlen(buf)); + std::lock_guard lock{print_mutex}; + std::printf("received SIGUSR1 on thread id: %" PRIx64 "\n", get_thread_id()); + can_exit_now = true; } static void thread_func() { + // this ensures that all threads start before we stop pseudo_barrier_wait(barrier); - for (int i = 0; i < 300; ++i) { + + // wait till the main thread indicates that we can go + // (note: using a mutex here causes hang on FreeBSD when another thread + // is suspended) + while (!can_work.load()) + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + // the mutex guarantees that two writes don't get interspersed + { + std::lock_guard lock{print_mutex}; std::printf("thread %" PRIx64 " running\n", get_thread_id()); + } + + // give other threads a fair chance to run + for (int i = 0; i < 5; ++i) { + std::this_thread::yield(); std::this_thread::sleep_for(std::chrono::milliseconds(200)); + if (can_exit_now) + return; } + + // if we didn't get signaled, terminate the program explicitly. + _exit(0); } int main(int argc, char **argv) { @@ -33,15 +58,19 @@ pseudo_barrier_init(barrier, num + 1); + signal(SIGINT, sigint_handler); signal(SIGUSR1, sigusr1_handler); std::vector threads; - for(int i = 0; i < num; ++i) + for (int i = 0; i < num; ++i) threads.emplace_back(thread_func); + // use the barrier to make sure all threads start before we stop pseudo_barrier_wait(barrier); + std::raise(SIGINT); - std::puts("@started"); + // allow the threads to work + can_work.store(true); for (std::thread &thread : threads) thread.join();