Windows資源監視器軟件的原理


微軟給我們提供了一些很好的程序,比如資源監視器,可以從這個軟件里獲取分析windows的自身的一些性能數據,比如CPU、內存、磁盤數據、文件讀寫、進程線程等,他具體怎么實現呢,今天這天文章就帶你去獲取其真實的原理。

1.分析

打開windows任務欄管理器,在其性能選項里,可以看到性能監控的一些機器性能圖表

 

這個只是一些概要數據,如果要看詳細的內容,可以點擊左下角的“打開資源監視器”,會自己開啟一個進程perfmon.exe的進程,這個進程界面會顯示詳細的資源信息

 

 

 

 

我們可以看到每個進程打開了什么文件、讀寫了什么磁盤數據、以及訪問了什么網絡的IP都有詳細的信息,這個進程既沒有文件過濾驅動也沒有網絡驅動他是如何實現去獲取這些詳細的信息呢,下面我們來具體分析下。

打開這個軟件所在的目錄,可以看到一些對應的perfmon.exe 、perfnet.dll、perfDisk.dll、perfos.dll、perfpro.dll進程和模塊

 

 

 

IDA打開perfmon.exe看看其進程一些函數

 

 

 

 也只是一些UI相關的函數,說明核心功能並不在這里exe里 ,在繼續換一個,用IDA打開perdisk.dll這個模塊

 

 

 QQ圖片20170616162416.png

 

模塊里有一些比較明顯的函數CollectDiskObjectData、CollectPDiskObjectData、CollectLDiskObjectData等函數,選擇一個函數用windbg調試器去下斷點

 

 QQ圖片20170616162449.png

 

當第一放過運行時,調試器立馬就停了下來,看堆棧區域

 

 QQ圖片20170616162548.png

 

大概是這樣的

 

# 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這個模塊。

 

 QQ圖片20170616162649.png

 

QQ圖片20170616162701.png

 

 

 

查看WdcTraceControl::QueryThread這個函數實現,確實調用了一些監控實例的Query方法,這里我們可以確認wc.dll這個模塊是核心功能所在的模塊,現在可以分析下是怎么開啟這個線程的,開啟前做了哪些工作。

 

 QQ圖片20170616162742.png

 

發現只有一處調用,就是WdcTraceControl::Start函數調用了這個函數開啟線程

 

 QQ圖片20170616162753.png

 

繼續往上翻閱WdcTraceControl::Start函數實現

 

 QQ圖片20170616162806.png

 

QQ圖片20170616162818.png

 

 

 

有一個WdcTraceControl::TraceStart 函數,進入該函數 

 

 QQ圖片20170616162927.png

 

QQ圖片20170616162938.png

 

QQ圖片20170616162949.png

 

QQ圖片20170616163000.png

 

 

 

 發現該函數調用OpenTrace、StartTrace、EnableTraceEx、processTrace這些函數,從EnableTraceEx的參數ThreadPoolGuid、PsProvGuid、DiskProvGuid、FileProvGuid、NetProvGuid可以判斷這些函數就是核心功能,查看微軟的CSDN終於發現了秘密,原來這些函數是 微軟事件診斷函數(ETW),其中OpenTrace的里的有一個參數是事件的回掉接收函數,我們看到的WdcTraceControl::CallbackEvent這個函數就是,下個斷點,確實斷了下來

 

 QQ圖片20170616163123.png

 

QQ圖片20170616163135.png

 

 

 

就是WdcTraceControl::TraceThread線程中的ProcessTrace函數處理獲取了內核日志數據然后調用了設置的回調函數WdcTraceControl::CallbackEvent去處理,為了進一步驗證,翻閱該回調函數的實現

 

 QQ圖片20170616163224.png

 

可以知道里面處理了各種過來的數據包括網絡、磁盤、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!\n" );
return TRUE;
}
/* 可以調用SetTraceCallback 設置單獨事件GUID的回調函數,即使如此,EventCallback仍然會收到所有的事件 */
VOID WINAPI EventRecordCallback( __in PEVENT_RECORD Event )
{
if ( IsEqualGUID( Event->EventHeader.ProviderId, ProcessGuid ) )
{
/* 需要解析數據格式 */
printf( "EventRecordCallback  ProcessGuid!\n" );
}
printf( "EventRecordCallback!\n" );
}
VOID WINAPI EventCallback( PEVENT_TRACE pEvent )
{
printf( "EventCallback!\n" );
}
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.\n", 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.\n");
else
wprintf(L"EnableTrace() failed with %lu\n", 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\n", status);
}
else
{
/* 開啟會話后,若不關閉,即使進程退出,依然會保持開啟狀態,單獨關閉可使用如下方式 */
status = ControlTrace(NULL, KERNEL_LOGGER_NAME, pSessionProperties, EVENT_TRACE_CONTROL_STOP);
if (ERROR_SUCCESS != status)
wprintf(L"ControlTrace(stop) failed with %lu\n", status);
}
if (pSessionProperties)
free(pSessionProperties);
}
int _tmain(int argc, _TCHAR* argv[])
{
EventController();
//EventConsumer();
while (true)
{
Sleep(300000);
}
return 0;
}

 

運行后,當有數據來時就會進入我們設置的回調函數

 

 QQ圖片20170616163401.png

 

          此處我們大概知道了該軟件的實現原理,剩下具體的對數據的內容的解析不再繼續贅述,讀者可以自行去研究,從這篇文章里我們知道了微軟提供了一套內核事件診斷的函數方便我們去分析系統一些的性能,我們不光可以用這些函數去分析診斷系統,我們可以充分利用這些函數去實現一些我們想要的功能,大家可以自行去發揮。

轉自:https://bbs.pediy.com/thread-218627.htm


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM