1.分析
打开windows任务栏管理器,在其性能选项里,可以看到性能监控的一些机器性能图表
这个只是一些概要数据,如果要看详细的内容,可以点击左下角的“打开资源监视器”,会自己开启一个进程perfmon.exe的进程,这个进程界面会显示详细的资源信息
我们可以看到每个进程打开了什么文件、读写了什么磁盘数据、以及访问了什么网络的IP都有详细的信息,这个进程既没有文件过滤驱动也没有网络驱动他是如何实现去获取这些详细的信息呢,下面我们来具体分析下。
打开这个软件所在的目录,可以看到一些对应的perfmon.exe 、perfnet.dll、perfDisk.dll、perfos.dll、perfpro.dll进程和模块
用IDA打开perfmon.exe看看其进程一些函数
也只是一些UI相关的函数,说明核心功能并不在这里exe里 ,在继续换一个,用IDA打开perdisk.dll这个模块
模块里有一些比较明显的函数CollectDiskObjectData、CollectPDiskObjectData、CollectLDiskObjectData等函数,选择一个函数用windbg调试器去下断点
当第一放过运行时,调试器立马就停了下来,看堆栈区域
大概是这样的
# RetAddr : Args to Child : Call Site 00 00007ffc`2d132060 : ffffffff`feced300 00000000`00000010 00000000`00000014 00000000`00000000 : perfdisk!CollectDiskObjectData 01 00007ffc`2d1316b5 : 00000000`00007f88 00000000`00000000 00000000`00000000 00000000`00000000 : ADVAPI32!QueryExtensibleData+0x540 02 00007ffc`29a92a99 : 0000021e`67749c66 00000000`000602ff 000000fc`368ff728 00007ffc`ffffffff : ADVAPI32!PerfRegQueryValue+0x325 03 00007ffc`29a9204d : ffffffff`80000004 000000fc`368ff7a4 0000021e`6b4904f0 000000fc`368ff820 : KERNELBASE!MapPredefinedHandleInternal+0x8e9 04 00007ffc`25f063ec : ffffffff`80000004 0000021e`6b490490 0000021e`00008000 000000fc`368ff7a4 : KERNELBASE!RegQueryValueExW+0xed 05 00007ffc`25f056ad : 0000021e`6b490490 0000021e`6ad74c10 00000000`00008000 00000000`00000000 : pdh!GetSystemPerfData+0x9c 06 00007ffc`25f054c9 : 01d2e6b6`135f9140 000000fc`368ff8b8 00000000`00000000 01d2e673`053c5140 : pdh!GetQueryPerfData+0xcd 07 00007ffc`01548566 : 00000000`00000000 00000000`00000003 00000000`00000000 00000000`000002b0 : pdh!PdhCollectQueryData+0x59 08 00007ffc`2ad58364 : 0000021e`6adc1b00 00000000`00000000 00000000`00000000 00000000`00000000 : wdc!WdcTraceControl::QueryThread+0x1a6 09 00007ffc`2d2370d1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14 0a 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
堆栈看该函数被调用是从wdc!WdcTraceControl::QueryThread这个函数过来的,看样子是一个单独的数据线程,是wdc.dll这个模块里的函数,继续用IDA打开wdc这个模块。
查看WdcTraceControl::QueryThread这个函数实现,确实调用了一些监控实例的Query方法,这里我们可以确认wc.dll这个模块是核心功能所在的模块,现在可以分析下是怎么开启这个线程的,开启前做了哪些工作。
发现只有一处调用,就是WdcTraceControl::Start函数调用了这个函数开启线程
继续往上翻阅WdcTraceControl::Start函数实现
有一个WdcTraceControl::TraceStart 函数,进入该函数
发现该函数调用OpenTrace、StartTrace、EnableTraceEx、processTrace这些函数,从EnableTraceEx的参数ThreadPoolGuid、PsProvGuid、DiskProvGuid、FileProvGuid、NetProvGuid可以判断这些函数就是核心功能,查看微软的CSDN终于发现了秘密,原来这些函数是 微软事件诊断函数(ETW),其中OpenTrace的里的有一个参数是事件的回掉接收函数,我们看到的WdcTraceControl::CallbackEvent这个函数就是,下个断点,确实断了下来
就是WdcTraceControl::TraceThread线程中的ProcessTrace函数处理获取了内核日志数据然后调用了设置的回调函数WdcTraceControl::CallbackEvent去处理,为了进一步验证,翻阅该回调函数的实现
可以知道里面处理了各种过来的数据包括网络、磁盘、cpu、内存、线程、进程日志信息,去写个demo实例验证我们的结果。
2.代码demo
#include "stdafx.h" #define INITGUID //Include this #define to use SystemTraceControlGuid in Evntrace.h. #include <Windows.h> #include <wmistr.h> #include <evntrace.h> #include <evntcons.h> #include <strsafe.h> #pragma comment(lib,"Advapi32.lib") #define LOGFILE_PATH L"kernellogfile.etl" #define ETL_FILE L"G:\OpenSource\GitHub\WindowsSDK7-Samples\winbase\Eventing\EtwConsumer\Output\16.pdf.etl" #define __REAL_TIME_MODE /* 不同类型的GUID,从MSDN手册中找,固定的 */ DEFINE_GUID ( /* 3d6fa8d0-fe05-11d0-9dda-00c04fd7ba7c */ ProcessGuid, 0x3d6fa8d0, 0xfe05, 0x11d0, 0x9d, 0xda, 0x00, 0xc0, 0x4f, 0xd7, 0xba, 0x7c ); ULONG WINAPI BufferCallback( __in PEVENT_TRACE_LOGFILE LogFile ) { printf( "BufferCallback! " ); return TRUE; } /* 可以调用SetTraceCallback 设置单独事件GUID的回调函数,即使如此,EventCallback仍然会收到所有的事件 */ VOID WINAPI EventRecordCallback( __in PEVENT_RECORD Event ) { if ( IsEqualGUID( Event->EventHeader.ProviderId, ProcessGuid ) ) { /* 需要解析数据格式 */ printf( "EventRecordCallback ProcessGuid! " ); } printf( "EventRecordCallback! " ); } VOID WINAPI EventCallback( PEVENT_TRACE pEvent ) { printf( "EventCallback! " ); } VOID EventConsumer() { TRACEHANDLE hTrace = NULL; EVENT_TRACE_LOGFILE traceFile; #ifdef __REAL_TIME_MODE traceFile.LogFileName = NULL; traceFile.LoggerName = KERNEL_LOGGER_NAME; traceFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD; #else traceFile.LogFileName = ETL_FILE; traceFile.LoggerName = NULL; traceFile.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD; #endif traceFile.BufferCallback = BufferCallback; traceFile.EventCallback = EventCallback; if ( traceFile.ProcessTraceMode & PROCESS_TRACE_MODE_EVENT_RECORD ) traceFile.EventRecordCallback = EventRecordCallback; traceFile.Context = NULL; hTrace = OpenTrace( &traceFile ); if ( hTrace == (TRACEHANDLE)INVALID_HANDLE_VALUE || hTrace == 0x0 ) return ; ULONG status = ProcessTrace( &hTrace, 1, NULL, NULL ); } void EventController(void) { ULONG status = ERROR_SUCCESS; TRACEHANDLE SessionHandle = 0; EVENT_TRACE_PROPERTIES* pSessionProperties = NULL; ULONG BufferSize = 0; BufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(KERNEL_LOGGER_NAME) + sizeof(LOGFILE_PATH) ; pSessionProperties = (EVENT_TRACE_PROPERTIES*) malloc(BufferSize); if (NULL == pSessionProperties) { wprintf(L"Unable to allocate %d bytes for properties structure. ", BufferSize); goto cleanup; } ZeroMemory(pSessionProperties, BufferSize); pSessionProperties->Wnode.BufferSize = BufferSize; pSessionProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; pSessionProperties->Wnode.ClientContext = 1; //QPC clock resolution pSessionProperties->Wnode.Guid = SystemTraceControlGuid; pSessionProperties->EnableFlags = EVENT_TRACE_FLAG_PROCESS; // 关注事件 #ifdef __REAL_TIME_MODE pSessionProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; // EVENT_TRACE_USE_PAGED_MEMORY 该标识在win7上会导致失败 #else pSessionProperties->LogFileMode = EVENT_TRACE_FILE_MODE_CIRCULAR; #endif pSessionProperties->MaximumFileSize = 5; // 5 MB pSessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); pSessionProperties->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(KERNEL_LOGGER_NAME); #ifndef __REAL_TIME_MODE // 也可在RealTime模式下开启,但是没必要RealTime都记录到文件 StringCbCopy((LPWSTR)((char*)pSessionProperties + pSessionProperties->LogFileNameOffset), sizeof(LOGFILE_PATH), LOGFILE_PATH); #endif status = StartTrace((PTRACEHANDLE)&SessionHandle, KERNEL_LOGGER_NAME, pSessionProperties); if (ERROR_SUCCESS != status) { if (ERROR_ALREADY_EXISTS == status) wprintf(L"The NT Kernel Logger session is already in use. "); else wprintf(L"EnableTrace() failed with %lu ", status); goto cleanup; } EventConsumer(); wprintf(L"Press any key to end trace session "); getchar(); cleanup: if (SessionHandle) { status = ControlTrace(SessionHandle, KERNEL_LOGGER_NAME, pSessionProperties, EVENT_TRACE_CONTROL_STOP); if (ERROR_SUCCESS != status) wprintf(L"ControlTrace(stop) failed with %lu ", status); } else { /* 开启会话后,若不关闭,即使进程退出,依然会保持开启状态,单独关闭可使用如下方式 */ status = ControlTrace(NULL, KERNEL_LOGGER_NAME, pSessionProperties, EVENT_TRACE_CONTROL_STOP); if (ERROR_SUCCESS != status) wprintf(L"ControlTrace(stop) failed with %lu ", status); } if (pSessionProperties) free(pSessionProperties); } int _tmain(int argc, _TCHAR* argv[]) { EventController(); //EventConsumer(); while (true) { Sleep(300000); } return 0; }
运行后,当有数据来时就会进入我们设置的回调函数
此处我们大概知道了该软件的实现原理,剩下具体的对数据的内容的解析不再继续赘述,读者可以自行去研究,从这篇文章里我们知道了微软提供了一套内核事件诊断的函数方便我们去分析系统一些的性能,我们不光可以用这些函数去分析诊断系统,我们可以充分利用这些函数去实现一些我们想要的功能,大家可以自行去发挥。