event tracing for windows 2 controller

其实Windows系统自开机之后就已经运行着很多事件跟踪会话。我们可以从“计算机管理->系统工具->性能->数据收集器->事件跟踪会话” 里看到,如下图所示:

event tracing model

我们这个在这个界面里,可以控制或者查看某个事件跟踪会话的各种信息。如下图所示。

event tracing model

此外,我们还可以通过logman这个命令行工具,logman query -ets来查询目前系统运行的事件跟踪会话,如下图所示:

event tracing model

也可以通过QueryAllTraces来枚举系统中的事件跟踪会话。具体实现可以参考QueryAllTraces文档中的示例代码。

创建事件跟踪会话

创建事件跟踪会话,我们需要定义一个EVENT_TRACE_PROPERTIES结构体,用它来控制事件跟踪会话的各种属性。分配的EVENT_TRACE_PROPERTIES结构体内存必须足够大到包括会话名和log文件名,LoggerNameOffset属性包含了会话名的偏移量,LogFileNameOffset属性包含了文件名的偏移量。然后可以通过StartTrace来启动会话,如果函数成功,则SessionHandle参数是会话句柄。

代码如下:

#include <windows.h>

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

#define LOGFILE_PATH L"event_tracing_session.etl"
#define LOGSESSION_NAME L"my ets"

// {C12A320D-50B6-4328-BB80-90C424CBAA70}
static const GUID SessionGuid = {
    0xc12a320d,
    0x50b6,
    0x4328,
    {0xbb, 0x80, 0x90, 0xc4, 0x24, 0xcb, 0xaa, 0x70}};

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

  BufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGFILE_PATH) +
               sizeof(LOGSESSION_NAME);
  pSessionProperties = (EVENT_TRACE_PROPERTIES *)malloc(BufferSize);
  if (NULL == pSessionProperties) {
    wprintf(L"Unable to allocate %d bytes for properties structure.\n",
            BufferSize);
    return;
  }

  ZeroMemory(pSessionProperties, BufferSize);
  pSessionProperties->Wnode.BufferSize = BufferSize;
  pSessionProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
  pSessionProperties->Wnode.ClientContext = 1; // QPC clock resolution
  pSessionProperties->Wnode.Guid = SessionGuid;
  pSessionProperties->LogFileMode = EVENT_TRACE_FILE_MODE_SEQUENTIAL;
  pSessionProperties->MaximumFileSize = 1; // 1 MB
  pSessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
  pSessionProperties->LogFileNameOffset =
      sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGSESSION_NAME);

  StringCbCopy((LPWSTR)((char *)pSessionProperties +
                        pSessionProperties->LoggerNameOffset),
               sizeof(LOGSESSION_NAME), LOGSESSION_NAME);
  StringCbCopy((LPWSTR)((char *)pSessionProperties +
                        pSessionProperties->LogFileNameOffset),
               sizeof(LOGFILE_PATH), LOGFILE_PATH);

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

  if (pSessionProperties) {
    free(pSessionProperties);
    pSessionProperties = NULL;
  }
}

成功之后,就可以在“计算机管理->系统工具->性能->数据收集器->事件跟踪会话”里面看到我们创建的名为Lmy ets的事件跟踪会话。

ControlTrace

StartTrace是启动事件跟踪会话,而ControlTrace可以对事件跟踪会话有更多的控制,比如更新配置,查询配置,关闭等等。既可以通过之前打开的会话句柄来控制,也可以根据会话名的控制。

NT Kernel Logger会话

我们之前创建的事件跟踪会话仅仅是个空壳子,里面没有任何Provider来提供事件信息,所以我们上面的代码例子没有调用EnableTrace去启用Provider。

事实上操作系统已经预定义了许多内核事件信息的Provider。我们可以启动一个NT Kernel Logger会话去记录这些信息。

NT Kernel Logger会话也不需要去调用EnableTrace去启用Provider,而是在EVENT_TRACE_PROPERTIES结构体的EnableFlags去指定需要记录那些内核事件。

#define INITGUID  // Include this #define to use SystemTraceControlGuid in Evntrace.h.

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

#define LOGFILE_PATH L"nt_kernel_logger.etl"

void wmain(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;
    }

    // Set the session properties. You only append the log file name
    // to the properties structure; the StartTrace function appends
    // the session name for you.

    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_THREAD | EVENT_TRACE_FLAG_DISK_FILE_IO |
      EVENT_TRACE_FLAG_DISK_IO | EVENT_TRACE_FLAG_PROCESS |
      EVENT_TRACE_FLAG_FILE_IO | EVENT_TRACE_FLAG_FILE_IO_INIT |
      EVENT_TRACE_FLAG_CSWITCH | EVENT_TRACE_FLAG_PROCESS_COUNTERS;
    pSessionProperties->LogFileMode = EVENT_TRACE_FILE_MODE_CIRCULAR;
    pSessionProperties->MaximumFileSize = 500;  // 500 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);
}

可以看到生成了一个nt_kernel_logger.etl文件,短短的几秒钟就抓取到了几十MB的数据.我们可以用wpa软件打开它,如下:

event tracing model

私有Logger会话

系统中同时运行的事件跟踪会话的最大数量是64个,此外事件跟踪会话是公开的,所有进程都可以去控制它。因此有时候我们需要创建一种私有Logger会话,仅在本进程中可以控制,也不受64个最大数量的限制。

创建一个私有Logger会话跟一般的事件跟踪会话类似,但是要求事件跟踪的controller和provider必须在同一个进程里。此外会话的EVENT_TRACE_PROPERTIESWnode.Guid字段值是provider的GUID