3 Copyright 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
4 2009, 2010 Red Hat Inc.
6 Written by Chris Faylor <cgf@redhat.com>
8 This file is part of Cygwin.
10 This software is a copyrighted work licensed under the terms of the
11 Cygwin license. Please consult the file "CYGWIN_LICENSE" for
14 #define cygwin_internal cygwin_internal_dontuse
25 #include "cygwin/include/sys/strace.h"
26 #include "cygwin/include/sys/cygwin.h"
28 #undef cygwin_internal
31 /* we *know* we're being built with GCC */
32 #define alloca __builtin_alloca
35 static const char version[] = "$Revision: 1.21 $";
37 static const char *pgm;
38 static int forkdebug = 1;
39 static int numerror = 1;
40 static int show_usecs = 1;
44 static int new_window;
45 static long flush_period;
46 static int include_hex;
47 static int quiet = -1;
49 static unsigned char strace_active = 1;
52 static BOOL close_handle (HANDLE h, DWORD ok);
54 #define CloseHandle(h) close_handle(h, 0)
64 struct child_list *next;
65 child_list ():id (0), hproc (NULL), saw_stars (0), nfields (0),
66 start_time (0), last_usecs (0), next (NULL)
74 warn (int geterrno, const char *fmt, ...)
80 sprintf (buf, "%s: ", pgm);
81 vsprintf (strchr (buf, '\0'), fmt, args);
91 static void __attribute__ ((noreturn))
92 error (int geterrno, const char *fmt, ...)
98 sprintf (buf, "%s: ", pgm);
99 vsprintf (strchr (buf, '\0'), fmt, args);
105 fputs ("\n", stderr);
117 for (c = &children; (c = c->next) != NULL;)
125 add_child (DWORD id, HANDLE hproc)
129 child_list *c = children.next;
130 children.next = (child_list *) calloc (1, sizeof (child_list));
131 children.next->next = c;
132 lastid = children.next->id = id;
133 lasth = children.next->hproc = hproc;
136 fprintf (stderr, "Windows process %lu attached\n", id);
141 remove_child (DWORD id)
146 for (c = &children; c->next != NULL; c = c->next)
147 if (c->next->id == id)
149 child_list *c1 = c->next;
153 fprintf (stderr, "Windows process %lu detached\n", id);
158 error (0, "no process id %d found", id);
161 #define LINE_BUF_CHUNK 128
180 void add (const char *what, int len);
181 void add (const char *what)
183 add (what, strlen (what));
185 void prepend (const char *what, int len);
189 linebuf::add (const char *what, int len)
192 if ((newix = ix + len) >= alloc)
194 alloc += LINE_BUF_CHUNK + len;
195 buf = (char *) realloc (buf, alloc + 1);
197 memcpy (buf + ix, what, len);
203 linebuf::prepend (const char *what, int len)
207 if ((newix = ix + len) >= alloc)
209 alloc += LINE_BUF_CHUNK + len;
210 buf = (char *) realloc (buf, alloc + 1);
213 if ((buflen = strlen (buf)))
214 memmove (buf + len, buf, buflen + 1);
217 memcpy (buf, what, len);
222 make_command_line (linebuf & one_line, char **argv)
224 for (; *argv; argv++)
227 const char *a = *argv;
229 int len = strlen (a);
230 if (len != 0 && !(p = strpbrk (a, " \t\n\r\"")))
231 one_line.add (a, len);
234 one_line.add ("\"", 1);
235 for (; p; a = p, p = strchr (p, '"'))
237 one_line.add (a, ++p - a);
239 one_line.add ("\"", 1);
243 one_line.add ("\"", 1);
245 one_line.add (" ", 1);
249 one_line.buf[one_line.ix - 1] = '\0';
251 one_line.add ("", 1);
254 static DWORD child_pid;
260 if ((tic ^= 1) && !GenerateConsoleCtrlEvent (CTRL_C_EVENT, 0))
261 error (0, "couldn't send CTRL-C to child, win32 error %d\n",
267 unsigned long (*cygwin_internal) (int, ...);
268 WCHAR cygwin_dll_path[32768];
282 if (!(h = LoadLibrary ("cygwin1.dll")))
287 GetModuleFileNameW (h, cygwin_dll_path, 32768);
288 if (!(cygwin_internal = (DWORD (*) (int, ...)) GetProcAddress (h, "cygwin_internal")))
297 attach_process (pid_t pid)
299 child_pid = (DWORD) cygwin_internal (CW_CYGWIN_PID_TO_WINPID, pid);
303 if (!DebugActiveProcess (child_pid))
304 error (0, "couldn't attach to pid %d for debugging", child_pid);
311 create_child (char **argv)
316 PROCESS_INFORMATION pi;
320 if (strchr (*argv, '/'))
321 *argv = cygpath (*argv, NULL);
322 memset (&si, 0, sizeof (si));
325 flags = CREATE_DEFAULT_ERROR_MODE
326 | (forkdebug ? DEBUG_PROCESS : DEBUG_ONLY_THIS_PROCESS);
328 flags |= CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP;
330 make_command_line (one_line, argv);
332 SetConsoleCtrlHandler (NULL, 0);
333 const char *cygwin_env = getenv ("CYGWIN");
338 space = cygwin_env = "";
339 char *newenv = (char *) malloc (sizeof ("CYGWIN=noglob") + strlen (space) + strlen (cygwin_env));
340 sprintf (newenv, "CYGWIN=noglob%s%s", space, cygwin_env);
342 ret = CreateProcess (0, one_line.buf, /* command line */
345 TRUE, /* inherit handles */
346 flags, /* start flags */
347 NULL, /* default environment */
348 NULL, /* current directory */
351 error (0, "error creating process %s, (error %d)", *argv,
354 CloseHandle (pi.hThread);
355 CloseHandle (pi.hProcess);
356 child_pid = pi.dwProcessId;
357 SetConsoleCtrlHandler (ctrl_c, 1);
361 output_winerror (FILE *ofile, char *s)
363 char *winerr = strstr (s, "Win32 error ");
367 DWORD errnum = atoi (winerr + sizeof ("Win32 error ") - 1);
372 * NOTE: Currently there is no policy for how long the
373 * the buffers are, and looks like 256 is a smallest one
374 * (dlfcn.cc). Other than error 1395 (length 213) and
375 * error 1015 (length 249), the rest are all under 188
376 * characters, and so I'll use 189 as the buffer length.
377 * For those longer error messages, FormatMessage will
378 * return FALSE, and we'll get the old behaviour such as
379 * ``Win32 error 1395'' etc.
382 if (!FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM
383 | FORMAT_MESSAGE_IGNORE_INSERTS,
386 MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
387 (LPTSTR) buf, sizeof (buf), NULL))
390 /* Get rid the trailing CR/NL pair. */
391 char *p = strchr (buf, '\0');
405 static SYSTEMTIME st;
406 long long now = t /*+ ((long long) usecs * 10)*/;
407 n.dwHighDateTime = now >> 32;
408 n.dwLowDateTime = now & 0xffffffff;
409 FileTimeToSystemTime (&n, &st);
413 static void __stdcall
414 handle_output_debug_string (DWORD id, LPVOID p, unsigned mask, FILE *ofile)
418 char alen[3 + 8 + 1];
420 child_list *child = get_child (id);
422 error (0, "no process id %d found", id);
423 HANDLE hchild = child->hproc;
424 #define INTROLEN (sizeof (alen) - 1)
426 if (id == lastid && hchild != lasth)
427 warn (0, "%p != %p", hchild, lasth);
429 alen[INTROLEN] = '\0';
430 if (!ReadProcessMemory (hchild, p, alen, INTROLEN, &nbytes))
435 "couldn't get message length from subprocess %d<%p>, windows error %d",
436 id, hchild, GetLastError ());
439 if (strncmp (alen, "cYg", 3))
441 len = (int) strtoul (alen + 3, NULL, 16);
450 if (special == _STRACE_INTERFACE_ACTIVATE_ADDR || special == _STRACE_CHILD_PID)
455 buf = (char *) alloca (len + 85) + 20;
457 if (!ReadProcessMemory (hchild, ((char *) p) + INTROLEN, buf, len, &nbytes))
458 error (0, "couldn't get message from subprocess, windows error %d",
462 char *s = strtok (buf, " ");
464 unsigned long n = strtoul (s, NULL, 16);
466 s = strchr (s, '\0') + 1;
468 if (special == _STRACE_CHILD_PID)
470 if (!DebugActiveProcess (n))
471 error (0, "couldn't attach to subprocess %d for debugging, "
472 "windows error %d", n, GetLastError ());
476 if (special == _STRACE_INTERFACE_ACTIVATE_ADDR)
478 if (!WriteProcessMemory (hchild, (LPVOID) n, &strace_active,
479 sizeof (strace_active), &nbytes))
480 error (0, "couldn't write strace flag to subprocess at %p, "
481 "windows error %d", n, GetLastError ());
489 else if (!(mask & _STRACE_ALL) || (n & _STRACE_NOTALL))
490 return; /* This should not be included in "all" output */
493 char *ptusec, *ptrest;
495 dusecs = strtoul (s, &ptusec, 10);
501 usecs = strtoul (q, &ptrest, 10);
502 while (*ptrest == ' ')
508 ptusec = show_usecs ? s : ptrest;
512 if (child->saw_stars == 0)
517 GetSystemTimeAsFileTime (&st);
518 FileTimeToLocalFileTime (&st, &st);
519 child->start_time = st.dwHighDateTime;
520 child->start_time <<= 32;
521 child->start_time |= st.dwLowDateTime;
522 if (*(news = ptrest) != '[')
523 child->saw_stars = 2;
527 while ((news = strchr (news, ' ')) != NULL && *++news != '*')
538 else if (child->saw_stars < 2)
542 if (*(news = ptrest) != '[')
543 child->saw_stars = 2;
546 for (i = 0; i < child->nfields; i++)
547 if ((news = strchr (news, ' ')) == NULL)
548 break; // Should never happen
553 child->saw_stars = 2;
559 SYSTEMTIME *st = syst (child->start_time);
561 "Date/Time: %d-%02d-%02d %02d:%02d:%02d\n",
562 st->wYear, st->wMonth, st->wDay, st->wHour,
563 st->wMinute, st->wSecond);
570 long long d = usecs - child->last_usecs;
573 if (child->saw_stars < 2 || s != origs)
578 SYSTEMTIME *st = syst (child->start_time + (long long) usecs * 10);
579 sprintf (s, "%02d:%02d:%02d", st->wHour, st->wMinute, st->wSecond);
580 *strchr (s, '\0') = ' ';
587 sprintf (intbuf, "%5d ", (int) d);
588 int len = strlen (intbuf);
590 memcpy ((s -= len), intbuf, len);
596 sprintf (s, "%p", (void *) n);
597 strchr (s, '\0')[0] = ' ';
599 child->last_usecs = usecs;
600 if (numerror || !output_winerror (ofile, s))
607 proc_child (unsigned mask, FILE *ofile, pid_t pid)
611 time_t cur_time, last_time;
613 SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_HIGHEST);
614 last_time = time (NULL);
617 BOOL debug_event = WaitForDebugEvent (&ev, 1000);
618 DWORD status = DBG_CONTINUE;
620 if (bufsize && flush_period > 0 &&
621 (cur_time = time (NULL)) >= last_time + flush_period)
623 last_time = cur_time;
632 (void) cygwin_internal (CW_STRACE_TOGGLE, pid);
636 switch (ev.dwDebugEventCode)
638 case CREATE_PROCESS_DEBUG_EVENT:
639 if (ev.u.CreateProcessInfo.hFile)
640 CloseHandle (ev.u.CreateProcessInfo.hFile);
641 add_child (ev.dwProcessId, ev.u.CreateProcessInfo.hProcess);
644 case CREATE_THREAD_DEBUG_EVENT:
647 case LOAD_DLL_DEBUG_EVENT:
648 if (ev.u.LoadDll.hFile)
649 CloseHandle (ev.u.LoadDll.hFile);
652 case OUTPUT_DEBUG_STRING_EVENT:
653 handle_output_debug_string (ev.dwProcessId,
654 ev.u.DebugString.lpDebugStringData,
658 case EXIT_PROCESS_DEBUG_EVENT:
659 res = ev.u.ExitProcess.dwExitCode >> 8;
660 remove_child (ev.dwProcessId);
662 case EXCEPTION_DEBUG_EVENT:
663 if (ev.u.Exception.ExceptionRecord.ExceptionCode != STATUS_BREAKPOINT)
665 status = DBG_EXCEPTION_NOT_HANDLED;
666 if (ev.u.Exception.dwFirstChance)
667 fprintf (ofile, "--- Process %lu, exception %p at %p\n", ev.dwProcessId,
668 (void *) ev.u.Exception.ExceptionRecord.ExceptionCode,
669 ev.u.Exception.ExceptionRecord.ExceptionAddress);
673 if (!ContinueDebugEvent (ev.dwProcessId, ev.dwThreadId, status))
674 error (0, "couldn't continue debug event, windows error %d",
686 child_pid = (DWORD) cygwin_internal (CW_CYGWIN_PID_TO_WINPID, pid);
689 warn (0, "no such cygwin pid - %d", pid);
692 if (cygwin_internal (CW_STRACE_TOGGLE, child_pid))
693 error (0, "failed to toggle tracing for process %d<%d>", pid, child_pid);
699 dostrace (unsigned mask, FILE *ofile, pid_t pid, char **argv)
704 attach_process (pid);
706 return proc_child (mask, ofile, pid);
709 typedef struct tag_mask_mnemonic
716 static const mask_mnemonic mnemonic_table[] = {
717 {_STRACE_ALL, "all"},
718 {_STRACE_FLUSH, "flush"},
719 {_STRACE_INHERIT, "inherit"},
720 {_STRACE_UHOH, "uhoh"},
721 {_STRACE_SYSCALL, "syscall"},
722 {_STRACE_STARTUP, "startup"},
723 {_STRACE_DEBUG, "debug"},
724 {_STRACE_PARANOID, "paranoid"},
725 {_STRACE_TERMIOS, "termios"},
726 {_STRACE_SELECT, "select"},
728 {_STRACE_SIGP, "sigp"},
729 {_STRACE_MINIMAL, "minimal"},
730 {_STRACE_EXITDUMP, "exitdump"},
731 {_STRACE_SYSTEM, "system"},
732 {_STRACE_NOMUTEX, "nomutex"},
733 {_STRACE_MALLOC, "malloc"},
734 {_STRACE_THREAD, "thread"},
735 {_STRACE_PTHREAD, "pthread"},
736 {_STRACE_SPECIAL, "special"},
741 mnemonic2ul (const char *nptr, char **endptr)
743 // Look up mnemonic in table, return value.
744 // *endptr = ptr to char that breaks match.
745 const mask_mnemonic *mnp = mnemonic_table;
747 while (mnp->text != NULL)
749 if (strcmp (mnp->text, nptr) == 0)
754 *endptr = ((char *) nptr) + strlen (mnp->text);
764 *endptr = (char *) nptr;
770 parse_mask (const char *ms, char **endptr)
774 unsigned long retval = 0, thisval;
775 const size_t bufsize = 16;
776 char buffer[bufsize];
781 // First extract the term, terminate it, and lowercase it.
782 strncpy (buffer, p, bufsize);
783 buffer[bufsize - 1] = '\0';
784 len = strcspn (buffer, "+,\0");
788 // Check if this is a mnemonic. We have to do this first or strtoul()
789 // will false-trigger on anything starting with "a" through "f".
790 thisval = mnemonic2ul (buffer, &newp);
793 // This term isn't mnemonic, check if it's hex.
794 thisval = strtoul (buffer, &newp, 16);
795 if (newp != buffer + len)
797 // Not hex either, syntax error.
798 *endptr = (char *) p;
809 if ((*p == '+') || (*p == ','))
811 // For now these both equate to addition/ORing. Until we get
812 // fancy and add things like "all-<something>", all we need do is
813 // continue the looping.
820 *endptr = (char *) p;
825 *endptr = (char *) p;
830 usage (FILE *where = stderr)
833 Usage: %s [OPTIONS] <command-line>\n\
834 Usage: %s [OPTIONS] -p <pid>\n\
835 Trace system calls and signals\n\
837 -b, --buffer-size=SIZE set size of output file buffer\n\
838 -d, --no-delta don't display the delta-t microsecond timestamp\n\
839 -f, --trace-children trace child processes (toggle - default true)\n\
840 -h, --help output usage information and exit\n\
841 -m, --mask=MASK set message filter mask\n\
842 -n, --crack-error-numbers output descriptive text instead of error\n\
843 numbers for Windows errors\n\
844 -o, --output=FILENAME set output file to FILENAME\n\
845 -p, --pid=n attach to executing program with cygwin pid n\n\
846 -q, --quiet suppress messages about attaching, detaching, etc.\n\
847 -S, --flush-period=PERIOD flush buffered strace output every PERIOD secs\n\
848 -t, --timestamp use an absolute hh:mm:ss timestamp insted of \n\
849 the default microsecond timestamp. Implies -d\n\
850 -T, --toggle toggle tracing in a process already being\n\
851 traced. Requires -p <pid>\n\
852 -u, --usecs toggle printing of microseconds timestamp\n\
853 -v, --version output version information and exit\n\
854 -w, --new-window spawn program under test in a new window\n\
856 if ( where == stdout)
858 MASK can be any combination of the following mnemonics and/or hex values\n\
859 (0x is optional). Combine masks with '+' or ',' like so:\n\
861 --mask=wm+system,malloc+0x00800\n\
863 Mnemonic Hex Corresponding Def Description\n\
864 =========================================================================\n\
865 all 0x000001 (_STRACE_ALL) All strace messages.\n\
866 flush 0x000002 (_STRACE_FLUSH) Flush output buffer after each message.\n\
867 inherit 0x000004 (_STRACE_INHERIT) Children inherit mask from parent.\n\
868 uhoh 0x000008 (_STRACE_UHOH) Unusual or weird phenomenon.\n\
869 syscall 0x000010 (_STRACE_SYSCALL) System calls.\n\
870 startup 0x000020 (_STRACE_STARTUP) argc/envp printout at startup.\n\
871 debug 0x000040 (_STRACE_DEBUG) Info to help debugging. \n\
872 paranoid 0x000080 (_STRACE_PARANOID) Paranoid info.\n\
873 termios 0x000100 (_STRACE_TERMIOS) Info for debugging termios stuff.\n\
874 select 0x000200 (_STRACE_SELECT) Info on ugly select internals.\n\
875 wm 0x000400 (_STRACE_WM) Trace Windows msgs (enable _strace_wm).\n\
876 sigp 0x000800 (_STRACE_SIGP) Trace signal and process handling.\n\
877 minimal 0x001000 (_STRACE_MINIMAL) Very minimal strace output.\n\
878 pthread 0x002000 (_STRACE_PTHREAD) Pthread calls.\n\
879 exitdump 0x004000 (_STRACE_EXITDUMP) Dump strace cache on exit.\n\
880 system 0x008000 (_STRACE_SYSTEM) Serious error; goes to console and log.\n\
881 nomutex 0x010000 (_STRACE_NOMUTEX) Don't use mutex for synchronization.\n\
882 malloc 0x020000 (_STRACE_MALLOC) Trace malloc calls.\n\
883 thread 0x040000 (_STRACE_THREAD) Thread-locking calls.\n\
884 special 0x100000 (_STRACE_SPECIAL) Special debugging printfs for\n\
885 non-checked-in code\n\
888 fprintf (stderr, "Try '%s --help' for more information.\n", pgm);
889 exit (where == stderr ? 1 : 0 );
892 struct option longopts[] = {
893 {"buffer-size", required_argument, NULL, 'b'},
894 {"help", no_argument, NULL, 'h'},
895 {"flush-period", required_argument, NULL, 'S'},
896 {"hex", no_argument, NULL, 'H'},
897 {"mask", required_argument, NULL, 'm'},
898 {"new-window", no_argument, NULL, 'w'},
899 {"output", required_argument, NULL, 'o'},
900 {"no-delta", no_argument, NULL, 'd'},
901 {"pid", required_argument, NULL, 'p'},
902 {"quiet", no_argument, NULL, 'q'},
903 {"timestamp", no_argument, NULL, 't'},
904 {"toggle", no_argument, NULL, 'T'},
905 {"trace-children", no_argument, NULL, 'f'},
906 {"translate-error-numbers", no_argument, NULL, 'n'},
907 {"usecs", no_argument, NULL, 'u'},
908 {"version", no_argument, NULL, 'v'},
912 static const char *const opts = "+b:dhHfm:no:p:qS:tTuvw";
917 const char *v = strchr (version, ':');
927 len = strchr (v, ' ') - v;
932 Copyright 2000, 2001, 2002, 2003, 2004, 2005 Red Hat, Inc.\n\
934 ", pgm, len, v, __DATE__);
938 main (int argc, char **argv)
949 char **av = (char **) cygwin_internal (CW_ARGV);
950 if (av && (DWORD) av != (DWORD) -1)
951 for (argc = 0, argv = av; *av; av++)
955 if (!(pgm = strrchr (*argv, '\\')) && !(pgm = strrchr (*argv, '/')))
960 while ((opt = getopt_long (argc, argv, opts, longopts, NULL)) != EOF)
964 bufsize = atoi (optarg);
973 // Print help and exit
982 mask = parse_mask (optarg, &endptr);
985 // Bad mask expression.
986 error (0, "syntax error in mask expression \"%s\" near \
987 character #%d.\n", optarg, (int) (endptr - optarg), endptr);
995 if ((ofile = fopen (cygpath (optarg, NULL), "wb")) == NULL)
996 error (1, "can't open %s", optarg);
998 (void) fcntl (fileno (ofile), F_SETFD, 0);
1002 pid = strtoul (optarg, NULL, 10);
1012 flush_period = strtoul (optarg, NULL, 10);
1021 // FIXME: currently unimplemented
1026 // Print version info and exit
1033 fprintf (stderr, "Try '%s --help' for more information.\n", pgm);
1037 if (pid && argv[optind])
1038 error (0, "cannot provide both a command line and a process id");
1040 if (!pid && !argv[optind])
1041 error (0, "must provide either a command line or a process id");
1044 error (0, "must provide a process id to toggle tracing");
1047 quiet = sawquiet < 0 || !sawquiet;
1048 else if (sawquiet < 0)
1057 setvbuf (ofile, (char *) alloca (bufsize), _IOFBF, bufsize);
1066 res = dostrace (mask, ofile, pid, argv + optind);
1073 close_handle (HANDLE h, DWORD ok)
1076 for (c = &children; (c = c->next) != NULL;)
1077 if (c->hproc == h && c->id != ok)
1078 error (0, "Closing child handle %p", h);
1079 return CloseHandle (h);