2991 lines
92 KiB
C
2991 lines
92 KiB
C
/*++
|
||
|
||
Copyright (c) 1996 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
tracelog.c
|
||
|
||
Abstract:
|
||
|
||
This is the source file that implements the private routines for
|
||
the performance event tracing and logging facility.
|
||
The routines here work on a single event tracing session, the logging
|
||
thread, and buffer synchronization within a session.
|
||
|
||
Author:
|
||
|
||
Jee Fung Pang (jeepang) 03-Dec-1996
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
// TODO: In future, may need to align buffer size to larger of disk alignment
|
||
// or 1024.
|
||
|
||
#pragma warning(disable:4214)
|
||
#pragma warning(disable:4115)
|
||
#pragma warning(disable:4201)
|
||
#pragma warning(disable:4127)
|
||
#include "ntverp.h"
|
||
#include "ntos.h"
|
||
#include "wmikmp.h"
|
||
#include <zwapi.h>
|
||
#pragma warning(default:4214)
|
||
#pragma warning(default:4115)
|
||
#pragma warning(default:4201)
|
||
#pragma warning(default:4127)
|
||
|
||
#ifndef _WMIKM_
|
||
#define _WMIKM_
|
||
#endif
|
||
|
||
#include "evntrace.h"
|
||
|
||
//
|
||
// Constants and Types used locally
|
||
//
|
||
#if DBG
|
||
ULONG WmipTraceDebugLevel=0;
|
||
// 5 All messages
|
||
// 4 Messages up to event operations
|
||
// 3 Messages up to buffer operations
|
||
// 2 Flush operations
|
||
// 1 Common operations and debugging statements
|
||
// 0 Always on - use for real error
|
||
#endif
|
||
|
||
#define ERROR_RETRY_COUNT 100
|
||
|
||
#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
|