/* profiler.cc Periodically samples IP of a process and its DLLs; writes gprof data files. Written by Mark Geisert , who admits to copying pretty liberally from strace.cc. h/t to cgf for strace! This file is part of Cygwin. This software is a copyrighted work licensed under the terms of the Cygwin license. Please consult the file "CYGWIN_LICENSE" for details. */ #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include #include #include #include #include #include "cygwin/version.h" #include "gcc_seh.h" typedef unsigned long ulong; typedef unsigned short ushort; typedef uint16_t u_int16_t; // Non-standard sized type needed by ancient gmon.h #define NO_GLOBALS_H #include "gmon.h" #include "path.h" /* Undo this #define from winsup.h. */ #ifdef ExitThread #undef ExitThread #endif /* Undo this #define from debug.h. */ #ifdef CloseHandle #undef CloseHandle #endif #define SCALE_SHIFT 2 // == 4 bytes of address space per bucket #define MS_VC_EXCEPTION 0x406D1388 // thread name notification from child DWORD child_pid; int debugging = 0; void *drive_map; int events = 0; int forkprofile = 0; int new_window; int numprocesses; FILE *ofile = stdout; const char *pgm; char *prefix = (char *) "gmon.out"; int samplerate = 100; // in Hz; up to 1000 might work int verbose = 0; void __attribute__ ((__noreturn__)) usage (FILE *where = stderr) { fprintf (where, "\ Usage: %s [OPTIONS] \n\ or: %s [OPTIONS] -p \n\ \n\ Profiles a command or process by sampling its IP (instruction pointer).\n\ OPTIONS are:\n\ \n\ -d, --debug Display debugging messages (toggle: default false)\n\ -e, --events Display Windows DEBUG_EVENTS (toggle: default false)\n\ -f, --fork-profile Profile child processes (toggle: default false)\n\ -h, --help Display usage information and exit\n\ -o, --output=FILENAME Write output to file FILENAME rather than stdout\n\ -p, --pid=N Attach to running program with Cygwin pid N\n\ ... or with Windows pid -N\n\ -s, --sample-rate=N Set IP sampling rate to N Hz (default 100)\n\ -v, --verbose Display more status messages (toggle: default false)\n\ -V, --version Display version information and exit\n\ -w, --new-window Launch given command in a new window\n\ \n", pgm, pgm); exit (where == stderr ? 1 : 0 ); } /* A span is a memory address range covering an EXE's or DLL's .text segment. */ struct span_list { WCHAR *name; LPVOID base; size_t textlo; size_t texthi; int hitcount; int hitbuckets; int numbuckets; int *buckets; struct span_list *next; }; /* A thread. */ struct thread_list { DWORD tid; HANDLE hthread; WCHAR *name; struct thread_list *next; }; /* A child is any process being sampled in this profiler run. */ struct child_list { DWORD pid; volatile int profiling; HANDLE hproc; HANDLE hquitevt; HANDLE hprofthr; CONTEXT *context; struct thread_list *threads; struct span_list *spans; struct child_list *next; }; child_list children; typedef struct child_list child; void note (const char *fmt, ...) { va_list args; char buf[BUFSIZ]; va_start (args, fmt); vsprintf (buf, fmt, args); va_end (args); fputs (buf, ofile); fflush (ofile); } void warn (int geterrno, const char *fmt, ...) { va_list args; char buf[BUFSIZ]; va_start (args, fmt); sprintf (buf, "%s: ", pgm); vsprintf (strchr (buf, '\0'), fmt, args); va_end (args); if (geterrno) perror (buf); else { fputs (buf, ofile); fputs ("\n", ofile); fflush (ofile); } } void __attribute__ ((noreturn)) error (int geterrno, const char *fmt, ...) { va_list args; char buf[BUFSIZ]; va_start (args, fmt); vsprintf (buf, fmt, args); va_end (args); warn (geterrno, "%s", buf); exit (1); } size_t sample (CONTEXT *context, HANDLE h) { size_t status; if (-1U == SuspendThread (h)) return 0ULL; status = GetThreadContext (h, context); if (-1U == ResumeThread (h)) if (verbose) note ("*** unable to resume thread %d; continuing anyway\n", h); if (0 == status) { if (verbose) note ("*** unable to get context for thread %d\n", h); return 0ULL; } else #ifdef __x86_64__ return context->Rip; #else #error unimplemented for this target #endif } void bump_bucket (child *c, size_t pc) { span_list *s = c->spans; //note ("%lu %p\n", (ulong) c->pid, pc); if (pc == 0ULL) return; while (s) { if (pc >= s->textlo && pc < s->texthi) { if (0 == s->buckets[(pc - s->textlo) >> SCALE_SHIFT]++) ++s->hitbuckets; ++s->hitcount; return; } s = s->next; } /*TODO If the child has dynamically created an executable memory region, we * won't notice it until the profiler thread happens to sample an * instruction in that region. We could then add a new span to record * hits on this new region. (QueryVirtualMemory to obtain limits?) * * Note that if the app dynamically adds and deletes such regions, the * profiling info on them will be confusing if their addresses overlap. */ if (verbose) note ("*** pc %p out of range for pid %lu\n", pc, (ulong) c->pid); } /* profiler runs on its own thread; each child has a separate profiler. */ DWORD WINAPI profiler (void *vp) { child *c = (child *) vp; while (c->profiling) { thread_list *t = c->threads; while (t) { if (t->hthread) bump_bucket (c, sample (c->context, t->hthread)); t = t->next; } if (WaitForSingleObject (c->hquitevt, 1000 / samplerate) == WAIT_OBJECT_0) break; } return 0; } void start_profiler (child *c) { DWORD tid; if (verbose) note ("*** start profiler thread on pid %lu\n", (ulong) c->pid); c->context = (CONTEXT *) calloc (1, sizeof (CONTEXT)); if (!c->context) error (0, "unable to allocate CONTEXT buffer"); c->context->ContextFlags = CONTEXT_CONTROL; c->hquitevt = CreateEvent (NULL, TRUE, FALSE, NULL); if (!c->hquitevt) error (0, "unable to create quit event"); c->profiling = 1; c->hprofthr = CreateThread (NULL, 0, profiler, (void *) c, 0, &tid); if (!c->hprofthr) error (0, "unable to create profiling thread"); /* There is a temptation to raise the execution priority of the profiling * threads. Don't do this, or at least don't do it this way. Testing * showed that it was possible to starve system processes which makes the * system unresponsive. Raising prio doesn't seem to be needed at all. * SetThreadPriority (c->hprofthr, THREAD_PRIORITY_TIME_CRITICAL); */ } void stop_profiler (child *c) { if (verbose) note ("*** stop profiler thread on pid %lu\n", (ulong) c->pid); c->profiling = 0; SignalObjectAndWait (c->hquitevt, c->hprofthr, INFINITE, FALSE); CloseHandle (c->hquitevt); CloseHandle (c->hprofthr); c->hquitevt = c->hprofthr = 0; } /* Create a gmon.out file for each EXE or DLL that has at least one sample. */ void dump_profile_data (child *c) { int fd; char filename[MAX_PATH + 1]; struct gmonhdr hdr; span_list *s = c->spans; while (s) { if (s->hitbuckets == 0) { s = s->next; continue; } if (s->name) { WCHAR *name = 1 + wcsrchr (s->name, L'\\'); sprintf (filename, "%s.%lu.%ls", prefix, (ulong) c->pid, name); } else sprintf (filename, "%s.%lu", prefix, (ulong) c->pid); fd = open (filename, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY); if (fd < 0) error (0, "dump_profile_data: unable to create %s", filename); memset (&hdr, 0, sizeof (hdr)); hdr.lpc = s->textlo; hdr.hpc = s->texthi; hdr.ncnt = s->numbuckets * sizeof (short) + sizeof (hdr); hdr.version = GMONVERSION; hdr.profrate = samplerate; /* Our buckets hold more than gmon standard buckets, so truncate here. */ ushort *gmonbuckets = (ushort *) calloc (s->numbuckets, sizeof (ushort)); for (int i = 0; i < s->numbuckets; i++) { if (s->buckets[i]) { if (s->buckets[i] > 65535) { note (" WARNING: bucket %d: value %d truncated to %d\n", i, s->buckets[i], 65535); gmonbuckets[i] = 65535; } else gmonbuckets[i] = s->buckets[i]; } } write (fd, &hdr, sizeof (hdr)); write (fd, gmonbuckets, hdr.ncnt - sizeof (hdr)); note ("%d %s across %d %s written to %s\n", s->hitcount, s->hitcount == 1 ? "sample" : "samples", s->hitbuckets, s->hitbuckets == 1 ? "bucket" : "buckets", filename); close (fd); chmod (filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); free (gmonbuckets); s = s->next; } } HANDLE lasth; DWORD lastpid = 0; child * get_child (DWORD pid) { child *c; for (c = &children; (c = c->next) != NULL;) if (c->pid == pid) return (child *) c; return NULL; } void add_span (DWORD, WCHAR *, LPVOID, HANDLE); void add_child (DWORD pid, WCHAR *name, LPVOID base, HANDLE hproc) { if (!get_child (pid)) { child *c = children.next; children.next = (child *) calloc (1, sizeof (child)); children.next->next = c; lastpid = children.next->pid = pid; lasth = children.next->hproc = hproc; add_span (pid, name, base, hproc); start_profiler (children.next); numprocesses++; if (verbose) note ("*** Windows process %lu attached\n", (ulong) pid); } } void remove_child (DWORD pid) { child *c; if (pid == lastpid) lastpid = 0; for (c = &children; c->next != NULL; c = c->next) if (c->next->pid == pid) { child *c1 = c->next; c->next = c1->next; stop_profiler (c1); dump_profile_data (c1); CloseHandle (c1->hproc); c1->hproc = 0; free (c1); if (verbose) note ("*** Windows process %lu detached\n", (ulong) pid); numprocesses--; return; } error (0, "no process id %d found", pid); } void add_thread (DWORD pid, DWORD tid, HANDLE h, WCHAR *name) { child *c = get_child (pid); if (!c) error (0, "add_thread: pid %lu not found", (ulong) pid); thread_list *t = (thread_list *) calloc (1, sizeof (thread_list)); t->tid = tid; t->hthread = h; t->name = name; t->next = c->threads; c->threads = t; } void remove_thread (DWORD pid, DWORD tid) { child *c = get_child (pid); if (!c) error (0, "remove_thread: pid %lu not found", (ulong) pid); thread_list *t = c->threads; while (t) { if (t->tid == tid) { /*TODO We don't free(t), we just zero it out. Maybe revisit this. */ t->tid = 0; CloseHandle (t->hthread); t->hthread = 0; if (t->name) free (t->name); t->name = NULL; return; } t = t->next; } error (0, "remove_thread: pid %lu tid %lu not found", (ulong) pid, (ulong) tid); } void read_child (void *buf, SIZE_T size, void *addr, HANDLE h) { SIZE_T len; if (debugging) note ("read %d bytes at %p from handle %d\n", size, addr, h); if (0 == ReadProcessMemory (h, addr, buf, size, &len)) error (0, "read_child: failed"); if (len != size) error (0, "read_child: asked for %d bytes but got %d", size, len); } IMAGE_SECTION_HEADER * find_text_section (LPVOID base, HANDLE h) { static IMAGE_SECTION_HEADER asect; DWORD lfanew; WORD machine; WORD nsects; DWORD ntsig; char *ptr = (char *) base; IMAGE_DOS_HEADER *idh = (IMAGE_DOS_HEADER *) ptr; read_child ((void *) &lfanew, sizeof (lfanew), &idh->e_lfanew, h); ptr += lfanew; IMAGE_NT_HEADERS *inth = (IMAGE_NT_HEADERS *) ptr; read_child ((void *) &ntsig, sizeof (ntsig), &inth->Signature, h); if (ntsig != IMAGE_NT_SIGNATURE) error (0, "find_text_section: NT signature not found"); read_child ((void *) &machine, sizeof (machine), &inth->FileHeader.Machine, h); #ifdef __x86_64__ if (machine != IMAGE_FILE_MACHINE_AMD64) #else #error unimplemented for this target #endif error (0, "target program was built for different machine architecture"); read_child ((void *) &nsects, sizeof (nsects), &inth->FileHeader.NumberOfSections, h); ptr += sizeof (*inth); IMAGE_SECTION_HEADER *ish = (IMAGE_SECTION_HEADER *) ptr; for (int i = 0; i < nsects; i++) { read_child ((void *) &asect, sizeof (asect), ish, h); if (0 == memcmp (".text\0\0\0", &asect.Name, 8)) return &asect; ish++; } error (0, ".text section not found"); } //TODO Extend add_span to add all executable sections of this exe/dll void add_span (DWORD pid, WCHAR *name, LPVOID base, HANDLE h) { child *c = get_child (pid); if (!c) error (0, "add_span: pid %lu not found", (ulong) pid); IMAGE_SECTION_HEADER *sect = find_text_section (base, c->hproc); span_list *s = (span_list *) calloc (1, sizeof (span_list)); s->name = name; s->base = base; s->textlo = sect->VirtualAddress + (size_t) base; s->texthi = s->textlo + sect->Misc.VirtualSize; s->numbuckets = (s->texthi - s->textlo) >> SCALE_SHIFT; s->buckets = (int *) calloc (s->numbuckets, sizeof (int)); if (debugging) note (" span %p - %p, size %X, numbuckets %d\n", s->textlo, s->texthi, s->texthi - s->textlo, s->numbuckets); s->next = c->spans; c->spans = s; } #define LINE_BUF_CHUNK 128 class linebuf { size_t alloc; public: size_t ix; char *buf; linebuf () { ix = 0; alloc = 0; buf = NULL; } ~linebuf () { if (buf) free (buf); } void add (const char *what, int len); void add (const char *what) { add (what, strlen (what)); } void prepend (const char *what, int len); }; void linebuf::add (const char *what, int len) { size_t newix; if ((newix = ix + len) >= alloc) { alloc += LINE_BUF_CHUNK + len; buf = (char *) realloc (buf, alloc + 1); } memcpy (buf + ix, what, len); ix = newix; buf[ix] = '\0'; } void linebuf::prepend (const char *what, int len) { int buflen; size_t newix; if ((newix = ix + len) >= alloc) { alloc += LINE_BUF_CHUNK + len; buf = (char *) realloc (buf, alloc + 1); buf[ix] = '\0'; } if ((buflen = strlen (buf))) memmove (buf + len, buf, buflen + 1); else buf[newix] = '\0'; memcpy (buf, what, len); ix = newix; } void make_command_line (linebuf & one_line, char **argv) { for (; *argv; argv++) { char *p = NULL; const char *a = *argv; int len = strlen (a); if (len != 0 && !(p = strpbrk (a, " \t\n\r\""))) one_line.add (a, len); else { one_line.add ("\"", 1); for (; p; a = p, p = strchr (p, '"')) { one_line.add (a, ++p - a); if (p[-1] == '"') one_line.add ("\"", 1); } if (*a) one_line.add (a); one_line.add ("\"", 1); } one_line.add (" ", 1); } if (one_line.ix) one_line.buf[one_line.ix - 1] = '\0'; else one_line.add ("", 1); } BOOL WINAPI ctrl_c (DWORD) { static int tic = 1; if ((tic ^= 1) && !GenerateConsoleCtrlEvent (CTRL_C_EVENT, 0)) error (0, "couldn't send CTRL-C to child, Windows error %u", GetLastError ()); return TRUE; } WCHAR cygwin_dll_path[32768]; // required by path.cc helper funcs used herein #define DEBUG_PROCESS_DETACH_ON_EXIT 0x00000001 #define DEBUG_PROCESS_ONLY_THIS_PROCESS 0x00000002 void attach_process (pid_t pid) { child_pid = pid < 0 ? (DWORD) -pid : (DWORD) cygwin_internal (CW_CYGWIN_PID_TO_WINPID, pid); if (!DebugActiveProcess (child_pid)) error (0, "couldn't attach to pid %d for debugging", child_pid); if (forkprofile) { HANDLE h = OpenProcess (PROCESS_ALL_ACCESS, FALSE, child_pid); if (h) { /* Try to turn off DEBUG_ONLY_THIS_PROCESS so we can follow forks. */ ULONG DebugFlags = DEBUG_PROCESS_DETACH_ON_EXIT; NTSTATUS status = NtSetInformationProcess (h, ProcessDebugFlags, &DebugFlags, sizeof (DebugFlags)); if (!NT_SUCCESS (status)) warn (0, "Could not clear DEBUG_ONLY_THIS_PROCESS (%x), " "will not trace child processes", status); CloseHandle (h); } } return; } void create_child (char **argv) { DWORD flags; linebuf one_line; PROCESS_INFORMATION pi; BOOL ret; STARTUPINFO si; if (strchr (*argv, '/')) *argv = cygpath (*argv, NULL); memset (&si, 0, sizeof (si)); si.cb = sizeof (si); flags = CREATE_DEFAULT_ERROR_MODE | (forkprofile ? DEBUG_PROCESS : DEBUG_ONLY_THIS_PROCESS); if (new_window) flags |= CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP; make_command_line (one_line, argv); SetConsoleCtrlHandler (NULL, 0); const char *cygwin_env = getenv ("CYGWIN"); const char *space; if (cygwin_env && strlen (cygwin_env) <= 256) /* sanity check */ space = " "; else space = cygwin_env = ""; char *newenv = (char *) malloc (sizeof ("CYGWIN=noglob") + strlen (space) + strlen (cygwin_env)); sprintf (newenv, "CYGWIN=noglob%s%s", space, cygwin_env); putenv (newenv); ret = CreateProcess (0, one_line.buf, /* command line */ NULL, /* Security */ NULL, /* thread */ TRUE, /* inherit handles */ flags, /* start flags */ NULL, /* default environment */ NULL, /* current directory */ &si, &pi); if (!ret) error (0, "error creating process %s, Windows error %u", *argv, GetLastError ()); CloseHandle (pi.hThread); CloseHandle (pi.hProcess); child_pid = pi.dwProcessId; SetConsoleCtrlHandler (ctrl_c, 1); } void handle_output_debug_string (DWORD pid, OUTPUT_DEBUG_STRING_INFO *ev) { char *buf = (char *) alloca (ev->nDebugStringLength); child *c = get_child (pid); if (!c) error (0, "handle_output_debug_string: pid %lu not found", (ulong) pid); read_child (buf, ev->nDebugStringLength, ev->lpDebugStringData, c->hproc); if (strncmp (buf, "cYg", 3)) { // string is not from Cygwin, it's from the target app; just display it if (ev->fUnicode) note ("%ls\n", buf); else note ("%s\n", buf); } //else TODO Possibly decode and display Cygwin-internal debug string } BOOL GetFileNameFromHandle (HANDLE hFile, WCHAR pszFilename[MAX_PATH+1]) { BOOL result = FALSE; ULONG len = 0; OBJECT_NAME_INFORMATION *ntfn = (OBJECT_NAME_INFORMATION *) alloca (65536); NTSTATUS status = NtQueryObject (hFile, ObjectNameInformation, ntfn, 65536, &len); if (NT_SUCCESS (status)) { PWCHAR win32path = ntfn->Name.Buffer; win32path[ntfn->Name.Length / sizeof (WCHAR)] = L'\0'; /* NtQueryObject returns a native NT path. (Try to) convert to Win32. */ if (drive_map) win32path = (PWCHAR) cygwin_internal (CW_MAP_DRIVE_MAP, drive_map, win32path); pszFilename[0] = L'\0'; wcsncat (pszFilename, win32path, MAX_PATH); result = TRUE; } return result; } char * cygwin_pid (DWORD winpid) { static char buf[48]; DWORD cygpid; static DWORD max_cygpid = 0; if (!max_cygpid) max_cygpid = (DWORD) cygwin_internal (CW_MAX_CYGWIN_PID); cygpid = (DWORD) cygwin_internal (CW_WINPID_TO_CYGWIN_PID, winpid); if (cygpid >= max_cygpid) snprintf (buf, sizeof (buf), "%lu", (ulong) winpid); else snprintf (buf, sizeof (buf), "%lu (pid: %lu)", (ulong) winpid, (ulong) cygpid); return buf; } DWORD profile1 (FILE *ofile, pid_t pid) { DEBUG_EVENT ev; DWORD res = 0; SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_HIGHEST); while (1) { BOOL debug_event = WaitForDebugEvent (&ev, INFINITE); DWORD status = DBG_CONTINUE; if (!debug_event) continue; /* Usually continue event here so child resumes while we process event. */ if (ev.dwDebugEventCode != EXCEPTION_DEBUG_EVENT && ev.dwDebugEventCode != OUTPUT_DEBUG_STRING_EVENT) debug_event = ContinueDebugEvent (ev.dwProcessId, ev.dwThreadId, status); switch (ev.dwDebugEventCode) { case CREATE_PROCESS_DEBUG_EVENT: WCHAR exename[MAX_PATH+1]; if (!GetFileNameFromHandle (ev.u.CreateProcessInfo.hFile, exename)) wcscpy (exename, L"(unknown)"); if (events) { note ("--- Process %s created from %ls\n", cygwin_pid (ev.dwProcessId), exename); note ("--- Process %s thread %lu created at %p\n", cygwin_pid (ev.dwProcessId), ev.dwThreadId, ev.u.CreateProcessInfo.lpStartAddress); } if (ev.u.CreateProcessInfo.hFile) CloseHandle (ev.u.CreateProcessInfo.hFile); add_child (ev.dwProcessId, wcsdup (exename), ev.u.CreateProcessInfo.lpBaseOfImage, ev.u.CreateProcessInfo.hProcess); add_thread (ev.dwProcessId, ev.dwThreadId, ev.u.CreateProcessInfo.hThread, wcsdup (exename)); break; case CREATE_THREAD_DEBUG_EVENT: if (events) note ("--- Process %s thread %lu created at %p\n", cygwin_pid (ev.dwProcessId), ev.dwThreadId, ev.u.CreateThread.lpStartAddress); add_thread (ev.dwProcessId, ev.dwThreadId, ev.u.CreateThread.hThread, NULL); break; case LOAD_DLL_DEBUG_EVENT: WCHAR dllname[MAX_PATH+1]; /* lpImageName is not always populated, so find the filename for hFile instead. */ if (!GetFileNameFromHandle (ev.u.LoadDll.hFile, dllname)) wcscpy (dllname, L"(unknown)"); if (events) note ("--- Process %s loaded %ls at %p\n", cygwin_pid (ev.dwProcessId), dllname, ev.u.LoadDll.lpBaseOfDll); add_span (ev.dwProcessId, wcsdup (dllname), ev.u.LoadDll.lpBaseOfDll, ev.u.LoadDll.hFile); if (ev.u.LoadDll.hFile) CloseHandle (ev.u.LoadDll.hFile); break; case UNLOAD_DLL_DEBUG_EVENT: if (events) note ("--- Process %s unloaded DLL at %p\n", cygwin_pid (ev.dwProcessId), ev.u.UnloadDll.lpBaseOfDll); break; case OUTPUT_DEBUG_STRING_EVENT: handle_output_debug_string (ev.dwProcessId, &ev.u.DebugString); status = DBG_EXCEPTION_HANDLED; debug_event = ContinueDebugEvent (ev.dwProcessId, ev.dwThreadId, status); break; case EXIT_PROCESS_DEBUG_EVENT: if (events) note ("--- Process %s exited with status 0x%lx\n", cygwin_pid (ev.dwProcessId), ev.u.ExitProcess.dwExitCode); res = ev.u.ExitProcess.dwExitCode; remove_child (ev.dwProcessId); break; case EXIT_THREAD_DEBUG_EVENT: if (events) note ("--- Process %s thread %lu exited with status 0x%lx\n", cygwin_pid (ev.dwProcessId), ev.dwThreadId, ev.u.ExitThread.dwExitCode); remove_thread (ev.dwProcessId, ev.dwThreadId); break; case EXCEPTION_DEBUG_EVENT: status = DBG_EXCEPTION_HANDLED; switch (ev.u.Exception.ExceptionRecord.ExceptionCode) { case MS_VC_EXCEPTION: //TODO Decode exception info to get thread name; set it internally // fall thru case STATUS_BREAKPOINT: break; case STATUS_GCC_THROW: case STATUS_GCC_UNWIND: case STATUS_GCC_FORCED: status = DBG_EXCEPTION_NOT_HANDLED; break; default: status = DBG_EXCEPTION_NOT_HANDLED; if (ev.u.Exception.dwFirstChance) note ("--- Process %s thread %lu exception %08x at %p\n", cygwin_pid (ev.dwProcessId), ev.dwThreadId, ev.u.Exception.ExceptionRecord.ExceptionCode, ev.u.Exception.ExceptionRecord.ExceptionAddress); break; } debug_event = ContinueDebugEvent (ev.dwProcessId, ev.dwThreadId, status); break; } if (!debug_event) error (0, "couldn't continue debug event, Windows error %u", GetLastError ()); if (!numprocesses) break; } return res; } DWORD doprofile (FILE *ofile, pid_t pid, char **argv) { if (pid) attach_process (pid); else create_child (argv); return profile1 (ofile, pid); } struct option longopts[] = { {"debug", no_argument, NULL, 'd'}, {"events", no_argument, NULL, 'e'}, {"help", no_argument, NULL, 'h'}, {"new-window", no_argument, NULL, 'w'}, {"output", required_argument, NULL, 'o'}, {"pid", required_argument, NULL, 'p'}, {"fork-profile",no_argument, NULL, 'f'}, {"sample-rate", required_argument, NULL, 's'}, {"verbose", no_argument, NULL, 'v'}, {"version", no_argument, NULL, 'V'}, {NULL, 0, NULL, 0 } }; const char *const opts = "+dehfo:p:s:vVw"; void __attribute__ ((__noreturn__)) print_version () { char *year_of_build = strrchr (__DATE__, ' ') + 1; printf ("profiler (cygwin) %d.%d.%d\n" "IP-Sampling Profiler\n" "Copyright (C) %s%s Cygwin Authors\n" "This is free software; see the source for copying conditions. " "There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS " "FOR A PARTICULAR PURPOSE.\n", CYGWIN_VERSION_DLL_MAJOR / 1000, CYGWIN_VERSION_DLL_MAJOR % 1000, CYGWIN_VERSION_DLL_MINOR, strncmp (year_of_build, "2021", 4) ? "2021 - " : "", year_of_build); exit (0); } int main (int argc, char **argv) { int opt; pid_t pid = 0; char *ptr; DWORD ret = 0; _setmode (1, O_BINARY); _setmode (2, O_BINARY); if (!(pgm = strrchr (*argv, '\\')) && !(pgm = strrchr (*argv, '/'))) pgm = *argv; else pgm++; while ((opt = getopt_long (argc, argv, opts, longopts, NULL)) != EOF) switch (opt) { case 'd': debugging ^= 1; if (debugging) verbose = events = 1; // debugging turns these on too break; case 'e': events ^= 1; events |= debugging; // debugging turns on events too break; case 'f': forkprofile ^= 1; break; case 'h': /* Print help and exit. */ usage (ofile); case 'o': if ((ofile = fopen (cygpath (optarg, NULL), "wb")) == NULL) error (1, "can't open %s", optarg); #ifdef F_SETFD (void) fcntl (fileno (ofile), F_SETFD, 0); #endif break; case 'p': pid = strtoul (optarg, NULL, 10); break; case 's': samplerate = strtoul (optarg, NULL, 10); if (samplerate < 1 || samplerate > 1000) error (0, "sample rate must be between 1 and 1000 inclusive"); break; case 'v': verbose ^= 1; verbose |= debugging; // debugging turns on verbose too break; case 'V': /* Print version info and exit. */ print_version (); case 'w': new_window ^= 1; break; default: note ("Try `%s --help' for more information.\n", pgm); exit (1); } if (pid && argv[optind]) error (0, "cannot provide both a command line and a process id"); if (!pid && !argv[optind]) error (0, "must provide either a command line or a process id"); /* Honor user-supplied gmon file name prefix, if available. */ ptr = getenv ("GMON_OUT_PREFIX"); if (ptr && strlen (ptr) > 0) prefix = ptr; drive_map = (void *) cygwin_internal (CW_ALLOC_DRIVE_MAP); ret = doprofile (ofile, pid, argv + optind); if (drive_map) cygwin_internal (CW_FREE_DRIVE_MAP, drive_map); if (ofile && ofile != stdout) fclose (ofile); return (ret); }