Windows 事件跟踪

Windows 7 中的核心检测事件,第 2 部分

Dr Insung ParkAlex Bendetov

欢迎使用后我们的两个部分文章系列的第二个部分:核心工具 Windows 7 中的事件。 第一个本文我们提供的 Windows 的事件跟踪 (ETW) 技术和核心操作系统检测的高级别概述。 我们还讨论了工具支持,以获取并使用操作系统的事件。 在此第二个,我们继续提供有关从不同的子组件在核心操作系统中事件的详细信息。 我们还介绍了如何不同系统事件可以被组合以生成全面,我们使用的一组 Windows PowerShell 脚本演示的系统行为的图片。

磁盘、 文件、 文件的详细信息和事件椹卞姩绋嬪簭

从程序的角度操作 (如打开、 读取,或写入文件是以访问磁盘上的内容的方法。 由于对等缓存预提取的优化不是所有的文件 IO 请求会导致直接磁盘访问。 此外,可能文件的内容分散在磁盘,和某些磁盘设备支持镜像和条带化,等等。 对于这样的情况从文件读取一个数据块的转换多个访问一个或多个磁盘。 事件的文件和磁盘访问帐户的文件 IO 开始文件 IO 完成、 磁盘访问启动、 磁盘访问结束,拆分 IO 椹卞姩绋嬪簭活动,文件 (唯一键的名称) 映射。

从用户应用程序访问文件和相应的用户应用程序的请求发回完成一个请求经过多个组件的堆栈。 在 Windows IO 系统中跟踪 IO 操作调用的 IO 请求数据包 (IRP) 的实体。 在用户启动的 IO 操作被转换的 IRP 它进入 IO 管理器时。 为一个 IRP 遍历一系列组件,每个组件将执行必要的任务来处理该请求,更新在 IRP 并传递它,如有必要,将在下一步处理该请求的组件。 IO 请求的所有要求都满足时 (在简单的情况下一块请求的文件从一个磁盘中检索),注册的完成例程的调用来执行在的数据的任何其他处理,并请求的数据返回给用户应用程序。

层一个更高的核心 IO 系统,文件 IO 事件记录发出应用程序的操作。 文件 IO 事件包括以下类型:创建、 读取、 写入、 刷新、 重命名、 清理关闭、 集信息、 查询信息、 枚举目录和目录更改通知中删除。 操作如创建、 读取、 写入、 刷新、 重命名和删除简单,以及它们如包含数据的项目文件密钥、 IO 请求数据包 (IRP) 指针、 块的大小和偏移量到文件,如有必要。 设置信息和查询信息的事件表示的文件属性的设置,或查询。 关闭最后句柄到文件时,将记录一个清理事件。 在关闭事件指定文件对象已被释放。 枚举目录或目录更改通知发送到已注册的侦听器分别时记录事件枚举目录和目录更改通知事件。 文件 IO 事件记录到 ETW 请求操作时。 兴趣完成文件 IO 操作的持续时间的那些可以启用可以将原始文件 IO 事件通过 IRP 指针相关的文件 IO 完成事件。 文件 IO 完成事件记录 IRP 指针,并返回状态。

磁盘的事件记录在 IO 堆栈中较低级别,并且它们包含特定于磁盘的访问的信息。 读取和写入操作生成的磁盘读取和写入事件包含磁盘号,传输的大小字节偏移量被访问的地址,IRP 指针,和访问的响应时间。 刷新事件记录磁盘的刷新操作。 与记录操作的开头的文件 IO 事件不同磁盘 IO 事件记录在 IO 完成的时间。 用户可以将收集的所有磁盘 IO 事件 (ReadInit WriteInit,FlushInit 事件) 的其他磁盘 IO 初始化事件。 前面提到不是所有的文件 IO 事件如果实例请求的内容已经可用在缓存中或写入磁盘的操作进行缓冲处理有匹配的磁盘 IO 事件。 分割 IO 事件表示的 IO 请求已被拆分到多个磁盘 IO 请求由于基础镜像磁盘硬件。 用户没有这样的硬件将看不到拆分 IO 事件,即使它们启用它们。 它映射到多个子 IRP 的原始父级 IRP。

磁盘 IO、 文件 IO 和拆分 IO 事件包含打开的文件的创建唯一的文件项。 此文件的密钥可用于跟踪 IO 系统中的相关的 IO 操作。 但是,文件操作的实际文件名不中任何文件或磁盘 IO 事件可用。 文件的名称解析,需要文件详细信息的事件。 记录的文件密钥和文件名,将枚举所有打开的文件。 一个模拟的状态计算机中文件对象的文件密钥跟踪记录文件 IO 请求和实际磁盘的访问,然后名称更新了的对象中时遇到文件详细信息的事件。 磁盘 IO 和文件的详细信息的事件中的文件密钥被命名为 FileObject 出于历史记录的原因。 大多数文件 IO 事件同时包含文件的对象和文件的密钥。

椹卞姩绋嬪簭事件表示的具体取决于该设备类型也不能与磁盘 IO 活动重叠驱动程序中的活动。 椹卞姩绋嬪簭事件可能是用户所熟悉的 Windows 驱动程序模型 (WDM) 感兴趣。 椹卞姩绋嬪簭检测添加椹卞姩绋嬪簭 IO 函数调用和完成例程的事件。 椹卞姩绋嬪簭事件包含椹卞姩绋嬪簭数据 (如文件的密钥,IRP 指针和日常地址 (主要和次要函数,完成例程),适合单个事件类型。

IO 事件通常会导致非常大量的事件,可能需要增加内核会话的编号和/或缓冲区的大小 (-nb logman 中的选项)。 此外,IO 事件可在分析文件用法、 磁盘访问模式和椹卞姩绋嬪簭活动。 但是,在 IO 事件以外的磁盘 IO 的事件的进程和线程 ID 值是无效。 要关联正确原始线程,因此该进程这些活动,一个需要考虑跟踪上下文开关事件。

网络事件

网络活动发生时记录网络事件。 在 TCP/IP 和 UDP/IP 层发送从内核会话的网络事件。 记录 TCP/IP 事件时执行下列操作:发送、 接收、 断开连接,重新传输,重新连接,复制,和失败。 它们都包含在数据包大小、 源和目标 IP 地址和以外的失败事件,因为没有这样的信息是可用的端口。 此外,在原始处理发送类型事件的 ID 和目标进程 ID 为接收类型的事件是包含的因为通常这些网络操作不会执行源/接收过程上下文。 这意味着网络事件可能由于一个的过程,但不是能一个线程。 UDP/IP 事件发送或接收包含上面列出的数据项的事件。 最后,每个操作类型 (发送,接收等) 对应于一对事件中:一个用于 IPV4 协议,另一个用于 IPV6。 在 Windows Vista 中添加的 IPV6 协议事件。

注册表事件

大多数注册表操作使用 ETW,检测,这些事件可能由于进程和线程通过事件标头中的进程和线程 ID。 注册表事件负载中不包含完整注册表项名称。 它包括一个唯一的键的而,记为密钥控制块 (KCB) 下的每个打开的注册表项。 末尾的内核会话 rundown 事件记录到完整的注册表名称映射这些键的。 若要鑻 ヨ  瑙 e 喅注册表一个需要使用一个类似的方法利用在这些映射事件的文件的名称解析的名称用于更新注册表状态机中的对象。 注册表事件已分析访问模式很有用,并标识冗余的优化访问。 注册表事件负载中包含操作可用于监视注册表操作失败,并可能的应用程序问题疑难解答返回的状态。

图 1 获取 ETW 的 PID 列表打印进程表的脚本

<####################
 # Get-ETW-PID-List
 # \@brief Takes in an XML stream and prints the list of process names
 # with PIDs that were detected in the incoming stream.
 ####################>
function Get-ETW-PID-List([Switch] $print) {
    begin {
        $pidlist = new-object System.Collections.Hashtable
        $procid = 0
        $procname = ""
    }

    process {
        $xmldata = $_
        
        # For each Process event, grab process id and binary name 
        # and add to the list. 
        foreach ($e in $xmldata.Events.Event) {
            if ($e.RenderingInfo.EventName.InnerText -eq "Process") {
                foreach ($d in $e.EventData.Data) {
                    # Grab process id.
                    if ($d.Name -eq "ProcessId") {
                       $procid = $d.InnerText
                    # Grab the process name.
                    } elseif ($d.Name -eq "ImageFileName") {
                        $procname = $d.InnerText
                    }
                }
                if (!$pidlist.Contains($procid)) {
                    $pidlist.add($procid, $procname) | Out-Null
                }
            }
        }
    }

    end {
        remove-variable xmldata

        if ($print) {
            "{0,-29}| PID" -f "Binary Name"
            "-------------------------------------"
            foreach ($item in $pidlist.Keys) {
                "{0,-30}({1})" -f $pidlist.$item,$item
            }
        } else {
            return $pidlist
        }
    }
}

获取 ETW 的 PID 列表脚本的图 2 输出

PS C:\tests> $xmldata | Get-ETW-PID-List -print
Binary Name                  | PID
-------------------------------------
dwm.exe                       (2776)
powershell.exe                (2384)
svchost.exe                   (708)
notepad.exe                   (4052)
iexplore.exe                  (4284)
...
iexplore.exe                  (4072)
svchost.exe                   (3832)
smss.exe                      (244)
System                        (4)
spoolsv.exe                   (1436)
Idle                          (0)

基于样本的配置文件事件

基于样本的配置文件事件 (配置文件事件) hereafter 允许的跟踪 CPU 花费的时间的位置。 配置文件事件可用于计算 CPU 使用率的一个很好近似值。 当启用配置文件事件时,Windows 鍐呮牳打开分析中断的引起将导致配置事件将记录从每个处理器 (默认值为 1000年倍秒,每个处理器上) 以固定速度。 应该注意在低功耗模式下,在某些计算机上,配置事件可能会被暂时关掉。 配置事件负载包含当前运行的处理器和指令指针寄存器的值上的分析中断该线程的线程 ID。 在 Windows 7,配置事件有效负载部分还包含确定执行上下文 (线程/DPC/ISR) 所需的标志。

图 3 获取 ETW 的 PID 信息脚本打印过程的详细信息

<####################
 # Get-ETW-PID-Info 
 # \@brief Retrieves various information about a particular process id
 ####################>
function Get-ETW-PID-Info([Int] $p, [switch] $i, [switch] $t) {
    begin {
        $threadlist = New-Object System.Collections.ArrayList
        $imagelist = New-Object System.Collections.ArrayList
        $procname = ""
    }

    process {

        $xmldata = $_
        
        $sievedxml = $($xmldata | Get-ETW-PID-Events $p).$p

        foreach ($e in $sievedxml.Events.Event) {
            if ($e.RenderingInfo.EventName.InnerText -eq "Process") {
                foreach ($d in $e.EventData.Data) {
                    # Grab the process binary name 
                    if ($d.Name -eq "ImageFileName") {
                        $procname = $d.InnerText
                    }
                }
            }
            if ($e.RenderingInfo.EventName.InnerText -eq "Image") {
                foreach ($d in $e.EventData.Data) {
                    # Grab the loaded image name and add it to the list
                    if ($d.Name -eq "FileName") {
                        if (!$imagelist.contains($d.InnerText)) {
                            $imagelist.add($d.InnerText) | Out-Null
                        }
                    }
                }
            }
            if ($e.RenderingInfo.EventName.InnerText -eq "Thread") {
                foreach ($d in $e.EventData.Data) {
                    # Grab thread id and add it to the list
                    if ($d.Name -eq "TThreadId") {
                        $tid = $d.InnerText
                        if (!$threadlist.contains($tid)) {
                            $threadlist.add($tid) | Out-Null
                        }
                    }
                }
            }
        }
    }

    end {
        "Process Name: $procname"
        if ($t) {
            "Thread List: ($($threadlist.Count))"
            $threadlist | Sort -descending
        } else {
            "Thread Count: $($threadlist.Count)"
        }
        if ($i) {
            "Image List ($($imagelist.Count)):"
            $imagelist | Sort
        } else {
            "Images: $($imagelist.Count)"
        }
    }
}

CPU 使用率可以被趋近于配置的事件的是配置文件以外的空闲线程的线程 ID 的事件的百分比。 每个进程的 CPU 使用率分发需要跟踪单个进程线程 ID 在该配置文件事件负载中的一个多个的步骤。 如果状态机使用如这两个部分文章系列的第 1 部分中所述的进程和线程事件,它是直接生成每个进程 CPU 使用情况报告。 也可能是跟踪通过图像加载事件加载模块的 CPU 使用率。 比较已加载的模块将导致较二进制的图像,因此每个模块的 CPU 使用率的配置文件的指令指针的位置的地址范围,指令指针。 如果可用二进制符号,一个可以从指令指针使用 DbgHelp 库获取函数名。 调用的堆栈启用配置文件事件,使用一个可以推断甚至出如何调用该函数。 当指令指针指向经常使用的库函数时,此操作非常非常有用。

准备就绪的线程事件

有几个原因,系统将切换出正在运行的线程的原因。 最常见的原因是它需要等待其他线程完成相关的任务,然后它才能继续。 阻止从设置,如事件、 信号量、 计时器等对象在等待时执行的线程为类相关性执行图面中。 跟踪成为取消阻止通过另一个线程、 一个 DPC 或计时器的线程的 Windows 操作系统计划程序 (也称为调度程序)。 组件和线程之间的相关性不容易看到,预测在开发过程中。 当发生意外的延迟时, 变得困难跟踪该问题的根源。

准备就绪的线程 (或调度程序) 事件已添加到帮助诊断这些问题。 当一个线程取消阻止 (或者 readied) 放在调度程序准备就绪队列中,将记录一个就绪线程事件,其有效负载包含 readied 线程的线程 ID。 通过以下的就绪线程事件链向上,一个可以跟踪执行依赖关系链其展开。 上面显示的上下文切换事件指示当 readied 的线程实际计划运行。 调用堆栈启用的就绪线程事件可能会导致在代码中负责的等待线程取消阻止该点。 准备就绪的线程事件有新 Windows 7。

系统调用事件

系统调用事件表示中的条目和退出 Windows 核心操作系统的系统调用。 系统调用是在 Windows 内核到接口包含一个数的 API。 此规范已添加到监视系统所做的用户模式应用程序和内核模式组件的调用。 有两种类型的系统调用的事件。 在输入系统调用的事件指示系统调用的调用,并记录该例程与被调用的系统服务相对应的地址。 在系统调用退出事件指示从一个系统调用的退出,并且其负载包含 API 调用从返回的值。 系统调用事件用于在系统调用活动和延迟,统计分析但类似于某些 IO 和内存事件在记录没有当前进程和线程 ID 信息标头中。 要关联的系统与进程和线程的调用活动,一个必须收集一次上下文切换事件和,状态机器模拟,期间跟踪当前线程在 CPU 使用 CPU ID 在事件上运行系统调用事件,所述的标头部分中的这两个部分文章一系列 1。

高级本地过程调用事件

已为本地过程调用 (LPC) 的有效本地 Inter-Process 通讯 (IPC) 机制在 Windows 平台上为年。 Windows Vista 提供了一种称为高级本地过程调用 (ALPC) 的 IPC 需要更加有效和安全的方法。 ALPC 还用作传输机制的本地远程过程调用 (RPC)。 ALPC 组件使用 ETW,发出发送检测、 接收、 等待新的答复,等待新消息和 Unwait 事件。

系统配置事件

鍐呮牳会话的结束 ETW 记录几个描述系统配置的计算机收集事件的事件。 在很多性能分析方案,了解基础硬件和软件配置有助于大大了解系统的行为,尤其是当分析事件的计算机从计算机中收集事件的。 这些系统配置的事件提供有关 CPU、 图形卡、 网卡、 PnP 设备、 IDE 设备,物理和逻辑磁盘配置和服务的信息。 系统配置事件有效负载部分取决于该设备,或配置,但通常描述了包含描述字符串和密钥的规范。

图 4 获取 ETW 的 PID 信息脚本输出

PS C:\tests> $xml | Get-ETW-PID-Info 4052 -i
Process Name: notepad.exe
Thread Count: 2
Image List (31):
\Device\HarddiskVolume2\Windows\System32\advapi32.dll
\Device\HarddiskVolume2\Windows\System32\dwmapi.dll
\Device\HarddiskVolume2\Windows\System32\gdi32.dll
\Device\HarddiskVolume2\Windows\System32\imm32.dll
\Device\HarddiskVolume2\Windows\System32\kernel32.dll
\Device\HarddiskVolume2\Windows\System32\KernelBase.dll
...
\Device\HarddiskVolume2\Windows\System32\version.dll
\Device\HarddiskVolume2\Windows\System32\winspool.drv

图 5 获取 ETW-顶部 VMops 打印调用大多数 VM 操作的前 n 进程的脚本

<####################
 # Get-ETW-Top-VMops
 # \@brief Gets the top $num processes ranked by the number of virtual 
   memory events
 ####################>
function Get-ETW-Top-VMops ([Int] $num) {
    begin {
        $hold = @{}
    }

    process {

        $xmldata = $_

        # Get a list of the PIDs
        $list = $xmldata | Get-ETW-PID-List
       
        # Iterate through each PID
        $eventxml = $xmldata | Get-ETW-PID-Events $list.Keys

        foreach ($pd in $list.Keys) {

            $vmops = 0

            # Count the number of VM events
            foreach ($e in $eventxml.$pd.Events.Event) {
                [String] $type = $e.RenderingInfo.EventName.InnerText
                [String] $opcode = $e.RenderingInfo.Opcode

                if ($type -eq "PageFault") {
                    if ($opcode -eq "VirtualAlloc" –or 
                      $opcode -eq "VirtualFree") {
                        $ vmops++
                    }
                }
            }
            $hold.$pd = $vmops
        }
    }

    end {
        "{0,-20}|{1,-7}| VM Event Count" -f "Binar"," PID"
        "--------------------------------------------------------"
        
        $j = 0
        foreach ($e in ($hold.GetEnumerator() | Sort Value  -Descending)) {
            if ($j -lt $num) {
                $key = $e.Key
                "{0,-20} ({1,-6} {2}" -f $list.$key,"$($e.Key))",$e.Value
            } else {
                return
            }
            $j++
        }
    }
}

简单的核心操作系统事件分析示例

本节中,我们提供了演示几个基本的记帐技术引入以前操作系统事件的一些简单的脚本。 我们将使用 Windows Powershell 脚本为简单起见,但基础的算法可以采用更有效地处理应用程序中。 一旦 tracerpt 工具创建的 XML 转储,一个可以作为 Windows Powershell 使用下列命令中的对象导入事件:

>$ xmldata = System.Xml.XmlDocument 新对象
>$ xmldata.Load (< XML 杞  偍鏂囦欢 >)

图 1 中第一个 Windows Powershell 脚本只是通过扫描处理事件的打印在日志文件中的所有进程。 它更新进程 ID 和名称对哈希表,并打印出末尾的表,如果指定 –print 选项。 将默认,传递以及对的数组到管道,以便其他脚本可以使用它的输出。 请注意我们跳过常规参数、 备注和错误检查,以简化示例脚本的处理。 我们还假定进程和线程 ID 不回收在此脚本,内核会话期间但执行获取实际上回收它们。 需要其他代码正确处理。

此脚本的输出结果如果 XML 转储包含过程的事件是的图 2 所示。 下一个脚本的目的是生成基本的状态,计算机和,给定一个进程 ID 打印的线程数和在该进程中加载映像的列表。 如果初始化或 –t 选项给出该脚本将打印加载图像的名称和该进程,而不是图像和线程的计数中的线程的 ID。 图 3 显示脚本获取 ETW 的 PID 信息写入该功能。 此脚本调用另一个脚本名为获取-ETW 的 PID 的事件 (即我们不会显示在此处) 选取的只在事件相关为给定的进程 ID。

我们从图 2 查找在记事本过程,获取 ETW 的 PID 信息脚本从获取下面的输出。 图 4 列出 notepad.exe 由加载的所有模块。

我们最后,将打印与大多数的虚拟内存操作的前 n 进程表。 在此调用获取的 ETW 的顶部-VMops,图 5中, 中所示的脚本中我们运行获取-ETW 的 PID-列表构造的进程 ID 的列表。 然后我们筛选事件的每个进程 ID,并计算 VM 事件。 最后一次,我们的排序和打印的前 n 个进程所在的表。

图 6 中给出此脚本与大多数的虚拟内存操作的前 10 个进程的输出。 注意 logman.exe 的两个实例显示在表,一个用于开始,另一个用于停止内核会话。

图 6 获取 ETW-顶部 VMops 从输出

PS C:\tests> $xml | Get-ETW-Top-Vmops 10
Binary              | PID   | VM Event Count
--------------------------------------------------------
svchost.exe          (536)   486
iexplore.exe         (3184)  333
svchost.exe          (1508)  206
logman.exe           (2836)  98
svchost.exe          (892)   37
sidebar.exe          (3836)  37
logman.exe           (1192)  19
svchost.exe          (2052)  18
audiodg.exe          (7048)  18
services.exe         (480)   13

增强您的工具功能

Windows 7 功能数百个事件提供程序从各种组件。 此两个部分文章系列中我们已提供核心操作系统 ETW 的一组 Windows 7 和我们使用许多年分析技术上可用的事件。 单个事件指示某些活动的核心操作系统,但如果组合通过区分上下文的分析方法,它们可以用于生成有意义的报告提供深入了解模式和资源使用情况的异常的。 此外,我们知道相当多的情况下这些事件的子集的有效和仔细检查导致研究的特殊字段中很好的结果。 与 ETW 检测的应用程序可以受益甚至进一步精确相关应用程序和操作系统的活动 ;例如,如果应用程序发出的事件,该值指示一个开始和结束的一个重要的应用程序活动,收集一次的核心操作系统事件将包含准确操作系统资源的使用信息属性化的活动。 管理和性能工具开发人员可以利用核心系统事件和各种分析技术,以提高其工具为功能中受益 IT 工作人员。 应用程序开发人员能够更好地诊断应用程序的问题,如果这样的分析在其环境中合并。 我们希望我们的两个部分文章系列将导致声音工程设计做法、 更大的软件质量和更好的用户体验的提升。

Dr。 Insung Park 用于 Windows 检测和诊断平台团队的开发经理。 他已发布为十几个性能分析、 跟踪的请求,检测技术,和编程方法和支持的纸张。 insungp@microsoft.com 他的电子邮件地址。

Alex Bendetov 用于 Windows 检测和诊断平台团队的开发潜在顾客。 他适用于 Windows 事件跟踪和性能计数器技术。 他能到达在 alexbe@microsoft.com