驱动TraceEvent使用指南

文章目录

  • 驱动TraceEvent使用指南
    • 1. 使用
    • 2. ETW日志原理
      • 2.1 WPP_INIT_TRACING
      • 2.2 TraceEvents
      • 2.3 WPP_CLEANUP
      • 2.4 总结
    • 3. 编译后的结构

驱动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文件,还需要做如下的设置:
驱动TraceEvent使用指南_第1张图片

至此我们就可以编译使用日志了。

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. 编译后的结构

编译之后整个过程如下:

驱动TraceEvent使用指南_第2张图片

WppLoadTracingSupport 的实现如下:
驱动TraceEvent使用指南_第3张图片
WppInitKm 实现 如下:
驱动TraceEvent使用指南_第4张图片

WPP_SF 日志输出过程如下:
在这里插入图片描述

WppCleanupKm 过程 如下:
驱动TraceEvent使用指南_第5张图片

你可能感兴趣的:(Windows开发)