256 lines
6.2 KiB
C
256 lines
6.2 KiB
C
|
/* 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);
|
||
|
}
|
||
|
|