我们已经看了不少Linux的core dump分析案例了,这次我们来看一个案例,其中利用到了Windows memory dump的分析技巧。Windows的memory dump基本原理几乎和Linux并无太大区别,如果是Crash - 内核崩溃类型的dump,分析思路几乎是完全一致的,当然难度主要在于Windows系统封闭性,即无法提供私有符号和源码,所以多需要一些汇编层面的理解。但是对于夯机的dump分析就需要一些对系统的理解了,这一点Windows和Linux系统的区别就体现出来了。与之前一样,我们还是先来看一下具体的现象:
问题现象:
问题现象相对之前要复杂一些,用户反应业务应用经常会有响应慢的现象。此外看到的一个现象是RDP登录会比较慢,有时会停滞在如下界面5分钟以上:
前期分析
第一步我们还是做一些前期的分析,因为我们必须要找到分析的切入点,这决定了我们之后排查的方向是否正确。这里需要多提一句,在分析一些疑难问题时,做一些前期分析,确保排查方向正确是非常必要的,否则一旦方向错误,会耗费大量的时间和精力。
这里我们前期分析的一个目标就是确认问题原因确实是在这台机器上,而不是因为网络或者RDP客户端的问题。那么一个简单的区分方法就是做一些本地RDP,即在这台机器对127.0.0.1发起RDP连接,确认确实也有以上类似现象,那么我们基本就可以放心继续我们在这台机器上的排查了。
信息抓取
夯机Memory Dump的抓取步骤其实相对比较简单,直接在NC上运行virsh dump即可。但是注意一点,我们经常会犯的一个错误是,用户报告机器响应慢,于是我们就直接抓取dump了。这里的一个问题在于,当我们分析memory dump时,我们需要一个切入点,比如需要知道大概是哪个进程出现了夯住的问题,那么我们就可以从这个进程的线程堆栈出发进行分析。如果没有这个切入点,我们会陷入一种不知从何处开始的窘境,当然如果你对系统足够了解,可以做一些猜测,甚至整个系统的所有关键进程进行逐一扫描。但是这将耗费大量的时间,排查效率也大大下降了。
所以在这个实际案例中,我们要求用户在本机上进行RDP,并确保停滞在以上画面5分钟以上,开始抓取dump。这样的好处在于,当分析dump时,我们便可以直接从登录的相关进程开始分析了。
Memory Dump分析
下面就到了真正的分析环节了,面对夯机的memory dump分析个人会有自己思路,对Windows系统一个简单的想法是首先看一下内核锁的情况,而Linux往往更关注CPU上运行的进程。Windows系统中executive resource是一种非常常见的内核锁,关于eresource的具体描述可以参考:https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-eresource-routines。简而言之他是Windows内核层面比较常用的锁形式,并且是Windows特有,在内核层面,一些全局的大锁往往是由eresource的形式实现的。我们首先来看一下他的情况:
我们可以看到有大量的内核取锁的线程,而一般我们首先看一下第一个共享锁的所有者:
我们可以看到executive resource的所有者正在获取另外一个锁,因此我们要做的是把他正在尝试获取的锁的所有者线程堆栈打印出来。首先取得锁的地址:
使用!locks命令打印出锁的竞争情况:
我们可以从输出中看到对应锁的所有者为线程 fffffa800b61bb50。所以我们可以再次应用!thread命令打印出该线程的堆栈:
我们可以看到该线程目前正在CPU上运行,但是不幸的是我们针对Windows虚拟机的core dump抓取和转换是有些问题的,对于正在CPU上运行的堆栈无法输出。但是我们可以上面等待的线程中获取一些信息,注意看Ticks一项:
Ticks代表该线程处于等待未运行状态的时间,可以看到该等待线程已经等待锁仅有1秒钟左右,因此可以判断该问题并不是一个锁死的问题,这意味着对应在CPU上运行的这个线程并不会一直占据CPU,而是过一会儿会释放CPU。所以这仅是一个比较忙的问题,并不是整个内核都被锁死而无法运行的问题。那么下个问题就是,这些线程在忙什么?
我们来看看正在运行的这个线程的IRP吧。IRP即是Windows是分层IO的结构,Windows是通过IRP来实现IO的层层传递的,大家可以参考:https://en.wikipedia.org/wiki/I/O_request_packet 来具体了解一下。我们可以使用windbg的!irp命令来打印出IO结构,其地址来自于!thread输出的"IRP List"部分:
可以看到IRP的分层结构,IO是有输出的底层向上层传递,当前已经传递到文件系统驱动层面Ntfs。注意我们在这里要始终记住一点,就是线程等待的时间并不长,说明他是忙着做一些事情,并不是停滞不前。所以面对这个IRP,我们会更关注Ntfs在忙着做什么。因此我们可以挑选IRP中的操作文件对象的地址来看看这个IO是发向那个文件的,或者说线程是在操作哪个文件:
我们发现了该线程是在操作c:\Windows\Temp目录下的一些临时文件,如果下一步骤自然是我们会去看看这个目录里就是有些什么样的问题。
验证阶段
当我们去访问c:\Windows\Temp该目录是,真相就大白了,其中包含了大量的sess_xxx的文件,导致explorer直接访问都会夯住很长时间,如果采用命令行dir的方式需要等待很长时间才能打印完所有文件。
当然用户会关注是哪些进程产生了这些文件,利用我们之前文章里提到的process monitor可以轻松地确认文件是由业务进程php-cgi创建的。解决方法当然是删除这些临时文件,这事实上需要一个晚上的时间!