|
|
/*++
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
#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];
//
// 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
//
NTSTATUS WmipSwitchBuffer( IN PWMI_LOGGER_CONTEXT LoggerContext, IN PWMI_BUFFER_HEADER *BufferPointer, IN PVOID BufferPointerLocation, IN ULONG ProcessorNumber );
NTSTATUS WmipPrepareHeader( IN PWMI_LOGGER_CONTEXT LoggerContext, IN OUT PWMI_BUFFER_HEADER Buffer, IN USHORT BufferFlag );
VOID FASTCALL WmipPushDirtyBuffer ( PWMI_LOGGER_CONTEXT LoggerContext, PWMI_BUFFER_HEADER Buffer );
VOID FASTCALL WmipPushFreeBuffer ( PWMI_LOGGER_CONTEXT LoggerContext, PWMI_BUFFER_HEADER Buffer );
//
// Logger functions
//
NTSTATUS WmipCreateLogFile( IN PWMI_LOGGER_CONTEXT LoggerContext, IN ULONG SwitchFile, IN ULONG Append );
NTSTATUS WmipSwitchToNewFile( IN PWMI_LOGGER_CONTEXT LoggerContext );
NTSTATUS WmipRequestLogFile( IN PWMI_LOGGER_CONTEXT LoggerContext );
NTSTATUS WmipFinalizeHeader( IN HANDLE FileHandle, IN PWMI_LOGGER_CONTEXT LoggerContext );
NTSTATUS WmipFlushBuffersWithMarker ( IN PWMI_LOGGER_CONTEXT LoggerContext, IN PSLIST_ENTRY List, IN USHORT BufferFlag );
#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(PAGE, WmipSwitchToNewFile)
#pragma alloc_text(PAGE, WmipRequestLogFile)
#pragma alloc_text(PAGE, WmipAdjustFreeBuffers)
#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)
#pragma alloc_text(PAGEWMI, WmipAllocateFreeBuffers)
#pragma alloc_text(PAGEWMI, WmipSwitchBuffer)
#pragma alloc_text(PAGEWMI, WmipReleaseTraceBuffer)
#pragma alloc_text(PAGEWMI, WmiReleaseKernelBuffer)
#pragma alloc_text(PAGEWMI, WmipResetBufferHeader)
#pragma alloc_text(PAGEWMI, WmipPushDirtyBuffer)
#pragma alloc_text(PAGEWMI, WmipPushFreeBuffer)
#pragma alloc_text(PAGEWMI, WmipPopFreeContextSwapBuffer)
#pragma alloc_text(PAGEWMI, WmipPushDirtyContextSwapBuffer)
#pragma alloc_text(PAGEWMI, WmipFlushBuffersWithMarker)
#ifdef NTPERF
#pragma alloc_text(PAGEWMI, WmipSwitchPerfmemBuffer)
#endif //NTPERF
#endif
//
// Actual code starts here
//
PWMI_BUFFER_HEADER WmipGetFreeBuffer( IN PWMI_LOGGER_CONTEXT LoggerContext ) //
// This routine works at any IRQL
//
{ PWMI_BUFFER_HEADER Buffer; PSLIST_ENTRY Entry; if (LoggerContext->SwitchingInProgress == 0) { //
// Not in the middle of switching.
//
Entry = InterlockedPopEntrySList(&LoggerContext->FreeList);
if (Entry != NULL) { Buffer = CONTAINING_RECORD (Entry, WMI_BUFFER_HEADER, SlistEntry); //
// Reset the buffer.
// For circular persist mode, we want to write the buffers as
// RunDown buffers so that post processing would work properly.
//
if (LoggerContext->RequestFlag & REQUEST_FLAG_CIRCULAR_PERSIST) { WmipResetBufferHeader( LoggerContext, Buffer, WMI_BUFFER_TYPE_RUNDOWN); } else { WmipResetBufferHeader( LoggerContext, Buffer, WMI_BUFFER_TYPE_GENERIC); }
//
// 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); WmipPushFreeBuffer (LoggerContext, Buffer); } } InterlockedDecrement((PLONG) &LoggerContext->SwitchingInProgress); } 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, (PSLIST_ENTRY) &Buffer->SlistEntry); InterlockedPushEntrySList(&LoggerContext->GlobalList, (PSLIST_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
//
PSYSTEM_TRACE_HEADER FASTCALL WmiReserveWithSystemHeader( IN ULONG LoggerId, IN ULONG AuxSize, IN PETHREAD Thread, OUT PWMI_BUFFER_HEADER *BufferResource ) //
// It returns with LoggerContext locked, so caller must explicitly call
// WmipDereferenceLogger() after call WmipReleaseTraceBuffer()
//
{ PSYSTEM_TRACE_HEADER Header; PWMI_LOGGER_CONTEXT LoggerContext; LARGE_INTEGER TimeStamp; #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, &TimeStamp); if (Header != NULL) {
//
// Now copy the necessary information into the buffer
//
Header->SystemTime = TimeStamp; 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 ) //
// It returns with LoggerContext locked, so caller must explicitly call
// WmipDereferenceLogger() after call WmipReleaseTraceBuffer()
//
{ PPERFINFO_TRACE_HEADER Header; ULONG LoggerId = WmipKernelLogger; LARGE_INTEGER TimeStamp; #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, &TimeStamp); if (Header != NULL) { //
// Now copy the necessary information into the buffer
//
Header->SystemTime = TimeStamp; 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; }
PVOID FASTCALL WmipReserveTraceBuffer( IN PWMI_LOGGER_CONTEXT LoggerContext, IN ULONG RequiredSize, OUT PWMI_BUFFER_HEADER *BufferResource, OUT PLARGE_INTEGER TimeStamp ) /*++
Routine Description: This function is the main logging function that reserves spaces for any events. The algorithm is as follows:
Every time a space is needed, we InterlockedExchangeAdd CurrentOffset. A local variable Offset is used to track the initial value when InterlockedExchangeAdd is taken. If there is enough space for this event (i.e., (Offset + RequiredSize) <= BufferSize), then we have successfully reserved the space. If there is not enough space left on this buffer, we will call WmipSwitchBuffer for a new buffer. In this case, CurrentOffset should be larger than the buffersize. Since other thread can still be trying to reserve space using thie buffer, we saved the offset on SavedOffset the the logger thread knows the real offset to be written to disk.
Note that, since the CurrentOffset if growing monotonically, only one thread is advancing the CurrentOffset from below BufferSize to beyond BufferSize. It is this thread's responsibility to set the SavedOffset properly.
Arguments:
LoggerContext - Logger context from current logging session.
RequiredSize - The space needed for logging the data.
Buffer - Pointer to a buffer header
TimeStamp - TimeStamp of the event
Return Value:
The status of running the buffer manager
Environment:
Kernel mode. This routine should work at any IRQL.
--*/ { PVOID ReservedSpace; PWMI_BUFFER_HEADER Buffer = NULL; ULONG Offset; //
// ISSUES: shsiao 2002/07/26
// Mark it volatile to work around compiler bug.
//
volatile ULONG Processor; NTSTATUS Status;
if (!WmipIsValidLogger(LoggerContext)) { return NULL; } if (!LoggerContext->CollectionOn) { return NULL; }
*BufferResource = NULL;
RequiredSize = (ULONG) ALIGN_TO_POWER2(RequiredSize, WmiTraceAlignment);
if (RequiredSize > LoggerContext->BufferSize - sizeof (WMI_BUFFER_HEADER)) { goto LostEvent; }
//
// Get processor number again here due to possible context switch
//
Processor = KeGetCurrentProcessorNumber();
//
// Get the processor specific buffer pool
//
Buffer = LoggerContext->ProcessorBuffers[Processor];
if (Buffer == NULL) { //
// Nothing in per process list, ask to get a new buffer
//
Status = WmipSwitchBuffer(LoggerContext, &Buffer, &LoggerContext->ProcessorBuffers[Processor], Processor);
if (!NT_SUCCESS(Status)) { //
// Nothing available
//
goto LostEvent; }
ASSERT(Buffer != NULL); }
TryFindSpace:
//
// Increment refcount to buffer first to prevent it from going away
//
InterlockedIncrement(&Buffer->ReferenceCount); //
// Check if there is enough space in this buffer.
//
Offset = (ULONG) InterlockedExchangeAdd( (PLONG) &Buffer->CurrentOffset, RequiredSize); if (Offset+RequiredSize <= LoggerContext->BufferSize) { //
// Successfully reserved the space
// Get the timestamp of the event
//
if (TimeStamp) { #ifdef NTPERF
PerfTimeStamp((*TimeStamp)); #else
TimeStamp->QuadPart = (*LoggerContext->GetCpuClock)(); #endif
}
//
// Set up the space pointer
//
ReservedSpace = (PVOID) (Offset + (char*)Buffer); if (LoggerContext->SequencePtr) { *((PULONG) ReservedSpace) = (ULONG)InterlockedIncrement(LoggerContext->SequencePtr); } goto FoundSpace; } else { //
// There is not enough space left to log this event,
// Ask for buffer switch. The WmipSwitchBuffer()
// will push this current buffer into Dirty list.
//
// Before asking for buffer switch,
// check if I am the one that got overboard.
// If yes, put the correct offset back.
//
if (Offset <= LoggerContext->BufferSize) { Buffer->SavedOffset = Offset; }
//
// Also, dereference the buffer, so it can be freed.
//
InterlockedDecrement((PLONG) &Buffer->ReferenceCount);
//
// Nothing in per process list, ask to get a new buffer
//
Status = WmipSwitchBuffer(LoggerContext, &Buffer, &LoggerContext->ProcessorBuffers[Processor], Processor);
if (!NT_SUCCESS(Status)) { //
// Nothing available
//
goto LostEvent; }
ASSERT (Buffer != NULL); goto TryFindSpace; }
LostEvent: //
// Will get here it we are throwing away the event
//
ASSERT(Buffer == NULL); 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; }
NTSTATUS WmipSwitchToNewFile( IN PWMI_LOGGER_CONTEXT LoggerContext ) /*++
Routine Description:
This routine is called to get a LogFileHandle for GlobalLogger or when a fileswitch is needed for NEWFILE mode. It will create the file and add logfileheader to it. It closes the oldfile by properly finalizing its header.
Arguments:
LoggerContext - Logger Context
Return Value:
Status
Environment:
Kernel mode.
--*/ { IO_STATUS_BLOCK IoStatus; HANDLE OldHandle, NewHandle; UNICODE_STRING NewFileName, OldFileName; ULONG BufferSize = LoggerContext->BufferSize; PWMI_BUFFER_HEADER NewHeaderBuffer; NTSTATUS Status=STATUS_SUCCESS;
PAGED_CODE();
NewFileName.Buffer = NULL;
if (LoggerContext->LoggerMode & EVENT_TRACE_FILE_MODE_NEWFILE) {
Status = WmipGenerateFileName( &LoggerContext->LogFilePattern, (PLONG) &LoggerContext->FileCounter, &NewFileName );
if (!NT_SUCCESS(Status)) { TraceDebug((1, "WmipSwitchToNewFile: Error %x generating filename\n", Status)); return Status; }
} else { //
// GlobalLogger path. It is executed only once to set up
// the logfile.
//
if (LoggerContext->LogFileHandle != NULL) { LoggerContext->RequestFlag &= ~REQUEST_FLAG_NEW_FILE; return STATUS_SUCCESS; } if (LoggerContext->LogFileName.Buffer == NULL) { TraceDebug((1, "WmipSwitchToNewFile: No LogFileName\n")); return STATUS_INVALID_PARAMETER; } if (! RtlCreateUnicodeString( &NewFileName, LoggerContext->LogFileName.Buffer) ) { TraceDebug((1, "WmipSwitchToNewFile: No Memory for NewFileName\n")); return STATUS_NO_MEMORY; } }
//
// We have a NewFileName. Create the File
//
Status = WmipDelayCreate(&NewHandle, &NewFileName, FALSE);
if (NT_SUCCESS(Status)) { NewHeaderBuffer = (PWMI_BUFFER_HEADER) ExAllocatePoolWithTag(LoggerContext->PoolType, LoggerContext->BufferSize, TRACEPOOLTAG); if (NewHeaderBuffer != NULL) { //
// Now we have all the resources we need for the new file.
// Let's close out the old file, if necessary and switch
//
OldFileName = LoggerContext->LogFileName; OldHandle = LoggerContext->LogFileHandle; if (OldHandle) { WmipFinalizeHeader(OldHandle, LoggerContext); ZwClose(OldHandle); }
// NOTE: Assumes LogFileName cannot be changed
// for NEWFILE mode!!!
if (OldFileName.Buffer != NULL) { RtlFreeUnicodeString(&OldFileName); }
LoggerContext->BuffersWritten = 1; LoggerContext->LogFileHandle = NewHandle; LoggerContext->LogFileName = NewFileName;
NewFileName.Buffer = NULL;
RtlZeroMemory( NewHeaderBuffer, LoggerContext->BufferSize ); WmipResetBufferHeader(LoggerContext, NewHeaderBuffer, WMI_BUFFER_TYPE_RUNDOWN);
WmipAddLogHeader(LoggerContext, NewHeaderBuffer);
LoggerContext->LastFlushedBuffer = 1; LoggerContext->ByteOffset.QuadPart = BufferSize; LoggerContext->RequestFlag &= ~REQUEST_FLAG_NEW_FILE; LoggerContext->LoggerMode &= ~EVENT_TRACE_DELAY_OPEN_FILE_MODE; LoggerContext->LoggerMode &= ~EVENT_TRACE_ADD_HEADER_MODE;
Status = WmipPrepareHeader(LoggerContext, NewHeaderBuffer, WMI_BUFFER_TYPE_RUNDOWN); if (NT_SUCCESS(Status)) { Status = ZwWriteFile( NewHandle, NULL, NULL, NULL, &IoStatus, NewHeaderBuffer, BufferSize, NULL, NULL); } if (!NT_SUCCESS(Status) ) { TraceDebug((1, "WmipSwitchToNewFile: Write Failed\n", Status)); }
WmipSendNotification(LoggerContext, STATUS_MEDIA_CHANGED, STATUS_SEVERITY_INFORMATIONAL);
ExFreePool(NewHeaderBuffer); } } if (NewFileName.Buffer != NULL) { ExFreePool(NewFileName.Buffer); }
return Status; }
NTSTATUS WmipRequestLogFile( IN PWMI_LOGGER_CONTEXT LoggerContext ) /*++
Routine Description:
This routine switches logfiles for an active logger. This routine must be called only by the Logger Thread. It will close the previous logfile, if any, properly by terminating it with FLUSH_MARKER and Finalizing the LogHeader.
Two different cases to consider here are: 1. The newfile was created in user mode with headers and rundown data 2. The newfile is created in kernel (need to add LogFileHeader)
The callers of this function are 1. UpdateLogger: Sets the REQUEST_FLAG_NEW_FILE after presenting a user mode created logfile in NewLogFile. 2. NT Kernel Logger session: Switches from DELAY_OPEN mode with a user mode created logfile 3. FILE_MODE_NEWFILE: When current logfile reaches the FileLimit FlushBuffer requests a newfile. 4. GlobalLogger: Started in DELAY_OPEN && ADD_HEADER mode needs to create the logfile when the FileSystem is ready.
In all cases, when the switch is made the old logfile needs to be properly closed after Finalizing its header.
Arguments:
LoggerContext - Logger Context
Return Value:
Status
Environment:
Kernel mode. --*/
{ NTSTATUS Status = STATUS_SUCCESS;
PAGED_CODE();
if (!WmipFileSystemReady) { //
// FileSystem is not ready yet, so return for now.
//
return Status; }
//
// In order for us to act on this request we need something to create
// a file with, such as FileName, Pattern etc.,
//
if ((LoggerContext->LogFileName.Buffer == NULL ) && (LoggerContext->LogFilePattern.Buffer == NULL) && (LoggerContext->NewLogFileName.Buffer == NULL) ) {
return Status; } //
// With the REQUEST_FLAG_NEW_FILE set, flush all active buffers.
//
if (LoggerContext->LogFileHandle != NULL ) { Status = WmipFlushActiveBuffers(LoggerContext, TRUE); }
if (NT_SUCCESS(Status)) { if ( (LoggerContext->LoggerMode & EVENT_TRACE_FILE_MODE_NEWFILE) || ( (LoggerContext->LoggerMode & EVENT_TRACE_DELAY_OPEN_FILE_MODE) && (LoggerContext->LoggerMode & EVENT_TRACE_ADD_HEADER_MODE))) {
Status = WmipSwitchToNewFile(LoggerContext); } else { //
// UpdateTrace case
//
TraceDebug((3, "WmipLogger: New File\n")); Status = WmipCreateLogFile(LoggerContext, TRUE, EVENT_TRACE_FILE_MODE_APPEND); if (NT_SUCCESS(Status)) { LoggerContext->LoggerMode &= ~EVENT_TRACE_DELAY_OPEN_FILE_MODE; } //
// This is to release the Update thread from the wait
//
KeSetEvent(&LoggerContext->FlushEvent, 0, FALSE); } }
if (! NT_SUCCESS(Status)) { LoggerContext->LoggerStatus = Status; } return Status; }
//
// 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; ULONG FlushTimeOut; ULONG64 LastFlushTime=0;
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
//
KeSetBasePriorityThread(KeGetCurrentThread(), LOW_REALTIME_PRIORITY-1);
while (LoggerContext->CollectionOn) {
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, &WmiOneSecond); //
// Check if number of buffers need to be adjusted.
//
WmipAdjustFreeBuffers(LoggerContext);
LoggerContext->LoggerStatus = STATUS_SUCCESS;
if ((LoggerContext->RequestFlag & REQUEST_FLAG_NEW_FILE) || ((LoggerContext->LoggerMode & EVENT_TRACE_DELAY_OPEN_FILE_MODE) && (LoggerContext->LoggerMode & EVENT_TRACE_ADD_HEADER_MODE)) ) { Status = WmipRequestLogFile( LoggerContext); }
//
// Check to see if we need to FlushAll
//
if (FlushTimeOut) { ULONG64 Now; KeQuerySystemTime((PLARGE_INTEGER) &Now); if ( ((Now - LastFlushTime) / 10000000) >= FlushTimeOut) { FlushAll = 1; LastFlushTime = Now; } else { FlushAll = 0; } }
FlushFlag = (LoggerContext->RequestFlag & REQUEST_FLAG_FLUSH_BUFFERS); if ( FlushFlag ) FlushAll = TRUE;
#ifdef NTPERF
if (!PERFINFO_IS_LOGGING_TO_PERFMEM()) { #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.
//
if (LoggerContext->RequestFlag & REQUEST_FLAG_CIRCULAR_TRANSITION) { if (LoggerContext->LogFileHandle != NULL) { WmipFinalizeHeader(LoggerContext->LogFileHandle, LoggerContext); }
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
//
// This is to force buffers to be written
// in FlushBuffer without snapping back to this routine to create a file.
LoggerContext->RequestFlag |= REQUEST_FLAG_NEW_FILE;
while ((LoggerContext->NumberOfBuffers > 0) && (LoggerContext->NumberOfBuffers > LoggerContext->BuffersAvailable)) { Status = KeWaitForSingleObject( &LoggerContext->LoggerSemaphore, Executive, KernelMode, FALSE, &WmiOneSecond); WmipFlushActiveBuffers(LoggerContext, 1); TraceDebug((2, "WmipLogger: Stop %d %d %d %d %d\n", LoggerContext->LoggerId, LoggerContext->BuffersAvailable, LoggerContext->BuffersInUse, LoggerContext->BuffersDirty, LoggerContext->NumberOfBuffers)); }
//
// 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, IN USHORT BufferFlag ) /*++
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
Buffer -
BufferFlag -
Return Value:
The status of flushing the buffer
--*/ { IO_STATUS_BLOCK IoStatus; NTSTATUS Status; ULONG BufferSize; ULONG BufferPersistenceData = LoggerContext->RequestFlag & ( REQUEST_FLAG_CIRCULAR_PERSIST | REQUEST_FLAG_CIRCULAR_TRANSITION);
ASSERT(LoggerContext != NULL); ASSERT(Buffer != NULL);
if (LoggerContext == NULL || Buffer == NULL) { return STATUS_SEVERITY_ERROR; }
//
// 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 %8x %8x %5d\n", Buffer, Buffer->ClientContext, Buffer->SavedOffset, Buffer->CurrentOffset, LoggerContext->BuffersWritten));
Status = WmipPrepareHeader(LoggerContext, Buffer, BufferFlag);
if (Status == STATUS_SUCCESS) {
//
// 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) {
if (LoggerContext->MaximumFileSize > 0) { // if quota given
ULONG64 FileSize = LoggerContext->LastFlushedBuffer * BufferSize; ULONG64 FileLimit = LoggerContext->MaximumFileSize * BYTES_PER_MB; if (LoggerContext->LoggerMode & EVENT_TRACE_USE_KBYTES_FOR_SIZE) { FileLimit = LoggerContext->MaximumFileSize * 1024; }
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 || LoggerMode == EVENT_TRACE_FILE_MODE_CIRCULAR_PERSIST) { 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) {
//
// We will set the RequestFlag to initiate a file switch.
// If that flag is already set, then we continnue to flush
// past the FileLimit.
//
// There should be no race condition with an UpdateTrace
// setting the RequestFlag since we do not allow change of
// filename for NEWFILE mode.
//
if ( (LoggerContext->RequestFlag & REQUEST_FLAG_NEW_FILE) != REQUEST_FLAG_NEW_FILE) { LoggerContext->RequestFlag |= REQUEST_FLAG_NEW_FILE; } } } }
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)); } } }
// Now do callbacks. This happens whether a file exists or not.
if (WmipGlobalBufferCallback) { (WmipGlobalBufferCallback) (Buffer, WmipGlobalCallbackContext); } if (LoggerContext->BufferCallback) { (LoggerContext->BufferCallback) (Buffer, LoggerContext->CallbackContext); } }
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++; }
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 = {0}; 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; } }
//
// Before Switching to the newfile, let's finalize the old file
//
if ( SwitchFile && (tempHandle != NULL) ) { WmipFinalizeHeader(tempHandle, LoggerContext); }
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: if (LoggerContext->ClientSecurityContext.ClientToken != NULL) { 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; }
ULONG FASTCALL WmipReleaseTraceBuffer( IN PWMI_BUFFER_HEADER BufferResource, IN PWMI_LOGGER_CONTEXT LoggerContext ) { ULONG BufRefCount; LONG ReleaseQueue;
ASSERT(LoggerContext); ASSERT(BufferResource);
BufRefCount = InterlockedDecrement((PLONG) &BufferResource->ReferenceCount);
//
// 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); }
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 WmipFlushBuffersWithMarker ( IN PWMI_LOGGER_CONTEXT LoggerContext, IN PSLIST_ENTRY List, IN USHORT BufferFlag ) { PSLIST_ENTRY LocalList, Entry; PWMI_BUFFER_HEADER Buffer; PWMI_BUFFER_HEADER TmpBuffer=NULL; NTSTATUS Status = STATUS_SUCCESS; ULONG Retry; ULONG BufferCounts = 0; USHORT Flag = WMI_BUFFER_FLAG_NORMAL; ULONG ErrorCount = 0;
LocalList = List;
//
// Reverse the list to preserve the FIFO order
//
Entry = NULL; while (LocalList!=NULL) { PSLIST_ENTRY Next; Next = LocalList->Next; LocalList->Next = Entry; Entry = LocalList; LocalList = Next; BufferCounts++; } LocalList = Entry;
//
// Write all buffers into disk
//
while (LocalList != NULL){ BufferCounts--; if (BufferCounts == 0) { //
// Only set the flag at the last buffer.
//
Flag = BufferFlag; }
Entry = LocalList; LocalList = LocalList->Next;
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); }
//
//
// Wait until no one else is using the buffer before
// writing it out.
//
Retry = 0; TmpBuffer = Buffer; while (Buffer->ReferenceCount) { TraceDebug((1,"Waiting for reference count %3d, retry: %3d\n", Buffer->ReferenceCount, Retry)); //
//
//
KeDelayExecutionThread (KernelMode, FALSE, (PLARGE_INTEGER)&WmiShortTime); Retry++; if (Retry > 10) { //
// The buffer is still in use, we cannot overwite the header.
// Otherwise it will cause buffer corrution.
// Use a tempamory buffer instead.
//
ULONG BufferSize = LoggerContext->BufferSize; TmpBuffer = ExAllocatePoolWithTag(NonPagedPool, BufferSize, TRACEPOOLTAG); if (TmpBuffer) { TraceDebug((1,"Buffer %p has ref count %3d, Tmporary buffer %p Allocated\n", Buffer, Buffer->ReferenceCount, TmpBuffer));
RtlCopyMemory(TmpBuffer, Buffer, BufferSize); } else { Status = STATUS_NO_MEMORY; } break; } }
if (TmpBuffer) { Status = WmipFlushBuffer(LoggerContext, TmpBuffer, Flag); } else { //
// The buffer still in use but allocation of temporary
// buffer failed.
// Cannot write this buffer out, claim it as buffer lost
//
// If this were the last buffer on file, post processing can
// fail due to marker buffer failing.
LoggerContext->LogBuffersLost++; }
if (TmpBuffer != Buffer) { if (TmpBuffer != NULL) { ExFreePool(TmpBuffer); } InterlockedPushEntrySList(&LoggerContext->WaitList, (PSLIST_ENTRY) &Buffer->SlistEntry); } else { //
// Reference count is overwriten during the flush,
// Set it back.
//
Buffer->ReferenceCount = 0; WmipPushFreeBuffer (LoggerContext, Buffer); }
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) { #if DBG
NTSTATUS CloseStatus = #endif
ZwClose(LoggerContext->LogFileHandle); TraceDebug((1, "WmipLogger: Close logfile with status=%X\n", CloseStatus)); } LoggerContext->LogFileHandle = NULL;
WmipSendNotification(LoggerContext, Status, (Status & 0xC0000000) >> 30);
}
return Status; }
NTSTATUS WmipFlushActiveBuffers( IN PWMI_LOGGER_CONTEXT LoggerContext, IN ULONG FlushAll ) { PWMI_BUFFER_HEADER Buffer; ULONG i; NTSTATUS Status = STATUS_SUCCESS; ULONG LoggerMode; PSLIST_ENTRY LocalList, Entry;
PAGED_CODE();
//
// If we have no LogFileHandle or RealTime mode, and the collection
// is still on, we simply return and let the buffers back up potentially
// losing events. If the Collection is Off under these conditions
// we will simply move the FlushList to FreeList as in Buffering mode.
//
LoggerMode = LoggerContext->LoggerMode;
if ( (LoggerContext->LogFileHandle == NULL) && (!(LoggerMode & EVENT_TRACE_REAL_TIME_MODE)) && (LoggerContext->CollectionOn) ) { return Status; }
LocalList = NULL; if (FlushAll) { PWMI_BUFFER_HEADER Buffers[MAXIMUM_PROCESSORS]; //
// First switch all in-used buffers.
// Put them in a tighter loop to minimize the chance of
// events out of order.
//
for (i=0; i<(ULONG)KeNumberProcessors; i++) { Buffers[i] = InterlockedExchangePointer(&LoggerContext->ProcessorBuffers[i], NULL); }
//
// Put all in-used buffers into flush list
//
for (i=0; i<(ULONG)KeNumberProcessors; i++) { if (Buffers[i]) { WmipPushDirtyBuffer ( LoggerContext, Buffers[i] ); } }
if (LoggerContext->LoggerId == WmipKernelLogger) { if ( PERFINFO_IS_GROUP_ON(PERF_CONTEXT_SWITCH) ) { for (i=0; i<(ULONG)KeNumberProcessors; i++) { //
// Flush all Buffers used for Context swaps
//
Buffer = InterlockedExchangePointer(&WmipContextSwapProcessorBuffers[i], NULL); if (Buffer) { WmipPushDirtyBuffer ( LoggerContext, Buffer); } } } #ifdef NTPERF
//
// Flush all buffer logging from user mode
//
if (PERFINFO_IS_LOGGING_TO_PERFMEM()) { PPERFINFO_TRACEBUF_HEADER pPerfBufHdr; pPerfBufHdr = PerfBufHdr();
for (i=0; i<(ULONG)KeNumberProcessors; i++) { Buffer = pPerfBufHdr->UserModePerCpuBuffer[i]; if (Buffer) { pPerfBufHdr->UserModePerCpuBuffer[i] = NULL; WmipPushDirtyBuffer ( LoggerContext, Buffer); } } } #endif //NTPERF
}
//
// Now push all dirty buffers in the local list.
// This almost guarantees that the flush marker will work.
//
if (ExQueryDepthSList(&LoggerContext->FlushList) != 0) { LocalList = ExInterlockedFlushSList (&LoggerContext->FlushList); WmipFlushBuffersWithMarker (LoggerContext, LocalList, WMI_BUFFER_FLAG_FLUSH_MARKER); }
} else if (ExQueryDepthSList(&LoggerContext->FlushList) != 0) { LocalList = ExInterlockedFlushSList (&LoggerContext->FlushList); WmipFlushBuffersWithMarker (LoggerContext, LocalList, WMI_BUFFER_FLAG_NORMAL); }
//
// Now check if any of the in-used buffer is freed.
//
if (ExQueryDepthSList(&LoggerContext->WaitList) != 0) { LocalList = ExInterlockedFlushSList (&LoggerContext->WaitList); while (LocalList != NULL){ Entry = LocalList; LocalList = LocalList->Next;
Buffer = CONTAINING_RECORD(Entry, WMI_BUFFER_HEADER, SlistEntry);
TraceDebug((1,"Wait List Buffer %p RefCount: %3d\n", Buffer, Buffer->ReferenceCount));
if (Buffer->ReferenceCount) {
//
// Still in use, put it back to WaitList
//
InterlockedPushEntrySList(&LoggerContext->WaitList, (PSLIST_ENTRY) &Buffer->SlistEntry);
} else { //
// Push it to free list
//
WmipPushFreeBuffer (LoggerContext, Buffer); } }
} return Status; }
NTSTATUS WmipGenerateFileName( IN PUNICODE_STRING FilePattern, IN OUT PLONG FileCounter, OUT PUNICODE_STRING FileName ) { LONG FileCount, Size; PWCHAR Buffer = NULL; HRESULT hr; PWCHAR wcptr;
PAGED_CODE();
if (FilePattern->Buffer == NULL) return STATUS_INVALID_PARAMETER_MIX;
// Check for valid format string
wcptr = wcschr(FilePattern->Buffer, L'%'); if (NULL == wcptr || wcptr != wcsrchr(FilePattern->Buffer, L'%')) { return STATUS_OBJECT_NAME_INVALID; } else if (NULL == wcsstr(FilePattern->Buffer, L"%d")) { return STATUS_OBJECT_NAME_INVALID; }
FileCount = InterlockedIncrement(FileCounter); Size = FilePattern->MaximumLength + 64; // 32 digits: plenty for ULONG
Buffer = ExAllocatePoolWithTag(PagedPool, Size, TRACEPOOLTAG); if (Buffer == NULL) { return STATUS_NO_MEMORY; }
hr = StringCbPrintfW(Buffer, Size, FilePattern->Buffer, FileCount);
if (FAILED(hr) || 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, IN USHORT BufferFlag ) /*++
Routine Description: This routine prepares the buffer header before writing the buffer out to Disk.
If (SavedOffset > 0), it must be the case the we have overflown CurrentOffset during WmipReserveTraceBuffer. SavedOffset should be the real offset of dirty buffer area.
If SavedOffset is not set, it is either a ContextSwap buffer or a buffer flushed due to flush timer. CurrentOffset should be used for writting it out.
Calling Functions: - WmipFlushBuffer
Arguments:
LoggerContext - Logger context
Buffer - Pointer to a buffer header that we wish to write to disk
Return Value:
NtStatus.
--*/ { ULONG BufferSize; PAGED_CODE();
BufferSize = LoggerContext->BufferSize;
if (Buffer->SavedOffset > 0) { Buffer->Offset = Buffer->SavedOffset; } else { if (Buffer->CurrentOffset > BufferSize) { //
// Some thread has incremented CurrentOffset but was swapped out
// and did not come back until the buffer is about to be flushed.
// We will correct the CurrentOffset here and hope that post
// processing will handle this correctly.
//
TraceDebug((3, "WmipPrepareHeader: correcting Buffer Offset %d, RefCount: %d\n", Buffer->CurrentOffset, Buffer->ReferenceCount)); Buffer->Offset = BufferSize; } else { Buffer->Offset = Buffer->CurrentOffset; } }
ASSERT (Buffer->Offset >= sizeof(WMI_BUFFER_HEADER)); ASSERT (Buffer->Offset <= LoggerContext->BufferSize);
//
// We write empty buffers if they have FLUSH_MARKER to facilitate
// post processing
//
if ( (BufferFlag != WMI_BUFFER_FLAG_FLUSH_MARKER) && (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; Buffer->BufferFlag = BufferFlag;
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;
if (LoggerContext->RequestFlag & REQUEST_FLAG_CIRCULAR_TRANSITION) { FileHeader->StartBuffers = (ULONG) (LoggerContext->FirstBufferOffset.QuadPart / LoggerContext->BufferSize); }
KeQuerySystemTime(&FileHeader->EndTime); if (LoggerContext->Wow && !LoggerContext->KernelTraceOn) { // We need to adjust a log file header for a non-kernel WOW64 logger.
*((PULONG)((PUCHAR)(&FileHeader->BuffersLost) - 8)) = LoggerContext->LogBuffersLost; } else { 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) {
StringCbVPrintfA((PCHAR)TraceDebugBuffer, DEBUG_BUFFER_LENGTH, DebugMessage, ap);
Clock = KeQueryPerformanceCounter(NULL); Tid = HandleToUlong(PsGetCurrentThreadId()); DbgPrintEx(DPFLTR_WMILIB_ID, DPFLTR_INFO_LEVEL, "%u (%5u): %s", Clock.LowPart, Tid, TraceDebugBuffer); }
va_end(ap);
} #endif //DBG
VOID FASTCALL WmipResetBufferHeader ( PWMI_LOGGER_CONTEXT LoggerContext, PWMI_BUFFER_HEADER Buffer, USHORT BufferType ) /*++
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
BufferType - Buffer type (e.g., Generic, ContextSwap, etc.). This is to make postprocessing easier.
Return Value:
None
--*/ { ASSERT (BufferType < WMI_BUFFER_TYPE_MAXIMUM); Buffer->SavedOffset = 0; Buffer->CurrentOffset = sizeof(WMI_BUFFER_HEADER); Buffer->Wnode.ClientContext = 0; Buffer->LoggerContext = LoggerContext; Buffer->BufferType = BufferType; 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
--*/ { ASSERT(Buffer->State.Flush == 0); ASSERT(Buffer->State.Free == 0); ASSERT(Buffer->State.InUse == 1); //
// Set the buffer flags to "flush" state
//
Buffer->State.InUse = 0; Buffer->State.Flush = 1;
//
// 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, (PSLIST_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())); }
VOID FASTCALL WmipPushFreeBuffer ( 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 Free list.
Note that this function manages a few logger context reference counts
Calling Functions: - WmipFlushActiveBuffers - WmipGetFreeBuffer
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 "free" state and save the offset
//
Buffer->State.Flush = 0; Buffer->State.InUse = 0; Buffer->State.Free = 1;
//
// Push the buffer onto the free list.
//
InterlockedPushEntrySList(&LoggerContext->FreeList, (PSLIST_ENTRY) &Buffer->SlistEntry);
//
// Maintain the reference counts
//
InterlockedIncrement((PLONG) &LoggerContext->BuffersAvailable); InterlockedDecrement((PLONG) &LoggerContext->BuffersDirty);
TraceDebug((2, "Push Free 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, WMI_BUFFER_TYPE_CTX_SWAP); //
// 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;
UNREFERENCED_PARAMETER (CurrentProcessor);
//
// 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; }
NTSTATUS WmipSwitchBuffer( IN PWMI_LOGGER_CONTEXT LoggerContext, IN PWMI_BUFFER_HEADER *BufferPointer, IN PVOID BufferPointerLocation, IN ULONG ProcessorNumber ) /*++
Routine Description:
This routine is used to switch buffers when a buffer is Full. The mechanism is as follows:
1. The caller gives us a buffer (OldBuffer) that needs to be switched. 2. Get a new buufer and use InterlockedCompareExchangePointer to switch buffers only if the OldBuffer is still not switched. 3. If the OldBuffer has been switched, ask the caller to try using the newly switched buffer for logging. Assumptions:
- The LoggerContext is locked before this routine is called.
Arguments:
LoggerContext - Logger Context.
BufferPointer - The Old buffer that needs to be switched.
BufferPointerLocation - The location of the buffer pointer for switching.
ProcessorNumber - Processor Id. Processor Id is set before switching.
Return Value:
Status --*/ { PWMI_BUFFER_HEADER CurrentBuffer, NewBuffer, OldBuffer; NTSTATUS Status = STATUS_SUCCESS; //
// Get a new buffer from Free List
//
if (!LoggerContext->CollectionOn) { Status = STATUS_WMI_ALREADY_DISABLED; NewBuffer = NULL; } else { //
// Allocate a buffer.
//
NewBuffer = WmipGetFreeBuffer (LoggerContext); if (NewBuffer) { NewBuffer->ClientContext.ProcessorNumber = (UCHAR) ProcessorNumber;
OldBuffer = *BufferPointer; CurrentBuffer = InterlockedCompareExchangePointer( BufferPointerLocation, NewBuffer, OldBuffer); //
// There are 3 cases that we need to consider depending on the outcome
// of InterlockedCompareExchangePointer.
//
// 1. CurrentBuffer is NULL and OldBuffer is not. This means FlushAll
// Code path has replaced ProcessorBuffers with NULL after this
// thread got into WmipReserveTraceBuffer. If this is the case, we
// need to do InterlockedCompareExchangePointer with NULL pointer
// one more time to push the good free buffer into ProcessorBuffer.
// 2. CurrentBuffer is not NULL, but it is not the same buffer we had,
// which means somebody already switched the buffer with a new one.
// We push the new buffer we have into FreeList and use
// the current ProcessorBuffer.
// 3. CurrentBuffer is the same as OldBuffer and it is not NULL, which
// means Switching succeeded. Push the old buffer into FlushList and
// wake up the logger thread.
//
// If both CurrentBuffer and OldBuffer are NULL, we just switch.
//
if (OldBuffer != NULL && CurrentBuffer == NULL) { CurrentBuffer = InterlockedCompareExchangePointer( BufferPointerLocation, NewBuffer, NULL);
//
// If CurrentBuffer is NULL, we successfully pushed the clean free
// buffer into ProcessorBuffer. NewBuffer already points to a new clean
// Buffer, and WmipFlushActiveBuffers already handled the old buffer
// (Pusing to FlushList and all), so there is no need to do anything.
//
if (CurrentBuffer != NULL) { //
// Somebody pushed a new buffer to ProcessorBuffer between two
// InterlockedCompareExchangePointer calls.
// We will use the ProcessorBuffer and push our new buffer into
// FreeList.
//
InterlockedPushEntrySList(&LoggerContext->FreeList, (PSLIST_ENTRY) &NewBuffer->SlistEntry); InterlockedIncrement((PLONG) &LoggerContext->BuffersAvailable); InterlockedDecrement((PLONG) &LoggerContext->BuffersInUse); NewBuffer = CurrentBuffer; } } else if (OldBuffer != CurrentBuffer) { //
// Someone has switched the buffer, use this one
// and push the new allocated buffer back to free list.
//
InterlockedPushEntrySList(&LoggerContext->FreeList, (PSLIST_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 );
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); } } } } else { //
// There is no free buffer to switch with. NewBuffer is NULL.
// We just push the processor buffer into FlushList and exit.
// If we don't do this, CurrentOffset in the processor buffer header
// may overflow.
//
OldBuffer = *BufferPointer; CurrentBuffer = InterlockedCompareExchangePointer( BufferPointerLocation, NULL, OldBuffer); //
// CurrentBuffer is not NULL, so either we switched it to NULL
// ourselves, or someone else has done so right before us.
//
if (CurrentBuffer != NULL) { if (CurrentBuffer == OldBuffer) { // We switched successfully.
// Push the processor buffer to FlushList.
WmipPushDirtyBuffer (LoggerContext, OldBuffer); Status = STATUS_NO_MEMORY; } else { // Someone has pushed a new free buffer to a processor.
// We will try using this buffer.
NewBuffer = CurrentBuffer; } } else { Status = STATUS_NO_MEMORY; } } }
TraceDebug((2, "Switching CPU Buffers, CurrentOne: %p\n", *BufferPointer));
*BufferPointer = NewBuffer;
TraceDebug((2, "Switching CPU Buffers, New One : %p, %x\n", *BufferPointer, Status));
return(Status); }
#ifdef NTPERF
NTSTATUS WmipSwitchPerfmemBuffer( PWMI_SWITCH_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_LOGGER_CONTEXT LoggerContext = WmipLoggerContext[WmipKernelLogger]; NTSTATUS Status = STATUS_SUCCESS; PPERFINFO_TRACEBUF_HEADER pPerfBufHdr;
WmipReferenceLogger(WmipKernelLogger);
if ((PERFINFO_IS_LOGGING_TO_PERFMEM()) && (SwitchBufferInfo->ProcessorId <= MAXIMUM_PROCESSORS)) {
pPerfBufHdr = PerfBufHdr();
if( (SwitchBufferInfo->Buffer == NULL)) { //
// Must be first time, initialize the buffer size
//
pPerfBufHdr->TraceBufferSize = LoggerContext->BufferSize; }
Status = WmipSwitchBuffer(LoggerContext, &SwitchBufferInfo->Buffer, &pPerfBufHdr->UserModePerCpuBuffer[SwitchBufferInfo->ProcessorId], SwitchBufferInfo->ProcessorId); }
WmipDereferenceLogger(WmipKernelLogger); return (Status); } #endif //NTPERF
|