WinDBG 的命令简(难)洁(懂),多(难)样(记),功(反)能(人)强(类)。许多同学都感觉它功能强大但是不容易上手。今天我介绍另一种方式希望对大家有所帮助。
WinDBG 是一个调试器,那么什么是调试呢?我的理解,调试可以认为是确认应用程序在此刻的状态正确与否。如果状态正确,那么一切正常,如果状态错误,那么说明应用程序的执行出现了问题。而应用程序的状态是什么呢?其实就是应用程序在内存中的表示。那么,如果我们能够理解应用程序在内存中的构成,那么我们就可以找到各种各样的状态,从而判断程序运行的正确性了。
因此,WinDBG 的命令是什么并不重要,重要的是你知道如何用这些命令探索应用程序各种状态之间的联系。下面我们就先简要的看一看应用程序的状态是什么样子的(我以 CLR 的应用程序为例)。
应用程序在内存中是这样的
这篇文章的关键在于思路而非包罗万象的介绍应用程序在内存中的表示。如果要深入探索,还需要多下下功夫。
应用程序的最上层结构是进程,进程中包含了正在执行的若干线程。
- 一个线程实际上包含线程的上下文(例如一些安全上下文,当前线程的线程内存储结构等等),还有当前线程的函数栈。
- 函数栈包含若干的调用栈帧。每一个调用栈帧实际上代表了一个函数调用。包含调用的地址,本地栈变量,参数,以及引用的堆上的变量。
- 变量所引用的对象在内存中也是有一定的结构的。变量实际上就是一个托管的指针。
- 而对象包含对象头部信息(同步信息块、运行时类型信息),和对象本身的数据(成员变量)
- 运行时类型信息中又包含 MethodTable 信息。
- 而对象所在的堆可能是第 0 代托管堆、第 1 代托管堆和第 2 代托管堆。还有可能在 LOH 上(大对象堆)。
如果我们画一张图,那么这张图就类似这样:
调试就是一次旅游
那么调试就是从这张图的任意一个点进入,并且通过应用程序运行时状态的各个部分的关系,通过图中的线进行任意的游走,确定每一个部分是否正确的过程。
通过上述分析,那么我们只要解决两个事情就可以了:
- 找一个入口点
- 在状态的各个部分之间游走
我们不妨来尝试一下。
找到一个入口点
现在请查一下 winDBG 的命令手册,你可以从网上搜到各种各样的资料。例如如下几个:
- 用如下的命令可以找到 Thread(线程)相关的信息:
~
、!runaway
、!threads
、~$thread id$s
、!threadpool
- 用如下的命令可以找到托管堆上存储的变量的信息:
!dumpheap
、!pe
- 用如下的命令可以找到特定类型的 MethodTable 信息:
!name2ee
- 在使用 sosex 扩展的情况下,可以用如下的命令获得托管堆存储的对象信息:
!gcgen
、!dumpgen
- 使用如下的命令可以直接获得对象的同步对象的信息:
!synblk
在状态的各个部分之间游走
现在我们有一个切入点了,那么我们就可以通过各个部分之间的关系将应用程序执行涉及到的信息一个不剩的找出来。例如:
- 如果想从线程的信息查看线程的调用栈的信息,可以先使用
~$thread id$s
切换到线程的上下文,然后使用!clrstack
命令查看调用栈信息。 - 如果想从调用栈查找某一个栈帧的局部变量和参数的话可以分别使用:
!clrstack -l
和!clrstack -p
- 如果想从变量的地址查看变量的存储结构那么可以使用
!dumpobj
或者!dumparray
命令。 - 如果有一个变量的地址但是想知道变量被那一个调用栈引用可以使用
!gcroot
命令,再通过!clrstack
确认特定的栈帧。
旅游总结
综合前两个小标题的内容我们可以制定如下的旅游攻略:
此图不全,仅仅是为了抛砖,希望各位能够自己总结,形成自己的知识体系。
旅游也有套路
应用程序的内部结构还是有些复杂的。为了节省时间,大家都会总结各种套路。这就需要大家整理思路了。
例如,对于某次高的 CPU 占用。我选择先从线程入手。首先使用 !runaway
命令查看线程的 CPU 时间。发现 20 多个线程没完没了的执行了若干小时。而后通过 !clrstack
查看线程的调用栈发现这些线程的调用栈都在调用一个叫做 RenameXxx
的函数。
其实应用程序很少会出现巧合,因此怀疑这个函数有问题。使用 !clrstack -p
查看了一下函数的参数和局部变量,发现一个参数含有一个字符串变量,其长度已达 1MB。使用 !dumpobj
发现其内容为:fileName (1) (1) (1) (1) ...
至此基本断定这个函数里有死循环的逻辑了。
网上还有很多既有的套路。例如 Tess 的博客。强烈建议各位读者也用自己的博客、小本子、Evernote、OneNote 等各种媒介记录自己的旅游套路。不知不觉你就会成为 winDBG 调试的老司机。
其实,这篇博客不光是为 winDBG 写的。