Windows软件调试初探-事件追踪ETW
入门
ETW使用提供器-消耗器-控制器设计模式。输出追踪消息的目标程序是提供器,接收和查看追踪消息的工具或文件是消耗器,负责控制追踪会话的工具是控制器。用于支持ETW传输追踪信息的通信连接称为ETW会话,Windows最多支持64个ETW会话,其中有两个是系统专用的,分别是启动早期用的Global Logger Session和记录系统预定义事件的NT Kernel Logger Session。
ETW基础设施核心部分是实现在内核模块的,作为WMI一个部分实现。有一部分函数在DDK中公开,供驱动调用,用户模式API在ADVAPI32.DLL输出。
提供ETW消息
ETW提供器通过RegisterTraceGuids
向系统注册自己的GUID,这样ETW控制器才能通过GUID找到该提供器。若ETW控制器启动或停止该提供器,则系统调用回调函数。
1 2 3 4 5 6 7 8 9 10 11 12
| #include <evntrace.h> #define RegisterTraceGuids RegisterTraceGuidsW EXTERN_C ULONG WMIAPI RegisterTraceGuidsW ( _In_ WMIDPREQUEST RequestAddress, _In_opt_ PVOID RequestContext, _In_ LPCGUID ControlGuid, _In_ ULONG GuidCount, _In_reads_opt_(GuidCount) PTRACE_GUID_REGISTRATION TraceGuidReg, _In_opt_ LPCWSTR MofImagePath, _In_opt_ LPCWSTR MofResourceName, _Out_ PTRACEHANDLE RegistrationHandle );
|
一个典型ETW提供器回调函数:
1 2 3 4 5 6 7 8 9 10
| ULONG WINAPI MyControlCallback(WMIDPREQUESTCODE RequestCode,PVOID Context,ULONG* Reserved,PVOID Buffer){ if(RequestCode==WMI_ENABLE_EVENTS){ g_hTrace=GetTraceLoggerHandle(Buffer); g_dwFlags=GetTraceEnableFlags(Buffer); g_dwLevel=GetTraceEnableLevel(Buffer); } else if(RequestCode==WMI_DISABLE_EVENTS) g_hTrace=NULL; return 0; };
|
有了ETW会话句柄便可用TraceEvent
、TraceMessage
或TraceMessageVa
向ETW会话输出信息,他们都调用NtTraceEvent
内核服务,后者又调用ETW内核函数WmiTraceMessage
。
1 2 3 4 5 6 7 8 9 10 11
| EXTERN_C ULONG WMIAPI TraceEvent ( _In_ TRACEHANDLE TraceHandle, _In_ PEVENT_TRACE_HEADER EventTrace ); EXTERN_C ULONG __cdecl TraceMessage ( _In_ TRACEHANDLE LoggerHandle, _In_ ULONG MessageFlags, _In_ LPCGUID MessageGuid, _In_ USHORT MessageNumber, ... );
|
一个组织缓冲区、填写头结构并调用TraceEvent
的一个例子:
1 2 3 4 5 6 7 8 9 10 11 12
| typedef struct _MyEvent{ EVENT_TRACE_HEADER m_Header; ULONG m_ulDataInfo; }MyEvent; MyEvent e; e.m_Header.Size=sizeof(e); e.m_Header.Guid=MyEventGUID; e.m_Header.Class.Type=uType; e.m_Header.Flags=WNODE_FLAG_TRACED_GUID; e.m_Header.Level=TRACE_LEVEL_INFORMATION; e.m_uDataInfo=uVarValue; Status=TraceEvent(g_hTrace,(PEVENT_TRACE_HEADER)&e);
|
控制ETW会话
ETW控制器程序用StartTrace
开始一个ETW追踪会话,并取得该会话的信息和句柄。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #define StartTrace StartTraceW EXTERN_C ULONG WMIAPI StartTraceW ( _Out_ PTRACEHANDLE TraceHandle, _In_ LPCWSTR InstanceName, _Inout_ PEVENT_TRACE_PROPERTIES Properties ); typedef struct _EVENT_TRACE_PROPERTIES { WNODE_HEADER Wnode; ULONG BufferSize; ULONG MinimumBuffers; ULONG MaximumBuffers; ULONG MaximumFileSize; ULONG LogFileMode; ULONG FlushTimer; ULONG EnableFlags; union { LONG AgeLimit; LONG FlushThreshold; } DUMMYUNIONNAME; ULONG NumberOfBuffers; ULONG FreeBuffers; ULONG EventsLost; ULONG BuffersWritten; ULONG LogBuffersLost; ULONG RealTimeBuffersLost; HANDLE LoggerThreadId; ULONG LogFileNameOffset; ULONG LoggerNameOffset; } EVENT_TRACE_PROPERTIES, *PEVENT_TRACE_PROPERTIES;
|
消息投递模式又文件模式和实时模式。前者将ETW消息写入ETL文件,此时EVENT_TRACE_PROPERTIES后应放置完整路径文件名。后者实时将ETW消息递送给ETW消耗器。
用EnableTrace
启动ETW提供器,使其向该ETW会话输出追踪消息:
1 2 3 4 5 6 7
| EXTERN_C ULONG WMIAPI EnableTrace ( _In_ ULONG Enable, _In_ ULONG EnableFlag, _In_ ULONG EnableLevel, _In_ LPCGUID ControlGuid, _In_ TRACEHANDLE TraceHandle );
|
启动会话后ETW控制器用ControlTrace
查询ETW会话状态或执行其他控制动作:
1 2 3 4 5 6
| EXTERN_C ULONG WMIAPI ControlTraceW ( _In_ TRACEHANDLE TraceHandle, _In_opt_ LPCWSTR InstanceName, _Inout_ PEVENT_TRACE_PROPERTIES Properties, _In_ ULONG ControlCode );
|
TraceHandle和InstanceName选一个就行。ControlCode如下:
值 |
含义 |
EVENT_TRACE_CONTROL_FLUSH |
冲转会话缓冲区 |
EVENT_TRACE_CONTROL_QUERY |
读取会话属性和统计信息 |
EVENT_TRACE_CONTROL_STOP |
停止会话 |
EVENT_TRAFCE_CONTROL_UPDATE |
更新会话属性 |
ETW控制器还可用QueryAllTraces
查询当前系统内启动的所有ETW会话,用EnumerateTraceGuids
枚举系统内注册的所有ETW提供器。
消耗ETW消息
ETW消耗器用OpenTrace
打开一个ETL文件或实时ETW会话,同时注册自己用于接收消息的回调函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| EXTERN_C ETW_APP_DECLSPEC_DEPRECATED TRACEHANDLE WMIAPI OpenTraceW ( _Inout_ PEVENT_TRACE_LOGFILEW Logfile ); typedef struct _EVENT_TRACE_LOGFILEW EVENT_TRACE_LOGFILEW, *PEVENT_TRACE_LOGFILEW; struct _EVENT_TRACE_LOGFILEW { LPWSTR LogFileName; LPWSTR LoggerName; LONGLONG CurrentTime; ULONG BuffersRead; union { ULONG LogFileMode; ULONG ProcessTraceMode; } DUMMYUNIONNAME; EVENT_TRACE CurrentEvent; TRACE_LOGFILE_HEADER LogfileHeader; PEVENT_TRACE_BUFFER_CALLBACKW BufferCallback; ULONG BufferSize; ULONG Filled; ULONG EventsLost; union { PEVENT_CALLBACK EventCallback; PEVENT_RECORD_CALLBACK EventRecordCallback; } DUMMYUNIONNAME2; ULONG IsKernelTrace; PVOID Context; };
|
之后ETW消耗器用ProcessTrace
通知系统启动消息递送:
1 2 3 4 5 6
| EXTERN_C ETW_APP_DECLSPEC_DEPRECATED ULONG WMIAPI ProcessTrace ( _In_reads_(HandleCount) PTRACEHANDLE HandleArray, _In_ ULONG HandleCount, _In_opt_ LPFILETIME StartTime, _In_opt_ LPFILETIME EndTime );
|
ETW消耗器可注册BufferCallback、EventCallback和EventClassCallback回调,第一个用于接收会话缓冲区统计信息,后两个用来接收追踪事件。
1 2 3 4
| typedef VOID (WINAPI *PEVENT_CALLBACK)( PEVENT_TRACE pEvent ); typedef ULONG (WINAPI * PEVENT_TRACE_BUFFER_CALLBACKW)(PEVENT_TRACE_LOGFILEW Logfile);
|
系统将指定的所有会话的所有事件都发给EventCallback,ETW允许用SetTraceCallback
通知系统按事件所属分类发给不同回调函数,此时EventCallback也会收到所有事件:
1 2 3 4
| EXTERN_C ULONG WMIAPI SetTraceCallback ( _In_ LPCGUID pGuid, _In_ PEVENT_CALLBACK EventCallback );
|