驅動TraceEvent使用指南

驅動TraceEvent使用指南

Windows對於驅動開發提供了ETW跟蹤日誌,本文來談一下ETW日誌的使用和基本原理。

1. 使用

在MSDN上面有文章比較詳細的說明了怎麼使用ETW日誌 https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/adding-wpp-software-tracing-to-a-windows-driver

現在看下整個過程原理。

第一步 : 新建一個trace.h的頭文件,頭文件中輸入如下的類容:

#define WPP_CONTROL_GUIDS                                              \
    WPP_DEFINE_CONTROL_GUID(                                           \
        TRACE_LOG_GUID, (8aa187bd,e3fa,4254,bf7c,ca494c3ac901), \
                                                                            \
        WPP_DEFINE_BIT(MYDRIVER_ALL_INFO)                              \
        WPP_DEFINE_BIT(TRACE_DRIVER)                                   \
        WPP_DEFINE_BIT(TRACE_DEVICE)                                   \
        WPP_DEFINE_BIT(TRACE_QUEUE)                                    \
        )                             

#define WPP_FLAG_LEVEL_LOGGER(flag, level)                                  \
    WPP_LEVEL_LOGGER(flag)

#define WPP_FLAG_LEVEL_ENABLED(flag, level)                                 \
    (WPP_LEVEL_ENABLED(flag) &&                                             \
     WPP_CONTROL(WPP_BIT_ ## flag).Level >= level)

#define WPP_LEVEL_FLAGS_LOGGER(lvl,flags) \
           WPP_LEVEL_LOGGER(flags)

#define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) \
           (WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl)

//           
// WPP orders static parameters before dynamic parameters. To support the Trace function
// defined below which sets FLAGS=MYDRIVER_ALL_INFO, a custom macro must be defined to
// reorder the arguments to what the .tpl configuration file expects.
//
#define WPP_RECORDER_FLAGS_LEVEL_ARGS(flags, lvl) WPP_RECORDER_LEVEL_FLAGS_ARGS(lvl, flags)
#define WPP_RECORDER_FLAGS_LEVEL_FILTER(flags, lvl) WPP_RECORDER_LEVEL_FLAGS_FILTER(lvl, flags)

第二步 : 在trace.h的頭文件加入如下語言:

//
// This comment block is scanned by the trace preprocessor to define our
// Trace function.
//
// begin_wpp config
// FUNC Trace{FLAGS=MYDRIVER_ALL_INFO}(LEVEL, MSG, ...);
// FUNC TraceEvents(LEVEL, FLAGS, MSG, ...);
// end_wpp
//

這個語句必須是註釋的,作用是生成TraceEvents宏,提供調用。

第三步 : 在輸入日誌的地方引入頭文件

#include "Trace.h"
#include "main.tmh"

第四步 : 初始化並使用日誌


NTSTATUS
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	WPP_INIT_TRACING(DriverObject, RegistryPath);

	TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "HelloWorld");

	WPP_CLEANUP(DriverObject);

	return STATUS_SUCCESS;
}

其中:

  1. WPP_INIT_TRACING(DriverObject, RegistryPath); : 初始化日誌。
  2. TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "HelloWorld"); : 輸出日誌。
  3. WPP_CLEANUP(DriverObject); : 清理日誌。

設置了上面這些之後,並不能編譯,因爲編譯器不知道編譯trace.h文件,還需要做如下的設置:
在這裏插入圖片描述

至此我們就可以編譯使用日誌了。

2. ETW日誌原理

WPP日誌輸出的三個重要語句如下:

  1. WPP_INIT_TRACING(DriverObject, RegistryPath); : 初始化日誌。
  2. TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "HelloWorld"); : 輸出日誌。
  3. WPP_CLEANUP(DriverObject); : 清理日誌。

下面我們分部看一下這個的基本原理。

2.1 WPP_INIT_TRACING

在.tmh 文件中編譯了這個的宏定義,如下:

#ifndef _SDV_
#define WPP_INIT_TRACING(DriverObject, RegPath)                             \
    {                                                                       \
      WppDebug(0,("WPP_INIT_TRACING: &WPP_CB[0] %p\n", &WPP_MAIN_CB[0]));   \
      WPP_INIT_STATIC_DATA;                                                 \
      WppLoadTracingSupport();                                              \
      ( WPP_CONTROL_ANNOTATION(),                                           \
        WPP_MAIN_CB[0].Control.RegistryPath = NULL,                         \
        WppInitKm( (PDRIVER_OBJECT)DriverObject, RegPath )                  \
      );                                                                    \
    }
#else
#define WPP_INIT_TRACING(DriverObject, RegPath)
#endif

首先,WppLoadTracingSupport是初始化使用的函數信息,代碼如下:

