/* Copyright 2007, Red Hat Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "debugger.h" #if 0 #define USLEEP (1000000 / 2) #endif #define TIMEOUT_SECS 10 #define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0])) void delay (void) { #ifdef USLEEP int i; i = usleep (USLEEP); assert (i == 0); #endif } static __attribute__((__noreturn__)) void crash (void) { #if 0 void *array[0x100]; int count; #endif char command[256]; fputs (">>> CRASH START\n", stderr); #if 0 count = backtrace (array, ARRAY_SIZE (array)); backtrace_symbols_fd (array, count, STDERR_FILENO); #endif snprintf (command, sizeof (command), "echo -e \"bt\\nquit\"" " >/tmp/debugger.%d; gdb --batch -nx --command=/tmp/debugger.%d" " /proc/%d/exe %d >> CRASH FINISH\n", stderr); abort (); } enum state { /* Separate the valid `1 << enum state' and `enum state' ranges. */ STATE_FIRST = 4, STATE_INSTABLE, STATE_ENOENT, STATE_SLEEPING, STATE_RUNNING, STATE_STOPPED, STATE_PTRACED, STATE_ZOMBIE, STATE_LAST }; static const char *state_to_name (enum state state) { switch (state) { case STATE_INSTABLE: return "STATE_INSTABLE"; case STATE_ENOENT: return "STATE_ENOENT"; case STATE_SLEEPING: return "STATE_SLEEPING"; case STATE_RUNNING: return "STATE_RUNNING"; case STATE_STOPPED: return "STATE_STOPPED"; case STATE_PTRACED: return "STATE_PTRACED"; case STATE_ZOMBIE: return "STATE_ZOMBIE"; default: crash (); } /* NOTREACHED */ crash (); } static enum state state_get (pid_t pid) { char status_name[32]; char line[LINE_MAX]; FILE *f; enum state found; delay (); snprintf (status_name, sizeof (status_name), "/proc/%d/status", (int) pid); f = fopen (status_name, "r"); if (f == NULL && errno == ENOENT) found = STATE_ENOENT; else if (f == NULL) { fprintf (stderr, "errno = %d\n", errno); crash (); } else { int i; found = STATE_INSTABLE; while (errno = 0, fgets (line, sizeof (line), f) != NULL) { const char *const string = "State:\t"; const size_t length = sizeof "State:\t" - 1; if (strncmp (line, string, length) != 0) continue; if (strcmp (line + length, "S (sleeping)\n") == 0) found = STATE_SLEEPING; else if (strcmp (line + length, "R (running)\n") == 0) found = STATE_RUNNING; else if (strcmp (line + length, "T (stopped)\n") == 0) found = STATE_STOPPED; else if (strcmp (line + length, "T (tracing stop)\n") == 0) found = STATE_PTRACED; else if (strcmp (line + length, "Z (zombie)\n") == 0) found = STATE_ZOMBIE; else { fprintf (stderr, "Found an unknown state: %s", line + length); crash (); } } assert (found != STATE_INSTABLE || errno == ESRCH); i = fclose (f); assert (i == 0); } return found; } #define STATE(pid, expect_mask) state ((pid), (expect_mask), #expect_mask ) static enum state state (pid_t pid, unsigned expect_mask, const char *expect_mask_string) { enum state found; time_t timeout = time (NULL) + TIMEOUT_SECS; /* Sanity check `1 << enum state' was not misplaced with `enum state'. */ assert (1 << (STATE_FIRST + 1) >= STATE_LAST); assert (expect_mask != 0); #define MASK_UNDER_EXCLUSIVE(bit) ((1 << (bit)) - 1) #define MASK_ABOVE_INCLUSIVE(bit) (~MASK_UNDER_EXCLUSIVE (bit)) assert ((expect_mask & MASK_UNDER_EXCLUSIVE (STATE_FIRST + 1)) == 0); assert ((expect_mask & MASK_ABOVE_INCLUSIVE (STATE_LAST)) == 0); #undef MASK_ABOVE_INCLUSIVE #undef MASK_UNDER_EXCLUSIVE do { found = state_get (pid); if (((1 << found) & expect_mask) != 0) return found; } while (time (NULL) < timeout); fprintf (stderr, "Found for PID %d state %s but expecting (%s)\n", (int) pid, state_to_name (found), expect_mask_string); crash (); } /* Debugging only: Number of signal needing redelivery on PTRACE_ATTACH. */ int attach_redelivered; int attach (pid_t pid) { int i; int status; int stopped; attach_redelivered = 0; delay (); #if 1 stopped = (STATE (pid, (1 << STATE_SLEEPING) | (1 << STATE_RUNNING) | (1 << STATE_STOPPED)) == STATE_STOPPED); #endif i = ptrace (PTRACE_ATTACH, pid, NULL, NULL); assert (i == 0); /* FIXME: Why it does not work? Disable also the STATE () call above. */ #if 0 delay(); i = ptrace (PTRACE_CONT, pid, (void *) 1, (void *) SIGSTOP); /* `STOPPED == 1' may be false, even if the process was not stopped. */ if (i == 0) stopped = 1; else if (errno == ESRCH) stopped = 0; else crash (); #endif for (;;) { delay (); i = waitpid (pid, &status, 0); assert (i == pid); if (!WIFSTOPPED (status)) { /* Process may have exited. */ fprintf (stderr, "PID %d waitpid(2) status %d\n", (int) pid, status); exit (EXIT_FAILURE); } if (WSTOPSIG (status) == SIGSTOP) break; if (attach_redelivered == 0) attach_redelivered = WSTOPSIG (status); else attach_redelivered = -1; delay (); /* Re-deliver the signal received before SIGSTOP. It happens with about only 1:200000 probability. */ i = ptrace (PTRACE_CONT, pid, (void *) 1, (void *) (unsigned long) WSTOPSIG (status)); assert (i == 0); } return stopped; } void detach (pid_t pid, int stopped) { int i; delay (); i = ptrace (PTRACE_DETACH, pid, NULL, (void *) (unsigned long) (stopped ? SIGSTOP : 0)); assert (i == 0); delay (); } #ifndef LIBRARY int main (int argc, char **argv) { pid_t pid; int stopped; if (argc != 2) { fprintf (stderr, "Usage: %s \n", argv[0]); exit (EXIT_FAILURE); } pid = atoi (argv[1]); stopped = attach (pid); detach (pid, stopped); return EXIT_SUCCESS; } #endif /* !LIBRARY */