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

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

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

也可以通过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软件打开它,如下:

私有Logger会话
系统中同时运行的事件跟踪会话的最大数量是64个,此外事件跟踪会话是公开的,所有进程都可以去控制它。因此有时候我们需要创建一种私有Logger会话,仅在本进程中可以控制,也不受64个最大数量的限制。
创建一个私有Logger会话跟一般的事件跟踪会话类似,但是要求事件跟踪的controller和provider必须在同一个进程里。此外会话的EVENT_TRACE_PROPERTIES
的Wnode.Guid
字段值是provider的GUID