VOID
WppLoadTracingSupport(
    VOID
    )
{
    ULONG MajorVersion = 0;
    UNICODE_STRING name;

    PAGED_CODE();

    RtlInitUnicodeString(&name, L"PsGetVersion");
    pfnWppGetVersion = (PFN_WPPGETVERSION) (INT_PTR)
        MmGetSystemRoutineAddress(&name);

    RtlInitUnicodeString(&name, L"WmiTraceMessage");
    pfnWppTraceMessage = (PFN_WPPTRACEMESSAGE) (INT_PTR)
        MmGetSystemRoutineAddress(&name);


    //
    // WinXp
    //

    RtlInitUnicodeString(&name, L"WmiQueryTraceInformation");
    pfnWppQueryTraceInformation = (PFN_WPPQUERYTRACEINFORMATION) (INT_PTR)
        MmGetSystemRoutineAddress(&name);
    WPPTraceSuite = WppTraceWinXP;

    //
    // Server08
    //

    if (pfnWppGetVersion != NULL) {
        pfnWppGetVersion(&MajorVersion,
                         NULL,
                         NULL,
                         NULL);
    }

    if (MajorVersion >= 6) {

        RtlInitUnicodeString(&name, L"EtwRegisterClassicProvider");
        pfnEtwRegisterClassicProvider = (PFN_ETWREGISTERCLASSICPROVIDER) (INT_PTR)
            MmGetSystemRoutineAddress(&name);

        if (pfnEtwRegisterClassicProvider != NULL) {
            //
            // For Vista SP1 and later
            //
            RtlInitUnicodeString(&name, L"EtwUnregister");
            pfnEtwUnregister = (PFN_ETWUNREGISTER) (INT_PTR)
                MmGetSystemRoutineAddress(&name);

            WPPTraceSuite = WppTraceServer08;
        }
    }
}

這裏主要得到了幾個函數:

  1. WmiTraceMessage
  2. WmiQueryTraceInformation
  3. EtwRegisterClassicProvider
  4. EtwUnregister

然後開始是調用WppInitKm,這個函數的過程如下,主要是調用EtwRegisterClassicProvider來註冊提供者。

VOID
WppInitKm(
    _When_(_ENABLE_WPP_RECORDER, _In_) _When_(!_ENABLE_WPP_RECORDER, _In_opt_) PDRIVER_OBJECT DriverObject,
    _When_(_ENABLE_WPP_RECORDER, _In_) _When_(!_ENABLE_WPP_RECORDER, _In_opt_) PCUNICODE_STRING RegPath
    )
{
    C_ASSERT(WPP_MAX_FLAG_LEN_CHECK);

    NTSTATUS Status;
    PWPP_TRACE_CONTROL_BLOCK WppReg = NULL;

    PAGED_CODE();

    UNREFERENCED_PARAMETER(DriverObject);
    UNREFERENCED_PARAMETER(RegPath);

    if (WPP_CB != WPP_MAIN_CB) {

        WPP_CB = WPP_MAIN_CB;

    } else {
      //
      // WPP_INIT_TRACING already called
      //
      WppDebug(0,("Warning : WPP_INIT_TRACING already called, ignoring this one"));
      return;
    }

    WppReg = &WPP_CB[0].Control;

    WppDebug(0,("WPP Init.\n"));

    if (WppTraceServer08 == WPPTraceSuite) {

        //
        // Windows version >= Vista SP1
        //
        while (WppReg) {

            WppReg->RegHandle = 0;
            Status = pfnEtwRegisterClassicProvider(
                WppReg->ControlGuid,
                0,
                WppClassicProviderCallback,
                (PVOID)WppReg,
                &WppReg->RegHandle);

            if (!NT_SUCCESS(Status)) {
                WppDebug(0,("EtwRegisterClassicProvider Status = %d, ControlBlock = %p.\n", Status, WppReg));
            }

            WppReg = WppReg->Next;
        }

    } else if (WppTraceWinXP == WPPTraceSuite) {


        WppReg -> Callback = WppTraceCallback;

#pragma prefast(suppress:__WARNING_BANNED_API_ARGUMENT_USAGE, "WPP generated, requires legacy providers");
        Status = IoWMIRegistrationControl(
                                    (PDEVICE_OBJECT)WppReg,
                                    WMIREG_ACTION_REGISTER  |
                                    WMIREG_FLAG_CALLBACK    |
                                    WMIREG_FLAG_TRACE_PROVIDER
                                    );

        if (!NT_SUCCESS(Status)) {
            WppDebug(0,("IoWMIRegistrationControl Status = %08X\n",Status));
        }

    }

#if ENABLE_WPP_RECORDER
    WppAutoLogStart(&WPP_CB[0], DriverObject, RegPath);
#endif

}

這個操作的主要作用是使用EtwRegisterClassicProvider來註冊一個Provider。

2.2 TraceEvents

TraceEvents 主要就是用來輸出日誌,我們看下這個的實現原理:

#define WPP_FLATTEN(...) __VA_ARGS__
#define WPP_EVAL(x) x

#define WPP_(Id) WPP_EVAL(WPP_) ## WPP_EVAL(Id) ## WPP_EVAL(_) ## WPP_EVAL(WPP_THIS_FILE) ## WPP_EVAL(__LINE__)

