+LOOPS_MIN
[debugger.git] / debugger.c
1 /* Copyright 2007, Red Hat Inc.  */
2
3 #include <unistd.h>
4 #include <assert.h>
5 #include <stdlib.h>
6 #include <signal.h>
7 #include <errno.h>
8 #include <stdio.h>
9 #include <sys/ptrace.h>
10 #include <sys/types.h>
11 #include <sys/wait.h>
12 #include <limits.h>
13 #include <string.h>
14 #include <time.h>
15
16 #include "debugger.h"
17
18
19 #if 0
20 #define USLEEP (1000000 / 2)
21 #endif
22 #define TIMEOUT_SECS 20
23 /* LOOPS_MIN is a safety as QEMU clock time sucks.
24    100000 is 4s natively and 53s in QEMU.  */
25 #define LOOPS_MIN 500000
26
27
28 #define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0]))
29
30 void delay (void)
31 {
32 #ifdef USLEEP
33   int i;
34
35   i = usleep (USLEEP);
36   assert (i == 0);
37 #endif
38 }
39
40 static __attribute__((__noreturn__)) void crash (void)
41 {
42 #if 0
43   void *array[0x100];
44   int count;
45 #endif
46   char command[256];
47
48   fputs (">>> CRASH START\n", stderr);
49 #if 0
50   count = backtrace (array, ARRAY_SIZE (array));
51   backtrace_symbols_fd (array, count, STDERR_FILENO);
52 #endif
53   snprintf (command, sizeof (command), "echo -e \"bt\\nquit\""
54             " >/tmp/debugger.%d; gdb --batch -nx --command=/tmp/debugger.%d"
55             " /proc/%d/exe %d </dev/null;rm -f /tmp/debugger.%d",
56             (int) getpid(), (int) getpid(), (int) getpid(), (int) getpid(),
57             (int) getpid());
58   system (command);
59   fputs (">>> CRASH FINISH\n", stderr);
60   abort ();
61 }
62
63 enum state
64   {
65     /* Separate the valid `1 << enum state' and `enum state' ranges.  */
66     STATE_FIRST = 4,
67     STATE_INSTABLE,
68     STATE_ENOENT,
69     STATE_SLEEPING,
70     STATE_RUNNING,
71     STATE_STOPPED,
72     STATE_PTRACED,
73     STATE_ZOMBIE,
74     STATE_DEAD,
75     STATE_LAST
76   };
77
78 static const char *state_to_name (enum state state)
79 {
80   switch (state)
81     {
82       case STATE_INSTABLE: return "STATE_INSTABLE";
83       case STATE_ENOENT:   return "STATE_ENOENT";
84       case STATE_SLEEPING: return "STATE_SLEEPING";
85       case STATE_RUNNING:  return "STATE_RUNNING";
86       case STATE_STOPPED:  return "STATE_STOPPED";
87       case STATE_PTRACED:  return "STATE_PTRACED";
88       case STATE_ZOMBIE:   return "STATE_ZOMBIE";
89       case STATE_DEAD:     return "STATE_DEAD";
90       default: crash ();
91     }
92   /* NOTREACHED */
93   crash ();
94 }
95
96 static enum state state_get (pid_t pid)
97 {
98   char status_name[32];
99   char line[LINE_MAX];
100   FILE *f;
101   enum state found;
102
103   delay ();
104
105   snprintf (status_name, sizeof (status_name), "/proc/%d/status", (int) pid);
106   f = fopen (status_name, "r");
107   if (f == NULL && errno == ENOENT)
108     found = STATE_ENOENT;
109   else if (f == NULL)
110     {
111       fprintf (stderr, "errno = %d\n", errno);
112       crash ();
113     }
114   else
115     {
116       int i;
117
118       found = STATE_INSTABLE;
119       while (errno = 0, fgets (line, sizeof (line), f) != NULL)
120         {
121           const char *const string = "State:\t";
122           const size_t length = sizeof "State:\t" - 1;
123
124           if (strncmp (line, string, length) != 0)
125             continue;
126           if (strcmp (line + length, "S (sleeping)\n") == 0)
127             found = STATE_SLEEPING;
128           else if (strcmp (line + length, "R (running)\n") == 0)
129             found = STATE_RUNNING;
130           else if (strcmp (line + length, "T (stopped)\n") == 0)
131             found = STATE_STOPPED;
132           else if (strcmp (line + length, "T (tracing stop)\n") == 0)
133             found = STATE_PTRACED;
134           else if (strcmp (line + length, "Z (zombie)\n") == 0)
135             found = STATE_ZOMBIE;
136           /* FIXME: What does it mean?  */
137           else if (strcmp (line + length, "X (dead)\n") == 0)
138             found = STATE_DEAD;
139           else
140             {
141               fprintf (stderr, "Found an unknown state: %s", line + length);
142               crash ();
143             }
144         }
145       assert (found != STATE_INSTABLE || errno == ESRCH);
146       i = fclose (f);
147       assert (i == 0);
148     }
149   return found;
150 }
151
152 #define STATE(pid, expect_mask) state ((pid), (expect_mask), #expect_mask )
153
154 static enum state state (pid_t pid, unsigned expect_mask, const char *expect_mask_string)
155 {
156   enum state found;
157   time_t timeout = time (NULL) + TIMEOUT_SECS;
158   unsigned loops = 0;
159
160   /* Sanity check `1 << enum state' was not misplaced with `enum state'.  */
161   assert (1 << (STATE_FIRST + 1) >= STATE_LAST);
162   assert (expect_mask != 0);
163 #define MASK_UNDER_EXCLUSIVE(bit) ((1 << (bit)) - 1)
164 #define MASK_ABOVE_INCLUSIVE(bit) (~MASK_UNDER_EXCLUSIVE (bit))
165   assert ((expect_mask & MASK_UNDER_EXCLUSIVE (STATE_FIRST + 1)) == 0);
166   assert ((expect_mask & MASK_ABOVE_INCLUSIVE (STATE_LAST)) == 0);
167 #undef MASK_ABOVE_INCLUSIVE
168 #undef MASK_UNDER_EXCLUSIVE
169
170   do
171     {
172       found = state_get (pid);
173
174       if (((1 << found) & expect_mask) != 0)
175         return found;
176     }
177   while (loops++ < LOOPS_MIN || time (NULL) < timeout);
178
179   fprintf (stderr, "Found for PID %d state %s but expecting (%s)\n",
180            (int) pid, state_to_name (found), expect_mask_string);
181   crash ();
182 }
183
184 /* Debugging only: Number of signal needing redelivery on PTRACE_ATTACH.  */
185 int attach_redelivered;
186
187 int attach (pid_t pid)
188 {
189   int i;
190   int status;
191   int stopped;
192
193   attach_redelivered = 0;
194
195   delay ();
196
197 #if 1
198   stopped = (STATE (pid, (1 << STATE_SLEEPING) | (1 << STATE_RUNNING)
199                          | (1 << STATE_STOPPED)) == STATE_STOPPED);
200 #endif
201
202   i = ptrace (PTRACE_ATTACH, pid, NULL, NULL);
203   assert (i == 0);
204
205   /* FIXME: Why it does not work?
206      Disable also the STATE () call above.  */
207 #if 0
208   delay();
209
210   i = ptrace (PTRACE_CONT, pid, (void *) 1, (void *) SIGSTOP);
211   /* `STOPPED == 1' may be false, even if the process was not stopped.  */
212   if (i == 0)
213     stopped = 1;
214   else if (errno == ESRCH)
215     stopped = 0;
216   else
217     crash ();
218 #endif
219
220   for (;;)
221     {
222       delay ();
223
224       i = waitpid (pid, &status, 0);
225       assert (i == pid);
226       if (!WIFSTOPPED (status))
227         {
228           /* Process may have exited.  */
229           fprintf (stderr, "PID %d waitpid(2) status %d\n", (int) pid,
230                    status);
231           exit (EXIT_FAILURE);
232         }
233       if (WSTOPSIG (status) == SIGSTOP)
234         break;
235
236       if (attach_redelivered == 0)
237         attach_redelivered = WSTOPSIG (status);
238       else
239         attach_redelivered = -1;
240
241       delay ();
242
243       /* Re-deliver the signal received before SIGSTOP.
244          It happens with about only 1:200000 probability.  */
245       i = ptrace (PTRACE_CONT, pid, (void *) 1,
246                   (void *) (unsigned long) WSTOPSIG (status));
247       assert (i == 0);
248     }
249
250   return stopped;
251 }
252
253 void detach (pid_t pid, int stopped)
254 {
255   int i;
256
257   delay ();
258
259   i = ptrace (PTRACE_DETACH, pid, NULL,
260               (void *) (unsigned long) (stopped ? SIGSTOP : 0));
261   assert (i == 0);
262
263   delay ();
264 }
265
266 #ifndef LIBRARY
267
268 int main (int argc, char **argv)
269 {
270   pid_t pid;
271   int stopped;
272
273   if (argc != 2)
274     {
275       fprintf (stderr, "Usage: %s <PID>\n", argv[0]);
276       exit (EXIT_FAILURE);
277     }
278
279   pid = atoi (argv[1]);
280
281   stopped = attach (pid);
282
283   detach (pid, stopped);
284
285   return EXIT_SUCCESS;
286 }
287
288 #endif /* !LIBRARY */