| /* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ |
| |
| #include "util.h" |
| |
| static int child_to_parent[2]; |
| |
| static void* run_child_thread(__attribute__((unused)) void* p) { |
| setpriority(PRIO_PROCESS, 0, 10); |
| atomic_printf("off-main-thread id = %d\n", sys_gettid()); |
| write(child_to_parent[1], "x", 1); |
| pause(); |
| return NULL; |
| } |
| |
| static int do_child(void) { |
| pthread_t thread; |
| |
| pthread_create(&thread, NULL, run_child_thread, NULL); |
| pause(); |
| return 66; |
| } |
| |
| /* Do a busy delay loop that changes registers so won't trigger |
| rr's spinlock-detection heuristic */ |
| static char delay(void) { |
| #if defined(__x86_64__) || defined(__i386__) |
| asm("mov $10000000,%%ecx\n\t" |
| "1: loop 1b\n\t" |
| : : : "ecx"); |
| return 0; |
| #else |
| /* Does this actually change registers on ARM??? */ |
| int i; |
| static volatile char ch; |
| for (i = 0; i < 10000000; ++i) { |
| ch = i % 3; |
| } |
| return ch; |
| #endif |
| } |
| |
| int main(int argc, __attribute__((unused)) char** argv) { |
| pid_t child; |
| pid_t exec_child; |
| int status; |
| char cc; |
| char* execv_argv[] = {"/proc/self/exe", "dummy", NULL}; |
| |
| if (argc > 1) { |
| return 99; |
| } |
| |
| pipe(child_to_parent); |
| child = fork(); |
| if (!child) { |
| return do_child(); |
| } |
| |
| read(child_to_parent[0], &cc, 1); |
| atomic_printf("sending SIGTERM to %d\n", child); |
| kill(child, SIGTERM); |
| /* Delay a bit. During this delay the child's main thread |
| will handle SIGTERM, rr will schedule it and process the SIGTERM, |
| triggering a thread-group exit; the child main thread will proceed |
| to its PTRACE_EVENT_EXIT stop, rr will process that too, and the |
| child main thread will complete exit and go into zombie state. |
| rr will not schedule the child off-main thread since it has lower priority, |
| so that thread will advance to its PTRACE_EVENT_EXIT stop and wait there. */ |
| delay(); |
| |
| atomic_printf("sending SIGKILL to %d\n", child); |
| kill(child, SIGKILL); |
| /* Delay a bit more. During this delay the child's off-main thread will |
| respond to the SIGKILL by exiting its PTRACE_EVENT_EXIT stop and going into |
| zombie state. rr still won't schedule that thread and doesn't know anything |
| about it exiting. */ |
| delay(); |
| |
| atomic_printf("Sleeping...\n"); |
| /* Do an execve and let the other task run during that */ |
| exec_child = fork(); |
| if (exec_child == 0) { |
| execve("/proc/self/exe", execv_argv, NULL); |
| } |
| test_assert(exec_child == waitpid(exec_child, &status, 0)); |
| test_assert(WIFEXITED(status) && WEXITSTATUS(status) == 99); |
| |
| atomic_puts("EXIT-SUCCESS"); |
| return 0; |
| } |