#undef DoDebugTrace
#define DoDebugTrace WPP_(CALL)
#undef DoTraceMessage
#define DoTraceMessage WPP_(CALL)
#undef TraceEvents
#define TraceEvents WPP_(CALL)

那麼這裏組織完成之後就成了:

WPP_CALL_main_c15

這個的大概定義就是如下:

#define WPP_CALL_main_c15(LEVEL, FLAGS, MSG) \
    WPP_LOG_ALWAYS(WPP_EX_LEVEL_FLAGS(LEVEL, FLAGS), MSG) \
    WPP_LEVEL_FLAGS_PRE(LEVEL, FLAGS) \
    WPP_ANNOTATE(main_c15) \
    (( \
        WPP_CHECK_INIT WPP_LEVEL_FLAGS_ENABLED(LEVEL, FLAGS) \
        ?   WPP_INVOKE_WPP_DEBUG((MSG)), \
            WPP_SF_( \
                WPP_LEVEL_FLAGS_LOGGER(LEVEL, FLAGS) \
                10, \
                WPP_LOCAL_TraceGuids+0), \
            1 \
        :   0 \
    )) \
    WPP_LEVEL_FLAGS_POST(LEVEL, FLAGS)

這裏主要的一個調用是:

#ifndef WPP_SF__def
# define WPP_SF__def
WPP_INLINE void WPP_SF_(WPP_LOGGER_ARG unsigned short id, LPCGUID TraceGuid)
{ WPP_TRACE(WPP_GET_LOGGER, WPP_TRACE_OPTIONS, (LPGUID)TraceGuid, id,  (void*)0); }
#endif // WPP_SF__def

WPP_TRACE 定義如下:

#ifndef WPP_TRACE
#define WPP_TRACE pfnWppTraceMessage
#endif

也就是說TraceEvents其實就是調用WmiTraceMessage來打印日誌的。

2.3 WPP_CLEANUP

從名字我們可以發現WPP_CLEANUP是清理作用,如下:

VOID WppCleanupKm(_When_(_ENABLE_WPP_RECORDER, _In_) _When_(!_ENABLE_WPP_RECORDER, _In_opt_) PDRIVER_OBJECT DriverObject);

#define WPP_CLEANUP(DriverObject) WppCleanupKm((PDRIVER_OBJECT)DriverObject)



WPPINIT_EXPORT
VOID
WppCleanupKm(
    _When_(_ENABLE_WPP_RECORDER, _In_) _When_(!_ENABLE_WPP_RECORDER, _In_opt_) PDRIVER_OBJECT DriverObject
    )
{
    UNREFERENCED_PARAMETER(DriverObject);

    PAGED_CODE();

    if (WPP_CB == (WPP_CB_TYPE*)&WPP_CB){
        //
        // WPP_INIT_TRACING macro has not been called
        //
        WppDebug(0,("Warning : WPP_CLEANUP already called, or called with out WPP_INIT_TRACING first"));
        return;
    }

    if (WppTraceServer08 == WPPTraceSuite) {

        PWPP_TRACE_CONTROL_BLOCK WppReg = &WPP_CB[0].Control;

        while (WppReg) {
            if (WppReg->RegHandle) {
                pfnEtwUnregister(WppReg->RegHandle);
                WppDebug(0,("EtwUnregister RegHandle = %lld.\n",WppReg->RegHandle));
                WppReg->RegHandle = 0;
            } else {
                WppDebug(0,("WppCleanupKm: invalid RegHandle.\n"));
            }
            WppReg = WppReg->Next;
        }

    } else if (WppTraceWinXP == WPPTraceSuite) {
        PWPP_TRACE_CONTROL_BLOCK WppReg = &WPP_CB[0].Control;

        IoWMIRegistrationControl(   (PDEVICE_OBJECT)WppReg,
                                    WMIREG_ACTION_DEREGISTER |
                                    WMIREG_FLAG_CALLBACK );

    }

#if ENABLE_WPP_RECORDER
        WppAutoLogStop(&WPP_CB[0], DriverObject);
#endif

    WPP_CB = (WPP_CB_TYPE*)&WPP_CB;
}

這裏的核心是調用EtwUnregister清理日誌的Provider。

2.4 總結

ETW的主要函數如下:

  1. EtwRegisterClassicProvider 或者 IoWMIRegistrationControl 註冊Provider。
  2. WmiTraceMessageNtTraceEvent) 打印日誌。
  3. EtwUnregister 或者 IoWMIRegistrationControl 清理Provider。

3. 編譯後的結構

編譯之後整個過程如下:

在這裏插入圖片描述

WppLoadTracingSupport 的實現如下:
在這裏插入圖片描述
WppInitKm 實現 如下:
在這裏插入圖片描述

WPP_SF 日誌輸出過程如下:
在這裏插入圖片描述

WppCleanupKm 過程 如下:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章