学习Windows的事件跟踪:理论与实践

下午好。最近,我需要处理Windows跟踪服务。该服务出现在Windows 2000中,但是Internet上关于该服务的文章很少,因此出现了撰写本文的想法。所以,让我们开始吧!

今天,我将尝试谈论:

  1. Windows跟踪服务理论
  2. 创建您的ETW会话
  3. 使用事件跟踪API与ETW一起使用
  4. 使用tracerpt和xperf与ETW一起使用

Windows跟踪服务理论


Windows事件跟踪(ETW)是一项服务,允许您在特定时间段内实时从一个或多个事件提供者或* .etl文件接收事件。不清楚?现在让我们弄清楚!

为了了解ETW的工作原理,您需要了解此服务的结构

图片

ETW架构包含4个元素

  1. 活动提供者
  2. 事件消费者
  3. ETW控制器
  4. ETW会话(事件跟踪会话)

工作原理如下。

在系统中注册了许多事件提供者,即 可以与ETW会议分享经验的应用程序。同样在此系统中,有一定数量的活动ETW会话可以使用来自一个或多个提供程序的事件,并将其实时提供给用户或将所有事件从供应商写入日志文件(* .etl)。控制器控制整个运动。

现在,我们将更详细地考虑上述架构的每个元素,以最终了解工作原理!

活动提供者


事件提供程序是包含事件跟踪工具的应用程序。提供者注册后,控制器可以在提供者中启用或禁用事件跟踪。提供者确定其对打开或关闭的解释。通常,启用的提供程序会生成事件,而禁用的提供程序则不会。这使您可以将事件跟踪添加到我们的应用程序中,而无需始终生成事件。

一个供应商可以一次与多个ETW会话共享他们的事件。

每个事件都包含两个元素:标头和数据!事件标头包含有关事件的信息:提供者标识符,事件标识符,时间戳等。其余数据由特定提供者确定:ETW接收任何数据并将其写入缓冲区,然后将其解释分配给信息使用者。
提供程序有四种主要类型:

MOF提供程序(经典)
提供
程序基于清单
提供程序TraceLogging的WPP 提供程序。

事件提供程序在事件有效负载中存储的字段类型有所不同。

事件提供者似乎已被整理。继续!

控制器


控制器是负责一个或多个ETW会话操作的应用程序。控制器确定日志文件的大小和位置,启动和停止事件跟踪会话(ETW会话),并允许提供程序在该会话中记录事件。如前所述,允许提供者共享事件的是控制器!

消费者


使用者是同时接收和处理来自一个或多个跟踪会话的事件的应用程序。使用者可以接收存储在日志文件中的事件或来自实时传递事件的会话中的事件。众所周知,一场ETW会议可以有多个供应商。问题出现了:会有混乱吗?来自各个ETW会议的事件将如何相互关联?事件按事件发生的时间排序,即 系统按时间顺序传送事件!

ETW会议


事件跟踪会话(ETW会话)记录控制器允许的来自一个或多个提供程序的事件。该会话还负责管理和刷新缓冲区。

事件跟踪最多支持64个同时运行的事件跟踪会话。在这些会议中,有两个特殊用途的会议。其余会话可供一般使用。两个特殊用途的会议:

  • 全球记录器会议
  • NT内核记录器会话

全局记录器事件跟踪会话记录在操作系统启动过程开始时发生的事件,例如由设备驱动程序生成的事件。
NT事件跟踪会话内核记录器记录由操作系统生成的预定义系统事件,例如磁盘I / O事件或页面故障。

所以,现在让我们开始练习!

创建您的ETW会话


在开始工作之前,我们需要了解一些实用程序,即:

特定操作系统上可用的提供程序列表

logman query providers

获取有关提供商的完整信息

wevtutil gp < > /ge /gm

所有活动的ETW会话列表

xperf -loggers

另外,对于查看文件,建议使用记事本++。

在查看了计算机上的提供程序列表之后(在Windows 10上有1000多个),我们将在会话中选择其中之一:

图片

我选择了Microsoft-Windows-WinINet(此服务记录了在Microsoft Edge浏览器中工作时的所有操作)。

1. Win + R-> compmgmt.msc
2.“性能”
3.“数据收集器集”
4.“事件跟踪会话”
5。 “新建”
6.“数据收集器集”
7.指定数据收集器的名称
8.“手动创建(高级)”(“手动创建(针对经验丰富的人”))

图片
9.添加有趣的内容每个会话我们提供程序
10.在“关键字(任意)”字段中
指定我们感兴趣的关键字-0xFFFFFFFFFFFFFFFFFFFF 11.指定日志记录级别0xFF
= 图片

12。选择将保存会话日志文件的路径。13
.选择“立即启动此数据收集器集“(“立即运行数据收集器组”)

现在,我们创建的会话开始工作。 Microsoft Edge需要一些工作才能使会话收集有关我们的信息!

经过一段时间后,我们转到保存日志文件的位置。我们在那里执行以下命令。

tracerpt "   .etl" -o -report -summary -lr

执行此命令后,将生成4个文件。

图片

我们目前对dumpfile.xml感兴趣。您可以通过记事本++打开此文件,也可以在Excel中执行此操作。

仔细研究了此文件后,您可以看到本次会议收集了有关我们在互联网上的活动的几乎所有信息!您可以在此处了解更多信息。我们研究ETW并获取利润

好吧,我们继续前进。我们刚刚创建了一个具有单个事件提供程序的会话。从日志文件接收的会话数据。是时候编写代码了!

使用事件跟踪API与ETW一起使用


在habr上有一篇有趣的文章,有史以来最差的API

在本文中,您将找到编写应用程序时很可能会遇到的许多问题的答案!

我们将使用C ++进行编码。

让我们从最简单的开始。

配置并启动事件跟踪会话


首先,考虑总体思路。

要启动跟踪会话,请执行以下操作:

1)设置结构EVENT_TRACE_PROPERTIES

2)使用StartTrace启动会话
接下来,启用事件提供程序

3)使用EnableTrace |打开提供程序| EnableTraceEx | EnableTraceEx2
要停止跟踪会话,您必须:

4)在停止跟踪会话之前,必须使用EnableTrace |禁用提供程序。 EnableTraceEx | EnableTraceEx2,传递EVENT_CONTROL_CODE_DISABLE_PROVIDER

5)调用ControlTrace函数并传递EVENT_TRACE_CONTROL_STOP

在下面的示例中,我正在创建一个名为MyEventTraceSession的会话。当前目录中的日志文件名为WriteThePuth.etl

,事件提供程序为Microsoft-Windows-Kernel-Process。您可以使用以下命令找到他的GUID

wevtutil gp Microsoft-Windows-Kernel-Process /ge /gm

直接编码:

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <strsafe.h>
#include <wmistr.h>
#include <evntrace.h>
#include <iostream>
#define LOGFILE_PATH L"WriteThePuth.etl"
#define LOGSESSION_NAME L"MyEventTraceSession"


// GUID,     .
//      GUID .

// {AE44CB98-BD11-4069-8093-770EC9258A12}
static const GUID SessionGuid =
{ 0xae44cb98, 0xbd11, 0x4069, { 0x80, 0x93, 0x77, 0xe, 0xc9, 0x25, 0x8a, 0x12 } };


// GUID,   ,   
//    .

//{22FB2CD6-0E7B-422B-A0C7-2FAD1FD0E716} Microsoft-Windows-Kernel-Process
static const GUID ProviderGuid =
{ 0xd22FB2CD6, 0x0E7B, 0x422B, {0xA0, 0xC7, 0x2F, 0xAD, 0x1F, 0xD0, 0xE7, 0x16 } };

void wmain(void)
{
    setlocale(LC_ALL, "ru");
    ULONG status = ERROR_SUCCESS;
    TRACEHANDLE SessionHandle = 0;
    EVENT_TRACE_PROPERTIES* pSessionProperties = NULL;
    ULONG BufferSize = 0;
    BOOL TraceOn = TRUE;

    
    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);
        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 = SessionGuid;
    pSessionProperties->LogFileMode = EVENT_TRACE_FILE_MODE_SEQUENTIAL;
    pSessionProperties->MaximumFileSize = 1024;  // 1024 MB
    pSessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
    pSessionProperties->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGSESSION_NAME);
    StringCbCopy((LPWSTR)((char*)pSessionProperties + pSessionProperties->LogFileNameOffset), sizeof(LOGFILE_PATH), LOGFILE_PATH);
    

    status = StartTrace((PTRACEHANDLE)&SessionHandle, LOGSESSION_NAME, pSessionProperties);
    if (ERROR_SUCCESS != status)
    {
        wprintf(L"StartTrace() failed with %lu\n", status);
        goto cleanup;
    }

    //  ,   ,      .

    status = EnableTraceEx2(
        SessionHandle,
        (LPCGUID)&ProviderGuid,
        EVENT_CONTROL_CODE_ENABLE_PROVIDER,
        TRACE_LEVEL_INFORMATION,
        0,
        0,
        0,
        NULL
        );

    if (ERROR_SUCCESS != status)
    {
        wprintf(L"EnableTrace() failed with %lu\n", status);
        TraceOn = FALSE;
        goto cleanup;
    }

    //   .    ,   
    wprintf(L"Run the provider application. Then hit any key to stop the session.\n");
    _getch();
   

cleanup:
    if (SessionHandle)
    {
        if (TraceOn)
        {
            status = EnableTraceEx2(
                SessionHandle,
                (LPCGUID)&ProviderGuid,
                EVENT_CONTROL_CODE_DISABLE_PROVIDER,
                TRACE_LEVEL_INFORMATION,
                0,
                0,
                0,
                NULL
                );
        }

        status = ControlTrace(SessionHandle, LOGSESSION_NAME, pSessionProperties, EVENT_TRACE_CONTROL_STOP);

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

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

我们将更详细地分析上述程序。

1)设置EVENT_TRACE_PROPERTIES结构

要配置事件跟踪会话,必须使用EVENT_TRACE_PROPERTIES结构指定会话属性。您为EVENT_TRACE_PROPERTIES结构分配的内存必须足够大,以包含遵循该结构的会话和日志文件的名称。

2)使用StartTrace启动会话

指定会话的属性后,调用StartTrace函数以启动会话。如果函数成功,则SessionHandle参数将包含会话描述符,而LoggerNameOffset属性将包含会话名称偏移量。

3)使用EnableTrace打开提供程序| EnableTraceEx | EnableTraceEx2

要启用要允许在会话中记录事件的提供程序,请调用EnableTrace函数以启用经典提供程序,并调用EnableTraceEx函数以启用基于清单的提供程序。在其他情况下-EnableTraceEx2。

4)在停止跟踪会话之前,必须使用EnableTrace |禁用提供程序。 EnableTraceEx |通过传递EVENT_CONTROL_CODE_DISABLE_PROVIDER启用EnableTraceEx2

要在收集事件后停止跟踪会话,请调用ControlTrace函数并传递EVENT_TRACE_CONTROL_STOP作为控制代码。若要指定要停止的会话,可以将从较早的调用获得的事件跟踪会话描述符传递给StartTrace函数,或者将先前启动的会话的名称传递给该对象。在停止会话之前,请确保断开所有提供程序的连接。如果您在第一次关闭提供程序之前停止了会话,则ETW将断开提供程序的连接,并尝试调用提供程序回调控制功能。如果启动会话的应用程序终止而没有断开提供程序或调用ControlTrace函数,则该提供程序保持打开状态。

5)要停止跟踪会话,请调用ControlTrace函数并将其传递给EVENT_TRACE_CONTROL_STOP

正如我们在上面的示例中看到的那样,使用事件跟踪API并非最简单。根据您的操作,您可以继续编写事件提供程序或编写事件使用者。但是,这两项任务都很繁琐,因此本文中将不予考虑!四种类型的事件提供程序以及相应的用于编写事件的4个选项和用于其使用的4个选项会增加额外的复杂性。使用事件跟踪在API上进行了详细描述,并且在Microsoft官方网站上也进行了详细介绍。使用事件跟踪

在使用事件跟踪API一段时间后,我有一个问题:是否有任何实用程序可以简化我的生活?

使用tracerpt和xperf与ETW一起使用


在本章中,我不会从理论角度考虑这些实用程序。

您可以使用Tracerpt命令来分析事件跟踪日志,性能监视器生成的日志文件以及实时事件跟踪提供程序。它创建转储文件,报告文件和报告架构。该实用程序具有大量参数,但是以下“最小值”适用于开始工作

tracerpt " 1- .etl" ... " n- .etl" -o <   > -report <    > -summary<    > 

xperf.exe实用工具是成熟的控制器。它支持用于管理ETW提供程序和会话的命令行参数。控制器可以请求当前活动会话的状态,并接收系统中注册的所有提供程序的列表。例如,要获取所有活动的会话,请使用以下命令:

C:\>xperf -loggers

并获取系统中注册的所有提供程序的列表,请使用以下命令:

C:\>xperf -providers

控制器还有其他一些关键功能。他们可以更新会话并将缓冲区刷新到磁盘。

目前为止就这样了!

不幸的是,在本文中,我没有解决许多有趣的问题(例如,实时使用事件或使用特殊用途的会话)。

您可以在以下站点上阅读有关此内容的信息:

事件跟踪 -Microsoft官方文档
我们研究了ETW并
从邪恶的角度提取了Windows事件跟踪的好处但这并不是
有史以来最糟糕的API

All Articles