1640 lines
50 KiB
C
1640 lines
50 KiB
C
|
/*++ BUILD Version: 0001 // Increment this if a change has global effects
|
|||
|
|
|||
|
Copyright (c) 1994-1997 Microsoft Corporation
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
utils.c
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
Utility functions used by the performance library functions
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
Russ Blake 11/15/91
|
|||
|
|
|||
|
Revision History:
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
#define UNICODE
|
|||
|
//
|
|||
|
// Include files
|
|||
|
//
|
|||
|
#pragma warning(disable:4306)
|
|||
|
#include <nt.h>
|
|||
|
#include <ntrtl.h>
|
|||
|
#include <nturtl.h>
|
|||
|
#include <windows.h>
|
|||
|
#include <winperf.h>
|
|||
|
#include <prflbmsg.h>
|
|||
|
#include <regrpc.h>
|
|||
|
#include "ntconreg.h"
|
|||
|
#include "perflib.h"
|
|||
|
#pragma warning(default:4306)
|
|||
|
|
|||
|
// test for delimiter, end of line and non-digit characters
|
|||
|
// used by IsNumberInUnicodeList routine
|
|||
|
//
|
|||
|
#define DIGIT 1
|
|||
|
#define DELIMITER 2
|
|||
|
#define INVALID 3
|
|||
|
|
|||
|
#define EvalThisChar(c,d) ( \
|
|||
|
(c == d) ? DELIMITER : \
|
|||
|
(c == 0) ? DELIMITER : \
|
|||
|
(c < '0') ? INVALID : \
|
|||
|
(c > '9') ? INVALID : \
|
|||
|
DIGIT)
|
|||
|
|
|||
|
#define MAX_KEYWORD_LEN (sizeof (ADDHELP_STRING) / sizeof(WCHAR))
|
|||
|
const WCHAR GLOBAL_STRING[] = L"GLOBAL";
|
|||
|
const WCHAR FOREIGN_STRING[] = L"FOREIGN";
|
|||
|
const WCHAR COSTLY_STRING[] = L"COSTLY";
|
|||
|
const WCHAR COUNTER_STRING[] = L"COUNTER";
|
|||
|
const WCHAR HELP_STRING[] = L"EXPLAIN";
|
|||
|
const WCHAR HELP_STRING2[] = L"HELP";
|
|||
|
const WCHAR ADDCOUNTER_STRING[] = L"ADDCOUNTER";
|
|||
|
const WCHAR ADDHELP_STRING[] = L"ADDEXPLAIN";
|
|||
|
const WCHAR ONLY_STRING[] = L"ONLY";
|
|||
|
const WCHAR DisablePerformanceCounters[] = L"Disable Performance Counters";
|
|||
|
|
|||
|
// minimum length to hold a value name understood by Perflib
|
|||
|
|
|||
|
const DWORD VALUE_NAME_LENGTH = ((sizeof(COSTLY_STRING) * sizeof(WCHAR)) + sizeof(UNICODE_NULL));
|
|||
|
|
|||
|
#define PL_TIMER_START_EVENT 0
|
|||
|
#define PL_TIMER_EXIT_EVENT 1
|
|||
|
#define PL_TIMER_NUM_OBJECTS 2
|
|||
|
|
|||
|
static HANDLE hTimerHandles[PL_TIMER_NUM_OBJECTS] = {NULL,NULL};
|
|||
|
|
|||
|
static HANDLE hTimerDataMutex = NULL;
|
|||
|
static HANDLE hPerflibTimingThread = NULL;
|
|||
|
static LPOPEN_PROC_WAIT_INFO pTimerItemListHead = NULL;
|
|||
|
#define PERFLIB_TIMER_INTERVAL 200 // 200 ms Timer
|
|||
|
#define PERFLIB_TIMEOUT_COUNT 64
|
|||
|
|
|||
|
extern HANDLE hEventLog;
|
|||
|
|
|||
|
#ifdef DBG
|
|||
|
#include <stdio.h> // for _vsnprintf
|
|||
|
#define DEBUG_BUFFER_LENGTH MAX_PATH*2
|
|||
|
|
|||
|
ULONG PerfLibDebug = 0;
|
|||
|
UCHAR PerfLibDebugBuffer[DEBUG_BUFFER_LENGTH];
|
|||
|
#endif
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Perflib functions:
|
|||
|
//
|
|||
|
NTSTATUS
|
|||
|
GetPerflibKeyValue (
|
|||
|
LPCWSTR szItem,
|
|||
|
DWORD dwRegType,
|
|||
|
DWORD dwMaxSize, // ... of pReturnBuffer in bytes
|
|||
|
LPVOID pReturnBuffer,
|
|||
|
DWORD dwDefaultSize, // ... of pDefault in bytes
|
|||
|
LPVOID pDefault
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
read and return the current value of the specified value
|
|||
|
under the Perflib registry key. If unable to read the value
|
|||
|
return the default value from the argument list.
|
|||
|
|
|||
|
the value is returned in the pReturnBuffer.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
HKEY hPerflibKey;
|
|||
|
OBJECT_ATTRIBUTES Obja;
|
|||
|
NTSTATUS Status;
|
|||
|
UNICODE_STRING PerflibSubKeyString;
|
|||
|
UNICODE_STRING ValueNameString;
|
|||
|
LONG lReturn = STATUS_SUCCESS;
|
|||
|
PKEY_VALUE_PARTIAL_INFORMATION pValueInformation, pTemp;
|
|||
|
ULONG ValueBufferLength;
|
|||
|
ULONG ResultLength;
|
|||
|
BOOL bUseDefault = TRUE;
|
|||
|
|
|||
|
// initialize UNICODE_STRING structures used in this function
|
|||
|
|
|||
|
RtlInitUnicodeString (
|
|||
|
&PerflibSubKeyString,
|
|||
|
(LPCWSTR)L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib");
|
|||
|
|
|||
|
RtlInitUnicodeString (
|
|||
|
&ValueNameString,
|
|||
|
(LPWSTR)szItem);
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the OBJECT_ATTRIBUTES structure and open the key.
|
|||
|
//
|
|||
|
InitializeObjectAttributes(
|
|||
|
&Obja,
|
|||
|
&PerflibSubKeyString,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
NULL,
|
|||
|
NULL
|
|||
|
);
|
|||
|
|
|||
|
Status = NtOpenKey(
|
|||
|
&hPerflibKey,
|
|||
|
KEY_READ,
|
|||
|
&Obja
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS( Status )) {
|
|||
|
// read value of desired entry
|
|||
|
|
|||
|
ValueBufferLength = ResultLength = 1024;
|
|||
|
pValueInformation = ALLOCMEM(ResultLength);
|
|||
|
|
|||
|
if (pValueInformation != NULL) {
|
|||
|
while ( (Status = NtQueryValueKey(hPerflibKey,
|
|||
|
&ValueNameString,
|
|||
|
KeyValuePartialInformation,
|
|||
|
pValueInformation,
|
|||
|
ValueBufferLength,
|
|||
|
&ResultLength))
|
|||
|
== STATUS_BUFFER_OVERFLOW ) {
|
|||
|
|
|||
|
pTemp = pValueInformation;
|
|||
|
pValueInformation = REALLOCMEM(pValueInformation,
|
|||
|
ResultLength);
|
|||
|
if ( pValueInformation == NULL) {
|
|||
|
FREEMEM(pTemp);
|
|||
|
Status = STATUS_NO_MEMORY;
|
|||
|
break;
|
|||
|
} else {
|
|||
|
ValueBufferLength = ResultLength;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (NT_SUCCESS(Status)) {
|
|||
|
// check to see if it's the desired type
|
|||
|
if (pValueInformation->Type == dwRegType) {
|
|||
|
// see if it will fit
|
|||
|
if (pValueInformation->DataLength <= dwMaxSize) {
|
|||
|
memcpy (pReturnBuffer, &pValueInformation->Data[0],
|
|||
|
pValueInformation->DataLength);
|
|||
|
bUseDefault = FALSE;
|
|||
|
lReturn = STATUS_SUCCESS;
|
|||
|
}
|
|||
|
}
|
|||
|
} else {
|
|||
|
// return the default value
|
|||
|
lReturn = Status;
|
|||
|
}
|
|||
|
// release temp buffer
|
|||
|
if (pValueInformation) {
|
|||
|
FREEMEM (pValueInformation);
|
|||
|
}
|
|||
|
} else {
|
|||
|
// unable to allocate memory for this operation so
|
|||
|
// just return the default value
|
|||
|
}
|
|||
|
// close the registry key
|
|||
|
NtClose(hPerflibKey);
|
|||
|
} else {
|
|||
|
// return default value
|
|||
|
}
|
|||
|
|
|||
|
if (bUseDefault) {
|
|||
|
memcpy (pReturnBuffer, pDefault, dwDefaultSize);
|
|||
|
lReturn = STATUS_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
return lReturn;
|
|||
|
}
|
|||
|
|
|||
|
BOOL
|
|||
|
MatchString (
|
|||
|
IN LPCWSTR lpValueArg,
|
|||
|
IN LPCWSTR lpNameArg
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
MatchString
|
|||
|
|
|||
|
return TRUE if lpName is in lpValue. Otherwise return FALSE
|
|||
|
|
|||
|
Arguments
|
|||
|
|
|||
|
IN lpValue
|
|||
|
string passed to PerfRegQuery Value for processing
|
|||
|
|
|||
|
IN lpName
|
|||
|
string for one of the keyword names
|
|||
|
|
|||
|
Return TRUE | FALSE
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
BOOL bFound = TRUE; // assume found until contradicted
|
|||
|
LPWSTR lpValue = (LPWSTR)lpValueArg;
|
|||
|
LPWSTR lpName = (LPWSTR)lpNameArg;
|
|||
|
|
|||
|
// check to the length of the shortest string
|
|||
|
|
|||
|
while ((*lpValue != 0) && (*lpName != 0)) {
|
|||
|
if (*lpValue++ != *lpName++) {
|
|||
|
bFound = FALSE; // no match
|
|||
|
break; // bail out now
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return (bFound);
|
|||
|
}
|
|||
|
|
|||
|
DWORD
|
|||
|
GetQueryType (
|
|||
|
IN LPWSTR lpValue
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
GetQueryType
|
|||
|
|
|||
|
returns the type of query described in the lpValue string so that
|
|||
|
the appropriate processing method may be used
|
|||
|
|
|||
|
Arguments
|
|||
|
|
|||
|
IN lpValue
|
|||
|
string passed to PerfRegQuery Value for processing
|
|||
|
|
|||
|
Return Value
|
|||
|
|
|||
|
QUERY_GLOBAL
|
|||
|
if lpValue == 0 (null pointer)
|
|||
|
lpValue == pointer to Null string
|
|||
|
lpValue == pointer to "Global" string
|
|||
|
|
|||
|
QUERY_FOREIGN
|
|||
|
if lpValue == pointer to "Foriegn" string
|
|||
|
|
|||
|
QUERY_COSTLY
|
|||
|
if lpValue == pointer to "Costly" string
|
|||
|
|
|||
|
QUERY_COUNTER
|
|||
|
if lpValue == pointer to "Counter" string
|
|||
|
|
|||
|
QUERY_HELP
|
|||
|
if lpValue == pointer to "Explain" string
|
|||
|
|
|||
|
QUERY_ADDCOUNTER
|
|||
|
if lpValue == pointer to "Addcounter" string
|
|||
|
|
|||
|
QUERY_ADDHELP
|
|||
|
if lpValue == pointer to "Addexplain" string
|
|||
|
|
|||
|
otherwise:
|
|||
|
|
|||
|
QUERY_ITEMS
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
WCHAR LocalBuff[MAX_KEYWORD_LEN+1];
|
|||
|
WORD i;
|
|||
|
|
|||
|
if (lpValue == 0 || *lpValue == 0)
|
|||
|
return QUERY_GLOBAL;
|
|||
|
|
|||
|
// convert the input string to Upper case before matching
|
|||
|
for (i=0; i < MAX_KEYWORD_LEN; i++) {
|
|||
|
if (*lpValue == TEXT(' ') || *lpValue == TEXT('\0')) {
|
|||
|
break;
|
|||
|
}
|
|||
|
LocalBuff[i] = *lpValue ;
|
|||
|
if (*lpValue >= TEXT('a') && *lpValue <= TEXT('z')) {
|
|||
|
LocalBuff[i] = LocalBuff[i] - TEXT('a') + TEXT('A');
|
|||
|
}
|
|||
|
lpValue++ ;
|
|||
|
}
|
|||
|
LocalBuff[i] = TEXT('\0');
|
|||
|
|
|||
|
// check for "Global" request
|
|||
|
if (MatchString (LocalBuff, GLOBAL_STRING))
|
|||
|
return QUERY_GLOBAL ;
|
|||
|
|
|||
|
// check for "Foreign" request
|
|||
|
if (MatchString (LocalBuff, FOREIGN_STRING))
|
|||
|
return QUERY_FOREIGN ;
|
|||
|
|
|||
|
// check for "Costly" request
|
|||
|
if (MatchString (LocalBuff, COSTLY_STRING))
|
|||
|
return QUERY_COSTLY;
|
|||
|
|
|||
|
// check for "Counter" request
|
|||
|
if (MatchString (LocalBuff, COUNTER_STRING))
|
|||
|
return QUERY_COUNTER;
|
|||
|
|
|||
|
// check for "Help" request
|
|||
|
if (MatchString (LocalBuff, HELP_STRING))
|
|||
|
return QUERY_HELP;
|
|||
|
|
|||
|
if (MatchString (LocalBuff, HELP_STRING2))
|
|||
|
return QUERY_HELP;
|
|||
|
|
|||
|
// check for "AddCounter" request
|
|||
|
if (MatchString (LocalBuff, ADDCOUNTER_STRING))
|
|||
|
return QUERY_ADDCOUNTER;
|
|||
|
|
|||
|
// check for "AddHelp" request
|
|||
|
if (MatchString (LocalBuff, ADDHELP_STRING))
|
|||
|
return QUERY_ADDHELP;
|
|||
|
|
|||
|
// None of the above, then it must be an item list
|
|||
|
return QUERY_ITEMS;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
DWORD
|
|||
|
GetNextNumberFromList (
|
|||
|
IN LPWSTR szStartChar,
|
|||
|
IN LPWSTR *szNextChar
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Reads a character string from the szStartChar to the next
|
|||
|
delimiting space character or the end of the string and returns
|
|||
|
the value of the decimal number found. If no valid number is found
|
|||
|
then 0 is returned. The pointer to the next character in the
|
|||
|
string is returned in the szNextChar parameter. If the character
|
|||
|
referenced by this pointer is 0, then the end of the string has
|
|||
|
been reached.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
DWORD dwThisNumber = 0;
|
|||
|
WCHAR *pwcThisChar = szStartChar;
|
|||
|
WCHAR wcDelimiter = L' ';
|
|||
|
BOOL bValidNumber = FALSE;
|
|||
|
|
|||
|
if (szStartChar != 0) {
|
|||
|
do {
|
|||
|
switch (EvalThisChar (*pwcThisChar, wcDelimiter)) {
|
|||
|
case DIGIT:
|
|||
|
// if this is the first digit after a delimiter, then
|
|||
|
// set flags to start computing the new number
|
|||
|
bValidNumber = TRUE;
|
|||
|
dwThisNumber *= 10;
|
|||
|
dwThisNumber += (*pwcThisChar - (WCHAR)'0');
|
|||
|
break;
|
|||
|
|
|||
|
case DELIMITER:
|
|||
|
// a delimter is either the delimiter character or the
|
|||
|
// end of the string ('\0') if when the delimiter has been
|
|||
|
// reached a valid number was found, then return it
|
|||
|
//
|
|||
|
if (bValidNumber || (*pwcThisChar == 0)) {
|
|||
|
*szNextChar = pwcThisChar;
|
|||
|
return dwThisNumber;
|
|||
|
} else {
|
|||
|
// continue until a non-delimiter char or the
|
|||
|
// end of the file is found
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case INVALID:
|
|||
|
// if an invalid character was encountered, ignore all
|
|||
|
// characters up to the next delimiter and then start fresh.
|
|||
|
// the invalid number is not compared.
|
|||
|
bValidNumber = FALSE;
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
break;
|
|||
|
|
|||
|
}
|
|||
|
pwcThisChar++;
|
|||
|
} while (pwcThisChar != NULL); // always TRUE - avoid W4 warning
|
|||
|
return 0;
|
|||
|
} else {
|
|||
|
*szNextChar = szStartChar;
|
|||
|
return 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
BOOL
|
|||
|
IsNumberInUnicodeList (
|
|||
|
IN DWORD dwNumber,
|
|||
|
IN LPWSTR lpwszUnicodeList
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
IsNumberInUnicodeList
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
IN dwNumber
|
|||
|
DWORD number to find in list
|
|||
|
|
|||
|
IN lpwszUnicodeList
|
|||
|
Null terminated, Space delimited list of decimal numbers
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
TRUE:
|
|||
|
dwNumber was found in the list of unicode number strings
|
|||
|
|
|||
|
FALSE:
|
|||
|
dwNumber was not found in the list.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
DWORD dwThisNumber;
|
|||
|
WCHAR *pwcThisChar;
|
|||
|
|
|||
|
if (lpwszUnicodeList == 0) return FALSE; // null pointer, # not founde
|
|||
|
|
|||
|
pwcThisChar = lpwszUnicodeList;
|
|||
|
dwThisNumber = 0;
|
|||
|
|
|||
|
while (*pwcThisChar != 0) {
|
|||
|
dwThisNumber = GetNextNumberFromList (
|
|||
|
pwcThisChar, &pwcThisChar);
|
|||
|
if (dwNumber == dwThisNumber) return TRUE;
|
|||
|
}
|
|||
|
// if here, then the number wasn't found
|
|||
|
return FALSE;
|
|||
|
|
|||
|
} // IsNumberInUnicodeList
|
|||
|
|
|||
|
BOOL
|
|||
|
MonBuildPerfDataBlock(
|
|||
|
PERF_DATA_BLOCK *pBuffer,
|
|||
|
PVOID *pBufferNext,
|
|||
|
DWORD NumObjectTypes,
|
|||
|
DWORD DefaultObject
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
MonBuildPerfDataBlock - build the PERF_DATA_BLOCK structure
|
|||
|
|
|||
|
Inputs:
|
|||
|
|
|||
|
pBuffer - where the data block should be placed
|
|||
|
|
|||
|
pBufferNext - where pointer to next byte of data block
|
|||
|
is to begin; DWORD aligned
|
|||
|
|
|||
|
NumObjectTypes - number of types of objects being reported
|
|||
|
|
|||
|
DefaultObject - object to display by default when
|
|||
|
this system is selected; this is the
|
|||
|
object type title index
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
// Initialize Signature and version ID for this data structure
|
|||
|
|
|||
|
pBuffer->Signature[0] = L'P';
|
|||
|
pBuffer->Signature[1] = L'E';
|
|||
|
pBuffer->Signature[2] = L'R';
|
|||
|
pBuffer->Signature[3] = L'F';
|
|||
|
|
|||
|
pBuffer->LittleEndian = TRUE;
|
|||
|
|
|||
|
pBuffer->Version = PERF_DATA_VERSION;
|
|||
|
pBuffer->Revision = PERF_DATA_REVISION;
|
|||
|
|
|||
|
//
|
|||
|
// The next field will be filled in at the end when the length
|
|||
|
// of the return data is known
|
|||
|
//
|
|||
|
|
|||
|
pBuffer->TotalByteLength = 0;
|
|||
|
|
|||
|
pBuffer->NumObjectTypes = NumObjectTypes;
|
|||
|
pBuffer->DefaultObject = DefaultObject;
|
|||
|
GetSystemTime(&pBuffer->SystemTime);
|
|||
|
NtQueryPerformanceCounter(&pBuffer->PerfTime,&pBuffer->PerfFreq);
|
|||
|
GetSystemTimeAsFileTime ((FILETIME *)&pBuffer->PerfTime100nSec.QuadPart);
|
|||
|
|
|||
|
if ( ComputerNameLength ) {
|
|||
|
|
|||
|
// There is a Computer name: i.e., the network is installed
|
|||
|
|
|||
|
pBuffer->SystemNameLength = ComputerNameLength;
|
|||
|
pBuffer->SystemNameOffset = sizeof(PERF_DATA_BLOCK);
|
|||
|
RtlMoveMemory(&pBuffer[1],
|
|||
|
pComputerName,
|
|||
|
ComputerNameLength);
|
|||
|
*pBufferNext = (PVOID) ((PCHAR) &pBuffer[1] +
|
|||
|
QWORD_MULTIPLE(ComputerNameLength));
|
|||
|
pBuffer->HeaderLength = (DWORD)((PCHAR) *pBufferNext - (PCHAR) pBuffer);
|
|||
|
} else {
|
|||
|
|
|||
|
// Member of Computers Anonymous
|
|||
|
|
|||
|
pBuffer->SystemNameLength = 0;
|
|||
|
pBuffer->SystemNameOffset = 0;
|
|||
|
*pBufferNext = &pBuffer[1];
|
|||
|
pBuffer->HeaderLength = sizeof(PERF_DATA_BLOCK);
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Timer functions
|
|||
|
//
|
|||
|
DWORD
|
|||
|
PerflibTimerFunction (
|
|||
|
LPDWORD dwArg
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
PerflibTimerFunction
|
|||
|
|
|||
|
Timing thread used to write an event log message if the timer expires.
|
|||
|
|
|||
|
This thread runs until the Exit event is set or the wait for the
|
|||
|
Exit event times out.
|
|||
|
|
|||
|
While the start event is set, then the timer checks the current events
|
|||
|
to be timed and reports on any that have expired. It then sleeps for
|
|||
|
the duration of the timing interval after which it checks the status
|
|||
|
of the start & exit events to begin the next cycle.
|
|||
|
|
|||
|
The timing events are added and deleted from the list only by the
|
|||
|
StartPerflibFunctionTimer and KillPerflibFunctionTimer functions.
|
|||
|
|
|||
|
Arguments
|
|||
|
|
|||
|
dwArg -- Not Used
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus = STATUS_SUCCESS;
|
|||
|
BOOL bKeepTiming = TRUE;
|
|||
|
LPOPEN_PROC_WAIT_INFO pLocalInfo;
|
|||
|
LPWSTR szMessageArray[2];
|
|||
|
LARGE_INTEGER liWaitTime;
|
|||
|
|
|||
|
UNREFERENCED_PARAMETER (dwArg);
|
|||
|
|
|||
|
// KdPrint (("\nPERFLIB: Entering Timing Thread: PID: %d, TID: %d",
|
|||
|
// GetCurrentProcessId(), GetCurrentThreadId()));
|
|||
|
|
|||
|
TRACE((WINPERF_DBG_TRACE_INFO),
|
|||
|
(& PerflibGuid,
|
|||
|
__LINE__,
|
|||
|
PERF_TIMERFUNCTION,
|
|||
|
0,
|
|||
|
STATUS_SUCCESS,
|
|||
|
NULL));
|
|||
|
|
|||
|
while (bKeepTiming) {
|
|||
|
liWaitTime.QuadPart =
|
|||
|
MakeTimeOutValue((PERFLIB_TIMING_THREAD_TIMEOUT));
|
|||
|
// wait for either the start or exit event flags to be set
|
|||
|
NtStatus = NtWaitForMultipleObjects (
|
|||
|
PL_TIMER_NUM_OBJECTS,
|
|||
|
&hTimerHandles[0],
|
|||
|
WaitAny, //wait for either one to be set
|
|||
|
FALSE, // not alertable
|
|||
|
&liWaitTime);
|
|||
|
|
|||
|
if ((NtStatus != STATUS_TIMEOUT) &&
|
|||
|
(NtStatus <= STATUS_WAIT_3)) {
|
|||
|
if ((NtStatus - STATUS_WAIT_0) == PL_TIMER_EXIT_EVENT ) {
|
|||
|
// KdPrint (("\nPERFLIB: Timing Thread received Exit Event (1): PID: %d, TID: %d",
|
|||
|
// GetCurrentProcessId(), GetCurrentThreadId()));
|
|||
|
|
|||
|
// then that's all
|
|||
|
bKeepTiming = FALSE;
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
break;
|
|||
|
} else if ((NtStatus - STATUS_WAIT_0) == PL_TIMER_START_EVENT) {
|
|||
|
// KdPrint (("\nPERFLIB: Timing Thread received Start Event: PID: %d, TID: %d",
|
|||
|
// GetCurrentProcessId(), GetCurrentThreadId()));
|
|||
|
// then the timer is running so wait the interval period
|
|||
|
// wait on exit event here to prevent hanging
|
|||
|
liWaitTime.QuadPart =
|
|||
|
MakeTimeOutValue((PERFLIB_TIMER_INTERVAL));
|
|||
|
NtStatus = NtWaitForSingleObject (
|
|||
|
hTimerHandles[PL_TIMER_EXIT_EVENT],
|
|||
|
FALSE,
|
|||
|
&liWaitTime);
|
|||
|
|
|||
|
if (NtStatus == STATUS_TIMEOUT) {
|
|||
|
// then the wait time expired without being told
|
|||
|
// to terminate the thread so
|
|||
|
// now evaluate the list of timed events
|
|||
|
// lock the data mutex
|
|||
|
DWORD dwTimeOut = 0;
|
|||
|
|
|||
|
// KdPrint (("\nPERFLIB: Timing Thread Evaluating Entries: PID: %d, TID: %d",
|
|||
|
// GetCurrentProcessId(), GetCurrentThreadId()));
|
|||
|
|
|||
|
liWaitTime.QuadPart =
|
|||
|
MakeTimeOutValue((PERFLIB_TIMER_INTERVAL * 2));
|
|||
|
|
|||
|
NtStatus = STATUS_TIMEOUT;
|
|||
|
while ( NtStatus == STATUS_TIMEOUT
|
|||
|
&& dwTimeOut < PERFLIB_TIMEOUT_COUNT) {
|
|||
|
NtStatus = NtWaitForSingleObject (
|
|||
|
hTimerDataMutex,
|
|||
|
FALSE,
|
|||
|
& liWaitTime);
|
|||
|
if (NtStatus == STATUS_TIMEOUT) {
|
|||
|
dwTimeOut ++;
|
|||
|
DebugPrint((2, "\nPERFLIB:NtWaitForSingleObject(TimerDataMutex,%d) time out for the %dth time. PID: %d, TID: %d",
|
|||
|
liWaitTime, dwTimeOut,
|
|||
|
GetCurrentProcessId(),
|
|||
|
GetCurrentThreadId()));
|
|||
|
TRACE((WINPERF_DBG_TRACE_WARNING),
|
|||
|
(& PerflibGuid,
|
|||
|
__LINE__,
|
|||
|
PERF_TIMERFUNCTION,
|
|||
|
0,
|
|||
|
STATUS_TIMEOUT,
|
|||
|
& dwTimeOut, sizeof(dwTimeOut),
|
|||
|
NULL));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (NtStatus != STATUS_WAIT_0) {
|
|||
|
// cannot grab hTimerDataMutex, there is no guarantee
|
|||
|
// that this is the exclusive one to work on
|
|||
|
// pTimerItemListHead list, so just bail out.
|
|||
|
//
|
|||
|
bKeepTiming = FALSE;
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
TRACE((WINPERF_DBG_TRACE_WARNING),
|
|||
|
(& PerflibGuid,
|
|||
|
__LINE__,
|
|||
|
PERF_TIMERFUNCTION,
|
|||
|
0,
|
|||
|
NtStatus,
|
|||
|
NULL));
|
|||
|
break;
|
|||
|
}
|
|||
|
else {
|
|||
|
for (pLocalInfo = pTimerItemListHead;
|
|||
|
pLocalInfo != NULL;
|
|||
|
pLocalInfo = pLocalInfo->pNext) {
|
|||
|
|
|||
|
// KdPrint (("\nPERFLIB: Timing Thread Entry %d. count %d: PID: %d, TID: %d",
|
|||
|
// (DWORD)pLocalInfo, pLocalInfo->dwWaitTime,
|
|||
|
// GetCurrentProcessId(), GetCurrentThreadId()));
|
|||
|
|
|||
|
if (pLocalInfo->dwWaitTime > 0) {
|
|||
|
if (pLocalInfo->dwWaitTime == 1) {
|
|||
|
// then this is the last interval so log error
|
|||
|
// if this DLL hasn't already been disabled
|
|||
|
|
|||
|
szMessageArray[0] = pLocalInfo->szServiceName;
|
|||
|
szMessageArray[1] = pLocalInfo->szLibraryName;
|
|||
|
|
|||
|
ReportEvent (hEventLog,
|
|||
|
EVENTLOG_ERROR_TYPE, // error type
|
|||
|
0, // category (not used)
|
|||
|
(DWORD)pLocalInfo->dwEventMsg, // event,
|
|||
|
NULL, // SID (not used),
|
|||
|
2, // number of strings
|
|||
|
0, // sizeof raw data
|
|||
|
szMessageArray, // message text array
|
|||
|
NULL); // raw data
|
|||
|
|
|||
|
if (pLocalInfo->pData != NULL) {
|
|||
|
if (lPerflibConfigFlags & PLCF_ENABLE_TIMEOUT_DISABLE) {
|
|||
|
if (!(((PEXT_OBJECT)pLocalInfo->pData)->dwFlags & PERF_EO_DISABLED)) {
|
|||
|
// then pData is an extensible counter data block
|
|||
|
// disable the ext. counter
|
|||
|
DisablePerfLibrary ((PEXT_OBJECT)pLocalInfo->pData);
|
|||
|
} // end if not already disabled
|
|||
|
} // end if disable DLL on Timeouts is enabled
|
|||
|
} // data is NULL so skip
|
|||
|
}
|
|||
|
pLocalInfo->dwWaitTime--;
|
|||
|
}
|
|||
|
}
|
|||
|
ReleaseMutex (hTimerDataMutex);
|
|||
|
}
|
|||
|
} else {
|
|||
|
// KdPrint (("\nPERFLIB: Timing Thread received Exit Event (2): PID: %d, TID: %d",
|
|||
|
// GetCurrentProcessId(), GetCurrentThreadId()));
|
|||
|
|
|||
|
// we've been told to exit so
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
bKeepTiming = FALSE;
|
|||
|
break;
|
|||
|
}
|
|||
|
} else {
|
|||
|
// some unexpected error was returned
|
|||
|
assert (FALSE);
|
|||
|
}
|
|||
|
} else {
|
|||
|
// KdPrint (("\nPERFLIB: Timing Thread Timed out: PID: %d, TID: %d",
|
|||
|
// GetCurrentProcessId(), GetCurrentThreadId()));
|
|||
|
// the wait timed out so it's time to go
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
bKeepTiming = FALSE;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// KdPrint (("\nPERFLIB: Leaving Timing Thread: PID: %d, TID: %d",
|
|||
|
// GetCurrentProcessId(), GetCurrentThreadId()));
|
|||
|
|
|||
|
return PerfpDosError(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
HANDLE
|
|||
|
StartPerflibFunctionTimer (
|
|||
|
IN LPOPEN_PROC_WAIT_INFO pInfo
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Starts a timing event by adding it to the list of timing events.
|
|||
|
If the timer thread is not running, then the is started as well.
|
|||
|
|
|||
|
If this is the first event in the list then the Start Event is
|
|||
|
set indicating that the timing thread can begin processing timing
|
|||
|
event(s).
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
LONG Status = ERROR_SUCCESS;
|
|||
|
LPOPEN_PROC_WAIT_INFO pLocalInfo = NULL;
|
|||
|
DWORD dwLibNameLen = 0;
|
|||
|
DWORD dwBufferLength = sizeof (OPEN_PROC_WAIT_INFO);
|
|||
|
LARGE_INTEGER liWaitTime;
|
|||
|
HANDLE hReturn = NULL;
|
|||
|
HANDLE hDataMutex;
|
|||
|
|
|||
|
if (pInfo == NULL) {
|
|||
|
// no required argument
|
|||
|
Status = ERROR_INVALID_PARAMETER;
|
|||
|
} else {
|
|||
|
// check on or create sync objects
|
|||
|
|
|||
|
// allocate timing events for the timing thread
|
|||
|
if (hTimerHandles[PL_TIMER_START_EVENT] == NULL) {
|
|||
|
// create the event as NOT signaled since we're not ready to start
|
|||
|
hTimerHandles[PL_TIMER_START_EVENT] = CreateEvent (NULL, TRUE, FALSE, NULL);
|
|||
|
if (hTimerHandles[PL_TIMER_START_EVENT] == NULL) {
|
|||
|
Status = GetLastError();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (hTimerHandles[PL_TIMER_EXIT_EVENT] == NULL) {
|
|||
|
hTimerHandles[PL_TIMER_EXIT_EVENT] = CreateEvent (NULL, TRUE, FALSE, NULL);
|
|||
|
if (hTimerHandles[PL_TIMER_EXIT_EVENT] == NULL) {
|
|||
|
Status = GetLastError();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// create data sync mutex if it hasn't already been created
|
|||
|
if (hTimerDataMutex == NULL) {
|
|||
|
hDataMutex = CreateMutex(NULL, FALSE, NULL);
|
|||
|
if (hDataMutex == NULL) {
|
|||
|
Status = GetLastError();
|
|||
|
}
|
|||
|
else {
|
|||
|
if (InterlockedCompareExchangePointer(& hTimerDataMutex,
|
|||
|
hDataMutex,
|
|||
|
NULL) != NULL) {
|
|||
|
CloseHandle(hDataMutex);
|
|||
|
hDataMutex = NULL;
|
|||
|
}
|
|||
|
else {
|
|||
|
hTimerDataMutex = hDataMutex;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (Status == ERROR_SUCCESS) {
|
|||
|
// continue creating timer entry
|
|||
|
if (hPerflibTimingThread != NULL) {
|
|||
|
// see if the handle is valid (i.e the thread is alive)
|
|||
|
Status = WaitForSingleObject (hPerflibTimingThread, 0);
|
|||
|
if (Status == WAIT_OBJECT_0) {
|
|||
|
// the thread has terminated so close the handle
|
|||
|
CloseHandle (hPerflibTimingThread);
|
|||
|
hPerflibTimingThread = NULL;
|
|||
|
Status = ERROR_SUCCESS;
|
|||
|
} else if (Status == WAIT_TIMEOUT) {
|
|||
|
// the thread is still running so continue
|
|||
|
Status = ERROR_SUCCESS;
|
|||
|
} else {
|
|||
|
// some other, probably serious, error
|
|||
|
// so pass it on through
|
|||
|
}
|
|||
|
} else {
|
|||
|
// the thread has never been created yet so continue
|
|||
|
}
|
|||
|
|
|||
|
if (hPerflibTimingThread == NULL) {
|
|||
|
// create the timing thread
|
|||
|
|
|||
|
assert (pTimerItemListHead == NULL); // there should be no entries, yet
|
|||
|
|
|||
|
// everything is ready for the timer thread
|
|||
|
|
|||
|
hPerflibTimingThread = CreateThread (
|
|||
|
NULL, 0,
|
|||
|
(LPTHREAD_START_ROUTINE)PerflibTimerFunction,
|
|||
|
NULL, 0, NULL);
|
|||
|
|
|||
|
assert (hPerflibTimingThread != NULL);
|
|||
|
if (hPerflibTimingThread == NULL) {
|
|||
|
Status = GetLastError();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (Status == ERROR_SUCCESS) {
|
|||
|
|
|||
|
// compute the length of the required buffer;
|
|||
|
|
|||
|
dwLibNameLen = (lstrlenW (pInfo->szLibraryName) + 1) * sizeof(WCHAR);
|
|||
|
dwBufferLength += dwLibNameLen;
|
|||
|
dwBufferLength += (lstrlenW (pInfo->szServiceName) + 1) * sizeof(WCHAR);
|
|||
|
dwBufferLength = QWORD_MULTIPLE (dwBufferLength);
|
|||
|
|
|||
|
pLocalInfo = ALLOCMEM (dwBufferLength);
|
|||
|
if (pLocalInfo == NULL)
|
|||
|
Status = ERROR_OUTOFMEMORY;
|
|||
|
}
|
|||
|
if ((Status == ERROR_SUCCESS) && (pLocalInfo != NULL)) {
|
|||
|
|
|||
|
// copy the arg buffer to the local list
|
|||
|
|
|||
|
pLocalInfo->szLibraryName = (LPWSTR)&pLocalInfo[1];
|
|||
|
lstrcpyW (pLocalInfo->szLibraryName, pInfo->szLibraryName);
|
|||
|
pLocalInfo->szServiceName = (LPWSTR)
|
|||
|
((LPBYTE)pLocalInfo->szLibraryName + dwLibNameLen);
|
|||
|
lstrcpyW (pLocalInfo->szServiceName, pInfo->szServiceName);
|
|||
|
// convert wait time in milliseconds to the number of "loops"
|
|||
|
pLocalInfo->dwWaitTime = pInfo->dwWaitTime / PERFLIB_TIMER_INTERVAL;
|
|||
|
if (pLocalInfo->dwWaitTime == 0) pLocalInfo->dwWaitTime =1; // have at least 1 loop
|
|||
|
pLocalInfo->dwEventMsg = pInfo->dwEventMsg;
|
|||
|
pLocalInfo->pData = pInfo->pData;
|
|||
|
|
|||
|
// wait for access to the data
|
|||
|
if (hTimerDataMutex != NULL) {
|
|||
|
NTSTATUS NtStatus;
|
|||
|
liWaitTime.QuadPart =
|
|||
|
MakeTimeOutValue((PERFLIB_TIMER_INTERVAL * 2));
|
|||
|
|
|||
|
NtStatus = NtWaitForSingleObject (
|
|||
|
hTimerDataMutex,
|
|||
|
FALSE,
|
|||
|
&liWaitTime);
|
|||
|
Status = PerfpDosError(NtStatus);
|
|||
|
} else {
|
|||
|
Status = ERROR_NOT_READY;
|
|||
|
}
|
|||
|
|
|||
|
if (Status == WAIT_OBJECT_0) {
|
|||
|
// KdPrint (("\nPERFLIB: Timing Thread Adding Entry: %d (%d) PID: %d, TID: %d",
|
|||
|
// (DWORD)pLocalInfo, pLocalInfo->dwWaitTime,
|
|||
|
// GetCurrentProcessId(), GetCurrentThreadId()));
|
|||
|
|
|||
|
// we have access to the data so add this item to the front of the list
|
|||
|
pLocalInfo->pNext = pTimerItemListHead;
|
|||
|
pTimerItemListHead = pLocalInfo;
|
|||
|
ReleaseMutex (hTimerDataMutex);
|
|||
|
|
|||
|
if (pLocalInfo->pNext == NULL) {
|
|||
|
// then the list was empty before this call so start the timer
|
|||
|
// going
|
|||
|
SetEvent (hTimerHandles[PL_TIMER_START_EVENT]);
|
|||
|
}
|
|||
|
|
|||
|
hReturn = (HANDLE)pLocalInfo;
|
|||
|
} else {
|
|||
|
SetLastError (Status);
|
|||
|
}
|
|||
|
} else {
|
|||
|
// unable to create thread
|
|||
|
SetLastError (Status);
|
|||
|
}
|
|||
|
} else {
|
|||
|
// unable to start timer
|
|||
|
SetLastError (Status);
|
|||
|
}
|
|||
|
|
|||
|
return hReturn;
|
|||
|
}
|
|||
|
|
|||
|
DWORD
|
|||
|
KillPerflibFunctionTimer (
|
|||
|
IN HANDLE hPerflibTimer
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Terminates a timing event by removing it from the list. When the last
|
|||
|
item is removed from the list the Start event is reset so the timing
|
|||
|
thread will wait for either the next start event, exit event or it's
|
|||
|
timeout to expire.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS Status;
|
|||
|
LPOPEN_PROC_WAIT_INFO pArg = (LPOPEN_PROC_WAIT_INFO)hPerflibTimer;
|
|||
|
LPOPEN_PROC_WAIT_INFO pLocalInfo;
|
|||
|
BOOL bFound = FALSE;
|
|||
|
LARGE_INTEGER liWaitTime;
|
|||
|
DWORD dwReturn = ERROR_SUCCESS;
|
|||
|
|
|||
|
if (hTimerDataMutex == NULL) {
|
|||
|
dwReturn = ERROR_NOT_READY;
|
|||
|
} else if (pArg == NULL) {
|
|||
|
dwReturn = ERROR_INVALID_HANDLE;
|
|||
|
} else {
|
|||
|
// so far so good
|
|||
|
// wait for access to the data
|
|||
|
liWaitTime.QuadPart =
|
|||
|
MakeTimeOutValue((PERFLIB_TIMER_INTERVAL * 2));
|
|||
|
Status = NtWaitForSingleObject (
|
|||
|
hTimerDataMutex,
|
|||
|
FALSE,
|
|||
|
&liWaitTime);
|
|||
|
|
|||
|
if (Status == STATUS_WAIT_0) {
|
|||
|
// we have access to the list so walk down the list and remove the
|
|||
|
// specified item
|
|||
|
// see if it's the first one in the list
|
|||
|
|
|||
|
// KdPrint (("\nPERFLIB: Timing Thread Removing Entry: %d (%d) PID: %d, TID: %d",
|
|||
|
// (DWORD)pArg, pArg->dwWaitTime,
|
|||
|
// GetCurrentProcessId(), GetCurrentThreadId()));
|
|||
|
|
|||
|
if (pArg == pTimerItemListHead) {
|
|||
|
// then remove it
|
|||
|
pTimerItemListHead = pArg->pNext;
|
|||
|
bFound = TRUE;
|
|||
|
} else {
|
|||
|
for (pLocalInfo = pTimerItemListHead;
|
|||
|
pLocalInfo != NULL;
|
|||
|
pLocalInfo = pLocalInfo->pNext) {
|
|||
|
if (pLocalInfo->pNext == pArg) {
|
|||
|
pLocalInfo->pNext = pArg->pNext;
|
|||
|
bFound = TRUE;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
assert (bFound);
|
|||
|
|
|||
|
if (bFound) {
|
|||
|
// it's out of the list so release the lock
|
|||
|
ReleaseMutex (hTimerDataMutex);
|
|||
|
|
|||
|
if (pTimerItemListHead == NULL) {
|
|||
|
// then the list is empty now so stop timing
|
|||
|
// going
|
|||
|
ResetEvent (hTimerHandles[PL_TIMER_START_EVENT]);
|
|||
|
}
|
|||
|
|
|||
|
// free memory
|
|||
|
|
|||
|
FREEMEM (pArg);
|
|||
|
dwReturn = ERROR_SUCCESS;
|
|||
|
} else {
|
|||
|
dwReturn = ERROR_NOT_FOUND;
|
|||
|
}
|
|||
|
} else {
|
|||
|
dwReturn = ERROR_TIMEOUT;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return dwReturn;
|
|||
|
}
|
|||
|
|
|||
|
DWORD
|
|||
|
DestroyPerflibFunctionTimer (
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
Terminates the timing thread and cancels any current timer events.
|
|||
|
NOTE: This routine can be called even if timer thread is not started!
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS Status = STATUS_WAIT_0;
|
|||
|
LPOPEN_PROC_WAIT_INFO pThisItem;
|
|||
|
LPOPEN_PROC_WAIT_INFO pNextItem;
|
|||
|
LARGE_INTEGER liWaitTime;
|
|||
|
HANDLE hTemp;
|
|||
|
|
|||
|
if (hTimerDataMutex != NULL) {
|
|||
|
DWORD dwTimeOut = 0;
|
|||
|
LONG dwStatus = ERROR_SUCCESS;
|
|||
|
|
|||
|
// wait for data mutex
|
|||
|
liWaitTime.QuadPart =
|
|||
|
MakeTimeOutValue((PERFLIB_TIMER_INTERVAL * 5));
|
|||
|
|
|||
|
Status = STATUS_TIMEOUT;
|
|||
|
while (Status == STATUS_TIMEOUT && dwTimeOut < PERFLIB_TIMEOUT_COUNT) {
|
|||
|
Status = NtWaitForSingleObject (
|
|||
|
hTimerDataMutex,
|
|||
|
FALSE,
|
|||
|
& liWaitTime);
|
|||
|
if (Status == STATUS_TIMEOUT) {
|
|||
|
if (hPerflibTimingThread != NULL) {
|
|||
|
// see if the handle is valid (i.e the thread is alive)
|
|||
|
dwStatus = WaitForSingleObject(hPerflibTimingThread,
|
|||
|
liWaitTime.LowPart);
|
|||
|
if (dwStatus == WAIT_OBJECT_0) {
|
|||
|
// the thread has terminated so close the handle
|
|||
|
Status = STATUS_WAIT_0;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if (Status == STATUS_TIMEOUT) {
|
|||
|
dwTimeOut ++;
|
|||
|
DebugPrint((2, "\nPERFLIB:NtWaitForSingleObject(TimerDataMutex,%d) time out for the %dth time in DestroyPErflibFunctionTimer(). PID: %d, TID: %d",
|
|||
|
liWaitTime, dwTimeOut,
|
|||
|
GetCurrentProcessId(),
|
|||
|
GetCurrentThreadId()));
|
|||
|
TRACE((WINPERF_DBG_TRACE_WARNING),
|
|||
|
(& PerflibGuid,
|
|||
|
__LINE__,
|
|||
|
PERF_DESTROYFUNCTIONTIMER,
|
|||
|
0,
|
|||
|
STATUS_TIMEOUT,
|
|||
|
& dwTimeOut, sizeof(dwTimeOut),
|
|||
|
NULL));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
assert (Status != STATUS_TIMEOUT);
|
|||
|
}
|
|||
|
|
|||
|
// free all entries in the list
|
|||
|
|
|||
|
if (Status == STATUS_WAIT_0) {
|
|||
|
for (pNextItem = pTimerItemListHead;
|
|||
|
pNextItem != NULL;) {
|
|||
|
pThisItem = pNextItem;
|
|||
|
pNextItem = pThisItem->pNext;
|
|||
|
FREEMEM (pThisItem);
|
|||
|
}
|
|||
|
}
|
|||
|
else {
|
|||
|
TRACE((WINPERF_DBG_TRACE_WARNING),
|
|||
|
(& PerflibGuid,
|
|||
|
__LINE__,
|
|||
|
PERF_DESTROYFUNCTIONTIMER,
|
|||
|
0,
|
|||
|
Status,
|
|||
|
NULL));
|
|||
|
}
|
|||
|
// all items have been freed so clear header
|
|||
|
pTimerItemListHead = NULL;
|
|||
|
|
|||
|
// set exit event
|
|||
|
if (hTimerHandles[PL_TIMER_EXIT_EVENT] != NULL) {
|
|||
|
SetEvent (hTimerHandles[PL_TIMER_EXIT_EVENT]);
|
|||
|
}
|
|||
|
|
|||
|
if (hPerflibTimingThread != NULL) {
|
|||
|
// wait for thread to terminate
|
|||
|
liWaitTime.QuadPart =
|
|||
|
MakeTimeOutValue((PERFLIB_TIMER_INTERVAL * 5));
|
|||
|
|
|||
|
Status = NtWaitForSingleObject (
|
|||
|
hPerflibTimingThread,
|
|||
|
FALSE,
|
|||
|
&liWaitTime);
|
|||
|
|
|||
|
assert (Status != STATUS_TIMEOUT);
|
|||
|
|
|||
|
hTemp = hPerflibTimingThread;
|
|||
|
hPerflibTimingThread = NULL;
|
|||
|
CloseHandle (hTemp);
|
|||
|
}
|
|||
|
|
|||
|
if (hTimerDataMutex != NULL) {
|
|||
|
hTemp = hTimerDataMutex;
|
|||
|
hTimerDataMutex = NULL;
|
|||
|
// close handles and leave
|
|||
|
ReleaseMutex (hTemp);
|
|||
|
CloseHandle (hTemp);
|
|||
|
}
|
|||
|
|
|||
|
if (hTimerHandles[PL_TIMER_START_EVENT] != NULL) {
|
|||
|
CloseHandle (hTimerHandles[PL_TIMER_START_EVENT]);
|
|||
|
hTimerHandles[PL_TIMER_START_EVENT] = NULL;
|
|||
|
}
|
|||
|
|
|||
|
if (hTimerHandles[PL_TIMER_EXIT_EVENT] != NULL) {
|
|||
|
CloseHandle (hTimerHandles[PL_TIMER_EXIT_EVENT]);
|
|||
|
hTimerHandles[PL_TIMER_EXIT_EVENT] = NULL;
|
|||
|
}
|
|||
|
|
|||
|
return ERROR_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
LONG
|
|||
|
PrivateRegQueryValueExT (
|
|||
|
HKEY hKey,
|
|||
|
LPVOID lpValueName,
|
|||
|
LPDWORD lpReserved,
|
|||
|
LPDWORD lpType,
|
|||
|
LPBYTE lpData,
|
|||
|
LPDWORD lpcbData,
|
|||
|
BOOL bUnicode
|
|||
|
)
|
|||
|
/*
|
|||
|
wrapper function to allow RegQueryValues while inside a RegQueryValue
|
|||
|
|
|||
|
*/
|
|||
|
{
|
|||
|
LONG ReturnStatus;
|
|||
|
NTSTATUS ntStatus = STATUS_SUCCESS;
|
|||
|
BOOL bStatus;
|
|||
|
|
|||
|
UNICODE_STRING usLocal = {0,0,NULL};
|
|||
|
PSTR AnsiValueBuffer;
|
|||
|
ULONG AnsiValueLength;
|
|||
|
PWSTR UnicodeValueBuffer;
|
|||
|
ULONG UnicodeValueLength;
|
|||
|
ULONG Index;
|
|||
|
|
|||
|
PKEY_VALUE_PARTIAL_INFORMATION pValueInformation;
|
|||
|
LONG ValueBufferLength;
|
|||
|
ULONG ResultLength;
|
|||
|
|
|||
|
|
|||
|
UNREFERENCED_PARAMETER (lpReserved);
|
|||
|
|
|||
|
if (bUnicode) {
|
|||
|
bStatus = RtlCreateUnicodeString (&usLocal, (LPCWSTR)lpValueName);
|
|||
|
} else {
|
|||
|
bStatus = RtlCreateUnicodeStringFromAsciiz (&usLocal, (LPCSTR)lpValueName);
|
|||
|
}
|
|||
|
|
|||
|
if (bStatus) {
|
|||
|
|
|||
|
ValueBufferLength =
|
|||
|
ResultLength =
|
|||
|
sizeof(KEY_VALUE_PARTIAL_INFORMATION) + *lpcbData;
|
|||
|
pValueInformation = ALLOCMEM(ResultLength);
|
|||
|
|
|||
|
if (pValueInformation != NULL) {
|
|||
|
ntStatus = NtQueryValueKey(
|
|||
|
hKey,
|
|||
|
&usLocal,
|
|||
|
KeyValuePartialInformation,
|
|||
|
pValueInformation,
|
|||
|
ValueBufferLength,
|
|||
|
&ResultLength);
|
|||
|
|
|||
|
if ((NT_SUCCESS(ntStatus) || ntStatus == STATUS_BUFFER_OVERFLOW)) {
|
|||
|
// return data
|
|||
|
if (ARGUMENT_PRESENT(lpType)) {
|
|||
|
*lpType = pValueInformation->Type;
|
|||
|
}
|
|||
|
|
|||
|
if (ARGUMENT_PRESENT(lpcbData)) {
|
|||
|
*lpcbData = pValueInformation->DataLength;
|
|||
|
}
|
|||
|
|
|||
|
if (NT_SUCCESS(ntStatus)) {
|
|||
|
if (ARGUMENT_PRESENT(lpData)) {
|
|||
|
if (!bUnicode &&
|
|||
|
(pValueInformation->Type == REG_SZ ||
|
|||
|
pValueInformation->Type == REG_EXPAND_SZ ||
|
|||
|
pValueInformation->Type == REG_MULTI_SZ)
|
|||
|
) {
|
|||
|
// then convert the unicode return to an
|
|||
|
// ANSI string before returning
|
|||
|
// the local wide buffer used
|
|||
|
|
|||
|
UnicodeValueLength = ResultLength;
|
|||
|
UnicodeValueBuffer = (LPWSTR)&pValueInformation->Data[0];
|
|||
|
|
|||
|
AnsiValueBuffer = (LPSTR)lpData;
|
|||
|
AnsiValueLength = ARGUMENT_PRESENT( lpcbData )?
|
|||
|
*lpcbData : 0;
|
|||
|
Index = 0;
|
|||
|
ntStatus = RtlUnicodeToMultiByteN(
|
|||
|
AnsiValueBuffer,
|
|||
|
AnsiValueLength,
|
|||
|
&Index,
|
|||
|
UnicodeValueBuffer,
|
|||
|
UnicodeValueLength);
|
|||
|
|
|||
|
if (NT_SUCCESS( ntStatus ) &&
|
|||
|
(ARGUMENT_PRESENT( lpcbData ))) {
|
|||
|
*lpcbData = Index;
|
|||
|
}
|
|||
|
} else {
|
|||
|
if (pValueInformation->DataLength <= *lpcbData) {
|
|||
|
// copy the buffer to the user's buffer
|
|||
|
memcpy (lpData, &pValueInformation->Data[0],
|
|||
|
pValueInformation->DataLength);
|
|||
|
ntStatus = STATUS_SUCCESS;
|
|||
|
} else {
|
|||
|
ntStatus = STATUS_BUFFER_OVERFLOW;
|
|||
|
}
|
|||
|
*lpcbData = pValueInformation->DataLength;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (pValueInformation != NULL) {
|
|||
|
// release temp buffer
|
|||
|
FREEMEM (pValueInformation);
|
|||
|
}
|
|||
|
} else {
|
|||
|
// unable to allocate memory for this operation so
|
|||
|
ntStatus = STATUS_NO_MEMORY;
|
|||
|
}
|
|||
|
|
|||
|
RtlFreeUnicodeString (&usLocal);
|
|||
|
} else {
|
|||
|
// this is a guess at the most likely cause for the string
|
|||
|
// creation to fail.
|
|||
|
ntStatus = STATUS_NO_MEMORY;
|
|||
|
}
|
|||
|
|
|||
|
ReturnStatus = PerfpDosError(ntStatus);
|
|||
|
|
|||
|
return ReturnStatus;
|
|||
|
}
|
|||
|
|
|||
|
LONG
|
|||
|
GetPerfDllFileInfo (
|
|||
|
LPCWSTR szFileName,
|
|||
|
PDLL_VALIDATION_DATA pDllData
|
|||
|
)
|
|||
|
{
|
|||
|
WCHAR szFullPath[MAX_PATH*2];
|
|||
|
DWORD dwStatus = ERROR_FILE_NOT_FOUND;
|
|||
|
DWORD dwRetValue;
|
|||
|
HANDLE hFile;
|
|||
|
BOOL bStatus;
|
|||
|
LARGE_INTEGER liSize;
|
|||
|
|
|||
|
dwRetValue = SearchPathW (
|
|||
|
NULL,
|
|||
|
szFileName,
|
|||
|
NULL,
|
|||
|
sizeof(szFullPath) / sizeof(szFullPath[0]),
|
|||
|
szFullPath,
|
|||
|
NULL);
|
|||
|
|
|||
|
if (dwRetValue > 0) {
|
|||
|
//then the file was found so open it.
|
|||
|
hFile = CreateFileW (
|
|||
|
szFullPath,
|
|||
|
GENERIC_READ,
|
|||
|
FILE_SHARE_READ,
|
|||
|
NULL,
|
|||
|
OPEN_EXISTING,
|
|||
|
FILE_ATTRIBUTE_NORMAL,
|
|||
|
NULL);
|
|||
|
|
|||
|
if (hFile != INVALID_HANDLE_VALUE) {
|
|||
|
// get file creation date/time
|
|||
|
bStatus = GetFileTime (
|
|||
|
hFile,
|
|||
|
&pDllData->CreationDate,
|
|||
|
NULL, NULL);
|
|||
|
if (bStatus) {
|
|||
|
// get file size
|
|||
|
liSize.LowPart = GetFileSize (
|
|||
|
hFile, (PULONG)&liSize.HighPart);
|
|||
|
if (liSize.LowPart != 0xFFFFFFFF) {
|
|||
|
pDllData->FileSize = liSize.QuadPart;
|
|||
|
dwStatus = ERROR_SUCCESS;
|
|||
|
} else {
|
|||
|
dwStatus = GetLastError();
|
|||
|
}
|
|||
|
} else {
|
|||
|
dwStatus = GetLastError();
|
|||
|
}
|
|||
|
|
|||
|
CloseHandle (hFile);
|
|||
|
} else {
|
|||
|
dwStatus = GetLastError();
|
|||
|
}
|
|||
|
} else {
|
|||
|
dwStatus = GetLastError();
|
|||
|
}
|
|||
|
|
|||
|
return dwStatus;
|
|||
|
}
|
|||
|
|
|||
|
DWORD
|
|||
|
DisablePerfLibrary (
|
|||
|
PEXT_OBJECT pObj
|
|||
|
)
|
|||
|
{
|
|||
|
// continue only if the "Disable" feature is enabled and
|
|||
|
// if this library hasn't already been disabled.
|
|||
|
if ((!(lPerflibConfigFlags & PLCF_NO_DISABLE_DLLS)) &&
|
|||
|
(!(pObj->dwFlags & PERF_EO_DISABLED))) {
|
|||
|
|
|||
|
// set the disabled bit in the info
|
|||
|
pObj->dwFlags |= PERF_EO_DISABLED;
|
|||
|
return DisableLibrary(pObj->hPerfKey, pObj->szServiceName);
|
|||
|
}
|
|||
|
return ERROR_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
DWORD
|
|||
|
DisableLibrary(
|
|||
|
IN HKEY hPerfKey,
|
|||
|
IN LPWSTR szServiceName
|
|||
|
)
|
|||
|
{
|
|||
|
//
|
|||
|
// This routine will disable regardless of settings
|
|||
|
//
|
|||
|
DWORD dwValue, dwSize;
|
|||
|
DWORD dwFnStatus = ERROR_SUCCESS;
|
|||
|
WORD wStringIndex = 0;
|
|||
|
LPWSTR szMessageArray[2];
|
|||
|
|
|||
|
// disable perf library entry in the service key
|
|||
|
dwSize = sizeof(dwValue);
|
|||
|
dwValue = 1;
|
|||
|
dwFnStatus = RegSetValueExW (
|
|||
|
hPerfKey,
|
|||
|
DisablePerformanceCounters,
|
|||
|
0L,
|
|||
|
REG_DWORD,
|
|||
|
(LPBYTE)&dwValue,
|
|||
|
dwSize);
|
|||
|
// report error
|
|||
|
|
|||
|
if (dwFnStatus == ERROR_SUCCESS) {
|
|||
|
// system disabled
|
|||
|
szMessageArray[wStringIndex++] =
|
|||
|
szServiceName;
|
|||
|
|
|||
|
ReportEvent (hEventLog,
|
|||
|
EVENTLOG_ERROR_TYPE, // error type
|
|||
|
0, // category (not used)
|
|||
|
(DWORD)PERFLIB_LIBRARY_DISABLED, // event,
|
|||
|
NULL, // SID (not used),
|
|||
|
wStringIndex, // number of strings
|
|||
|
0, // sizeof raw data
|
|||
|
szMessageArray, // message text array
|
|||
|
NULL); // raw data
|
|||
|
} else {
|
|||
|
// local disable only
|
|||
|
szMessageArray[wStringIndex++] =
|
|||
|
szServiceName;
|
|||
|
|
|||
|
ReportEvent (hEventLog,
|
|||
|
EVENTLOG_ERROR_TYPE, // error type
|
|||
|
0, // category (not used)
|
|||
|
(DWORD)PERFLIB_LIBRARY_TEMP_DISABLED, // event,
|
|||
|
NULL, // SID (not used),
|
|||
|
wStringIndex, // number of strings
|
|||
|
0, // sizeof raw data
|
|||
|
szMessageArray, // message text array
|
|||
|
NULL); // raw data
|
|||
|
}
|
|||
|
return ERROR_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
DWORD
|
|||
|
PerfUpdateErrorCount(
|
|||
|
PEXT_OBJECT pObj,
|
|||
|
DWORD ErrorCount
|
|||
|
)
|
|||
|
{
|
|||
|
DWORD Status;
|
|||
|
DWORD dwErrorCount, dwType, dwSize;
|
|||
|
|
|||
|
dwErrorCount = 0;
|
|||
|
if (ErrorCount == 0) { // reset to 0
|
|||
|
RegDeleteValueW(pObj->hPerfKey, cszFailureCount);
|
|||
|
return 0;
|
|||
|
}
|
|||
|
dwSize = sizeof(DWORD);
|
|||
|
dwType = REG_DWORD;
|
|||
|
Status = PrivateRegQueryValueExW(
|
|||
|
pObj->hPerfKey,
|
|||
|
cszFailureCount,
|
|||
|
NULL,
|
|||
|
&dwType,
|
|||
|
(LPBYTE)&dwErrorCount,
|
|||
|
&dwSize);
|
|||
|
if (Status != ERROR_SUCCESS)
|
|||
|
dwErrorCount = 0;
|
|||
|
|
|||
|
dwErrorCount += ErrorCount;
|
|||
|
dwSize = sizeof(DWORD);
|
|||
|
Status = RegSetValueExW(
|
|||
|
pObj->hPerfKey,
|
|||
|
cszFailureCount,
|
|||
|
0L,
|
|||
|
REG_DWORD,
|
|||
|
(LPBYTE)&dwErrorCount,
|
|||
|
dwSize);
|
|||
|
|
|||
|
if ((dwErrorCount >= pObj->dwErrorLimit) &&
|
|||
|
(pObj->dwErrorLimit != 0)) {
|
|||
|
DisablePerfLibrary(pObj);
|
|||
|
}
|
|||
|
if (dwErrorCount < 100)
|
|||
|
return dwErrorCount;
|
|||
|
ErrorCount = dwErrorCount % 100;
|
|||
|
if (ErrorCount > 10)
|
|||
|
return 0;
|
|||
|
return ErrorCount;
|
|||
|
}
|
|||
|
|
|||
|
DWORD
|
|||
|
PerfCheckRegistry(
|
|||
|
IN HKEY hPerfKey,
|
|||
|
IN LPCWSTR szServiceName
|
|||
|
)
|
|||
|
{
|
|||
|
DWORD dwType = 0;
|
|||
|
DWORD dwSize = sizeof(DWORD);
|
|||
|
DWORD dwData = 0;
|
|||
|
DWORD status;
|
|||
|
WORD wStringIndex;
|
|||
|
LPWSTR szMessageArray[2];
|
|||
|
|
|||
|
status = PrivateRegQueryValueExA(
|
|||
|
hPerfKey,
|
|||
|
FirstCounter,
|
|||
|
NULL,
|
|||
|
&dwType,
|
|||
|
(LPBYTE)&dwData,
|
|||
|
&dwSize);
|
|||
|
|
|||
|
if ((status != ERROR_SUCCESS) || (dwType != REG_DWORD) ||
|
|||
|
(dwData < LAST_BASE_INDEX)) {
|
|||
|
wStringIndex = 0;
|
|||
|
szMessageArray[wStringIndex++] = (LPWSTR) FirstCounter;
|
|||
|
szMessageArray[wStringIndex++] = (LPWSTR) szServiceName;
|
|||
|
ReportEvent(hEventLog,
|
|||
|
EVENTLOG_ERROR_TYPE,
|
|||
|
0,
|
|||
|
(DWORD)PERFLIB_REGVALUE_NOT_FOUND,
|
|||
|
NULL,
|
|||
|
wStringIndex,
|
|||
|
0,
|
|||
|
szMessageArray,
|
|||
|
NULL);
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
status = PrivateRegQueryValueExA(
|
|||
|
hPerfKey,
|
|||
|
LastCounter,
|
|||
|
NULL,
|
|||
|
&dwType,
|
|||
|
(LPBYTE)&dwData,
|
|||
|
&dwSize);
|
|||
|
|
|||
|
if ((status != ERROR_SUCCESS) || (dwType != REG_DWORD) ||
|
|||
|
(dwData <= LAST_BASE_INDEX)) {
|
|||
|
wStringIndex = 0;
|
|||
|
szMessageArray[wStringIndex++] = (LPWSTR) LastCounter;
|
|||
|
szMessageArray[wStringIndex++] = (LPWSTR) szServiceName;
|
|||
|
ReportEvent(hEventLog,
|
|||
|
EVENTLOG_ERROR_TYPE,
|
|||
|
0,
|
|||
|
(DWORD)PERFLIB_REGVALUE_NOT_FOUND,
|
|||
|
NULL,
|
|||
|
wStringIndex,
|
|||
|
0,
|
|||
|
szMessageArray,
|
|||
|
NULL);
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
status = PrivateRegQueryValueExA(
|
|||
|
hPerfKey,
|
|||
|
FirstHelp,
|
|||
|
NULL,
|
|||
|
&dwType,
|
|||
|
(LPBYTE)&dwData,
|
|||
|
&dwSize);
|
|||
|
|
|||
|
if ((status != ERROR_SUCCESS) || (dwType != REG_DWORD) ||
|
|||
|
(dwData < LAST_BASE_INDEX)) {
|
|||
|
wStringIndex = 0;
|
|||
|
szMessageArray[wStringIndex++] = (LPWSTR) FirstHelp;
|
|||
|
szMessageArray[wStringIndex++] = (LPWSTR) szServiceName;
|
|||
|
ReportEvent(hEventLog,
|
|||
|
EVENTLOG_ERROR_TYPE,
|
|||
|
0,
|
|||
|
(DWORD)PERFLIB_REGVALUE_NOT_FOUND,
|
|||
|
NULL,
|
|||
|
wStringIndex,
|
|||
|
0,
|
|||
|
szMessageArray,
|
|||
|
NULL);
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
status = PrivateRegQueryValueExA(
|
|||
|
hPerfKey,
|
|||
|
LastHelp,
|
|||
|
NULL,
|
|||
|
&dwType,
|
|||
|
(LPBYTE)&dwData,
|
|||
|
&dwSize);
|
|||
|
|
|||
|
if ((status != ERROR_SUCCESS) || (dwType != REG_DWORD) ||
|
|||
|
(dwData <= LAST_BASE_INDEX)) {
|
|||
|
wStringIndex = 0;
|
|||
|
szMessageArray[wStringIndex++] = (LPWSTR) LastHelp;
|
|||
|
szMessageArray[wStringIndex++] = (LPWSTR) szServiceName;
|
|||
|
ReportEvent(hEventLog,
|
|||
|
EVENTLOG_ERROR_TYPE,
|
|||
|
0,
|
|||
|
(DWORD)PERFLIB_REGVALUE_NOT_FOUND,
|
|||
|
NULL,
|
|||
|
wStringIndex,
|
|||
|
0,
|
|||
|
szMessageArray,
|
|||
|
NULL);
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
DWORD
|
|||
|
PerfpDosError(
|
|||
|
IN NTSTATUS Status
|
|||
|
)
|
|||
|
// Need to convert NtStatus that we generate to DosError
|
|||
|
{
|
|||
|
if (Status == STATUS_SUCCESS)
|
|||
|
return ERROR_SUCCESS;
|
|||
|
if (Status == STATUS_BUFFER_OVERFLOW)
|
|||
|
return ERROR_MORE_DATA;
|
|||
|
if (Status == STATUS_TIMEOUT)
|
|||
|
return WAIT_TIMEOUT;
|
|||
|
if (Status <= STATUS_WAIT_63)
|
|||
|
return (DWORD) Status;
|
|||
|
return RtlNtStatusToDosError(Status);
|
|||
|
}
|
|||
|
|
|||
|
#ifdef DBG
|
|||
|
VOID
|
|||
|
PerfpDebug(
|
|||
|
ULONG DebugPrintLevel,
|
|||
|
PCCHAR DebugMessage,
|
|||
|
...
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Debug print for all Perflib
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Debug print level between 0 and 3, with 3 being the most verbose.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
va_list ap;
|
|||
|
|
|||
|
if ((DebugPrintLevel <= (PerfLibDebug & 0x0000ffff)) ||
|
|||
|
((1 << (DebugPrintLevel + 15)) & PerfLibDebug)) {
|
|||
|
DbgPrint("%d:Perflib:", GetCurrentThreadId());
|
|||
|
}
|
|||
|
else
|
|||
|
return;
|
|||
|
|
|||
|
va_start(ap, DebugMessage);
|
|||
|
|
|||
|
|
|||
|
if ((DebugPrintLevel <= (PerfLibDebug & 0x0000ffff)) ||
|
|||
|
((1 << (DebugPrintLevel + 15)) & PerfLibDebug)) {
|
|||
|
|
|||
|
_vsnprintf(
|
|||
|
(LPSTR)PerfLibDebugBuffer, DEBUG_BUFFER_LENGTH, DebugMessage, ap);
|
|||
|
|
|||
|
DbgPrint((LPSTR)PerfLibDebugBuffer);
|
|||
|
}
|
|||
|
|
|||
|
va_end(ap);
|
|||
|
|
|||
|
}
|
|||
|
#endif // DBG
|