1. 程式人生 > >Windows資源監視器軟體的原理

Windows資源監視器軟體的原理

原文網址:https://www.freebuf.com/column/137615.html

 

微軟給我們提供了一些很好的程式,比如資源監視器,可以從這個軟體裡獲取分析windows的自身的一些效能資料,比如CPU、記憶體、磁碟資料、檔案讀寫、程序執行緒等,他具體怎麼實現呢,今天這天文章就帶你去獲取其真實的原理。

1.分析

開啟windows工作列管理器,在其效能選項裡,可以看到效能監控的一些機器效能圖表

 QQ圖片20170616162120.png

這個只是一些概要資料,如果要看詳細的內容,可以點選左下角的“開啟資源監視器”,會自己開啟一個程序perfmon.exe的程序,這個程序介面會顯示詳細的資源資訊

 QQ圖片20170616162135.png

QQ圖片20170616162237.png

 

我們可以看到每個程序打開了什麼檔案、讀寫了什麼磁碟資料、以及訪問了什麼網路的IP都有詳細的資訊,這個程序既沒有檔案過濾驅動也沒有網路驅動他是如何實現去獲取這些詳細的資訊呢,下面我們來具體分析下。

開啟這個軟體所在的目錄,可以看到一些對應的perfmon.exe 、perfnet.dll、perfDisk.dll、perfos.dll、perfpro.dll程序和模組

 QQ圖片20170616162301.png

IDA開啟perfmon.exe看看其程序一些函式

 QQ圖片20170616162337.png

也只是一些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

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