返回
顶部

references:

约定:

  • TMF --> trace message function

本章我们将会介绍如何使用WPP

WPP的全称为Windows software trace PreProcessor

他可以用来跟踪trace provider的操作,一个trace provider可以是一个内核驱动,也可以是一个用户模式的软件

WPP是WMI event tracing的补充和增强,他简化了追踪trace provider操作的方式

WPP非常高效,可以实时记录二进制日志数据,并且可以转换为human-readable trace log

WPP和ETW都可以用来搜集调试信息,两者的区别在于前者可以保证我们的调试信息不被用户看到,因为WPP产生的二进制日志文件需要使用TMF文件来进行解码,而TMF是通过PDB文件生成的,正常情况下我们是不会发布PDB文件的

WPP软件跟踪过程

WPP的基本过程可以分为下面这几步:

  • 定义control GUID,用于唯一标识一个trace provider
  • 添加WPP相关的C预处理指令和WPP宏来调用源文件中的函数
  • 修改VS项目配置以启动WPP预处理
  • 安装驱动,开启trace session并记录消息

需要注意的是,并不是所有的驱动类型都可以使用WPP,这一点可以通过在DriverEntry中加入WPP宏WPP_INIT_TRACING然后编译代码来进行验证,如果编译通不过,说明该类型的驱动不受WPP支持

WPP trace provider同一时刻只能存在于一个trace session中

在驱动代码中使用WPP

我们需要对原本的驱动代码进行更改,这里我们使用Windows File System Filter Driver中的代码来演示

如果使用了VS提供的KMDF模板,可以知己跳到第五步

image-20240509223131392

第一步,定义control GUID和trace flag

一般的做法是把WPP相关的宏定义放到一个单独的头文件中,然后在所有需要用到WPP trace的源文件中包含这个头文件

wpp.h:

#define WPP_CONTROL_GUIDS                                              \
    WPP_DEFINE_CONTROL_GUID(                                           \
        myDriverTraceGuid, (84bdb2e9,829e,41b3,b891,02f454bc2bd7), \
        WPP_DEFINE_BIT(MYDRIVER_ALL_INFO)        /* bit  0 = 0x00000001 */ \
        WPP_DEFINE_BIT(TRACE_DRIVER)             /* bit  1 = 0x00000002 */ \
        WPP_DEFINE_BIT(TRACE_DEVICE)             /* bit  2 = 0x00000004 */ \
        WPP_DEFINE_BIT(TRACE_QUEUE)              /* bit  3 = 0x00000008 */ \
        )

可以看到trace flag的值是2的次幂,从0开始

在使用TMF的时候,我们会带上trace flag,tracelog.exe可以通过指定trace flag来控制正在运行的驱动中哪些TMF会被执行

形式如下:

DoTraceMessage(TRACE_DRIVER, "Hello World!\n");

当tracelog.exe通过-flag选项指定flag为2的时候,这条语句就会执行,对应的message就会被记录下来

第二步,选择一个函数作为trace message function

所谓trace message function就和dbgprint差不多,就是用来写调试信息的

微软提供了一个默认的函数,DoTraceMessage宏,如果你使用这个默认函数,那么就不需要再定义WPP_LEVEL_ENABLED和WPP_LEVEL_LOGGER宏了

如果你使用自定义的函数,或者转换现存的函数,那么就需要进行以下操作

  • 比如你想把KdPrint函数转换成TMF,那么你需要定义WPP宏来标识并启用这个函数

  • 定义WPP宏来启用这个函数,每一个TMF都必须要有一对宏定义,这对宏用于标识trace provider并指定在什么条件下会产生trace message,形式如下: c WPP_<condition>_LOGGER WPP_<condition>_ENABLED

这里的<condition>就是你的TMF的参数列表,比如你的TMF签名为:

DoTraceLevelMessage(LEVEL, FLAGS, MESSAGE, ...);

那么对应的宏就应该长下面这个样子

#define WPP_LEVEL_FLAGS_LOGGER
#define WPP_LEVEL_FLAGS_ENABLED

默认的TMF是DoTraceMessage:

void  DoTraceMessage(     
    TraceFlagName,    
    Message,     
    [ VariableList ] 
);

它对应的宏就是

WPP_LEVEL_ENABLED
WPP_LEVEL_LOGGER

这就是遗留问题了,虽然DoTraceMessage支持的是trace flag,但是他这里宏定义上写的却是LEVEL,而且实际他也是不支持LEVEL的,而是支持trace flag

如果我们使用上面提到的DoTraceLevelMessage函数作为我们的TMF,那么最终的宏定义应该为:

#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)

只有在达到指定等级之后,才会启用loggger

下面我们需要使用begin_wppend_wpp来声明我们的TMF,你没看错,他就是被注释掉的,因为这个不是给编译器用的,是给WPP预处理器用的配置

DTLM是DoTraceLevelMessage的缩写

// begin_wpp config
// FUNC DTLM(LEVEL, FLAGS, MSG, ...);
// end_wpp

可以直接将KdPrint转换成TMF,如下:

// begin_wpp config
// FUNC KdPrint{LEVEL=TRACE_LEVEL_INFORMATION, FLAGS=TRACE_DRIVER}((MSG, ...));
// end_wpp

只有在TRACE_LEVEL_INFORMATION等级和TRACE_DRIVER标志启用时,才会记录日志,此时的KdPrint将不再直接输出到DbgView中,而是记录到日志中

我们也可以直接将KdPrintEx转换为我们的TMF

// begin_wpp config
// FUNC KdPrintEx((Flags, LEVEL, MSG, ...));  
// end_wpp

不过KdPrintEx和KdPrint不太一样,多了几个参数

DbgPrintEx (
    _In_ ULONG ComponentId,
    _In_ ULONG Level,
    _In_z_ _Printf_format_string_ PCSTR Format,
    ...
    );

因此我们要在WPP_CONTROL_GUIDS定义一个ComponentId的BIT,不然tracelog也没法用,对应的WPP宏对有需要更改,FLAGS跑到了LEVEL前面

#define WPP_CONTROL_GUIDS \
    WPP_DEFINE_CONTROL_GUID(GUIDFriendlyName, (DE1AB18A, 8A47, 43FB, B3BB, E503D293A33F),  \
        WPP_DEFINE_BIT(DPFLTR_IHVDRIVER_ID)  )

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

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

第三步,包含头文件wpp.h和自动生成的*.tmh到会用到trace的源文件中

准确来说*.tmh文件还没有生成,只有在我们build驱动的时候才会生出来

build之前需要先在项目属性中开启WPP

image-20240510000934305

我们有多少个.c文件,就会有多少个.tmh文件被生出来,WPP预处理器为每一个源文件都生成了一个.tmh文件

image-20240510000203527

第四步,初始化和clean up WPP

分别对应WPP_INIT_TRACING和WPP_CLEANUP

WPP_INIT_TRACING( DriverObject, RegistryPath );
WPP_CLEANUP(DriverObject);

前者放在DriverEntry里,后者放在Unload里

当然了,如果遇到意外程序提前结束了,也要在退出时先CLEANUP

测试

extra

#define WPP_CONTROL_GUIDS \
    WPP_DEFINE_CONTROL_GUID(GUIDFriendlyName, (DE1AB18A, 8A47, 43FB, B3BB, E503D293A33F),  \
        WPP_DEFINE_BIT(FOR_DEBUG) \
        WPP_DEFINE_BIT(FOR_FILTER_FILENAME) \
        WPP_DEFINE_BIT(FOR_DRIVER_STATUS)  )

这里的BIT我们可以随便定义,不一定非要和KdPrintEx的ComponentID保持一致,这样并不影响KdPrintEx正常工作,而且可以通过-flag选项控制trace log输出

将tracelog的-flag选项分别设置为1、2、4可以控制不同类型的trace log

完美!

项目文件

TraceView

TraceView还支持过滤器,我们可以设置过滤器来过滤掉不想看到的trace message

这里我们discard掉了所有不包含fsfilter字符串的message(不区分大小写),可以看到捕获的文件名并没有显示在TraceView中

image-20240510195706465

TraceViewPlus

推荐使用TraceVIewPlus,用了都说好,界面看着舒服,输出也能很方便导出,直接在这里添加pdb文件,然后启动即可

image-20240619130036398

image-20240619130336665