Search code examples
c++etw

ETW - Realtime consuming of event trace


I'm not a C++ developer so apologies for any imprecise language.

I have a ETW Kernel Logger configured (basically a tweaked version of the Microsoft examples). It writes events to a log and I can view the data from the etl file. I would like to switch LogFileMode to EVENT_TRACE_REAL_TIME_MODE and interact with the data as it passes through the trace.

A specific example would be something like

foreach ($event in $trace) {
    if ($string in $event) {
        print $event
    }
}

The documentation I've read suggests I need a consumer which would run the OpenTrace function, process the events with a callback, and then close the trace? Unfortunately, I have not seen an example of a consumer like this which I can understand. Is it possible take the msft example code below and modify it to do what I'm describing or is this not a possible approach?

#define INITGUID 
#define UNICODE 1

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <strsafe.h>
#include <wmistr.h>
#include <evntrace.h>

#define LOGFILE_PATH L"C:\\Users\\userplace\\testtrace.etl"

int main(void)
{
    ULONG status = ERROR_SUCCESS;
    TRACEHANDLE SessionHandle = 0;
    EVENT_TRACE_PROPERTIES* pSessionProperties = NULL;
    ULONG BufferSize = 0;

    // Allocate memory for the session properties. The memory must
    // be large enough to include the log file name and session name,
    // which get appended to the end of the session properties structure.
    
    BufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGFILE_PATH) + sizeof(KERNEL_LOGGER_NAME);
    pSessionProperties = (EVENT_TRACE_PROPERTIES*) malloc(BufferSize);    
    if (NULL == pSessionProperties)
    {
        wprintf(L"Unable to allocate %d bytes for properties structure.\n", BufferSize);
        goto cleanup;
    }
    


    ZeroMemory(pSessionProperties, BufferSize);
    pSessionProperties->Wnode.BufferSize = BufferSize;
    pSessionProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
    pSessionProperties->Wnode.ClientContext = 1; //QPC clock resolution
    pSessionProperties->Wnode.Guid = SystemTraceControlGuid; 
    pSessionProperties->EnableFlags = EVENT_TRACE_FLAG_FILE_IO_INIT | EVENT_TRACE_FLAG_PROCESS;
    pSessionProperties->LogFileMode = EVENT_TRACE_FILE_MODE_CIRCULAR;
    pSessionProperties->MaximumFileSize = 5;  // 5 MB
    pSessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
    pSessionProperties->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(KERNEL_LOGGER_NAME); 
    StringCbCopy((LPWSTR)((char*)pSessionProperties + pSessionProperties->LogFileNameOffset), sizeof(LOGFILE_PATH), LOGFILE_PATH);

    // Create the trace session.

    status = StartTrace((PTRACEHANDLE)&SessionHandle, KERNEL_LOGGER_NAME, pSessionProperties);

    if (ERROR_SUCCESS != status)
    {
        if (ERROR_ALREADY_EXISTS == status)
        {
            wprintf(L"The NT Kernel Logger session is already in use.\n");
        }
        else
        {
            wprintf(L"EnableTrace() failed with %lu\n", status);
        }

        goto cleanup;
    }

    wprintf(L"Press any key to end trace session ");
    _getch();

cleanup:

    if (SessionHandle)
    {
        status = ControlTrace(SessionHandle, KERNEL_LOGGER_NAME, pSessionProperties, EVENT_TRACE_CONTROL_STOP);

        if (ERROR_SUCCESS != status)
        {
            wprintf(L"ControlTrace(stop) failed with %lu\n", status);
        }
    }

    if (pSessionProperties)
        free(pSessionProperties);
}

Solution

  • Here is the minimalistic example based on MSDN:

    void create_realtime_consumer(const wchar_t * session_name, EVENT_RECORD_CALLBACK * event_callback, EVENT_TRACE_BUFFER_CALLBACKW * buffer_callback)
    {
        EVENT_TRACE_LOGFILE trace{};
        TRACE_LOGFILE_HEADER * pHeader = &trace.LogfileHeader;
        TDHSTATUS status = ERROR_SUCCESS;
        trace.LoggerName = (LPWSTR)session_name; // use KERNEL_LOGGER_NAMEW to consume Kernel events
        trace.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_RAW_TIMESTAMP | PROCESS_TRACE_MODE_EVENT_RECORD; // create real time sesion + event should be represented as EVENT_RECORD structure
        trace.EventRecordCallback = event_callback; // called on each event
        trace.BufferCallback = buffer_callback; // called on each ETWbuffer flush
    
        auto h_trace = OpenTrace(&trace);
        if(h_trace == INVALID_PROCESSTRACE_HANDLE)
            throw std::runtime_error("Unable to open trace");
    
        if(pHeader->PointerSize != sizeof(PVOID))
            pHeader = (PTRACE_LOGFILE_HEADER)((PUCHAR)pHeader + 2 * (pHeader->PointerSize - sizeof(PVOID)));
    
        try {
            status = ProcessTrace(&h_trace, 1, 0, 0); // this call blocks until either the session is stopped or an exception is occurred in event_callback
        }
        catch(...) {
            // catch exceptions occurred in event_callback
        }
        CloseTrace(h_trace);
    }
    

    This code runs the consumer to process Kernel events in real time. To make it work you should first create ETW session (like in the example you mentioned above but you need to specify EVENT_TRACE_REAL_TIME_MODE in LogFileMode) and then run the consumer. You should pass KERNEL_LOGGER_NAMEW as session_name to consume Kernel events.