zoukankan      html  css  js  c++  java
  • Windows性能监视器的原理

    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!
    " );
    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;
    }

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

     QQ图片20170616163401.png

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

  • 相关阅读:
    父容器直接清除浮动的css
    [原创 js]指定DOM中添加字符串函数
    跨浏览器的透明度解决方案(包括IE8 Beta 2)
    [原创 js]验证表单强度的js
    帧率、码流与分辩率之间关系
    隔行扫描和逐行扫描(interlace and progressive )
    VGA管脚定义
    红眼消除
    HDMI_VGA_CBVS同时显示
    分辨率与行场同步信号的关系
  • 原文地址:https://www.cnblogs.com/guantou1992/p/12771729.html
Copyright © 2011-2022 走看看