/* Copyright 2007 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Expected runs (on 2.6.22-rc4-git7.x86_64): 130 - after fork(): T (stopped); 0 150 - after non-debugger waitid (..., child, ..., WSTOPPED | ...): T (stopped); 0 158 - after PTRACE_ATTACH: T (stopped); 10368 170 - after tkill (SIGCONT): T (tracing stop); 10368 177 - after waitpid (child): T (tracing stop); 10368 Workaround disabled. 209 - after PTRACE_DETACH (SIGSTOP): S (sleeping); 0 FAIL 140 - after fork(): T (stopped); 0 160 - after non-debugger waitid (..., child, ..., WSTOPPED | ...): T (stopped); 0 168 - after PTRACE_ATTACH: T (stopped); 10406 180 - after tkill (SIGCONT): T (tracing stop); 10406 187 - after waitpid (child): T (tracing stop); 10406 Workaround active! 199 - after tkill (SIGSTOP): T (tracing stop); 10406 204 - after PTRACE_CONT (SIG_0): T (tracing stop); 10406 210 - after waitpid (child): T (tracing stop); 10406 219 - after PTRACE_DETACH (SIGSTOP): T (stopped); 0 PASS */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define tkill(tid, sig) syscall (__NR_tkill, (tid), (sig)) #define STATE(pid, label) state_impl (__LINE__, (pid), (label)) static const char *state_impl (int line, pid_t pid, const char *label) { char buf[LINE_MAX]; int i; FILE *f; char *state = NULL, *tracerpid = NULL; free (state); state = NULL; free (tracerpid); tracerpid = NULL; sleep (1); snprintf (buf, sizeof buf, "/proc/%d/status", (int) pid); f = fopen (buf, "r"); assert (f != NULL); while (fgets (buf, sizeof buf, f) != NULL) { char *s; if (strncmp (buf, "TracerPid:", strlen ("TracerPid:")) == 0) { for (s = buf + strlen ("TracerPid:"); isspace (*s); s++); tracerpid = strdup (s); s = strchr (tracerpid, '\n'); if (s != NULL) *s = 0; } if (strncmp (buf, "State:", strlen ("State:")) == 0) { for (s = buf + strlen ("State:"); isspace (*s); s++); state = strdup (s); s = strchr (state, '\n'); if (s != NULL) *s = 0; } if (state == NULL || tracerpid == NULL) continue; printf ("%d - %s: %s; %s\n", line, label, state, tracerpid); i = fclose (f); assert (i == 0); return state; } assert (0); } static pid_t child; static void cleanup (void) { if (child != 0) kill (child, SIGKILL); } static void handler_fail (int signo) { cleanup (); abort (); } int main (void) { pid_t got_pid; int status; const char *final; setbuf (stdout, NULL); child = fork (); switch (child) { case -1: abort (); case 0: raise (SIGSTOP); for (;;) pause (); /* NOTREACHED */ default: break; } atexit (cleanup); signal (SIGABRT, handler_fail); STATE (child, "after fork()"); /* `T (stopped)' here. */ #if 1 /* This step is not needed for the reproducibility below. Present here as it may happen before we - as a debugger - get started. Without this WAITPID -> SIGSTOP the `tkill (child, SIGCONT)' call below is not needed and the whole problem is no longer present. Only removing the `tkill (child, SIGCONT)' call is not the solution. */ { int i; siginfo_t siginfo; i = waitid (P_PID, child, &siginfo, WEXITED | WSTOPPED | WCONTINUED); assert (i == 0); assert (siginfo.si_pid == child); assert (siginfo.si_signo == SIGCHLD); assert (siginfo.si_code == CLD_STOPPED); assert (siginfo.si_status == SIGSTOP); } STATE (child, "after non-debugger waitid (..., child, ..., WSTOPPED | ...)"); /* `T (stopped)' here. */ #endif /* Here is a point where we - as a debugger - start to attach. */ errno = 0; ptrace (PTRACE_ATTACH, child, NULL, NULL); assert_perror (errno); STATE (child, "after PTRACE_ATTACH"); /* Still `T (stopped)' here. */ /* `PTRACE_CONT, SIGSTOP' does not work in 100% cases as sometimes SIGSTOP gets remembered by kernel during the first PTRACE_CONT later and we get spurious SIGSTOP event. Expecting the signal may get delivered to a different task of the thread group. `tkill (SIGSTOP)' has no effect in this moment (it is already stopped). */ errno = 0; tkill (child, SIGCONT); assert_perror (errno); errno = 0; STATE (child, "after tkill (SIGCONT)"); /* Turned `T (tracing stop)' here. */ got_pid = waitpid (child, &status, 0); assert (got_pid == child); assert (WIFSTOPPED (status)); assert (WSTOPSIG (status) == SIGCONT); STATE (child, "after waitpid (child)"); /* `T (tracing stop)' here. */ /* Workaround. In this moment everything is almost fine but we need a workaround as final `PTRACE_DETACH, SIGSTOP' would leave the process unstopped otherwise: */ #if 0 puts ("Workaround active!"); errno = 0; tkill (child, SIGSTOP); assert_perror (errno); STATE (child, "after tkill (SIGSTOP)"); errno = 0; ptrace (PTRACE_CONT, child, NULL, NULL); assert_perror (errno); STATE (child, "after PTRACE_CONT (SIG_0)"); got_pid = waitpid (child, &status, 0); assert (got_pid == child); assert (WIFSTOPPED (status)); assert (WSTOPSIG (status) == SIGSTOP); STATE (child, "after waitpid (child)"); #else puts ("Workaround disabled."); #endif /* We would like to leave the process stopped (`T (stopped)'). */ errno = 0; ptrace (PTRACE_DETACH, child, NULL, (void *) SIGSTOP); assert_perror (errno); final = STATE (child, "after PTRACE_DETACH (SIGSTOP)"); if (strcmp (final, "T (stopped)") == 0) { puts ("PASS"); return 0; } if (strcmp (final, "S (sleeping)") == 0) { /* 2.6.22-rc4-git7.x86_64 */ puts ("FAIL"); return 1; } puts ("FAIL (unexpected)"); return 2; }