| /* profil.c -- win32 profil.c equivalent |
| |
| 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. */ |
| |
| /* |
| * This file is taken from Cygwin distribution. Please keep it in sync. |
| * The differences should be within __MINGW32__ guard. |
| */ |
| |
| #ifndef WIN32_LEAN_AND_MEAN |
| #define WIN32_LEAN_AND_MEAN |
| #endif |
| #include <windows.h> |
| #include <stdio.h> |
| #include <sys/cygwin.h> |
| #include <sys/types.h> |
| #include <errno.h> |
| #include <pthread.h> |
| #include "profil.h" |
| |
| #define SLEEPTIME (1000 / PROF_HZ) |
| |
| /* global profinfo for profil() call */ |
| static struct profinfo prof; |
| |
| /* Get the pc for thread THR */ |
| |
| static size_t |
| get_thrpc (HANDLE thr) |
| { |
| CONTEXT ctx; |
| size_t pc; |
| int res; |
| |
| res = SuspendThread (thr); |
| if (res == -1) |
| return (size_t) - 1; |
| ctx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; |
| pc = (size_t) - 1; |
| if (GetThreadContext (thr, &ctx)) { |
| #ifndef _WIN64 |
| pc = ctx.Eip; |
| #else |
| pc = ctx.Rip; |
| #endif |
| } |
| ResumeThread (thr); |
| return pc; |
| } |
| |
| /* Display cell of profile buffer */ |
| #if 0 |
| static void |
| print_prof (struct profinfo *p) |
| { |
| printf ("profthr %x\ttarget thr %x\n", p->profthr, p->targthr); |
| printf ("pc: %x - %x\n", p->lowpc, p->highpc); |
| printf ("scale: %x\n", p->scale); |
| return; |
| } |
| #endif |
| |
| /* Every time we wake up sample the main thread's pc to hash into the cell |
| in the profile buffer ARG. Then all other pthreads' pc's are sampled. */ |
| |
| static void |
| profthr_byhandle (HANDLE thr) |
| { |
| size_t pc; |
| |
| /* Sample the pc of the thread indicated by thr; bail if anything amiss. */ |
| if (thr == INVALID_HANDLE_VALUE) |
| return; |
| pc = get_thrpc (thr); |
| if (pc == -1) |
| return; |
| |
| /* Code assumes there's only one profinfo in play: the static prof above. */ |
| if (pc >= prof.lowpc && pc < prof.highpc) |
| { |
| size_t idx = PROFIDX (pc, prof.lowpc, prof.scale); |
| prof.counter[idx]++; |
| } |
| } |
| |
| static void CALLBACK |
| profthr_func (LPVOID arg) |
| { |
| struct profinfo *p = (struct profinfo *) arg; |
| |
| for (;;) |
| { |
| /* Record profiling sample for main thread. */ |
| profthr_byhandle (p->targthr); |
| |
| /* Record profiling samples for other pthreads, if any. */ |
| cygwin_internal (CW_CYGHEAP_PROFTHR_ALL, profthr_byhandle); |
| |
| #if 0 |
| print_prof (p); |
| #endif |
| /* Check quit condition, WAIT_OBJECT_0 or WAIT_TIMEOUT */ |
| if (WaitForSingleObject (p->quitevt, SLEEPTIME) == WAIT_OBJECT_0) |
| return; |
| } |
| } |
| |
| /* Stop profiling to the profiling buffer pointed to by P. */ |
| |
| static int |
| profile_off (struct profinfo *p) |
| { |
| if (p->profthr) |
| { |
| SignalObjectAndWait (p->quitevt, p->profthr, INFINITE, FALSE); |
| CloseHandle (p->quitevt); |
| CloseHandle (p->profthr); |
| } |
| if (p->targthr) |
| CloseHandle (p->targthr); |
| p->targthr = 0; |
| return 0; |
| } |
| |
| /* Create a timer thread and pass it a pointer P to the profiling buffer. */ |
| |
| static int |
| profile_on (struct profinfo *p) |
| { |
| DWORD thrid; |
| |
| /* get handle for this thread */ |
| if (!DuplicateHandle (GetCurrentProcess (), GetCurrentThread (), |
| GetCurrentProcess (), &p->targthr, 0, FALSE, |
| DUPLICATE_SAME_ACCESS)) |
| { |
| p->targthr = 0; |
| errno = ESRCH; |
| return -1; |
| } |
| |
| p->quitevt = CreateEvent (NULL, TRUE, FALSE, NULL); |
| |
| if (!p->quitevt) |
| { |
| CloseHandle (p->targthr); |
| p->targthr = 0; |
| errno = EAGAIN; |
| return -1; |
| } |
| |
| p->profthr = CreateThread (0, 0, (DWORD (WINAPI *)(LPVOID)) profthr_func, |
| (void *) p, 0, &thrid); |
| |
| if (!p->profthr) |
| { |
| CloseHandle (p->targthr); |
| CloseHandle (p->quitevt); |
| p->targthr = 0; |
| errno = EAGAIN; |
| return -1; |
| } |
| |
| /* Set profiler thread priority to highest to be sure that it gets the |
| processor as soon it requests it (i.e. when the Sleep terminates) to get |
| the next data sample as quickly as possible. */ |
| |
| SetThreadPriority (p->profthr, THREAD_PRIORITY_TIME_CRITICAL); |
| |
| return 0; |
| } |
| |
| /* |
| * Restart profiling in child after fork. |
| * |
| * The profiling control info in prof needs to be selectively updated. |
| * Items counter, lowpc, highpc, and scale are correct as-is. But items |
| * targthr, profthr, and quitevt need updating because these copied HANDLE |
| * values are only valid in parent process. We also zero out the sample |
| * buffer so that samples aren't double-counted if multiple gmon.out files |
| * are aggregated. We calculate buffer's size from other data in prof. |
| */ |
| static void |
| profile_child (void) |
| { |
| /* Bail if this was a fork after profiling turned off or was never on. */ |
| if (!prof.targthr) |
| return; |
| |
| /* Figure out how big the sample buffer is and zero it out. */ |
| size_t size = PROFIDX (prof.highpc, prof.lowpc, prof.scale) << 1; |
| memset ((char *) prof.counter, 0, size); |
| |
| /* Replace stale HANDLEs in prof and create profiling thread. */ |
| profile_on (&prof); |
| } |
| |
| /* |
| * Start or stop profiling. |
| * |
| * Profiling data goes into the SAMPLES buffer of size SIZE (which is treated |
| * as an array of uint16_t of size SIZE/2). |
| * |
| * Each bin represents a range of pc addresses from OFFSET. The number |
| * of pc addresses in a bin depends on SCALE. (A scale of 65536 maps |
| * each bin to two addresses, A scale of 32768 maps each bin to 4 addresses, |
| * a scale of 1 maps each bin to 128k addreses). Scale may be 1 - 65536, |
| * or zero to turn off profiling. |
| */ |
| int |
| profile_ctl (struct profinfo * p, char *samples, size_t size, |
| size_t offset, uint32_t scale) |
| { |
| size_t maxbin; |
| |
| if (scale > 65536) |
| { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| profile_off (p); |
| if (scale) |
| { |
| memset (samples, 0, size); |
| memset (p, 0, sizeof *p); |
| maxbin = size >> 1; |
| prof.counter = (uint16_t *) samples; |
| prof.lowpc = offset; |
| prof.highpc = PROFADDR (maxbin, offset, scale); |
| prof.scale = scale; |
| |
| /* Set up callback to restart profiling in child after fork. */ |
| pthread_atfork (NULL, NULL, profile_child); |
| |
| return profile_on (p); |
| } |
| return 0; |
| } |
| |
| /* Equivalent to unix profil() |
| Every SLEEPTIME interval, the user's program counter (PC) is examined: |
| offset is subtracted and the result is multiplied by scale. |
| The word pointed to by this address is incremented. Buf is unused. */ |
| |
| int |
| profil (char *samples, size_t size, size_t offset, uint32_t scale) |
| { |
| return profile_ctl (&prof, samples, size, offset, scale); |
| } |
| |