Windows对于驱动开发提供了ETW跟踪日志,本文来谈一下ETW日志的使用和基本原理。
在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;
}
其中:
WPP_INIT_TRACING(DriverObject, RegistryPath);
: 初始化日志。TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "HelloWorld");
: 输出日志。WPP_CLEANUP(DriverObject);
: 清理日志。设置了上面这些之后,并不能编译,因为编译器不知道编译trace.h
文件,还需要做如下的设置:
至此我们就可以编译使用日志了。
WPP日志输出的三个重要语句如下:
WPP_INIT_TRACING(DriverObject, RegistryPath);
: 初始化日志。TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "HelloWorld");
: 输出日志。WPP_CLEANUP(DriverObject);
: 清理日志。下面我们分部看一下这个的基本原理。
在.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;
}
}
}
这里主要得到了几个函数:
WmiTraceMessage
WmiQueryTraceInformation
EtwRegisterClassicProvider
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。
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
来打印日志的。
从名字我们可以发现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。
ETW的主要函数如下:
EtwRegisterClassicProvider
或者 IoWMIRegistrationControl
注册Provider。WmiTraceMessage
(NtTraceEvent
) 打印日志。EtwUnregister
或者 IoWMIRegistrationControl
清理Provider。编译之后整个过程如下: