windows-nt/Source/XPSP1/NT/base/ntos/wmi/tracelog.c
2020-09-26 16:20:57 +08:00

2962 lines
90 KiB
C

/*++
Copyright (c) 1996 Microsoft Corporation
Module Name:
tracelog.c
Abstract:
This is the source file that implements the private routines for
the performance event tracing and logging facility.
The routines here work on a single event tracing session, the logging
thread, and buffer synchronization within a session.
Author:
Jee Fung Pang (jeepang) 03-Dec-1996
Revision History:
--*/
// TODO: In future, may need to align buffer size to larger of disk alignment
// or 1024.
#pragma warning(disable:4214)
#pragma warning(disable:4115)
#pragma warning(disable:4201)
#pragma warning(disable:4127)
#include "ntverp.h"
#include "ntos.h"
#include "wmikmp.h"
#include <zwapi.h>
#pragma warning(default:4214)
#pragma warning(default:4115)
#pragma warning(default:4201)
#pragma warning(default:4127)
#ifndef _WMIKM_
#define _WMIKM_
#endif
#include "evntrace.h"
//
// Constants and Types used locally
//
#if DBG
ULONG WmipTraceDebugLevel=0;
// 5 All messages
// 4 Messages up to event operations
// 3 Messages up to buffer operations
// 2 Flush operations
// 1 Common operations and debugging statements
// 0 Always on - use for real error
#endif
#define ERROR_RETRY_COUNT 100
//#define BUFFER_STATE_UNUSED 0 // Buffer is empty, not used
//#define BUFFER_STATE_DIRTY 1 // Buffer is being used
//#define BUFFER_STATE_FULL 2 // Buffer is filled up
//#define BUFFER_STATE_FLUSH 4 // Buffer ready for flush
#include "tracep.h"
// Non-paged global variables
//
ULONG WmiTraceAlignment = DEFAULT_TRACE_ALIGNMENT;
ULONG WmiUsePerfClock = EVENT_TRACE_CLOCK_SYSTEMTIME; // Global clock switch
LONG WmipRefCount[MAXLOGGERS];
ULONG WmipGlobalSequence = 0;
PWMI_LOGGER_CONTEXT WmipLoggerContext[MAXLOGGERS];
PWMI_BUFFER_HEADER WmipContextSwapProcessorBuffers[MAXIMUM_PROCESSORS];
#ifdef WMI_NON_BLOCKING
KSPIN_LOCK WmiSlistLock;
#endif //WMI_NON_BLOCKING
//
// Paged global variables
//
#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg("PAGEDATA")
#endif
ULONG WmiWriteFailureLimit = ERROR_RETRY_COUNT;
ULONG WmipFileSystemReady = FALSE;
WMI_TRACE_BUFFER_CALLBACK WmipGlobalBufferCallback = NULL;
PVOID WmipGlobalCallbackContext = NULL;
#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg()
#endif
//
// Function prototypes for routines used locally
//
PWMI_BUFFER_HEADER
WmipSwitchBuffer(
IN PWMI_LOGGER_CONTEXT LoggerContext,
IN PWMI_BUFFER_HEADER OldBuffer,
IN ULONG Processor
);
NTSTATUS
WmipPrepareHeader(
IN PWMI_LOGGER_CONTEXT LoggerContext,
IN OUT PWMI_BUFFER_HEADER Buffer
);
VOID
FASTCALL
WmipResetBufferHeader (
PWMI_LOGGER_CONTEXT LoggerContext,
PWMI_BUFFER_HEADER Buffer
);
VOID
FASTCALL
WmipPushDirtyBuffer (
PWMI_LOGGER_CONTEXT LoggerContext,
PWMI_BUFFER_HEADER Buffer
);
//
// Logger functions
//
NTSTATUS
WmipCreateLogFile(
IN PWMI_LOGGER_CONTEXT LoggerContext,
IN ULONG SwitchFile,
IN ULONG Append
);
NTSTATUS
WmipFinalizeHeader(
IN HANDLE FileHandle,
IN PWMI_LOGGER_CONTEXT LoggerContext
);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, WmipLogger)
#pragma alloc_text(PAGE, WmipSendNotification)
#pragma alloc_text(PAGE, WmipCreateLogFile)
#pragma alloc_text(PAGE, WmipFlushActiveBuffers)
#pragma alloc_text(PAGE, WmipGenerateFileName)
#pragma alloc_text(PAGE, WmipPrepareHeader)
#pragma alloc_text(PAGE, WmiBootPhase1)
#pragma alloc_text(PAGE, WmipFinalizeHeader)
#pragma alloc_text(PAGEWMI, WmipFlushBuffer)
#pragma alloc_text(PAGEWMI, WmipReserveTraceBuffer)
#pragma alloc_text(PAGEWMI, WmipGetFreeBuffer)
#pragma alloc_text(PAGEWMI, WmiReserveWithPerfHeader)
#pragma alloc_text(PAGEWMI, WmiReserveWithSystemHeader)
#ifdef WMI_NON_BLOCKING
#pragma alloc_text(PAGEWMI, WmipAllocateFreeBuffers)
#pragma alloc_text(PAGE, WmipAdjustFreeBuffers)
#else
#pragma alloc_text(PAGEWMI, WmipSwitchBuffer)
#endif //NWMI_NON_BLOCKING
#pragma alloc_text(PAGEWMI, WmipReleaseTraceBuffer)
#pragma alloc_text(PAGEWMI, WmiReleaseKernelBuffer)
#pragma alloc_text(PAGEWMI, WmipResetBufferHeader)
#pragma alloc_text(PAGEWMI, WmipPushDirtyBuffer)
#pragma alloc_text(PAGEWMI, WmipPopFreeContextSwapBuffer)
#pragma alloc_text(PAGEWMI, WmipPushDirtyContextSwapBuffer)
#ifdef NTPERF
#pragma alloc_text(PAGEWMI, WmipSwitchPerfmemBuffer)
#endif //NTPERF
#endif
//
// Actual code starts here
//
#ifdef WMI_NON_BLOCKING
PWMI_BUFFER_HEADER
WmipGetFreeBuffer(
IN PWMI_LOGGER_CONTEXT LoggerContext
)
//
// This routine works at any IRQL
//
{
PWMI_BUFFER_HEADER Buffer;
PSINGLE_LIST_ENTRY Entry;
if (LoggerContext->SwitchingInProgress == 0) {
//
// Not in the middle of switching.
//
ReTry:
Entry = InterlockedPopEntrySList(&LoggerContext->FreeList);
if (Entry != NULL) {
Buffer = CONTAINING_RECORD (Entry,
WMI_BUFFER_HEADER,
SlistEntry);
//
// Reset the buffer
//
WmipResetBufferHeader( LoggerContext, Buffer );
//
// Maintain some Wmi logger context buffer counts
//
InterlockedDecrement((PLONG) &LoggerContext->BuffersAvailable);
InterlockedIncrement((PLONG) &LoggerContext->BuffersInUse);
TraceDebug((2, "WmipGetFreeBuffer: %2d, %p, Free: %d, InUse: %d, Dirty: %d, Total: %d\n",
LoggerContext->LoggerId,
Buffer,
LoggerContext->BuffersAvailable,
LoggerContext->BuffersInUse,
LoggerContext->BuffersDirty,
LoggerContext->NumberOfBuffers));
return Buffer;
} else {
if (LoggerContext->LoggerMode & EVENT_TRACE_BUFFERING_MODE) {
//
// If we are in BUFFERING Mode, put all buffers from
// Flushlist into FreeList.
//
if (InterlockedIncrement((PLONG) &LoggerContext->SwitchingInProgress) == 1) {
while (Entry = InterlockedPopEntrySList(&LoggerContext->FlushList)) {
Buffer = CONTAINING_RECORD (Entry,
WMI_BUFFER_HEADER,
SlistEntry);
InterlockedPushEntrySList(&LoggerContext->FreeList,
(PSINGLE_LIST_ENTRY) &Buffer->SlistEntry);
Buffer->State.Flush = 0;
Buffer->State.Free = 1;
InterlockedIncrement((PLONG) &LoggerContext->BuffersAvailable);
InterlockedDecrement((PLONG) &LoggerContext->BuffersDirty);
TraceDebug((2, "WMI Buffer Reuse: %2d, %p, Free: %d, InUse: %d, Dirty: %d, Total: %d\n",
LoggerContext->LoggerId,
Buffer,
LoggerContext->BuffersAvailable,
LoggerContext->BuffersInUse,
LoggerContext->BuffersDirty,
LoggerContext->NumberOfBuffers));
}
}
InterlockedDecrement((PLONG) &LoggerContext->SwitchingInProgress);
goto ReTry;
}
return NULL;
}
} else {
return NULL;
}
}
ULONG
WmipAllocateFreeBuffers(
IN PWMI_LOGGER_CONTEXT LoggerContext,
IN ULONG NumberOfBuffers
)
/*++
Routine Description:
This routine allocate addition buffers into the free buffer list.
Logger can allocate more buffer to handle bursty logging behavior.
This routine can be called by multiple places and counters must be
manipulated using interlocked operations.
Arguments:
LoggerContext - Logger Context
NumberOfBuffers - Number of buffers to be allocated.
Return Value:
The total number of buffers actually allocated. When it is fewer than the requested number:
If this is called when trace is turned on, we fail to turn on trace.
If this is called by walker thread to get more buffer, it is OK.
Environment:
Kernel mode.
--*/
{
ULONG i;
PWMI_BUFFER_HEADER Buffer;
ULONG TotalBuffers;
for (i=0; i<NumberOfBuffers; i++) {
//
// Multiple threads can ask for more buffers, make sure
// we do not go over the maximum.
//
TotalBuffers = InterlockedIncrement(&LoggerContext->NumberOfBuffers);
if (TotalBuffers <= LoggerContext->MaximumBuffers) {
#ifdef NTPERF
if (PERFINFO_IS_LOGGING_TO_PERFMEM()) {
Buffer = (PWMI_BUFFER_HEADER)
PerfInfoReserveBytesFromPerfMem(LoggerContext->BufferSize);
} else {
#endif //NTPERF
Buffer = (PWMI_BUFFER_HEADER)
ExAllocatePoolWithTag(LoggerContext->PoolType,
LoggerContext->BufferSize,
TRACEPOOLTAG);
#ifdef NTPERF
}
#endif //NTPERF
if (Buffer != NULL) {
TraceDebug((3,
"WmipAllocateFreeBuffers: Allocated buffer size %d type %d\n",
LoggerContext->BufferSize, LoggerContext->PoolType));
InterlockedIncrement(&LoggerContext->BuffersAvailable);
//
// Initialize newly created buffer
//
RtlZeroMemory(Buffer, sizeof(WMI_BUFFER_HEADER));
Buffer->CurrentOffset = sizeof(WMI_BUFFER_HEADER);
KeQuerySystemTime(&Buffer->TimeStamp);
Buffer->State.Free = 1;
//
// Insert it into the free List
//
InterlockedPushEntrySList(&LoggerContext->FreeList,
(PSINGLE_LIST_ENTRY) &Buffer->SlistEntry);
InterlockedPushEntrySList(&LoggerContext->GlobalList,
(PSINGLE_LIST_ENTRY) &Buffer->GlobalEntry);
} else {
//
// Allocation failed, decrement the NumberOfBuffers
// we increment earlier.
//
InterlockedDecrement(&LoggerContext->NumberOfBuffers);
break;
}
} else {
//
// Maximum is reached, decrement the NumberOfBuffers
// we increment earlier.
//
InterlockedDecrement(&LoggerContext->NumberOfBuffers);
break;
}
}
TraceDebug((2, "WmipAllocateFreeBuffers %3d (%3d): Free: %d, InUse: %d, Dirty: %d, Total: %d\n",
NumberOfBuffers,
i,
LoggerContext->BuffersAvailable,
LoggerContext->BuffersInUse,
LoggerContext->BuffersDirty,
LoggerContext->NumberOfBuffers));
return i;
}
NTSTATUS
WmipAdjustFreeBuffers(
IN PWMI_LOGGER_CONTEXT LoggerContext
)
/*++
Routine Description:
This routine does buffer management. It checks the number of free buffers and
will allocate additonal or free some based on the situation.
Arguments:
LoggerContext - Logger Context
Return Value:
Status
Environment:
Kernel mode.
--*/
{
ULONG FreeBuffers;
ULONG AdditionalBuffers;
NTSTATUS Status = STATUS_SUCCESS;
//
// Check if we need to allocate more buffers
//
FreeBuffers = ExQueryDepthSList(&LoggerContext->FreeList);
if (FreeBuffers < LoggerContext->MinimumBuffers) {
AdditionalBuffers = LoggerContext->MinimumBuffers - FreeBuffers;
if (AdditionalBuffers != WmipAllocateFreeBuffers(LoggerContext, AdditionalBuffers)) {
Status = STATUS_NO_MEMORY;
}
}
return Status;
}
//
// Event trace/record and buffer related routines
//
#else
PWMI_BUFFER_HEADER
WmipGetFreeBuffer(
IN PWMI_LOGGER_CONTEXT LoggerContext
)
//
// This routine works at IRQL <= DISPATCH_LEVEL
//
{
PWMI_BUFFER_HEADER Buffer=NULL;
//
// Caller is responsible for setting up spinlock if necessary
//
if (IsListEmpty(&LoggerContext->FreeList)) {
if ((ULONG) LoggerContext->NumberOfBuffers
< LoggerContext->MaximumBuffers) {
//
// Try and grow the buffer pool by ONE buffer first if no free buffers left
//
TraceDebug((3, "WmipGetFreeBuffer: Adding buffer to pool\n"));
Buffer = (PWMI_BUFFER_HEADER)
ExAllocatePoolWithTag(LoggerContext->PoolType,
LoggerContext->BufferSize, TRACEPOOLTAG);
if (Buffer != NULL) { // not able to allocate another buffer
InterlockedIncrement(&LoggerContext->NumberOfBuffers);
RtlZeroMemory(Buffer, sizeof(WMI_BUFFER_HEADER));
//
// Initialize newly created buffer
//
Buffer->CurrentOffset = sizeof(WMI_BUFFER_HEADER);
Buffer->Flags = BUFFER_STATE_DIRTY;
Buffer->LoggerContext = LoggerContext;
} // if (Buffer != NULL)
} // if we can grow buffer
} else {
PLIST_ENTRY pEntry;
pEntry = RemoveHeadList(&LoggerContext->FreeList);
Buffer = CONTAINING_RECORD(
pEntry,
WMI_BUFFER_HEADER, Entry);
InterlockedDecrement(&LoggerContext->BuffersAvailable);
Buffer->Flags = BUFFER_STATE_DIRTY;
Buffer->SavedOffset = 0;
Buffer->CurrentOffset = sizeof(WMI_BUFFER_HEADER);
Buffer->ReferenceCount = 0;
Buffer->Wnode.ClientContext = 0;
Buffer->LoggerContext = LoggerContext;
}
#if DBG
if (Buffer != NULL) {
TraceDebug((3,
"WmipGetFreeBuffer: %X %d %d %d\n", Buffer->ClientContext,
Buffer->CurrentOffset, Buffer->SavedOffset,
Buffer->ReferenceCount));
}
#endif
return Buffer;
}
//
// Event trace/record and buffer related routines
//
#endif //WMI_NON_BLOCKING
PSYSTEM_TRACE_HEADER
FASTCALL
WmiReserveWithSystemHeader(
IN ULONG LoggerId,
IN ULONG AuxSize,
IN PETHREAD Thread,
OUT PWMI_BUFFER_HEADER *BufferResource
)
//
// This routine only works with IRQL <= DISPATCH_LEVEL
// It returns with LoggerContext locked, so caller must explicitly call
// WmipDereferenceLogger() after call WmipReleaseTraceBuffer()
//
{
PSYSTEM_TRACE_HEADER Header;
PWMI_LOGGER_CONTEXT LoggerContext;
#if DBG
LONG RefCount;
#endif
#if DBG
RefCount =
#endif
WmipReferenceLogger(LoggerId);
TraceDebug((4, "WmiReserveWithSystemHeader: %d %d->%d\n",
LoggerId, RefCount-1, RefCount));
LoggerContext = WmipGetLoggerContext(LoggerId);
AuxSize += sizeof(SYSTEM_TRACE_HEADER); // add header size first
Header = WmipReserveTraceBuffer(
LoggerContext, AuxSize, BufferResource);
if (Header != NULL) {
PerfTimeStamp(Header->SystemTime);
//
// Now copy the necessary information into the buffer
//
if (Thread == NULL) {
Thread = PsGetCurrentThread();
}
Header->Marker = SYSTEM_TRACE_MARKER;
Header->ThreadId = HandleToUlong(Thread->Cid.UniqueThread);
Header->ProcessId = HandleToUlong(Thread->Cid.UniqueProcess);
Header->KernelTime = Thread->Tcb.KernelTime;
Header->UserTime = Thread->Tcb.UserTime;
Header->Packet.Size = (USHORT) AuxSize;
}
else {
#if DBG
RefCount =
#endif
WmipDereferenceLogger(LoggerId); //Interlocked decrement
TraceDebug((4, "WmiReserveWithSystemHeader: %d %d->%d\n",
LoggerId, RefCount+1, RefCount));
}
// NOTE: Caller must still put in a proper MARKER
return Header;
}
PPERFINFO_TRACE_HEADER
FASTCALL
WmiReserveWithPerfHeader(
IN ULONG AuxSize,
OUT PWMI_BUFFER_HEADER *BufferResource
)
//
// This routine only works with IRQL <= DISPATCH_LEVEL
// It returns with LoggerContext locked, so caller must explicitly call
// WmipDereferenceLogger() after call WmipReleaseTraceBuffer()
//
{
PPERFINFO_TRACE_HEADER Header;
ULONG LoggerId = WmipKernelLogger;
#if DBG
LONG RefCount;
#endif
//
// We must have this check here to see the logger is still running
// before calling ReserveTraceBuffer.
// The stopping thread may have cleaned up the logger context at this
// point, which will cause AV.
// For all other kernel events, this check is made in callouts.c.
//
if (WmipIsLoggerOn(LoggerId) == NULL) {
return NULL;
}
#if DBG
RefCount =
#endif
WmipReferenceLogger(LoggerId);
TraceDebug((4, "WmiReserveWithPerfHeader: %d %d->%d\n",
LoggerId, RefCount-1, RefCount));
AuxSize += FIELD_OFFSET(PERFINFO_TRACE_HEADER, Data); // add header size first
Header = WmipReserveTraceBuffer(
WmipGetLoggerContext(LoggerId), AuxSize, BufferResource);
if (Header != NULL) {
PerfTimeStamp(Header->SystemTime);
//
// Now copy the necessary information into the buffer
//
Header->Marker = PERFINFO_TRACE_MARKER;
Header->Packet.Size = (USHORT) AuxSize;
} else {
#if DBG
RefCount =
#endif
WmipDereferenceLogger(LoggerId);
TraceDebug((4, "WmiWmiReserveWithPerfHeader: %d %d->%d\n",
LoggerId, RefCount+1, RefCount));
}
// NOTE: Caller must still put in a proper MARKER
return Header;
}
#ifdef WMI_NON_BLOCKING
PVOID
FASTCALL
WmipReserveTraceBuffer(
IN PWMI_LOGGER_CONTEXT LoggerContext,
IN ULONG RequiredSize,
OUT PWMI_BUFFER_HEADER *BufferResource
)
//
// This routine should work at any IRQL
//
{
PVOID ReservedSpace;
PWMI_BUFFER_HEADER Buffer;
ULONG Offset;
ULONG Processor;
PSINGLE_LIST_ENTRY SingleListEntry;
//
// Caller needs to ensure that the RequiredSize will not exceed
// BufferSize - sizeof(WMI_BUFFER_HEADER)
//
if (!WmipIsValidLogger(LoggerContext)) {
return NULL;
}
if (!LoggerContext->CollectionOn) {
return NULL;
}
*BufferResource = NULL;
RequiredSize = (ULONG) ALIGN_TO_POWER2(RequiredSize, WmiTraceAlignment);
TryFindSpace:
//
// Get processor number again here due to possible context switch
//
Processor = (ULONG) KeGetCurrentProcessorNumber();
//
// Get the processor specific buffer pool
//
SingleListEntry = InterlockedPopEntrySList(&LoggerContext->ProcessorBuffers[Processor]);
if (SingleListEntry == NULL) {
//
// Nothing in per process list, try to get one from free list
//
Buffer = WmipGetFreeBuffer (LoggerContext);
if (Buffer == NULL) {
//
// Nothing available
//
goto LostEvent;
} else {
//
// CPU information for the buffer.
//
Buffer->ClientContext.ProcessorNumber = (UCHAR) Processor;
}
} else {
//
// Found a Buffer.
//
Buffer = CONTAINING_RECORD (SingleListEntry,
WMI_BUFFER_HEADER,
SlistEntry);
}
//
// Check if there is enough space in this buffer.
//
Offset = Buffer->CurrentOffset + RequiredSize;
if (Offset < LoggerContext->BufferSize) {
//
// Space found.
//
ReservedSpace = (PVOID) (Buffer->CurrentOffset + (char*)Buffer);
Buffer->CurrentOffset = Offset;
if (LoggerContext->SequencePtr) {
*((PULONG) ReservedSpace) =
(ULONG)InterlockedIncrement(LoggerContext->SequencePtr);
}
goto FoundSpace;
} else {
WmipPushDirtyBuffer(LoggerContext, Buffer);
if (!(LoggerContext->LoggerMode & EVENT_TRACE_BUFFERING_MODE)) {
if (KeGetCurrentIrql() <= DISPATCH_LEVEL) {
//
// Wake up the walker thread to write it out to disk.
//
WmipNotifyLogger(LoggerContext);
} else {
//
// Queue the item.
//
InterlockedIncrement(&LoggerContext->ReleaseQueue);
}
}
goto TryFindSpace;
}
LostEvent:
//
// Will get here it we are throwing away the event
//
LoggerContext->EventsLost++; // best attempt to be accurate
ReservedSpace = NULL;
if (LoggerContext->SequencePtr) {
InterlockedIncrement(LoggerContext->SequencePtr);
}
FoundSpace:
//
// notify the logger after critical section
//
*BufferResource = Buffer;
return ReservedSpace;
}
#else
PVOID
FASTCALL
WmipReserveTraceBuffer(
IN PWMI_LOGGER_CONTEXT LoggerContext,
IN ULONG RequiredSize,
OUT PWMI_BUFFER_HEADER *BufferResource
)
//
// This routine should work at any IRQL
//
{
PVOID ReservedSpace;
PWMI_BUFFER_HEADER Buffer, OldBuffer;
ULONG Offset;
ULONG Processor;
ULONG CircularBufferOnly = FALSE;
//
// Caller needs to ensure that the RequiredSize will not exceed
// BufferSize - sizeof(WMI_BUFFER_HEADER)
//
if (!WmipIsValidLogger(LoggerContext)) {
return NULL;
}
if (!LoggerContext->CollectionOn) {
return NULL;
}
*BufferResource = NULL;
RequiredSize = (ULONG) ALIGN_TO_POWER2(RequiredSize, WmiTraceAlignment);
TryFindSpace:
// Get processor number again here due to possible context switch
Processor = (ULONG) KeGetCurrentProcessorNumber();
//
// Get the processor specific buffer pool
//
Buffer = LoggerContext->ProcessorBuffers[Processor];
if (Buffer == NULL)
return NULL;
//
// Increment refcount to buffer first to prevent it from going away
//
InterlockedIncrement(&Buffer->ReferenceCount);
if ( (Buffer->Flags != BUFFER_STATE_FULL) &&
(Buffer->Flags != BUFFER_STATE_UNUSED) ) {
//
// This should happen 99% of the time. Offset will have the old value
//
Offset = (ULONG) InterlockedExchangeAdd(
(PLONG) &Buffer->CurrentOffset, RequiredSize);
//
// First, check to see if there is enough space. If not, it will
// need to get another fresh buffer, and have the current buffer flushed
//
if (Offset+RequiredSize < LoggerContext->BufferSize) {
//
// Found the space so return it. This should happen 99% of the time
//
ReservedSpace = (PVOID) (Offset + (char*)Buffer);
if (LoggerContext->SequencePtr) {
*((PULONG) ReservedSpace) =
(ULONG)InterlockedIncrement(LoggerContext->SequencePtr);
}
goto FoundSpace;
}
}
else {
Offset = Buffer->CurrentOffset; // Initialize local variable
}
if (KeGetCurrentIrql() > DISPATCH_LEVEL) {
//
// Throw away event if at elevated IRQL.
//
goto LostEvent;
}
if (Offset < LoggerContext->BufferSize) {
Buffer->SavedOffset = Offset; // save this for FlushBuffer
}
//
// if there is absolutely no more buffers, then return quickly
//
if (((ULONG)LoggerContext->NumberOfBuffers == LoggerContext->MaximumBuffers)
&& (LoggerContext->BuffersAvailable == 0)) {
goto LostEvent;
}
//
// Out of buffer space. Need to take the long route to find a buffer
//
//
// Critical section starts here
//
Buffer->Flags = BUFFER_STATE_FULL;
OldBuffer = Buffer;
Buffer = WmipSwitchBuffer(
LoggerContext,
OldBuffer,
Processor);
if (Buffer == NULL) {
Buffer = OldBuffer;
goto LostEvent;
}
#if DBG
if (WmipTraceDebugLevel >= 3) {
DbgPrintEx(DPFLTR_WMILIB_ID,
DPFLTR_INFO_LEVEL,
"WmipReserveTraceBuffer: Inserted Buffer %X to FlushList\n",
OldBuffer);
DbgPrintEx(DPFLTR_WMILIB_ID,
DPFLTR_INFO_LEVEL,
"\t%X %d %d %d\n",
OldBuffer->ClientContext,
OldBuffer->ReferenceCount,
OldBuffer->CurrentOffset,
OldBuffer->SavedOffset);
}
#endif
//
// Decrement the refcount that we blindly incremented earlier
// so that it can be flushed by the logger thread
//
if (CircularBufferOnly) {
InterlockedDecrement(&OldBuffer->ReferenceCount);
}
else {
WmipReferenceLogger(LoggerContext->LoggerId); // since release will unlock
WmipReleaseTraceBuffer( OldBuffer, LoggerContext);
}
Buffer->ClientContext.ProcessorNumber = (UCHAR) Processor;
goto TryFindSpace;
//
// Will get here it we are throwing away the event
//
LostEvent:
LoggerContext->EventsLost++; // best attempt to be accurate
Buffer->EventsLost++;
InterlockedDecrement(&Buffer->ReferenceCount);
Buffer = NULL;
ReservedSpace = NULL;
if (LoggerContext->SequencePtr) {
InterlockedIncrement(LoggerContext->SequencePtr);
}
FoundSpace:
//
// notify the logger after critical section
//
*BufferResource = Buffer;
return ReservedSpace;
}
PWMI_BUFFER_HEADER
WmipSwitchBuffer(
IN PWMI_LOGGER_CONTEXT LoggerContext,
IN PWMI_BUFFER_HEADER OldBuffer,
IN ULONG Processor
)
//
// This routine works at IRQL <= DISPATCH_LEVEL
//
{
PWMI_BUFFER_HEADER Buffer;
KIRQL OldIrql;
ULONG CircularBufferOnly = FALSE;
#if DBG
TraceDebug((3, "WmipSwitchBuffer: Switching buffer %X proc %d\n",
OldBuffer, Processor));
#endif
if ( (LoggerContext->LoggerMode & EVENT_TRACE_BUFFERING_MODE) &&
(LoggerContext->BufferAgeLimit.QuadPart == 0) &&
(LoggerContext->LogFileHandle == NULL) ) {
CircularBufferOnly = TRUE;
}
ExAcquireSpinLock(&LoggerContext->BufferSpinLock, &OldIrql);
if (OldBuffer != LoggerContext->ProcessorBuffers[Processor]) {
ExReleaseSpinLock(&LoggerContext->BufferSpinLock, OldIrql);
#if DBG
TraceDebug((3, "WmipSwitchBuffer: Buffer is already switched!\n"));
#endif
return OldBuffer; // tell caller to try the new buffer
}
Buffer = WmipGetFreeBuffer(LoggerContext);
if (Buffer == NULL) {
// Release the spinlock immediately and return
ExReleaseSpinLock(&LoggerContext->BufferSpinLock, OldIrql);
#if DBG
TraceDebug((1, "WmipSwitchBuffer: Cannot locate free buffer\n"));
#endif
return NULL;
}
LoggerContext->ProcessorBuffers[Processor] = Buffer;
if (CircularBufferOnly) {
InsertTailList(&LoggerContext->FreeList, &OldBuffer->Entry);
InterlockedIncrement(&LoggerContext->BuffersAvailable);
LoggerContext->LastFlushedBuffer++;
#if DBG
TraceDebug((3, "WmipSwitchBuffer: Inserted Buf %X Entry %X to free\n",
OldBuffer, OldBuffer->Entry));
#endif
}
else {
InsertTailList(&LoggerContext->FlushList, &OldBuffer->Entry);
#if DBG
TraceDebug((3, "WmipSwitchBuffer: Inserted Buf %X Entry %X to flush\n",
OldBuffer, OldBuffer->Entry));
#endif
}
ExReleaseSpinLock(&LoggerContext->BufferSpinLock, OldIrql);
return Buffer;
}
#endif //WMI_NON_BLOCKING
//
// Actual Logger code starts here
//
VOID
WmipLogger(
IN PWMI_LOGGER_CONTEXT LoggerContext
)
/*++
Routine Description:
This function is the logger itself. It is started as a system thread.
It will not return until someone has stopped data collection or it
is not successful is flushing out a buffer (e.g. disk is full).
Arguments:
None.
Return Value:
The status of running the buffer manager
--*/
{
NTSTATUS Status;
ULONG ErrorCount;
#ifdef WMI_NON_BLOCKING
ULONG FlushCount = 0;
LARGE_INTEGER OneSecond = {(ULONG)(-1 * 1000 * 1000 * 10), -1};
ULONG FlushTimeOut;
#else
PSINGLE_LIST_ENTRY Entry;
PWMI_BUFFER_HEADER Buffer;
PLARGE_INTEGER FlushTimeOut;
PLIST_ENTRY pEntry;
ULONG NumberOfBuffers, i;
#endif //WMI_NON_BLOCKING
PAGED_CODE();
LoggerContext->LoggerThread = PsGetCurrentThread();
if ((LoggerContext->LoggerMode & EVENT_TRACE_DELAY_OPEN_FILE_MODE)
|| (LoggerContext->LogFileName.Length == 0)) {
// If EVENT_TRACE_DELAY_OPEN_FILE_MODE is specified, WMI does not
// need to create logfile now.
//
// If there is no LogFileName specified, WMI does not need to create
// logfile either. WmipStartLogger() already checks all possible
// combination of LoggerMode and LogFileName, so we don't need to
// perform the same check again.
//
Status = STATUS_SUCCESS;
} else {
Status = WmipCreateLogFile(LoggerContext,
FALSE,
LoggerContext->LoggerMode & EVENT_TRACE_FILE_MODE_APPEND);
}
LoggerContext->LoggerStatus = Status;
if (NT_SUCCESS(Status)) {
//
// This is the only place where CollectionOn will be turn on!!!
//
LoggerContext->CollectionOn = TRUE;
KeSetEvent(&LoggerContext->LoggerEvent, 0, FALSE);
} else {
if (LoggerContext->LogFileHandle != NULL) {
Status = ZwClose(LoggerContext->LogFileHandle);
LoggerContext->LogFileHandle = NULL;
}
KeSetEvent(&LoggerContext->LoggerEvent, 0, FALSE);
PsTerminateSystemThread(Status);
return;
}
ErrorCount = 0;
// by now, the caller has been notified that the logger is running
//
// Loop and wait for buffers to be filled until someone turns off CollectionOn
//
KeSetPriorityThread(KeGetCurrentThread(), LOW_REALTIME_PRIORITY-1);
#ifdef WMI_NON_BLOCKING
FlushCount = 0;
#endif //WMI_NON_BLOCKING
while (LoggerContext->CollectionOn) {
#ifdef WMI_NON_BLOCKING
if (LoggerContext->LoggerMode & EVENT_TRACE_BUFFERING_MODE) {
//
// Wait forever until signalled by when logging is terminated.
//
Status = KeWaitForSingleObject(
&LoggerContext->LoggerSemaphore,
Executive,
KernelMode,
FALSE,
NULL);
LoggerContext->LoggerStatus = STATUS_SUCCESS;
} else {
ULONG FlushAll = 0;
ULONG FlushFlag;
FlushTimeOut = LoggerContext->FlushTimer;
//
// Wake up every second to see if there are any buffers in
// flush list.
//
Status = KeWaitForSingleObject(
&LoggerContext->LoggerSemaphore,
Executive,
KernelMode,
FALSE,
&OneSecond);
//
// Check if number of buffers need to be adjusted.
//
WmipAdjustFreeBuffers(LoggerContext);
#else
FlushTimeOut = &LoggerContext->FlushTimer;
if ( (*FlushTimeOut).QuadPart == 0) // so that it can be set anytime
FlushTimeOut = NULL;
Status = KeWaitForSingleObject(
&LoggerContext->LoggerSemaphore,
Executive,
KernelMode,
FALSE,
FlushTimeOut);
#endif //WMI_NON_BLOCKING
LoggerContext->LoggerStatus = STATUS_SUCCESS;
if (LoggerContext->RequestFlag & REQUEST_FLAG_NEW_FILE) {
Status = STATUS_SUCCESS;
if (LoggerContext->LoggerMode & EVENT_TRACE_FILE_MODE_NEWFILE) {
if (LoggerContext->LogFilePattern.Buffer == NULL) {
Status = STATUS_INVALID_PARAMETER;
}
else {
Status = WmipGenerateFileName(
&LoggerContext->LogFilePattern,
(PLONG) &LoggerContext->FileCounter,
&LoggerContext->NewLogFileName);
}
}
if (NT_SUCCESS(Status)) {
//
// called to switch to a different file
// switch immediately
//
TraceDebug((3, "WmipLogger: New File\n"));
LoggerContext->LoggerStatus = WmipCreateLogFile(LoggerContext,
TRUE,
EVENT_TRACE_FILE_MODE_APPEND);
if (NT_SUCCESS(LoggerContext->LoggerStatus)) {
LoggerContext->LoggerMode &= ~EVENT_TRACE_DELAY_OPEN_FILE_MODE;
}
}
else {
LoggerContext->LoggerStatus = Status;
}
KeSetEvent(&LoggerContext->FlushEvent, 0, FALSE);
continue;
}
#ifdef WMI_NON_BLOCKING
if (Status == STATUS_TIMEOUT) {
if (FlushTimeOut) {
FlushCount++;
if (FlushCount >= FlushTimeOut) {
#if DBG
ULONG64 Now;
KeQuerySystemTime((PLARGE_INTEGER) &Now);
TraceDebug((3, "WmipLogger (%2d): Timeout at %I64u\n",
LoggerContext->LoggerId,
Now));
#endif
FlushAll = 1;
// reset the couter
FlushCount = 0;
} else {
FlushAll = 0;
}
} else {
FlushAll = 0;
}
}
#else
FlushAll = ((FlushTimeOut != NULL) && (Status == STATUS_TIMEOUT));
#endif //WMI_NON_BLOCKING
FlushFlag = (LoggerContext->RequestFlag & REQUEST_FLAG_FLUSH_BUFFERS);
if ( FlushFlag )
FlushAll = TRUE;
#ifdef NTPERF
if (PERFINFO_IS_LOGGING_TO_PERFMEM()) {
//
// Now check if we are in delay close mode.
// Create the log file.
//
ULONG LoggerMode = LoggerContext->LoggerMode;
if ((LoggerContext->LogFileHandle == NULL) &&
(LoggerMode & EVENT_TRACE_DELAY_OPEN_FILE_MODE) &&
(WmipFileSystemReady != 0)) {
if (LoggerContext->LogFileName.Buffer != NULL) {
ULONG Append = LoggerMode & EVENT_TRACE_FILE_MODE_APPEND;
Status = WmipDelayCreate(&LoggerContext->LogFileHandle,
&LoggerContext->LogFileName,
Append);
if (NT_SUCCESS(Status)) {
//
// Now the file has been created, add the log file header
//
LoggerContext->LoggerMode &= ~EVENT_TRACE_DELAY_OPEN_FILE_MODE;
if (!Append) {
Status = WmipAddLogHeader(LoggerContext, NULL);
}
}
}
}
} else {
#endif //NTPERF
Status = WmipFlushActiveBuffers(LoggerContext, FlushAll);
//
// Should check the status, and if failed to write a log file
// header, should clean up. As the log file is bad anyway.
//
if ( FlushFlag ) {
LoggerContext->RequestFlag &= ~REQUEST_FLAG_FLUSH_BUFFERS;
//
// If this was a flush for persistent events, this request flag must
// be reset here.
//
LoggerContext->RequestFlag &= ~REQUEST_FLAG_CIRCULAR_TRANSITION;
LoggerContext->LoggerStatus = Status;
KeSetEvent(&LoggerContext->FlushEvent, 0, FALSE);
}
if (!NT_SUCCESS(Status)) {
LoggerContext->LoggerStatus = Status;
WmipStopLoggerInstance(LoggerContext);
}
#ifdef NTPERF
}
#endif //NTPERF
}
} // while loop
if (Status == STATUS_TIMEOUT) {
Status = STATUS_SUCCESS;
}
//
// if a normal collection end, flush out all the buffers before stopping
//
TraceDebug((2, "WmipLogger: Flush all buffers before stopping...\n"));
//
// First, move the per processor buffer out to FlushList
//
#ifdef WMI_NON_BLOCKING
while ((LoggerContext->NumberOfBuffers > 0) &&
(LoggerContext->NumberOfBuffers > LoggerContext->BuffersAvailable)) {
Status = KeWaitForSingleObject(
&LoggerContext->LoggerSemaphore,
Executive,
KernelMode,
FALSE,
&OneSecond);
WmipFlushActiveBuffers(LoggerContext, 1);
TraceDebug((2, "WmipLogger: Stop %d %d %d %d %d\n",
LoggerContext->LoggerId,
LoggerContext->BuffersAvailable,
LoggerContext->BuffersInUse,
LoggerContext->BuffersDirty,
LoggerContext->NumberOfBuffers));
}
#else
for (i=0; i<(ULONG)KeNumberProcessors; i++) {
Buffer = LoggerContext->ProcessorBuffers[i];
LoggerContext->ProcessorBuffers[i] = NULL;
InsertTailList(&LoggerContext->FlushList, &Buffer->Entry);
#if DBG
if (WmipTraceDebugLevel >= 3) {
DbgPrintEx(DPFLTR_WMILIB_ID,
DPFLTR_INFO_LEVEL,
"WmipLogger: Inserted %d buffer %X to FlushList\n",
i,
Buffer);
DbgPrintEx(DPFLTR_WMILIB_ID,
DPFLTR_INFO_LEVEL,
"\t%X %d %d %d\n",
Buffer->ClientContext,
Buffer->CurrentOffset,
Buffer->SavedOffset,
Buffer->ReferenceCount);
}
#endif
}
NumberOfBuffers = LoggerContext->NumberOfBuffers;
while ( NumberOfBuffers > 0 &&
(LoggerContext->BuffersAvailable < LoggerContext->NumberOfBuffers) )
{
pEntry = ExInterlockedRemoveHeadList(
&LoggerContext->FlushList,
&LoggerContext->BufferSpinLock);
if (pEntry == NULL)
break;
if (NT_SUCCESS(Status) {
Buffer = CONTAINING_RECORD(pEntry, WMI_BUFFER_HEADER, Entry);
TraceDebug((3,
"WmipLogger: Removed buffer %X from FlushList\n",Buffer));
WmipFlushBuffer( LoggerContext, Buffer);
}
ExInterlockedInsertHeadList(
&LoggerContext->FreeList,
&Buffer->Entry,
&LoggerContext->BufferSpinLock);
NumberOfBuffers--;
}
#endif //WMI_NON_BLOCKING
//
// Note that LoggerContext->LogFileObject needs to remain set
// for QueryLogger to work after close
//
if (LoggerContext->LogFileHandle != NULL) {
ZwClose(LoggerContext->LogFileHandle);
TraceDebug((1, "WmipLogger: Close logfile with status=%X\n", Status));
}
LoggerContext->LogFileHandle = NULL;
KeSetEvent(&LoggerContext->FlushEvent, 0, FALSE);
KeSetEvent(&LoggerContext->LoggerEvent, 0, FALSE);
#if DBG
if (!NT_SUCCESS(Status)) {
TraceDebug((1, "WmipLogger: Aborting %d %X\n",
LoggerContext->LoggerId, LoggerContext->LoggerStatus));
}
#endif
WmipFreeLoggerContext(LoggerContext);
#ifdef NTPERF
//
// Check if we are logging into perfmem.
//
if (PERFINFO_IS_LOGGING_TO_PERFMEM()) {
PerfInfoStopPerfMemLog();
}
#endif //NTPERF
PsTerminateSystemThread(Status);
}
NTSTATUS
WmipSendNotification(
PWMI_LOGGER_CONTEXT LoggerContext,
NTSTATUS Status,
ULONG Flag
)
{
WMI_TRACE_EVENT WmiEvent;
RtlZeroMemory(& WmiEvent, sizeof(WmiEvent));
WmiEvent.Status = Status;
KeQuerySystemTime(& WmiEvent.Wnode.TimeStamp);
WmiEvent.Wnode.BufferSize = sizeof(WmiEvent);
WmiEvent.Wnode.Guid = TraceErrorGuid;
WmiSetLoggerId(
LoggerContext->LoggerId,
(PTRACE_ENABLE_CONTEXT) & WmiEvent.Wnode.HistoricalContext);
WmiEvent.Wnode.ClientContext = 0XFFFFFFFF;
WmiEvent.TraceErrorFlag = Flag;
WmipProcessEvent(&WmiEvent.Wnode,
FALSE,
FALSE);
return STATUS_SUCCESS;
}
//
// convenience routine to flush the current buffer by the logger above
//
NTSTATUS
WmipFlushBuffer(
IN PWMI_LOGGER_CONTEXT LoggerContext,
IN PWMI_BUFFER_HEADER Buffer
)
/*++
Routine Description:
This function is responsible for flushing a filled buffer out to
disk, or to a real time consumer.
Arguments:
LoggerContext Context of the logger
Return Value:
The status of flushing the buffer
--*/
{
IO_STATUS_BLOCK IoStatus;
NTSTATUS Status;
#ifndef WMI_NON_BLOCKING
PWMI_BUFFER_HEADER OldBuffer;
KIRQL OldIrql;
#endif
ULONG BufferSize;
ULONG BufferPersistenceData = LoggerContext->RequestFlag
& ( REQUEST_FLAG_CIRCULAR_PERSIST
| REQUEST_FLAG_CIRCULAR_TRANSITION);
ASSERT(LoggerContext != NULL);
ASSERT(Buffer != NULL);
//
// Grab the buffer to be flushed
//
BufferSize = LoggerContext->BufferSize;
//
// Put end of record marker in buffer if available space
//
TraceDebug((2, "WmipFlushBuffer: %p, Flushed %X %d %d %d\n",
Buffer,
Buffer->ClientContext, Buffer->SavedOffset,
Buffer->CurrentOffset, LoggerContext->BuffersWritten));
Status = WmipPrepareHeader(LoggerContext, Buffer);
if (Status != STATUS_SUCCESS)
goto ResetTraceBuffer;
//
// Buffering mode is mutually exclusive with REAL_TIME_MODE
//
if (!(LoggerContext->LoggerMode & EVENT_TRACE_BUFFERING_MODE)) {
if (LoggerContext->LoggerMode & EVENT_TRACE_REAL_TIME_MODE) {
if (LoggerContext->UsePerfClock == EVENT_TRACE_CLOCK_PERFCOUNTER)
Buffer->Wnode.Flags |= WNODE_FLAG_USE_TIMESTAMP;
// need to see if we can send anymore
// check for queue length
if (! NT_SUCCESS(WmipProcessEvent((PWNODE_HEADER)Buffer,
FALSE,
FALSE))) {
LoggerContext->RealTimeBuffersLost++;
}
}
}
if (LoggerContext->LogFileHandle == NULL) {
goto ResetTraceBuffer;
}
if (LoggerContext->MaximumFileSize > 0) { // if quota given
ULONG64 FileSize = LoggerContext->LastFlushedBuffer * BufferSize;
ULONG64 FileLimit = LoggerContext->MaximumFileSize * BYTES_PER_MB;
if ( FileSize >= FileLimit ) {
ULONG LoggerMode = LoggerContext->LoggerMode & 0X000000FF;
//
// Files from user mode always have the APPEND flag.
// We mask it out here to simplify the testing below.
//
LoggerMode &= ~EVENT_TRACE_FILE_MODE_APPEND;
//
// PREALLOCATE flag has to go, too.
//
LoggerMode &= ~EVENT_TRACE_FILE_MODE_PREALLOCATE;
if (LoggerMode == EVENT_TRACE_FILE_MODE_SEQUENTIAL) {
// do not write to logfile anymore
Status = STATUS_LOG_FILE_FULL; // control needs to stop logging
// need to fire up a Wmi Event to control console
WmipSendNotification(LoggerContext,
Status, STATUS_SEVERITY_ERROR);
}
else if (LoggerMode == EVENT_TRACE_FILE_MODE_CIRCULAR) {
if (BufferPersistenceData > 0) {
// treat circular logfile as sequential logfile if
// logger still processes Persistence events (events
// that cannot be overwritten in circular manner).
//
Status = STATUS_LOG_FILE_FULL;
WmipSendNotification(LoggerContext,
Status, STATUS_SEVERITY_ERROR);
}
else {
// reposition file
LoggerContext->ByteOffset
= LoggerContext->FirstBufferOffset;
LoggerContext->LastFlushedBuffer = (ULONG)
(LoggerContext->FirstBufferOffset.QuadPart
/ LoggerContext->BufferSize);
}
}
else if (LoggerMode & EVENT_TRACE_FILE_MODE_NEWFILE) {
HANDLE OldHandle, NewHandle;
UNICODE_STRING NewFileName, OldFileName;
ULONG BuffersWritten;
NewFileName.Buffer = NULL;
Status = WmipGenerateFileName(
&LoggerContext->LogFilePattern,
(PLONG) &LoggerContext->FileCounter,
&NewFileName);
if (NT_SUCCESS(Status)) {
//
// Now the file has been created, add the log file header
//
Status = WmipDelayCreate(&NewHandle,
&NewFileName,
FALSE);
Buffer = WmipGetFreeBuffer(LoggerContext);
if (NT_SUCCESS(Status) && (Buffer != NULL)) {
//
// Now the file has been created, add the log file header
//
BuffersWritten = LoggerContext->BuffersWritten;
LoggerContext->BuffersWritten = 1;
Status = WmipAddLogHeader(LoggerContext, Buffer);
if (NT_SUCCESS(Status)) {
LoggerContext->BuffersWritten = 0;
Status = WmipPrepareHeader(LoggerContext, Buffer);
if (Status == STATUS_SUCCESS) {
Status = ZwWriteFile(
NewHandle,
NULL, NULL, NULL,
&IoStatus,
Buffer,
BufferSize,
NULL, NULL);
// NOTE: Header contains oldfilename!
}
LoggerContext->BuffersWritten = BuffersWritten;
}
if (NT_SUCCESS(Status)) {
OldFileName = LoggerContext->LogFileName;
OldHandle = LoggerContext->LogFileHandle;
if (OldHandle) {
WmipFinalizeHeader(OldHandle, LoggerContext);
ZwClose(OldHandle);
}
LoggerContext->LogFileHandle = NewHandle;
LoggerContext->LogFileName = NewFileName;
LoggerContext->BuffersWritten = 1;
LoggerContext->LastFlushedBuffer = 1;
LoggerContext->ByteOffset.QuadPart = BufferSize;
// NOTE: Assumes LogFileName cannot be changed
// for NEWFILE mode!!!
if (OldFileName.Buffer != NULL) {
RtlFreeUnicodeString(&OldFileName);
}
WmipSendNotification(LoggerContext,
STATUS_MEDIA_CHANGED, STATUS_SEVERITY_INFORMATIONAL);
}
else {
ZwClose(NewHandle);
LoggerContext->BuffersWritten = BuffersWritten;
}
}
if (Buffer) {
InterlockedPushEntrySList(&LoggerContext->FreeList,
(PSINGLE_LIST_ENTRY) &Buffer->SlistEntry);
InterlockedIncrement((PLONG) &LoggerContext->BuffersAvailable);
InterlockedDecrement((PLONG) &LoggerContext->BuffersInUse);
}
}
if (!NT_SUCCESS(Status) && (NewFileName.Buffer != NULL)) {
ExFreePool(NewFileName.Buffer);
}
}
}
}
if (NT_SUCCESS(Status)) {
Status = ZwWriteFile(
LoggerContext->LogFileHandle,
NULL,
NULL,
NULL,
&IoStatus,
Buffer,
BufferSize,
&LoggerContext->ByteOffset,
NULL);
if (NT_SUCCESS(Status)) {
LoggerContext->ByteOffset.QuadPart += BufferSize;
if (BufferPersistenceData > 0) {
// update FirstBufferOffset so that persistence event will
// not be overwritten in circular logfile
//
LoggerContext->FirstBufferOffset.QuadPart += BufferSize;
}
}
else if (Status == STATUS_LOG_FILE_FULL ||
Status == STATUS_DISK_FULL) {
// need to fire up a Wmi Event to control console
WmipSendNotification(LoggerContext,
STATUS_LOG_FILE_FULL, STATUS_SEVERITY_ERROR);
}
else {
TraceDebug((2, "WmipFlushBuffer: Unknown WriteFile Failure with status=%X\n", Status));
}
}
ResetTraceBuffer:
if (NT_SUCCESS(Status)) {
LoggerContext->BuffersWritten++;
LoggerContext->LastFlushedBuffer++;
}
else {
#if DBG
if (Status == STATUS_NO_DATA_DETECTED) {
TraceDebug((2, "WmipFlushBuffer: Empty buffer detected\n"));
}
else if (Status == STATUS_SEVERITY_WARNING) {
TraceDebug((2, "WmipFlushBuffer: Buffer could be corrupted\n"));
}
else {
TraceDebug((2,
"WmipFlushBuffer: Unable to write buffer: status=%X\n",
Status));
}
#endif
if ((Status != STATUS_NO_DATA_DETECTED) &&
(Status != STATUS_SEVERITY_WARNING))
LoggerContext->LogBuffersLost++;
}
if (WmipGlobalBufferCallback) {
(WmipGlobalBufferCallback) (Buffer, WmipGlobalCallbackContext);
}
if (LoggerContext->BufferCallback) {
(LoggerContext->BufferCallback) (Buffer, LoggerContext->CallbackContext);
}
//
// Reset the buffer state
//
Buffer->EventsLost = 0;
Buffer->SavedOffset = 0;
#ifndef WMI_NON_BLOCKING
Buffer->ReferenceCount = 0;
#endif //WMI_NON_BLOCKING
Buffer->Flags = BUFFER_STATE_UNUSED;
//
// Try and remove an unused buffer if it has not been used for a while
//
#ifndef WMI_NON_BLOCKING
if ((LoggerContext->BufferAgeLimit.QuadPart > 0) &&
(WmipRefCount[LoggerContext->LoggerId] <= 2) &&
(LoggerContext->BuffersAvailable + (ULONG)KeNumberProcessors)
> LoggerContext->MinimumBuffers) {
PLIST_ENTRY Entry;
ExAcquireSpinLock(&LoggerContext->BufferSpinLock, &OldIrql);
Entry = LoggerContext->FreeList.Blink;
OldBuffer = (PWMI_BUFFER_HEADER)
CONTAINING_RECORD( Entry, WMI_BUFFER_HEADER, Entry);
#if DBG
TraceDebug((2, "Aging test %I64u %I64u %I64u\n",
Buffer->TimeStamp, OldBuffer->TimeStamp,
LoggerContext->BufferAgeLimit));
#endif
if (Entry != &LoggerContext->FreeList) {
if (((Buffer->TimeStamp.QuadPart - OldBuffer->TimeStamp.QuadPart)
+ LoggerContext->BufferAgeLimit.QuadPart) > 0) {
if ((ULONG) LoggerContext->NumberOfBuffers
> LoggerContext->MinimumBuffers){
RemoveTailList(&LoggerContext->FreeList);
ExFreePool(OldBuffer);
ExReleaseSpinLock(&LoggerContext->BufferSpinLock, OldIrql);
InterlockedDecrement((PLONG) &LoggerContext->NumberOfBuffers);
return Status; // do not increment BuffersAvailable
}
}
}
ExReleaseSpinLock(&LoggerContext->BufferSpinLock, OldIrql);
}
InterlockedIncrement((PLONG) &LoggerContext->BuffersAvailable);
#endif //WMI_NON_BLOCKING
return Status;
}
NTSTATUS
WmipCreateLogFile(
IN PWMI_LOGGER_CONTEXT LoggerContext,
IN ULONG SwitchFile,
IN ULONG Append
)
{
NTSTATUS Status;
HANDLE newHandle = NULL;
IO_STATUS_BLOCK IoStatus;
FILE_STANDARD_INFORMATION FileSize;
LARGE_INTEGER ByteOffset;
BOOLEAN FileSwitched = FALSE;
UNICODE_STRING OldLogFileName;
PWCHAR strLogFileName = NULL;
PUCHAR pFirstBuffer = NULL;
PAGED_CODE();
RtlZeroMemory(&OldLogFileName, sizeof(UNICODE_STRING));
LoggerContext->RequestFlag &= ~REQUEST_FLAG_NEW_FILE;
pFirstBuffer = (PUCHAR) ExAllocatePoolWithTag(
PagedPool, LoggerContext->BufferSize, TRACEPOOLTAG);
if(pFirstBuffer == NULL) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
RtlZeroMemory(pFirstBuffer, LoggerContext->BufferSize);
if (SwitchFile) {
Status = WmipCreateNtFileName(
LoggerContext->NewLogFileName.Buffer,
& strLogFileName);
}
else {
Status = WmipCreateNtFileName(
LoggerContext->LogFileName.Buffer,
& strLogFileName);
}
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
if (LoggerContext->ClientSecurityContext.ClientToken != NULL) {
Status = SeImpersonateClientEx(
&LoggerContext->ClientSecurityContext, NULL);
}
if (NT_SUCCESS(Status)) {
// first open logfile using user security context
//
Status = WmipCreateDirectoryFile(strLogFileName, FALSE, & newHandle, Append);
PsRevertToSelf();
}
if (!NT_SUCCESS(Status)) {
// if using user security context fails to open logfile,
// then try open logfile again using local system security context
//
Status = WmipCreateDirectoryFile(strLogFileName, FALSE, & newHandle, Append);
}
if (NT_SUCCESS(Status)) {
HANDLE tempHandle = LoggerContext->LogFileHandle;
PWMI_BUFFER_HEADER BufferChecksum;
PTRACE_LOGFILE_HEADER LogfileHeaderChecksum;
ULONG BuffersWritten = 0;
BufferChecksum = (PWMI_BUFFER_HEADER) LoggerContext->LoggerHeader;
LogfileHeaderChecksum = (PTRACE_LOGFILE_HEADER)
(((PUCHAR) BufferChecksum) + sizeof(WNODE_HEADER));
if (LogfileHeaderChecksum) {
BuffersWritten = LogfileHeaderChecksum->BuffersWritten;
}
ByteOffset.QuadPart = 0;
Status = ZwReadFile(
newHandle,
NULL,
NULL,
NULL,
& IoStatus,
pFirstBuffer,
LoggerContext->BufferSize,
& ByteOffset,
NULL);
if (NT_SUCCESS(Status)) {
PWMI_BUFFER_HEADER BufferFile;
PTRACE_LOGFILE_HEADER LogfileHeaderFile;
ULONG Size;
BufferFile =
(PWMI_BUFFER_HEADER) pFirstBuffer;
if (BufferFile->Wnode.BufferSize != LoggerContext->BufferSize) {
TraceDebug((1,
"WmipCreateLogFile::BufferSize check fails (%d,%d)\n",
BufferFile->Wnode.BufferSize,
LoggerContext->BufferSize));
Status = STATUS_FAIL_CHECK;
ZwClose(newHandle);
goto Cleanup;
}
if (RtlCompareMemory(BufferFile,
BufferChecksum,
sizeof(WNODE_HEADER))
!= sizeof(WNODE_HEADER)) {
TraceDebug((1,"WmipCreateLogFile::WNODE_HEAD check fails\n"));
Status = STATUS_FAIL_CHECK;
ZwClose(newHandle);
goto Cleanup;
}
LogfileHeaderFile = (PTRACE_LOGFILE_HEADER)
(((PUCHAR) BufferFile) + sizeof(WMI_BUFFER_HEADER)
+ sizeof(SYSTEM_TRACE_HEADER));
// We can only validate part of the header because a 32-bit
// DLL will be passing in 32-bit pointers
Size = FIELD_OFFSET(TRACE_LOGFILE_HEADER, LoggerName);
if (RtlCompareMemory(LogfileHeaderFile,
LogfileHeaderChecksum,
Size)
!= Size) {
TraceDebug((1,
"WmipCreateLogFile::TRACE_LOGFILE_HEAD check fails\n"));
Status = STATUS_FAIL_CHECK;
ZwClose(newHandle);
goto Cleanup;
}
}
else {
ZwClose(newHandle);
goto Cleanup;
}
if (LoggerContext->LoggerMode & EVENT_TRACE_FILE_MODE_PREALLOCATE) {
ByteOffset.QuadPart = ((LONGLONG) LoggerContext->BufferSize) * BuffersWritten;
}
else {
Status = ZwQueryInformationFile(
newHandle,
&IoStatus,
&FileSize,
sizeof (FILE_STANDARD_INFORMATION),
FileStandardInformation
);
if (!NT_SUCCESS(Status)) {
ZwClose(newHandle);
goto Cleanup;
}
ByteOffset = FileSize.EndOfFile;
}
//
// Force to 1K alignment. In future, if disk alignment exceeds this,
// then use that
//
if ((ByteOffset.QuadPart % 1024) != 0) {
ByteOffset.QuadPart = ((ByteOffset.QuadPart / 1024) + 1) * 1024;
}
if (!(LoggerContext->LoggerMode & EVENT_TRACE_FILE_MODE_PREALLOCATE)) {
// NOTE: Should also validate BuffersWritten from logfile header with
// the end of file to make sure that no one else has written garbage
// to it
//
if (ByteOffset.QuadPart !=
( ((LONGLONG) LoggerContext->BufferSize)
* BuffersWritten)) {
TraceDebug((1,
"WmipCreateLogFile::FileSize check fails (%I64d,%I64d)\n",
ByteOffset.QuadPart,
( ((LONGLONG) LoggerContext->BufferSize)
* BuffersWritten)));
Status = STATUS_FAIL_CHECK;
ZwClose(newHandle);
goto Cleanup;
}
}
LoggerContext->FirstBufferOffset = ByteOffset;
LoggerContext->ByteOffset = ByteOffset;
if (LoggerContext->LoggerMode & EVENT_TRACE_FILE_MODE_PREALLOCATE) {
LoggerContext->BuffersWritten = BuffersWritten;
}
else {
LoggerContext->BuffersWritten = (ULONG) (FileSize.EndOfFile.QuadPart / LoggerContext->BufferSize);
}
LoggerContext->LastFlushedBuffer = LoggerContext->BuffersWritten;
// Update the log file handle and log file name in the LoggerContext
LoggerContext->LogFileHandle = newHandle;
if (SwitchFile) {
OldLogFileName = LoggerContext->LogFileName;
LoggerContext->LogFileName = LoggerContext->NewLogFileName;
FileSwitched = TRUE;
if ( tempHandle != NULL) {
//
// just to be safe, close old file after the switch
//
TraceDebug((1, "WmipCreateLogFile: Closing handle %X\n",
tempHandle));
ZwClose(tempHandle);
}
}
}
#if DBG
else {
TraceDebug((1,
"WmipCreateLogFile: ZwCreateFile(%ws) failed with status=%X\n",
LoggerContext->LogFileName.Buffer, Status));
}
#endif
Cleanup:
SeDeleteClientSecurity(& LoggerContext->ClientSecurityContext);
LoggerContext->ClientSecurityContext.ClientToken = NULL;
// Clean up unicode strings.
if (SwitchFile) {
if (!FileSwitched) {
RtlFreeUnicodeString(&LoggerContext->NewLogFileName);
}
else if (OldLogFileName.Buffer != NULL) {
// OldLogFileName.Buffer can still be NULL if it is the first update
// for the Kernel Logger.
RtlFreeUnicodeString(&OldLogFileName);
}
// Must do this for the next file switch.
RtlZeroMemory(&LoggerContext->NewLogFileName, sizeof(UNICODE_STRING));
}
if (strLogFileName != NULL) {
ExFreePool(strLogFileName);
}
if (pFirstBuffer != NULL) {
ExFreePool(pFirstBuffer);
}
LoggerContext->LoggerStatus = Status;
return Status;
}
#ifdef WMI_NON_BLOCKING
ULONG
FASTCALL
WmipReleaseTraceBuffer(
IN PWMI_BUFFER_HEADER BufferResource,
IN PWMI_LOGGER_CONTEXT LoggerContext
)
{
ULONG Processor;
LONG ReleaseQueue;
Processor = BufferResource->ClientContext.ProcessorNumber;
// NOTE: It is important to remember LoggerContext here, since
// BufferResource can be modified after the ReleaseSemaphore
InterlockedPushEntrySList (&LoggerContext->ProcessorBuffers[Processor],
(PSINGLE_LIST_ENTRY) &BufferResource->SlistEntry);
//
// Check if there are buffers to be flushed.
//
if (LoggerContext->ReleaseQueue) {
if (KeGetCurrentIrql() <= DISPATCH_LEVEL) {
WmipNotifyLogger(LoggerContext);
LoggerContext->ReleaseQueue = 0;
}
}
ReleaseQueue = LoggerContext->ReleaseQueue;
WmipDereferenceLogger(LoggerContext->LoggerId);
return (ReleaseQueue);
}
#else
ULONG
FASTCALL
WmipReleaseTraceBuffer(
IN PWMI_BUFFER_HEADER BufferResource,
IN PWMI_LOGGER_CONTEXT LoggerContext
)
//
// Caller must have locked the logger context via WmipReferenceLogger()
// as this routine will unlock it
//
{
ULONG BufRefCount;
#if DBG
ULONG RefCount;
#endif
if (BufferResource == NULL)
return 0;
BufRefCount = InterlockedDecrement((PLONG) &BufferResource->ReferenceCount);
// NOTE: It is important to remember LoggerContext here, since
// BufferResource can be modified after the ReleaseSemaphore
if ((BufRefCount == 0) && (BufferResource->Flags == BUFFER_STATE_FULL)) {
if (KeGetCurrentIrql() <= DISPATCH_LEVEL) {
BufferResource->Flags = BUFFER_STATE_FLUSH;
TraceDebug((2, "WmipReleaseTraceBuffer: Releasing buffer\n"));
WmipNotifyLogger(LoggerContext);
while (LoggerContext->ReleaseQueue > 0) {
WmipNotifyLogger(LoggerContext);
InterlockedDecrement((PLONG) &LoggerContext->ReleaseQueue);
}
}
else {
InterlockedIncrement((PLONG) &LoggerContext->ReleaseQueue);
TraceDebug((2, "WmipReleaseTraceBuffer: %d\n",
KeGetCurrentIrql() ));
}
}
#if DBG
RefCount =
#endif
WmipDereferenceLogger(LoggerContext->LoggerId);
return BufRefCount;
}
#endif //WMI_NON_BLOCKING
NTKERNELAPI
ULONG
FASTCALL
WmiReleaseKernelBuffer(
IN PWMI_BUFFER_HEADER BufferResource
)
{
PWMI_LOGGER_CONTEXT LoggerContext = WmipLoggerContext[WmipKernelLogger];
if (LoggerContext == (PWMI_LOGGER_CONTEXT) &WmipLoggerContext[0]) {
LoggerContext = BufferResource->LoggerContext;
}
WmipAssert(LoggerContext != NULL);
WmipAssert(LoggerContext != (PWMI_LOGGER_CONTEXT) &WmipLoggerContext[0]);
return WmipReleaseTraceBuffer(BufferResource, LoggerContext);
}
NTSTATUS
WmipFlushActiveBuffers(
IN PWMI_LOGGER_CONTEXT LoggerContext,
IN ULONG FlushAll
)
{
PWMI_BUFFER_HEADER Buffer;
ULONG i, ErrorCount;
NTSTATUS Status = STATUS_SUCCESS;
ULONG LoggerMode;
#ifdef WMI_NON_BLOCKING
PSINGLE_LIST_ENTRY Entry;
#else
ULONG FlushCount = 0;
PLIST_ENTRY pEntry;
#endif //WMI_NON_BLOCKING
#ifdef WMI_NON_BLOCKING
PAGED_CODE();
if (FlushAll) {
//
// Put all in-used buffers into flush list
//
for (i=0; i<(ULONG)KeNumberProcessors; i++) {
while (Entry = InterlockedPopEntrySList(&LoggerContext->ProcessorBuffers[i])){
Buffer = CONTAINING_RECORD(Entry,
WMI_BUFFER_HEADER,
SlistEntry);
WmipPushDirtyBuffer ( LoggerContext, Buffer );
}
#ifdef NTPERF
//
// Flush all buffer logging from user mode
//
if (PERFINFO_IS_LOGGING_TO_PERFMEM()) {
if (LoggerContext->LoggerId == WmipKernelLogger) {
PPERFINFO_TRACEBUF_HEADER pPerfBufHdr;
pPerfBufHdr = PerfBufHdr();
Buffer = pPerfBufHdr->UserModePerCpuBuffer[i];
if (Buffer) {
WmipPushDirtyBuffer ( LoggerContext, Buffer );
pPerfBufHdr->UserModePerCpuBuffer[i] = NULL;
}
}
}
#endif //NTPERF
}
}
if (ExQueryDepthSList(&LoggerContext->FlushList) == 0) {
//
// nothing to write, return;
//
return Status;
}
//
// First check if the file need to be created.
//
LoggerMode = LoggerContext->LoggerMode;
if (LoggerContext->LogFileHandle == NULL) {
if ((LoggerMode & EVENT_TRACE_DELAY_OPEN_FILE_MODE) ||
(LoggerMode & EVENT_TRACE_ADD_HEADER_MODE)) {
UNICODE_STRING FileName;
if (!WmipFileSystemReady) {
//
// FileSystem is not ready yet, so return for now.
//
return Status;
}
if (LoggerMode & EVENT_TRACE_FILE_MODE_NEWFILE) {
if (LoggerContext->LogFilePattern.Buffer == NULL) {
return STATUS_INVALID_PARAMETER;
}
Status = WmipGenerateFileName(
&LoggerContext->LogFilePattern,
(PLONG) &LoggerContext->FileCounter,
&FileName);
if (!NT_SUCCESS(Status)) {
return Status;
}
if (LoggerContext->LogFileName.Buffer != NULL) {
RtlFreeUnicodeString(&LoggerContext->LogFileName);
}
LoggerContext->LogFileName = FileName;
}
if (LoggerContext->LogFileName.Buffer != NULL) {
ULONG Append = LoggerMode & EVENT_TRACE_FILE_MODE_APPEND;
Status = WmipDelayCreate(&LoggerContext->LogFileHandle,
&LoggerContext->LogFileName,
Append);
if (NT_SUCCESS(Status)) {
//
// Now the file has been created, add the log file header
//
LoggerContext->LoggerMode &= ~EVENT_TRACE_DELAY_OPEN_FILE_MODE;
if (!Append) {
Status = WmipAddLogHeader(LoggerContext, NULL);
}
WmipSendNotification(LoggerContext,
STATUS_MEDIA_CHANGED, STATUS_SEVERITY_INFORMATIONAL);
}
else {
// DelayCreate failed.
TraceDebug((2, "WmipFlushActiveBuffers: DelayCreate Failed with status=%X\n", Status));
LoggerContext->LoggerStatus = Status;
return Status;
}
}
if ((LoggerContext->CollectionOn) &&
!(LoggerContext->LoggerMode & EVENT_TRACE_REAL_TIME_MODE)) {
return Status;
}
} else {
// If something bad happens to the file (like drive dismounted),
// LoggerContext->LogFileHandle can be NULL due to the previous call to
// this routine (WmipFlushActiveBuffers). For that case, we just need
// to continue.
if (!(LoggerContext->LoggerMode & EVENT_TRACE_REAL_TIME_MODE)) {
NTSTATUS LoggerStatus = LoggerContext->LoggerStatus;
TraceDebug((2, "WmipFlushActiveBuffers: LogFileHandle NULL\n"));
if (NT_SUCCESS(LoggerStatus)) {
return STATUS_INVALID_PARAMETER;
}
else {
return LoggerStatus;
}
}
}
}
//
// Write all buffers into disk
//
while (Entry = InterlockedPopEntrySList(&LoggerContext->FlushList)) {
Buffer = CONTAINING_RECORD(Entry,
WMI_BUFFER_HEADER,
SlistEntry);
if (!(LoggerContext->LoggerMode & EVENT_TRACE_BUFFERING_MODE)) {
//
// When there is a bursty logging, we can be stuck in this loop.
// Check if we need to allocate more buffers
//
// Only do buffer adjustment if we are not in buffering mode
//
WmipAdjustFreeBuffers(LoggerContext);
}
Status = WmipFlushBuffer(LoggerContext, Buffer);
InterlockedPushEntrySList(&LoggerContext->FreeList,
(PSINGLE_LIST_ENTRY) &Buffer->SlistEntry);
InterlockedIncrement((PLONG) &LoggerContext->BuffersAvailable);
InterlockedDecrement((PLONG) &LoggerContext->BuffersDirty);
TraceDebug((2, "WmipFlushActiveBuffers: %2d, %p, Free: %d, InUse: %d, %Dirty: %d, Total: %d\n",
LoggerContext->LoggerId,
Buffer,
LoggerContext->BuffersAvailable,
LoggerContext->BuffersInUse,
LoggerContext->BuffersDirty,
LoggerContext->NumberOfBuffers));
if ((Status == STATUS_LOG_FILE_FULL) ||
(Status == STATUS_DISK_FULL) ||
(Status == STATUS_NO_DATA_DETECTED) ||
(Status == STATUS_SEVERITY_WARNING)) {
TraceDebug((1,
"WmipFlushActiveBuffers: Buffer flushed with status=%X\n",
Status));
if ((Status == STATUS_LOG_FILE_FULL) ||
(Status == STATUS_DISK_FULL)) {
ErrorCount ++;
} else {
ErrorCount = 0; // reset to zero otherwise
}
if (ErrorCount <= WmiWriteFailureLimit) {
Status = STATUS_SUCCESS; // Let tracing continue
continue; // for now. Should raise WMI event
}
}
if (!NT_SUCCESS(Status)) {
TraceDebug((1,
"WmipLogger: Flush failed, status=%X LoggerContext=%X\n",
Status, LoggerContext));
if (LoggerContext->LogFileHandle != NULL) {
Status = ZwClose(LoggerContext->LogFileHandle);
TraceDebug((1,
"WmipLogger: Close logfile with status=%X\n", Status));
}
LoggerContext->LogFileHandle = NULL;
WmipSendNotification(LoggerContext,
Status, (Status & 0xC0000000) >> 30);
return Status;
}
Buffer->State.Flush = 0;
Buffer->State.Free = 1;
}
#else
if (FlushAll) {
for (i=0; i<(ULONG)KeNumberProcessors; i++) {
Buffer = LoggerContext->ProcessorBuffers[i];
if (Buffer == NULL)
continue;
if (Buffer->CurrentOffset == sizeof(WMI_BUFFER_HEADER)) {
Buffer->Flags = BUFFER_STATE_UNUSED;
TraceDebug((3, "Ignoring buffer %X proc %d\n", Buffer, i));
}
if (Buffer->Flags != BUFFER_STATE_UNUSED) {
Buffer->Flags = BUFFER_STATE_FULL;
TraceDebug((3, "Mark buf %X proc %d offset %d\n",
Buffer, i, Buffer->CurrentOffset));
if (Buffer->ReferenceCount == 0) {
Buffer = WmipSwitchBuffer(LoggerContext, Buffer, i);
FlushCount++;
}
}
else
Buffer->Flags = BUFFER_STATE_DIRTY;
}
}
else
FlushCount = 1;
for (i=0; i<FlushCount; i++) {
TraceDebug((3, "WmipLogger: FlushCount is %d\n", FlushCount));
LoggerContext->TransitionBuffer = LoggerContext->FlushList.Flink;
pEntry = ExInterlockedRemoveHeadList(
&LoggerContext->FlushList,
&LoggerContext->BufferSpinLock);
if (pEntry == NULL) { // should not happen normally
TraceDebug((1, "WmipLogger: pEntry is NULL!!\n"));
continue;
}
Buffer = CONTAINING_RECORD( pEntry, WMI_BUFFER_HEADER, Entry);
#if DBG
if (WmipTraceDebugLevel >= 3) {
DbgPrintEx(DPFLTR_WMILIB_ID,
DPFLTR_INFO_LEVEL,
"WmipLogger: Removed buffer %X from flushlist\n",
Buffer);
DbgPrintEx(DPFLTR_WMILIB_ID,
DPFLTR_INFO_LEVEL,
"WmipLogger: %X %d %d %d\n", Buffer->ClientContext,
Buffer->CurrentOffset,
Buffer->SavedOffset,
Buffer->ReferenceCount);
}
#endif
if (Buffer->Flags == BUFFER_STATE_UNUSED) {
Buffer->Flags = BUFFER_STATE_DIRTY;
}
Status = WmipFlushBuffer(LoggerContext, Buffer);
if (LoggerContext->BufferAgeLimit.QuadPart == 0) {
ExInterlockedInsertTailList(
&LoggerContext->FreeList,
&Buffer->Entry,
&LoggerContext->BufferSpinLock);
}
else {
ExInterlockedInsertHeadList(
&LoggerContext->FreeList,
&Buffer->Entry,
&LoggerContext->BufferSpinLock);
}
if ((Status == STATUS_LOG_FILE_FULL) ||
(Status == STATUS_DISK_FULL) ||
(Status == STATUS_NO_DATA_DETECTED) ||
(Status == STATUS_SEVERITY_WARNING)) {
TraceDebug((1,
"WmipFlushActiveBuffers: Buffer flushed with status=%X\n",
Status));
if ((Status == STATUS_LOG_FILE_FULL) ||
(Status == STATUS_DISK_FULL))
{
ErrorCount ++;
}
else ErrorCount = 0; // reset to zero otherwise
if (ErrorCount <= WmiWriteFailureLimit) {
Status = STATUS_SUCCESS; // let tracing continue
continue; // for now. Should raise WMI event
}
}
if (!NT_SUCCESS(Status)) {
TraceDebug((1,
"WmipFlushActiveBuffers: Flush failed, status=%X LoggerContext=%X\n",
Status, LoggerContext));
if (LoggerContext->LogFileHandle != NULL) {
Status = ZwClose(LoggerContext->LogFileHandle);
TraceDebug((1,
"WmipFlushActiveBuffers: Close logfile with status=%X\n",
Status));
}
LoggerContext->LogFileHandle = NULL;
WmipSendNotification(LoggerContext,
Status, (Status & 0xC0000000) >> 30);
return Status;
}
}
#endif //WMI_NON_BLOCKING
return Status;
}
NTSTATUS
WmipGenerateFileName(
IN PUNICODE_STRING FilePattern,
IN OUT PLONG FileCounter,
OUT PUNICODE_STRING FileName
)
{
LONG FileCount, Size;
PWCHAR Buffer = NULL;
PAGED_CODE();
if (FilePattern->Buffer == NULL)
return STATUS_INVALID_PARAMETER_MIX;
FileCount = InterlockedIncrement(FileCounter);
Size = FilePattern->MaximumLength + 64; // 32 digits: plenty for ULONG
Buffer = ExAllocatePoolWithTag(PagedPool,Size,TRACEPOOLTAG);
if (Buffer == NULL) {
return STATUS_NO_MEMORY;
}
swprintf(Buffer, FilePattern->Buffer, FileCount);
if (RtlEqualMemory(FilePattern->Buffer, Buffer, FilePattern->Length)) {
ExFreePool(Buffer);
return STATUS_INVALID_PARAMETER_MIX;
}
RtlInitUnicodeString(FileName, Buffer);
return STATUS_SUCCESS;
}
NTSTATUS
WmipPrepareHeader(
IN PWMI_LOGGER_CONTEXT LoggerContext,
IN OUT PWMI_BUFFER_HEADER Buffer
)
{
ULONG BufferSize;
PAGED_CODE();
BufferSize = LoggerContext->BufferSize;
if (Buffer->SavedOffset > 0) {
Buffer->Offset = Buffer->SavedOffset;
}
else {
Buffer->Offset = Buffer->CurrentOffset;
}
ASSERT(Buffer->Offset >= sizeof(WMI_BUFFER_HEADER));
if (Buffer->Offset == sizeof(WMI_BUFFER_HEADER)) { // empty buffer
return STATUS_NO_DATA_DETECTED;
}
//
// Fill the rest of buffer with 0XFF
//
if ( Buffer->Offset < BufferSize ) {
RtlFillMemory(
(char *) Buffer + Buffer->Offset,
BufferSize - Buffer->Offset,
0XFF);
}
Buffer->Wnode.BufferSize = BufferSize;
Buffer->ClientContext.LoggerId = (USHORT) LoggerContext->LoggerId;
if (Buffer->ClientContext.LoggerId == 0)
Buffer->ClientContext.LoggerId = (USHORT) KERNEL_LOGGER_ID;
Buffer->ClientContext.Alignment = (UCHAR) WmiTraceAlignment;
Buffer->Wnode.Guid = LoggerContext->InstanceGuid;
Buffer->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
Buffer->Wnode.ProviderId = LoggerContext->BuffersWritten+1;
KeQuerySystemTime(&Buffer->Wnode.TimeStamp);
return STATUS_SUCCESS;
}
NTKERNELAPI
VOID
WmiBootPhase1(
)
/*++
Routine Description:
NtInitializeRegistry to inform WMI that autochk is performed
and it is OK now to write to disk.
Arguments:
None
Return Value:
None
--*/
{
PAGED_CODE();
WmipFileSystemReady = TRUE;
}
NTSTATUS
WmipFinalizeHeader(
IN HANDLE FileHandle,
IN PWMI_LOGGER_CONTEXT LoggerContext
)
{
LARGE_INTEGER ByteOffset;
NTSTATUS Status;
PTRACE_LOGFILE_HEADER FileHeader;
IO_STATUS_BLOCK IoStatus;
CHAR Buffer[PAGE_SIZE]; // Assumes Headers less than PAGE_SIZE
PAGED_CODE();
ByteOffset.QuadPart = 0;
Status = ZwReadFile(
FileHandle,
NULL,
NULL,
NULL,
& IoStatus,
&Buffer[0],
PAGE_SIZE,
& ByteOffset,
NULL);
if (!NT_SUCCESS(Status)) {
return Status;
}
FileHeader = (PTRACE_LOGFILE_HEADER)
&Buffer[sizeof(WMI_BUFFER_HEADER) + sizeof(SYSTEM_TRACE_HEADER)];
FileHeader->BuffersWritten = LoggerContext->BuffersWritten;
KeQuerySystemTime(&FileHeader->EndTime);
FileHeader->BuffersLost = LoggerContext->LogBuffersLost;
FileHeader->EventsLost = LoggerContext->EventsLost;
Status = ZwWriteFile(
FileHandle,
NULL,
NULL,
NULL,
&IoStatus,
&Buffer[0],
PAGE_SIZE,
&ByteOffset,
NULL);
return Status;
}
#if DBG
#define DEBUG_BUFFER_LENGTH 1024
UCHAR TraceDebugBuffer[DEBUG_BUFFER_LENGTH];
VOID
TraceDebugPrint(
ULONG DebugPrintLevel,
PCCHAR DebugMessage,
...
)
/*++
Routine Description:
Debug print for all DiskPerf
Arguments:
Debug print level between 0 and 3, with 3 being the most verbose.
Return Value:
None
--*/
{
LARGE_INTEGER Clock;
ULONG Tid;
va_list ap;
va_start(ap, DebugMessage);
if (WmipTraceDebugLevel >= DebugPrintLevel) {
_vsnprintf(TraceDebugBuffer, DEBUG_BUFFER_LENGTH, DebugMessage, ap);
Clock = KeQueryPerformanceCounter(NULL);
Tid = HandleToUlong(PsGetCurrentThreadId());
DbgPrintEx(DPFLTR_WMILIB_ID, DPFLTR_INFO_LEVEL,
"%u(%u): %s", Clock.LowPart, Tid, TraceDebugBuffer);
}
va_end(ap);
}
#endif
VOID
FASTCALL
WmipResetBufferHeader (
PWMI_LOGGER_CONTEXT LoggerContext,
PWMI_BUFFER_HEADER Buffer
)
/*++
Routine Description:
This is a function which initializes a few buffer header values
that are used by both WmipGetFreeBuffer and WmipPopFreeContextSwapBuffer
Note that this function increments a few logger context reference counts
Calling Functions:
- WmipGetFreeBuffer
- WmipPopFreeContextSwapBuffer
Arguments:
LoggerContext - Logger context from where we have acquired a free buffer
Buffer - Pointer to a buffer header that we wish to reset
Return Value:
None
--*/
{
Buffer->Flags = BUFFER_STATE_DIRTY;
Buffer->SavedOffset = 0;
Buffer->CurrentOffset = sizeof(WMI_BUFFER_HEADER);
Buffer->Wnode.ClientContext = 0;
Buffer->LoggerContext = LoggerContext;
Buffer->State.Free = 0;
Buffer->State.InUse = 1;
}
VOID
FASTCALL
WmipPushDirtyBuffer (
PWMI_LOGGER_CONTEXT LoggerContext,
PWMI_BUFFER_HEADER Buffer
)
/*++
Routine Description:
This is a function which prepares a buffer's header and places it on a
logger's flush list.
Note that this function manages a few logger context reference counts
Calling Functions:
- WmipFlushActiveBuffers
- WmipPushDirtyContextSwapBuffer
Arguments:
LoggerContext - Logger context from which we originally acquired a buffer
Buffer - Pointer to a buffer that we wish to flush
Return Value:
None
--*/
{
//
// Set the buffer flags to "flush" state and save the offset
//
Buffer->State.InUse = 0;
Buffer->State.Flush = 1;
Buffer->SavedOffset = Buffer->CurrentOffset;
//
// Push the buffer onto the flushlist. This could only
// fail if the Wmi kernel logger shut down without notifying us.
// If this happens, there is nothing we can do about it anyway.
// If Wmi is well behaved, this will never fail.
//
InterlockedPushEntrySList(
&LoggerContext->FlushList,
(PSINGLE_LIST_ENTRY) &Buffer->SlistEntry);
//
// Maintain some reference counts
//
InterlockedDecrement((PLONG) &LoggerContext->BuffersInUse);
InterlockedIncrement((PLONG) &LoggerContext->BuffersDirty);
TraceDebug((2, "Flush Dirty Buffer: %p, Free: %d, InUse: %d, %Dirty: %d, Total: %d, (Thread: %p)\n",
Buffer,
LoggerContext->BuffersAvailable,
LoggerContext->BuffersInUse,
LoggerContext->BuffersDirty,
LoggerContext->NumberOfBuffers,
PsGetCurrentThread()));
}
PWMI_BUFFER_HEADER
FASTCALL
WmipPopFreeContextSwapBuffer
( UCHAR CurrentProcessor
)
/*++
Routine Description:
Attempts to remove a buffer from the kernel logger free buffer list.
We confirm that logging is on, that buffer switching is
not in progress and that the buffers available count is greater than
zero. If we are unable to acquire a buffer, we increment LostEvents
and return. Otherwise, we initialize the buffer and pass it back.
Assumptions:
- This routine will only be called from WmiTraceContextSwap
- Inherits all assumptions listed in WmiTraceContextSwap
Calling Functions:
- WmiTraceContextSwap
Arguments:
CurrentProcessor- The current processor number (0 to (NumProc - 1))
Return Value:
Pointer to the newly acquired buffer. NULL on failure.
--*/
{
PWMI_LOGGER_CONTEXT LoggerContext;
PWMI_BUFFER_HEADER Buffer;
LoggerContext = WmipLoggerContext[WmipKernelLogger];
//
// Could only happen if for some reason the logger has not been initialized
// before we see the global context swap flag set. This should not happen.
//
if(! WmipIsValidLogger(LoggerContext) ) {
return NULL;
}
//
// "Switching" is a Wmi state available only while BUFFERING is
// enabled that occurs when the free buffer list is empty. During switching
// all the buffers in the flushlist are simply moved back to the free list.
// Normally if we found that the free list was empty we would perform the
// switch here, and if the switch was already occuring we would spin until
// it completed. Instead of introducing an indefinite spin, as well as a
// ton of interlocked pops and pushes, we opt to simply drop the event.
//
if ( !(LoggerContext->SwitchingInProgress)
&& LoggerContext->CollectionOn
&& LoggerContext->BuffersAvailable > 0) {
//
// Attempt to get a free buffer from the Kernel Logger FreeList
//
Buffer = (PWMI_BUFFER_HEADER)InterlockedPopEntrySList(
&LoggerContext->FreeList);
//
// This second check is necessary because
// LoggerContext->BuffersAvailable may have changed.
//
if(Buffer != NULL) {
Buffer = CONTAINING_RECORD (Buffer, WMI_BUFFER_HEADER, SlistEntry);
//
// Reset the buffer header
//
WmipResetBufferHeader( LoggerContext, Buffer );
//
// Maintain some Wmi logger context buffer counts
//
InterlockedDecrement((PLONG) &LoggerContext->BuffersAvailable);
InterlockedIncrement((PLONG) &LoggerContext->BuffersInUse);
Buffer->ClientContext.ProcessorNumber = CurrentProcessor;
Buffer->Offset = LoggerContext->BufferSize;
ASSERT( Buffer->Offset % WMI_CTXSWAP_EVENTSIZE_ALIGNMENT == 0);
// Return our buffer
return Buffer;
}
}
LoggerContext->EventsLost++;
return NULL;
}
VOID
FASTCALL
WmipPushDirtyContextSwapBuffer (
UCHAR CurrentProcessor,
PWMI_BUFFER_HEADER Buffer
)
/*++
Routine Description:
Prepares the current buffer to be placed on the Wmi flushlist
and then pushes it onto the flushlist. Maintains some Wmi
Logger reference counts.
Assumptions:
- The value of WmipContextSwapProcessorBuffers[CurrentProcessor]
is not equal to NULL, and the LoggerContext reference count
is greater than zero.
- This routine will only be called when the KernelLogger struct
has been fully initialized.
- The Wmi kernel WMI_LOGGER_CONTEXT object, as well as all buffers
it allocates are allocated from nonpaged pool. All Wmi globals
that we access are also in nonpaged memory
- This code has been locked into paged memory when the logger started
- The logger context reference count has been "Locked" via the
InterlockedIncrement() operation in WmipReferenceLogger(WmipLoggerContext)
Calling Functions:
- WmiTraceContextSwap
- WmipStopContextSwapTrace
Arguments:
CurrentProcessor Processor we are currently running on
Buffer Buffer to be flushed
Return Value:
None
--*/
{
PWMI_LOGGER_CONTEXT LoggerContext;
//
// Grab the kernel logger context
// This should never be NULL as long as we keep the KernelLogger
// reference count above zero via "WmipReferenceLogger"
//
LoggerContext = WmipLoggerContext[WmipKernelLogger];
if( ! WmipIsValidLogger(LoggerContext) ) {
return;
}
WmipPushDirtyBuffer( LoggerContext, Buffer );
//
// Increment the ReleaseQueue count here. We can't signal the
// logger semaphore here while holding the context swap lock.
//
InterlockedIncrement(&LoggerContext->ReleaseQueue);
return;
}
#ifdef NTPERF
NTSTATUS
WmipSwitchPerfmemBuffer(
IN OUT PWMI_SWITCH_PERFMEM_BUFFER_INFORMATION SwitchBufferInfo
)
/*++
Routine Description:
This routine is used to switch buffers when
Assumptions:
-
Arguments:
The WMI_SWITCH_PERFMEM_BUFFER_INFORMATION structure which contains
- The buffer pointer the user mode code currently has.
- The hint to which CPU for this thread
Return Value:
Status
--*/
{
PWMI_BUFFER_HEADER CurrentBuffer, NewBuffer, OldBuffer;
PSINGLE_LIST_ENTRY Entry;
PWMI_LOGGER_CONTEXT LoggerContext = WmipLoggerContext[WmipKernelLogger];
PPERFINFO_TRACEBUF_HEADER pPerfBufHdr;
//
// Get a new buffer from Free List
//
//
// First lock the logger context.
//
WmipReferenceLogger(WmipKernelLogger);
if (!WmipIsValidLogger(LoggerContext)) {
NewBuffer = NULL;
} else if (!LoggerContext->CollectionOn) {
NewBuffer = NULL;
} else if (!PERFINFO_IS_LOGGING_TO_PERFMEM()) {
NewBuffer = NULL;
} else if ( SwitchBufferInfo->ProcessorId > MAXIMUM_PROCESSORS) {
NewBuffer = NULL;
} else {
//
// Allocate a buffer.
//
pPerfBufHdr = PerfBufHdr();
NewBuffer = WmipGetFreeBuffer (LoggerContext);
if (NewBuffer) {
OldBuffer = SwitchBufferInfo->Buffer;
CurrentBuffer = InterlockedCompareExchangePointer(
&pPerfBufHdr->UserModePerCpuBuffer[SwitchBufferInfo->ProcessorId],
NewBuffer,
OldBuffer);
if (OldBuffer != CurrentBuffer) {
//
// Someone has switched the buffer, use this one
// and push the new allocated buffer back to free list.
//
InterlockedPushEntrySList(&LoggerContext->FreeList,
(PSINGLE_LIST_ENTRY) &NewBuffer->SlistEntry);
InterlockedIncrement((PLONG) &LoggerContext->BuffersAvailable);
InterlockedDecrement((PLONG) &LoggerContext->BuffersInUse);
NewBuffer = CurrentBuffer;
} else if (OldBuffer != NULL) {
//
// Successfully switched the buffer, push the current buffer into
// flush list
//
WmipPushDirtyBuffer( LoggerContext, OldBuffer );
} else {
//
// Must be first time, initialize the buffer size
//
pPerfBufHdr->TraceBufferSize = LoggerContext->BufferSize;
}
}
}
SwitchBufferInfo->Buffer = NewBuffer;
//
// Dereference the logger context.
//
WmipDereferenceLogger(WmipKernelLogger);
return(STATUS_SUCCESS);
}
#endif // NTPERF