.NET WinDBG 调试流程

创建Dump

可以通过下面几种方式来创建Dump

加载SOS.dll等调试扩展

调试扩展:

加载调试扩展

通过clr自动加载SOS

不能调试

有时候不能查看对象,通常是以下原因:

怎么样开始

  1. 先摸摸底,包扩非托管的代码
    • !peb,看看当前进程的环境变量,加载模块,命令行参数等信息
    • ~* k,看看所有线程的调用栈
    • !runaway,看看这些线程的CPU消耗统计
  2. !DumpDomain,看看当前进程加载了哪些Domain,以及每个Domain中加载的DLL
  3. 如果是异常的Dump,直接!analyze -v查看自动异常分析

CPU问题

  1. 看下线程的CPU统计,定位有问题的线程
    • !runaway, 看看这些线程的CPU消耗统计
    • !ThreadPool 看看.NET线程池的使用统计
    • !Threads 看看当前都有哪些线程
  2. ~[thread id] s,切到关心的线程,
    • !ClrStack 查看线程掉调用栈
    • ~* e !ClrStack 粗暴的看看每个托管栈上有什么
    • !DumpStack,看看当前线程的全部调用栈,包括非托管代码,使用-EE只显示托管栈
  3. .frame [stack frame id],切换到关心的Call Stack具体的Frame
  4. !DumpStackObjects,看看当前栈上的所有对象

内存问题

  1. !DumpHeap -stat,看看堆上都有哪些对象,或者加上-type来过滤下特定类型
    • !DumpHeap -stat -min 102400 看看那些类型(MethodTable)占用的内存超过0.1M(102400 bytes)。
  2. !DumpHeap -mt [MethodTable] 看看这些类型(MethodTable)和实例和其所占用的内存。
  3. !GCRoot [Obj Address] 看看对象的实例所有引用链,包括所在线程信息。

检查CLR对象

基础:The Book of the Runtime

在检查类型和对象的输出的大小单位默认都是bytes。

  1. !DumpHeap -stat,看看有哪些类型和他们的MethodTable
    • MethodTable是CLR用来描述类型(Class)的数据结构,MethodDesc是描述方法的数据结构
    • 想看一个类型有那些方法,可以使用!DumpMt -MD <MTAddress>或者更好看的!wclass <MTAddress>
    • 想看一个方法MethodDesc属于哪个类型可以使用DumpMD <MethodDescAddress>从而获取类型的MethodTable
    • EEClassMethodTable都是描述类型的数据结构,EEClass存的是类型的冷数据(不常用),而MethodTable是热数据(常用数据)。通常EEClass只被类型加载器用到。另外泛型类型可以共用一个EEClass,可以使用!DumpClass <EEClassAddress>查看EEClass的结构。
    • 在托管堆上搜索特定类型,字段或方法 使用SOSEX的!mx *[type/field/method name]*
  2. !DumpHeap /d -mt [MTAddress],看看这个类型有哪些实例,和他们的对象地址
    • 使用!DumpHeap -type <type name>在Heap上搜索特定类型,查找类型的MethodTable和其对象地址
    • 使用NetExt查看所有对象特定字段!wfrom -mt [MTAddress] select [filed_name, eg: m_username, m_email]
    • 特定类型中包含特定字段值:!wfrom -type Objects.User where $contains(m_username, "zjy") select $addr(), m_username
  3. !DumpObj [ObjAddress] ,看看对象实例的内容
    • 查看数组内容使用!DumpArray <ObjAddress>
    • 使用NetExt扩展的wdo命令兼容显示对象和数组!wdo <ObjAddress>
    • 显示字典内容(Dictionary)使用!wdict <ObjAddress>

调试ASP.NET应用

  1. !whelp,看看NetExt提供了哪些可能用到的功能
  2. !wapppool,看看当前用的ApplicationPool
  3. !whttp,看看正在处理哪些请求/页面,即现在正在运行的HttpContext。asp.net中使用HttpContext来处理请求
  4. !whttp [HttpContextAddress],看看自己关心页面的HttpContext细节信息
  5. !wsql,看看Heap上的SQLCommand对象,是不是有SQL在运行?
  6. !wthreads,看看当前所有运行的线程
  7. !wk!wclrstack,看看关心线程的调用栈,!wk会包括非托管代码
  8. !wdo, !wdict,看看自己关心的对象的值

查看代码

  1. 找到你想看到代码的MethodDesc
    • 使用函数签名 !Name2EE unittest.exe MainClass.Main ,看看Main函数的MethodTable(类型对象)和EEClass
    • 使用对象实例 !DumpObject,看看MethodTable和EEClass
    • 使用!ClrStack看到栈上方法的IP(Instruction Pointer),使用!IP2MD找到方法的MethodDesc
  2. !DumpMT -MD [MT_Address],看看MethodTable(类型对象)上有哪些方法,获得MethodDesc(方法体引用)
  3. !DumpMD [MD_Address],看代码是否被Jitted和所在模块。
  4. !DumpIL [MD_Address]!U [MD_Address],看看生成的IL代码
    • 使用SOSEX的!muf [IP or MDAddress],看看生成的IL代码
    • 使用NetExt的!wmakesource [IPAddress],直接生成C#代码

设置断点

  1. !DumpMT -md [MT_Address],查看类型所有方法,找到方法的MethodDesc
    • 或者!bpmd <ModuleName> <FunctionName>直接打断点,比如!bpmd System.Web.dll System.Web.HttpApplicationState.UnLock
  2. !DumpMD [MDAddress],看看方法是否被Jitted
  3. 已经Jitted,直接bp [CodeAddress]
  4. 没有Jitted,!BPMD -md [MDAddress]

死锁

  1. 找到等待/持有这个锁的线程
    • 使用EEStack -short,看看那些.NET的threads拥持有了锁
    • 使用~* e !ClrStack查看所有线程调用栈,通常是卡在System.Threading.Monitor类型Enter/Wait方法上。
    • 使用!Threads,看看Locks那一个栏位。
  2. 如果是等待锁,那么谁持有这个锁?可以通过
    • 查看当前线程的调用栈和参数!clrstack -p,哪个对象在获取锁,!do 查看这个对象能得到更多信息。比如HttpApplicationStateLock会记录当前持有者的线程id。
    • 或者!SyncBlk -all看看你关注持有锁的对象。
  3. 通过~[thread id]s跳掉持有锁的线程,看看当前线程在做什么,为什么不释放,通常到这里就找到问题了。

异常

  1. 0xe0434f4d,SEH的异常错误码,当时显示非托管栈时候这个参数下方(caller)是真正的异常对象。
  2. 异常时中断,方便调试,适用于live debug:
    • sxe 0xe0434f4dsxe clr,所有CLR异常时都中断
    • !StopOnException -create System.IndexOutOfRangeException,特定类型异常时候才中断,还有个扩展参数,可以指定特定类型异常的子类还中断:!soe -derived -create <BaseExceptionType>
    • sxn clr,只打印CLR异常,不产生中断
    • sxr , reset,恢复到默认状态
  3. 异常检查
    • ~#s跳转到导致异常的线程,适用于postmortem
    • 然后!pe显示当前线程最后异常信息,可以使用!pe <ExceptionObject>显示特定异常信息
    • 暴力一点,比如你想知道所有文件没有找到异常信息: .foreach(ex {!DumpHeap -type System.IO.FileNotFoundException -short}){!pe ex;.echo}NetExt中有个类似的命令打印所有的异常!wdae

图示

Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects

clr_type_internals.jpg

@ 2019-11-17 20:07

Comments:

Sharing your thoughts: