diff --git a/CMakeLists.txt b/CMakeLists.txt index 740e55d8b93..1c3a4780745 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1330,6 +1330,7 @@ set(BASIC_TESTS sigaltstack sigchld_interrupt_signal sigcont + sigcont_threaded sigframe_grow_stack sighandler_bad_rsp_sigsegv sighandler_fork diff --git a/src/RecordTask.cc b/src/RecordTask.cc index a8539597c21..4e2136927d0 100644 --- a/src/RecordTask.cc +++ b/src/RecordTask.cc @@ -1169,7 +1169,7 @@ void RecordTask::emulate_SIGCONT() { // All threads in the process are resumed. for (Task* t : thread_group()->task_set()) { auto rt = static_cast(t); - LOG(debug) << "setting " << tid << " to NOT_STOPPED due to SIGCONT"; + LOG(debug) << "setting " << rt->tid << " to NOT_STOPPED due to SIGCONT"; rt->clear_stashed_group_stop(); rt->emulated_stop_pending = false; rt->emulated_stop_type = NOT_STOPPED; diff --git a/src/Scheduler.cc b/src/Scheduler.cc index 97a9222a1fe..d94d9f3ed5c 100644 --- a/src/Scheduler.cc +++ b/src/Scheduler.cc @@ -318,10 +318,10 @@ bool Scheduler::is_task_runnable(RecordTask* t, WaitAggregator& wait_aggregator, } if (t->emulated_stop_type != NOT_STOPPED) { - if (t->is_signal_pending(SIGCONT)) { - // We have to do this here. RecordTask::signal_delivered can't always - // do it because if we don't PTRACE_CONT the task, we'll never see the - // SIGCONT. + if (t->is_stopped() && t->is_signal_pending(SIGCONT)) { + // We have to do this here. RecordTask::signal_delivered can't do it + // in the case where t->is_stopped(), because if we don't PTRACE_CONT + // the task, we'll never see the SIGCONT. t->emulate_SIGCONT(); // We shouldn't run any user code since there is at least one signal // pending. diff --git a/src/test/sigcont_threaded.c b/src/test/sigcont_threaded.c new file mode 100644 index 00000000000..b3a61f84e2f --- /dev/null +++ b/src/test/sigcont_threaded.c @@ -0,0 +1,63 @@ +/* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ + +#include "util.h" + +static void *child_process_extra_thread(__attribute__((unused)) void *extra_thread) { + int r; + + // Slap in a sched_yield or two here so that the parent process is going to be + // blocked in pthread_join. + sched_yield(); + sched_yield(); + + // Now, stop ourselves. We'll be unstopped by the parent process. + r = kill(gettid(), SIGSTOP); + test_assert(r == 0); + + // Now allow self to exit, and the thread-group-leader can continue. + return NULL; +} + +static void child_process(void) { + pthread_t extra_thread; + int r; + // Spawn an additional thread + r = pthread_create(&extra_thread, NULL, child_process_extra_thread, NULL); + test_assert(r == 0); + + // Wait for the child thread we made. It will send SIGSTOP to the process. + r = pthread_join(extra_thread, NULL); + test_assert(r == 0); +} + +static void parent_process(pid_t pid) { + int wait_status, r; + pid_t wpid; + + // Wait for the child process to have sent itself SIGSTOP + wpid = waitpid(pid, &wait_status, WUNTRACED); + test_assert(wpid == pid); + test_assert(WIFSTOPPED(wait_status)); + test_assert(WSTOPSIG(wait_status) == SIGSTOP); + + // Let it continue + r = kill(pid, SIGCONT); + test_assert(r == 0); + + // Now the child process should actually exit + wpid = waitpid(pid, &wait_status, 0); + test_assert(wpid == pid); + test_assert(WIFEXITED(wait_status)); +} + +int main(void) { + pid_t pid = fork(); + test_assert(pid != -1); + if (pid == 0) { + child_process(); + } else { + parent_process(pid); + atomic_puts("EXIT-SUCCESS"); + } + return 0; +}