文章目錄
驅動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;
}
其中:
WPP_INIT_TRACING(DriverObject, RegistryPath);
: 初始化日誌。TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "HelloWorld");
: 輸出日誌。WPP_CLEANUP(DriverObject);
: 清理日誌。
設置了上面這些之後,並不能編譯,因爲編譯器不知道編譯trace.h
文件,還需要做如下的設置:
至此我們就可以編譯使用日誌了。
2. ETW日誌原理
WPP日誌輸出的三個重要語句如下:
WPP_INIT_TRACING(DriverObject, RegistryPath);
: 初始化日誌。TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "HelloWorld");
: 輸出日誌。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;
}
}
}
這裏主要得到了幾個函數:
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。
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的主要函數如下:
EtwRegisterClassicProvider
或者IoWMIRegistrationControl
註冊Provider。WmiTraceMessage
(NtTraceEvent
) 打印日誌。EtwUnregister
或者IoWMIRegistrationControl
清理Provider。
3. 編譯後的結構
編譯之後整個過程如下:
WppLoadTracingSupport
的實現如下:
WppInitKm
實現 如下:
WPP_SF
日誌輸出過程如下:
WppCleanupKm
過程 如下: