Windows資源監視器軟體的原理
原文網址:https://www.freebuf.com/column/137615.html
微軟給我們提供了一些很好的程式,比如資源監視器,可以從這個軟體裡獲取分析windows的自身的一些效能資料,比如CPU、記憶體、磁碟資料、檔案讀寫、程序執行緒等,他具體怎麼實現呢,今天這天文章就帶你去獲取其真實的原理。
1.分析
開啟windows工作列管理器,在其效能選項裡,可以看到效能監控的一些機器效能圖表
這個只是一些概要資料,如果要看詳細的內容,可以點選左下角的“開啟資源監視器”,會自己開啟一個程序perfmon.exe的程序,這個程序介面會顯示詳細的資源資訊
我們可以看到每個程序打開了什麼檔案、讀寫了什麼磁碟資料、以及訪問了什麼網路的IP都有詳細的資訊,這個程序既沒有檔案過濾驅動也沒有網路驅動他是如何實現去獲取這些詳細的資訊呢,下面我們來具體分析下。
開啟這個軟體所在的目錄,可以看到一些對應的perfmon.exe 、perfnet.dll、perfDisk.dll、perfos.dll、perfpro.dll程序和模組
用IDA開啟perfmon.exe看看其程序一些函式
也只是一些UI相關的函式,說明核心功能並不在這裡exe裡 ,在繼續換一個,用IDA
模組裡有一些比較明顯的函式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!\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; }
執行後,當有資料來時就會進入我們設定的回撥函式
此處我們大概知道了該軟體的實現原理,剩下具體的對資料的內容的解析不再繼續贅述,讀者可以自行去研究,從這篇文章裡我們知道了微軟提供了一套核心事件診斷的函式方便我們去分析系統一些的效能,我們不光可以用這些函式去分析診斷系統,我們可以充分利用這些函式去實現一些我們想要的功能,大家可以自行去發揮。