Windows性能监视器的原理

Posted guantou1992

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Windows性能监视器的原理相关的知识,希望对你有一定的参考价值。

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:OpenSourceGitHubWindowsSDK7-SampleswinbaseEventingEtwConsumerOutput16.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;
}

运行后,当有数据来时就会进入我们设置的回调函数

 技术图片

          此处我们大概知道了该软件的实现原理,剩下具体的对数据的内容的解析不再继续赘述,读者可以自行去研究,从这篇文章里我们知道了微软提供了一套内核事件诊断的函数方便我们去分析系统一些的性能,我们不光可以用这些函数去分析诊断系统,我们可以充分利用这些函数去实现一些我们想要的功能,大家可以自行去发挥。

 

以上是关于Windows性能监视器的原理的主要内容,如果未能解决你的问题,请参考以下文章

如何使用windows性能监视器监控磁盘性能

如何使用windows性能监视器监控CPU性能

Windows性能计数器分析性能监视器的使用

Windows 性能监视器API

Windows 性能监视器API

Windows 性能监视器API