/*++ 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 #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; iNumberOfBuffers); 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; iTransitionBuffer = 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