newlib-cygwin/winsup/utils/profiler.cc

1094 lines
29 KiB
C++

/*
profiler.cc
Periodically samples IP of a process and its DLLs; writes gprof data files.
Written by Mark Geisert <mark@maxrnd.com>, 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 <winternl.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <io.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/cygwin.h>
#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] <command-line>\n\
or: %s [OPTIONS] -p <pid>\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);
}