.NET WinDbg 调试流程

创建Dump

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

加载SOS.dll等调试扩展

直接加载

通过clr自动加载SOS

不能调试

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

Dump 流程

  1. !DumpDomain,看看当前进程加载了哪些Domain,以及每个Domain中加载的DLL
    • 使用!peb,看看当前进程的环境变量,加载模块,命令行参数等信息
  2. !DumpHeap -stat,看看堆上都有哪些对象,或者加上-type来过滤下特定类型
  3. !Threads,看看当前都有哪些线程
    • 使用!runaway,看看这些线程的CPU消耗统计
  4. ~[thread id] s,切到关心的线程,或者粗暴的~* e !ClrStack看看每个托管栈上有什么
  5. !ClrStack看看托管调用栈,加-a查看每个调用参数(-p)和函数内本地变量(-l)
    • !DumpStack,看看当前线程的全部调用栈,包括非托管代码
  6. .frame [stack frame id],切换到关心的Call Stack具体的Frame
  7. !DumpStackObjects,看看当前栈上的所有对象

检查CLR对象

基础:The Book of the Runtime

  1. 先!DumpHeap -stat,看看有哪些类型和他们的MethodTable
    • 使用SOSEX的!mx *[type/field/method name]* ,在托管堆上搜索特定类型,字段或方法
  2. !DumpHeap /d -mt [MTAddress],看看这个类型有哪些实例,和他们的对象地址
    • 使用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] ,看看对象实例的内容

调试ASP.NET应用

调试扩展:NetExt

  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: