[Из песочницы] Изучаем Event Tracing for Windows: теория и практика

Добрый день. Недавно мне необходимо было разобраться со службой трассировки Windows. Эта служба появилась еще в Windows 2000, однако статей по этой службе в интернете оказалось крайне мало.Так появилась идея написания этой статьи. Итак, начнем!

Сегодня я попытаюсь рассказать про:

  1. Теоретические основы службы трассировки Windows
  2. Создание своей сессии ETW
  3. Использование event tracing API для работы с ETW
  4. Использование tracerpt и xperf для работы с ETW


Теоретические основы службы трассировки Windows


Event Tracing for Windows (ETW) — это служба, которая позволяет получать события от одного или нескольких поставщиков событий в режиме реального времени или из файла *.etl за некоторый временной период. Не понятно? Сейчас разберемся!

Для того, чтобы понять принцип работы ETW, необходимо разобраться со структурой этой службы

image

Архитектура ETW включает в себя 4 элемента

  1. поставщики событий (providers)
  2. потребители событий (consumers)
  3. контроллеры ETW (controllers)
  4. сессии ETW (event tracing sessions)


Принцип работы состоит в следующем.

В системе зарегистрировано некоторое число поставщиков событий, т.е. приложений, которые могут делиться своими событиями с сессиями ETW. Так же в этой системе есть некоторое число активных сессий ETW, которые могут потреблять события от одного или нескольких поставщиков и предоставлять их пользователю либо в режиме реального времени, либо записывать все события от поставщиков в файл логирования (*.etl). И управляют всем этим движением контроллеры.

А теперь рассмотрим каждый элемент рассмотренной выше архитектуры подробнее, чтобы окончательно разобраться с принципом работы!

Поставщики событий (providers)


Поставщики событий — это приложения, содержащие инструменты отслеживания событий. После того, как поставщик зарегистрировался, контроллер может включить или отключить отслеживание событий в поставщике. Поставщик определяет свою интерпретацию включения или выключения. Как правило, включенный поставщик генерирует события, а отключенный поставщик нет. Это позволяет добавлять отслеживание событий в наше приложение, не требуя, чтобы оно генерировало события все время.

Один поставщик может делиться своими событиями сразу с несколькими сессиями ETW.

Каждое событие состоит из двух элементов: заголовка и данных! Заголовок события включает информацию о событии: идентификатор провайдера, идентификатор события, временную метку и т.д. Остальные данные определяются конкретным провайдером: ETW принимает любые данные и записывает их в буфер, а их интерпретация возлагается на потребителей информации.
Существует четыре основных типа провайдеров:

поставщики MOF (классические)
провайдеры WPP
провайдеры на основе манифеста
провайдеры TraceLogging.

Поставщики событий различаются по типам полей, которые они хранят в полезных нагрузках событий.

С поставщиками событий вроде разобрались. Идем дальше!

Контроллеры


Контроллер — это приложение, которое отвечает за функционирование одной или нескольких сессий ETW. Именно контроллер определяет размер и местоположение файла журнала, запускает и останавливает сеансы трассировки событий (сессии ETW), позволяют поставщикам регистрировать события в сеансе. Как уже было сказано ранее, именно контроллер разрешает провайдеру делиться своими событиями!

Потребители


Потребители — это приложения, которые получают и обрабатывают события от одного или нескольких сеансов трассировки одновременно. Потребители могут получать события, хранящиеся в файлах журналов или из сеансов, которые доставляют события в режиме реального времени. Как мы уже знаем, у одной сессии ETW может быть несколько поставщиков. Возникает вопрос:, а не будет ли путаницы? Как события из различных сессий ETW будут располагаться друг относительно друга? События сортируются по времени их появления, т.е. система доставляет события в хронологическом порядке!

Сессии ETW


Сеансы отслеживания событий (сессии ETW) записывают события от одного или нескольких провайдеров, которые разрешает контроллер. Сессия также отвечает за управление и очистку буферов.

Трассировка событий поддерживает до 64 сеансов трассировки событий, выполняющихся одновременно. Из этих сессий есть две сессии специального назначения. Остальные сеансы доступны для общего пользования. Две сессии специального назначения:

  • Global Logger Session
  • NT Kernel Logger Session


Сеанс трассировки событий Global Logger записывает события, которые происходят в начале процесса загрузки операционной системы, например, генерируемые драйверами устройств.
Сеанс трассировки событий NT Kernel Logger записывает заранее определенные системные события, сгенерированные операционной системой, например, события дискового ввода-вывода или сбоя страницы.

Итак, а теперь переходим к практике!!!

Создание своей сессии ETW


Перед началом работы нам потребуется знание нескольких утилит, а именно:

список провайдеров, доступных на конкретной ОС

logman query providers


получить полную информацию о провайдере

wevtutil gp <имя провайдера> /ge /gm


список всех активный сессий ETW

xperf -loggers


Так же, для просмотра файлов, желательно иметь Notepad++.

Просмотрев список провайдеров на своем компьютере (а их более 1000 на Windows 10), выберем один из них для нашей сессии:

image

Я выбрал Microsoft-Windows-WinINet (эта служба записывает все наши действия при работе в браузере Microsoft Edge).

1. Win+R → compmgmt.msc
2. «Performance» («Производительность»)
3. «Data Collector Sets» («Группы сборщиков данных»)
4. «Event Trace Sessions» («Сеансы отслеживания событий»)
5. «New» («Создать»)
6. «Data Collector Set» («Группа сборщиков данных)
7. Указываем имя сборщика данных
8. «Create manually (Advanced)» («Создать вручную (для опытных)»)

image
9. Добавляем интересующие нас провайдеры в сессию
10. Указываем интересующие нас ключевые слова в поле «Keywords (Any)» («Ключевые слова (Любые)») — 0xFFFFFFFFFFFFFFFF
11. Указываем уровень логирования 0xFF
=image

12. Выбираем путь, по которому будет сохраняться файл журнала сессии
13. Выбираем флажок «Start this data collector set now» («Запустить группу сборщиков данных сейчас»)

Теперь созданная нами сессия работает. Необходимо поработать некоторое время в Microsoft Edge, чтобы сессия собрала о нас информацию!

После того, как прошло некоторое время переходим в место, куда мы сохранили файл логирования. Там выполняем следующую команду.

tracerpt "моя группа сборщиков данных.etl" -o -report -summary -lr


После выполнения этой команды сформируется 4 файла.

image

Нас в данный момент будет интересовать dumpfile.xml. Открывать этот файл можно либо через notepad++, можно также сделать это в Excel.

Внимательно изучив этот файл, можно заметить, что данная сессия собрала почти всю информацию о нашем перемещении в сети интернет!!! Более подробно об этом можно почитать здесь Изучаем ETW и извлекаем профиты.

Ну что же, а мы движемся дальше. Только что мы создали сессию с единственным поставщиком событий. Получили данные сессии из файла логирования. Пришло время кодить!

Использование event tracing API для работы с ETW


На хабре есть интересная статья, Самый худший из когда-либо созданных 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 
#include 
#include 
#include 
#include 
#include 
#include 
#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 | EnableTraceEx2, передав EVENT_CONTROL_CODE_DISABLE_PROVIDER

Чтобы остановить сеанс трассировки после сбора событий, вызовите функцию ControlTrace и передайте EVENT_TRACE_CONTROL_STOP в качестве управляющего кода. Чтобы указать сеанс для остановки, вы можете передать дескриптор сеанса трассировки событий, полученный из более раннего вызова функции StartTrace, или имя ранее запущенного сеанса. Обязательно отключите всех провайдеров перед остановкой сеанса. Если вы остановите сеанс перед первым отключением провайдера, ETW отключит провайдера и попытается вызвать контрольную функцию обратного вызова провайдера. Если приложение, запустившее сеанс, завершается без отключения поставщика или вызова функции ControlTrace, поставщик остается включенным.

5) Чтобы остановить сеанс трассировки, вызываем функцию ControlTrace и передаем ей EVENT_TRACE_CONTROL_STOP

Как мы убедились на приведенном выше примере, использование Event Tracing API является не самым простым. В зависимости от того, чем вы занимаетесь, дальше можно заниматься либо написание поставщиков событий, либо написанием потребителей событий. Однако обе эти задачи довольно объемные и в этой статье рассматриваться не будут! Дополнительную сложность создают 4 вида поставщиков событий, и, соответственно, 4 варианта написания событий и 4 варианта их потребления. Очень подробно и хорошо работа с Event Tracing API описана на официальном сайте Microsoft Using Event Tracing

Проработав некоторое время с Event Tracing API у меня появился вопрос:, а есть ли утилиты, которые упросят мне жизнь?

Использование tracerpt и xperf для работы с ETW


В этой главе я не буду рассматривать эти утилиты с теоретической точки зрения.

Команду Tracerpt можно использовать для анализа журналов трассировки событий, файлов журналов, созданных монитором производительности, и поставщиков трассировки событий в реальном времени. Он создает файлы дампа, файлы отчетов и схемы отчетов. У этой утилиты большое количество параметров, однако для начала работы подойдет следующий «минимум»

tracerpt "имя 1-ого файла.etl" ... "имя n-ого файла.etl" -o <имя текстового выходного файла> -report <имя текстового выходного файла отчета> -summary<имя текстового файла сводного отчета> 


Утилита xperf.exe является полноценным контроллером. Она поддерживает аргументы командной строки, позволяющие управлять ETW-провайдерами и сессиями. Контроллеры могут запрашивать состояние активных в данный момент сессий и получать списки всех зарегистрированных в системе провайдеров. Например, для получения всех активных сессий следует использовать следующую команду:

C:\>xperf -loggers


а для получения списка всех зарегистрированных в системе провайдеров — команду:

C:\>xperf -providers


Контроллеры обладают еще несколькими ключевыми функциями. Они могут обновлять сессии и сбрасывать содержимое буферов на диск.

На этом пока все!

К сожалению, в данной статье я не затронул некоторое количество интересных вопросов (например, потребление событий в режиме реального времени или работу с сессиями специального назначения).

Об этом можно почитать на следующих сайтах:

Event Tracing — официальная документация Microsoft
Изучаем ETW и извлекаем профиты
Event Tracing for Windows на стороне зла. Но это не точно
Самый худший из когда-либо созданных API

© Habrahabr.ru