创建Dump
可以通过下面几种方式来创建Dump
- Sysinternals - ProcessExplorer ;
- 可以创建full dump和mini dump
- 自动区分32位dump和64位dump
- Debug Dialog 2
- Sysinternals - ProcDump ;
procdump.exe -c 50 -s 10 -n 2 -ma CPUSTRES.exe
#CPU超50%,连续超过10秒,dump两次的FullDumpprocdump -e 1 -l -f 'SqlException' -ma -p [PID]
#监听特定进程,当出现SqlExeception的时候创建一个full dump。由于-e 1指定了first chance exception,所以栈还在,可以查看栈上对象,包括SqlCommand等。
- Windows Task Manager -> right click on process -> Create Dump ;
- it will create full dump;
- 不建议使用,如果在Windows 64位电脑上dump出来的是64位的dump文件,即使你dump的对象是32位的程序。
加载SOS.dll等调试扩展
调试扩展:
加载调试扩展
.load C:\Windows\Microsoft.NET\Framework64\v4.0.30319\SOS.dll
.load C:\MyDumps\SOSEx\x64\sosex.dll
.load C:\MyDumps\NetExt\x64\NetExt.dll
通过clr自动加载SOS
.loadby sos clr
.loadby sos coreclr
; dotnet core
不能调试
有时候不能查看对象,通常是以下原因:
- Symbols没有正确加载,使用命令
.symfix
修复lm
查看已经加载的的module和对应symbols!sym noisy
打开symbol加载详细日志,然后再.reload
重新加载一下Symbol
- 使用32位的ProcDump/WinDbg进行Dump和调试32位应用程序,使用64位的程序去Dump和调试64位的应用程序
- 本地调试和目标机器的.NET版本不一致,WinDbg会自动拉取Dump文件对应版本的符号文件和
SOS.dll
,mscordacwks.dll
等,如有问题,使用下面方式调试:- 使用
!sym noisy
打开符号加载日志,看是否网络问题。 - 执行
.cordll -ve -u -l
重新加载mscordacwks.dll
- 使用
怎么样开始
- 先摸摸底,包扩非托管的代码
!peb
,看看当前进程的环境变量,加载模块,命令行参数等信息~* k
,看看所有线程的调用栈!runaway
,看看这些线程的CPU消耗统计
- 再
!DumpDomain
,看看当前进程加载了哪些Domain,以及每个Domain中加载的DLL - 如果是异常的Dump,直接
!analyze -v
查看自动异常分析
CPU问题
- 看下线程的CPU统计,定位有问题的线程
!runaway
, 看看这些线程的CPU消耗统计!ThreadPool
看看.NET线程池的使用统计!Threads
看看当前都有哪些线程
- 再
~[thread id] s
,切到关心的线程,!ClrStack
查看线程掉调用栈~* e !ClrStack
粗暴的看看每个托管栈上有什么- 再
!DumpStack
,看看当前线程的全部调用栈,包括非托管代码,使用-EE只显示托管栈
- 再
.frame [stack frame id]
,切换到关心的Call Stack具体的Frame - 再
!DumpStackObjects
,看看当前栈上的所有对象
内存问题
- 先
!DumpHeap -stat
,看看堆上都有哪些对象,或者加上-type
来过滤下特定类型!DumpHeap -stat -min 102400
看看那些类型(MethodTable)
占用的内存超过0.1M(102400 bytes)。
- 再
!DumpHeap -mt [MethodTable]
看看这些类型(MethodTable)
和实例和其所占用的内存。 - 再
!GCRoot [Obj Address]
看看对象的实例所有引用链,包括所在线程信息。
检查CLR对象
在检查类型和对象的输出的大小单位默认都是bytes。
- 先
!DumpHeap -stat
,看看有哪些类型和他们的MethodTable
MethodTable
是CLR用来描述类型(Class)的数据结构,MethodDesc
是描述方法的数据结构- 想看一个类型有那些方法,可以使用
!DumpMt -MD <MTAddress>
或者更好看的!wclass <MTAddress>
- 想看一个方法MethodDesc属于哪个类型可以使用
DumpMD <MethodDescAddress>
从而获取类型的MethodTable
EEClass
同MethodTable
都是描述类型的数据结构,EEClass
存的是类型的冷数据(不常用),而MethodTable
是热数据(常用数据)。通常EEClass
只被类型加载器用到。另外泛型类型可以共用一个EEClass
,可以使用!DumpClass <EEClassAddress>
查看EEClass的结构。- 在托管堆上搜索特定类型,字段或方法 使用SOSEX的
!mx *[type/field/method name]*
,
- 再
!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
- 使用
- 再
!DumpObj [ObjAddress]
,看看对象实例的内容- 查看数组内容使用
!DumpArray <ObjAddress>
- 使用NetExt扩展的
wdo
命令兼容显示对象和数组!wdo <ObjAddress>
- 显示字典内容(Dictionary)使用
!wdict <ObjAddress>
- 查看数组内容使用
调试ASP.NET应用
- 先
!whelp
,看看NetExt提供了哪些可能用到的功能 - 再
!wapppool
,看看当前用的ApplicationPool - 再
!whttp
,看看正在处理哪些请求/页面,即现在正在运行的HttpContext。asp.net中使用HttpContext来处理请求 - 再
!whttp [HttpContextAddress]
,看看自己关心页面的HttpContext细节信息 - 再
!wsql
,看看Heap上的SQLCommand对象,是不是有SQL在运行? - 再
!wthreads
,看看当前所有运行的线程 - 再
!wk
或!wclrstack
,看看关心线程的调用栈,!wk
会包括非托管代码 - 再
!wdo
,!wdict
,看看自己关心的对象的值
查看代码
- 找到你想看到代码的MethodDesc
- 使用函数签名
!Name2EE unittest.exe MainClass.Main
,看看Main函数的MethodTable(类型对象)和EEClass - 使用对象实例
!DumpObject
,看看MethodTable和EEClass - 使用
!ClrStack
看到栈上方法的IP(Instruction Pointer),使用!IP2MD
找到方法的MethodDesc
- 使用函数签名
- 再
!DumpMT -MD [MT_Address]
,看看MethodTable(类型对象)上有哪些方法,获得MethodDesc(方法体引用) - 再
!DumpMD [MD_Address]
,看代码是否被Jitted和所在模块。 - 再
!DumpIL [MD_Address]
或!U [MD_Address]
,看看生成的IL代码- 使用SOSEX的
!muf [IP or MDAddress]
,看看生成的IL代码 - 使用NetExt的
!wmakesource [IPAddress]
,直接生成C#代码
- 使用SOSEX的
设置断点
- 先
!DumpMT -md [MT_Address]
,查看类型所有方法,找到方法的MethodDesc- 或者
!bpmd <ModuleName> <FunctionName>
直接打断点,比如!bpmd System.Web.dll System.Web.HttpApplicationState.UnLock
- 或者
- 再
!DumpMD [MDAddress]
,看看方法是否被Jitted - 已经Jitted,直接
bp [CodeAddress]
- 没有Jitted,
!BPMD -md [MDAddress]
死锁
- 找到等待/持有这个锁的线程
- 使用
EEStack -short
,看看那些.NET的threads拥持有了锁 - 使用
~* e !ClrStack
查看所有线程调用栈,通常是卡在System.Threading.Monitor
类型Enter/Wait
方法上。 - 使用
!Threads
,看看Locks那一个栏位。
- 使用
- 如果是等待锁,那么谁持有这个锁?可以通过
- 查看当前线程的调用栈和参数
!clrstack -p
,哪个对象在获取锁,!do
查看这个对象能得到更多信息。比如HttpApplicationStateLock
会记录当前持有者的线程id。 - 或者
!SyncBlk -all
看看你关注持有锁的对象。
- 查看当前线程的调用栈和参数
- 通过
~[thread id]s
跳掉持有锁的线程,看看当前线程在做什么,为什么不释放,通常到这里就找到问题了。
异常
0xe0434f4d
,SEH的异常错误码,当时显示非托管栈时候这个参数下方(caller)是真正的异常对象。- 异常时中断,方便调试,适用于live debug:
sxe 0xe0434f4d
或sxe clr
,所有CLR异常时都中断!StopOnException -create System.IndexOutOfRangeException
,特定类型异常时候才中断,还有个扩展参数,可以指定特定类型异常的子类还中断:!soe -derived -create <BaseExceptionType>
sxn clr
,只打印CLR异常,不产生中断sxr
, reset,恢复到默认状态
- 异常检查
- 先
~#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
Comments: