/* Copyright 2007, Red Hat Inc. */ #define _GNU_SOURCE 1 #include #include #include #include #include #include #include #include #include #include #include #include "debugger.h" #define LIBRARY 1 #include "debugger.c" #define TIMEOUT_SECS 4 #undef LOOPS_MIN #define LOOPS_MIN 1000 #define tkill(tid, sig) syscall (SYS_tkill, (tid), (sig)) static void detach_checked (struct attach_state_struct *attach_state) { pid_t pid = attach_state_pid_get (attach_state); if (attach_state_stopped_get (attach_state) != 0) { STATE (pid, 1 << STATE_STOPPED); detach (attach_state); STATE (pid, 1 << STATE_STOPPED); } else { STATE (pid, (1 << STATE_PTRACED)); detach (attach_state); STATE (pid, ((1 << STATE_SLEEPING) | (1 << STATE_RUNNING))); } } static volatile int spawned_threads_count; /* Set for the specific spawner. */ static struct attach_state_struct *attach_checked (pid_t pid, int redelivered_expect) { struct attach_state_struct *attach_state; time_t timeout = time (NULL) + TIMEOUT_SECS; unsigned loops = 0; STATE (pid, (1 << STATE_SLEEPING) | (1 << STATE_RUNNING) | (1 << STATE_STOPPED)); do { attach_state = attach (pid); if (redelivered_expect != attach_state_redelivered_get (attach_state)) { fprintf (stderr, "Expecting redelivery of %d but found %d\n", redelivered_expect, attach_state_redelivered_get (attach_state)); abort (); } /* During the inferior's initialization we may catch less threads. */ assert (attach_state_threads_count_get (attach_state) <= spawned_threads_count); if (attach_state_threads_count_get (attach_state) == spawned_threads_count /* Inferior may got stopped before it could initialize. */ || attach_state_stopped_get (attach_state) == 1) { /* FIXME: Why also STATE_STOPPED? */ STATE (pid, (1 << STATE_PTRACED) | (1 << STATE_STOPPED)); return attach_state; } /* WARNING: Currently we never use REDELIVERED_EXPECT but we would have to probably reset it back to 0 otherwise. */ assert (redelivered_expect == 0); detach_checked (attach_state); } while (loops++ < LOOPS_MIN || time (NULL) < timeout); abort (); } struct registry { struct registry *next; pid_t pid; }; struct registry *registry_list; static void registry_add (pid_t pid) { struct registry *new; new = malloc (sizeof (*new)); assert (new != NULL); new->pid = pid; new->next = registry_list; registry_list = new; } static void registry_remove (pid_t pid) { struct registry **iter_pointer; for (iter_pointer = ®istry_list; *iter_pointer != NULL; iter_pointer = &(*iter_pointer)->next) { struct registry *found = *iter_pointer; if (found->pid != pid) continue; *iter_pointer = found->next; free (found); return; } abort (); } static void registry_atexit (void) { struct registry *iter; for (iter = registry_list; iter != NULL; iter = iter->next) { tkill (iter->pid, SIGCONT); tkill (iter->pid, SIGKILL); kill (iter->pid, SIGKILL); } } static void registry_cleanup (void) { struct registry *iter; pid_t pid; registry_atexit (); while ((pid = wait (NULL)) != -1) { for (iter = registry_list; iter != NULL; iter = iter->next) if (iter->pid == pid) break; assert (iter != NULL); } assert (errno == ECHILD); while (registry_list) { iter = registry_list; registry_list = iter->next; free (iter); } } static void registry_handler (int signo) { signal (signo, SIG_DFL); registry_atexit (); raise (signo); } static __attribute__((__noreturn__)) void child_pause (void) { for (;;) pause (); /* NOTREACHED */ abort (); } static void child_alrm_handler (int signo) { assert (signo == SIGALRM); raise (SIGALRM); } static __attribute__((__noreturn__)) void child_alrm (void) { #if 0 int i; sigset_t oldset; #endif /* Assumed already setup SIGALRM for CHILD_ALRM_HANDLER. */ #if 0 i = sigprocmask (SIG_BLOCK, NULL, &oldset); assert (i == 0); printf ("sigprocmask () -> sigismember (SIGALRM) == %d\n", sigismember (&oldset, SIGALRM)); #endif for (;;) pause (); /* NOTREACHED */ abort (); } static pid_t spawn (void *(*child) (void *data, void *input), void *data, void *input, int fd_close) { pid_t pid; pid = fork(); switch (pid) { case -1: perror ("fork()"); exit (EXIT_FAILURE); /* NOTREACHED */ case 0: if (fd_close != -1) { int i; i = close (fd_close); assert (i == 0); } child (data, input); /* NOTREACHED */ abort (); default:; /* PASSTHRU */ } /* Parent. */ registry_add (pid); STATE (pid, (1 << STATE_SLEEPING) | (1 << STATE_RUNNING)); return pid; } static void murder (pid_t pid) { int i; pid_t pid_got; int status; if (pid == 0) return; i = kill (pid, SIGKILL); assert (i == 0); pid_got = waitpid (pid, &status, 0); if (!(pid_got == -1 && errno == ECHILD)) { assert (pid_got == pid); assert ((WIFSIGNALED (status) && (WTERMSIG (status) == SIGKILL || WTERMSIG (status) == SIGUSR2)) || (WIFEXITED (status) && WEXITSTATUS (status) == 0)); STATE (pid, 1 << STATE_ENOENT); } else STATE (pid, (1 << STATE_ENOENT) | (1 << STATE_ZOMBIE)); registry_remove (pid); } struct child_spawner { void *(*child) (void *data, void *input); void *data; int fd; }; static __attribute__((__noreturn__)) void *child_spawner (void *param_voidpointer, void *input) { struct child_spawner *param = param_voidpointer; pid_t inferior; ssize_t inferior_size; int i; inferior = spawn (param->child, param->data, input, param->fd); inferior_size = write (param->fd, &inferior, sizeof (inferior)); assert (inferior_size == sizeof (inferior)); i = close (param->fd); assert (i == 0); waitpid (inferior, NULL, 0); _exit (EXIT_SUCCESS); /* NOTREACHED */ abort (); } struct spawner { void *(*func) (void *data, void *input); void *data; }; static void *spawn_with_waiter (void *data, void *input) { pid_t waiter; struct child_spawner param_local; int pipefds[2]; int i; pid_t inferior; ssize_t inferior_size; unsigned char buf; ssize_t buf_size; struct spawner *param = data; assert (data != NULL); i = pipe (pipefds); assert (i == 0); param_local.child = param->func; param_local.data = param->data; param_local.fd = pipefds[1]; waiter = spawn (child_spawner, ¶m_local, input, pipefds[0]); i = close (pipefds[1]); assert (i == 0); inferior_size = read (pipefds[0], &inferior, sizeof (inferior)); assert (inferior_size == sizeof (inferior)); buf_size = read (pipefds[0], &buf, sizeof (buf)); assert (buf_size == 0); i = close (pipefds[0]); assert (i == 0); registry_add (inferior); return (void *) (unsigned long) inferior; } static void *spawn_without_waiter (void *data, void *input) { struct spawner *param = data; return (void *) (unsigned long) spawn (param->func, param->data, input, -1); } static void body_spawner (void *(*child) (void *data, void *input), void *data, void *input) { pid_t inferior; struct attach_state_struct *attach_state; int i; void (*handler_orig) (int signo); assert (input == NULL); /* Plain attach/detach. */ inferior = (unsigned long) (*child) (data, child_pause); attach_state = attach_checked (inferior, 0); assert (attach_state_stopped_get (attach_state) == 0); detach_checked (attach_state); murder (inferior); /* Attach to a process stopped by standard kill(2). */ inferior = (unsigned long) (*child) (data, child_pause); delay (); i = kill (inferior, SIGSTOP); assert (i == 0); STATE (inferior, 1 << STATE_STOPPED); attach_state = attach_checked (inferior, 0); assert (attach_state_stopped_get (attach_state) == 1); detach_checked (attach_state); murder (inferior); /* Attach to a process stopped by Linux specific tkill(2). */ inferior = (unsigned long) (*child) (data, child_pause); delay (); i = tkill (inferior, SIGSTOP); assert (i == 0); STATE (inferior, 1 << STATE_STOPPED); attach_state = attach_checked (inferior, 0); assert (attach_state_stopped_get (attach_state) == 1); detach_checked (attach_state); murder (inferior); /* Attach to a stopped process with already pending SIGALRM. */ /* Setup the handler already in the parent to avoid the child race. */ handler_orig = signal (SIGALRM, child_alrm_handler); assert (handler_orig == SIG_DFL); inferior = (unsigned long) (*child) (data, child_alrm); handler_orig = signal (SIGALRM, handler_orig); assert (handler_orig == child_alrm_handler); STATE (inferior, 1 << STATE_SLEEPING); delay (); i = tkill (inferior, SIGSTOP); assert (i == 0); /* Wait till it gets stopped otherwise we may get STATE_ENOENT below. */ STATE (inferior, 1 << STATE_STOPPED); delay (); i = tkill (inferior, SIGALRM); assert (i == 0); STATE (inferior, 1 << STATE_STOPPED); /* FIXME: SIGALRM did not get redelivered? */ #if 0 attach_state = attach_checked (inferior, SIGALRM); #else attach_state = attach_checked (inferior, 0); #endif assert (attach_state_stopped_get (attach_state) == 1); detach_checked (attach_state); STATE (inferior, 1 << STATE_STOPPED); delay (); i = tkill (inferior, SIGCONT); assert (i == 0); /* This is a race, we may not prove the successful SIGALRM delivery by it. Rather recheck it below. */ STATE (inferior, 1 << STATE_RUNNING); delay (); i = tkill (inferior, SIGSTOP); assert (i == 0); /* Wait till it gets stopped otherwise we may get STATE_ENOENT below. */ STATE (inferior, 1 << STATE_STOPPED); delay (); i = tkill (inferior, SIGUSR2); assert (i == 0); STATE (inferior, 1 << STATE_STOPPED); delay (); i = tkill (inferior, SIGCONT); assert (i == 0); /* Not just STATE_ZOMBIE as we can get spawn with waiter. FIXME. */ STATE (inferior, (1 << STATE_ENOENT) | (1 << STATE_ZOMBIE) | (1 << STATE_DEAD)); /* We would fail on: murder (inferior); */ } static void *pass (void *data, void *input) { struct spawner *param = data; void (*input_func) (void) = input; if (param != NULL) return (*param->func) (param->data, input); assert (input_func != NULL); (*input_func) (); /* NOTREACHED */ abort (); } static void *spawn_singlethreaded (void *data, void *input) { return pass (data, input); } static __attribute__((__noreturn__)) void *spawn_threaded_parent_start (void *arg) { for (;;) pause (); /* NOTREACHED */ abort (); } static void *libpthread_handle; static int (*libpthread_create_pointer) (pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); static int (*libpthread_join_pointer) (pthread_t thread, void **value_ptr); static void libpthread_open (void) { assert (libpthread_handle == NULL); libpthread_handle = dlopen ("libpthread.so.0", RTLD_LAZY); assert (libpthread_handle != NULL); libpthread_create_pointer = dlsym (libpthread_handle, "pthread_create"); assert (libpthread_create_pointer != NULL); libpthread_join_pointer = dlsym (libpthread_handle, "pthread_join"); assert (libpthread_join_pointer != NULL); } static void libpthread_close (void) { int i; assert (libpthread_handle != NULL); i = dlclose (libpthread_handle); assert (i == 0); libpthread_handle = NULL; } static void *spawn_threaded_parent (void *data, void *input) { pthread_t thread; int i; assert (libpthread_create_pointer != NULL); i = (*libpthread_create_pointer) (&thread, NULL, spawn_threaded_parent_start, NULL); assert (i == 0); return pass (data, input); } struct spawn_threaded_child_start { void *data; void *input; }; static void *spawn_threaded_child_start (void *arg_voidpointer) { struct spawn_threaded_child_start *arg = arg_voidpointer; return pass (arg->data, arg->input); } static __attribute__((__noreturn__)) void *spawn_threaded_child (void *data, void *input) { pthread_t thread; int i; struct spawn_threaded_child_start arg_local; arg_local.data = data; arg_local.input = input; assert (libpthread_create_pointer != NULL); i = (*libpthread_create_pointer) (&thread, NULL, spawn_threaded_child_start, &arg_local); assert (i == 0); assert (libpthread_join_pointer != NULL); i = (*libpthread_join_pointer) (thread, NULL); assert (i == 0); _exit (EXIT_SUCCESS); /* NOTREACHED */ abort (); } static void body_maywaiter (void *(*child) (void *data, void *input), void *data, void *input) { struct spawner param_local; param_local.func = child; param_local.data = data; body_spawner (spawn_without_waiter, ¶m_local, NULL); body_spawner (spawn_with_waiter, ¶m_local, NULL); } static volatile unsigned long loops = 0; static volatile int loops_print = 0; static void handler_sigusr1 (int signo) { assert (signo == SIGUSR1); loops_print++; } static __attribute__((__noreturn__)) void *tests_threaded (void *data, void *input) { libpthread_open (); spawned_threads_count = 2; body_maywaiter (spawn_threaded_parent, NULL, NULL); spawned_threads_count = 2; body_maywaiter (spawn_threaded_child, NULL, NULL); libpthread_close (); registry_cleanup (); _exit (EXIT_SUCCESS); /* NOTREACHED */ abort (); } int main (int argc, char **argv) { int loop = 0; int i; #ifdef MEASURE_STATE_PERFORMANCE STATE (1, 1 << STATE_ZOMBIE); return 0; #endif /* MEASURE_STATE_PERFORMANCE */ if (argc == 1) ; else if (argc == 2 && strcmp (argv[1], "-l") == 0) loop = 1; else abort (); i = nice (10); assert (i != -1); atexit (registry_atexit); signal (SIGINT, registry_handler); signal (SIGABRT, registry_handler); signal (SIGUSR1, handler_sigusr1); do { pid_t pid_threaded, pid_threaded_got; int status; while (loops_print > 0) { printf ("%lu\n", loops); loops_print--; } spawned_threads_count = 0; body_maywaiter (spawn_singlethreaded, NULL, NULL); /* We cannot rely on LIBPTHREAD_CLOSE as libpthread would be still loaded with ATTACH_STATE_REDELIVERED_GET () reporting 1 for SPAWN_SINGLETHREADED. */ pid_threaded = spawn (tests_threaded, NULL, NULL, -1); pid_threaded_got = waitpid (pid_threaded, &status, 0); assert (pid_threaded_got == pid_threaded); assert (!WIFSIGNALED (status)); /* Improve readability in the failed case. */ assert (!WIFSTOPPED (status)); /* Improve readability in the failed case. */ assert (WIFEXITED (status)); assert (WEXITSTATUS (status) == 0); STATE (pid_threaded, 1 << STATE_ENOENT); registry_remove (pid_threaded); registry_cleanup (); loops++; } /* Run at least twice to test libpthread_close (). */ while (loop != 0 || loops == 1); return EXIT_SUCCESS; }