使用 Windbg 分析一个 异步操作 引发的 Crash 异常

上周我们收到了一个客户的紧急求助,他们的一个 iis应用程序池 经历了频繁重启,即使从错误日志中也不得到任何有用的信息,异常信息如下:

System.NullReferenceException : Object reference not set to an instance of an object.
System.Web.ThreadContext.AssociateWithCurrentThread(Boolean)
   System.Web.HttpApplication.OnThreadEnterPrivate(Boolean)
   System.Web.LegacyAspNetSynchronizationContext.CallCallbackPossiblyUnderLock(System.Threading.SendOrPostCallback, System.Object)
   System.Web.LegacyAspNetSynchronizationContext.CallCallback(System.Threading.SendOrPostCallback, System.Object)
   System.Threading.Tasks.AwaitTaskContinuation.RunCallback(System.Threading.ContextCallback, System.Object, System.Threading.Tasks.Task ByRef)
   System.Threading.Tasks.AwaitTaskContinuation+<>c.b__18_0(System.Object)
   System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   System.Threading.ThreadPoolWorkQueue.Dispatch()

这种异常会杀掉进程,通过 google 搜索会发现这是 .NET 4.5 异步操作下的某种经典异常,大概就是说没有在合适的地方等待 Task, 参考:https://stackoverflow.com/questions/18759987/async-code-without-waiting-for-completion  ,参考如下代码:

var getDataTask = getData();

if(some_condition == true) {
    return some_object;
}

getDataTask.Wait();

return getDataTask.result;

如果代码无意走了 if(some_condition == true) 路径并没有 Wait 后续的 getDataTask 就会抛出这种异常,现在的问题是堆栈信息太少,并不能找到异常的源头在哪里?

使用 windbg 分析

如果你有 crash 的 dump,用windbg就能挖到很多有价值的信息。

  1. File → Open Crash Dump

首先使用 windbg 打开 crash 文件,如下图:

65a286b7fd9409651d3ae68495a95c9b.png

从图中可以看到,windbg 已经告知我们当前有一个 CLR 异常。

  1. Symbols

虽然没有客户的 debug 符号,这里使用微软的符号服务器, srv*C:\symbols*http://msdl.microsoft.com/download/symbols; 如下图:

f15a69a5254397ff39d66def7fc0cb87.png
  1. 加载 SOS 扩展

在 windbg 命令窗口中输入  .loadby sos clr

  1. 异常分析命令

使用 windbg 自带的异常分析自动化命令 !analyze -v ,然后你会得到很多的 异常信息, 参见下图:

c6d25a200001def634e15f01d1b244fe.png

上面的图中会告诉我们出问题的线程,接下来我们怎么看是谁创建了 Task,它的源头在哪里?可以用 !dso 查看这个问题线程的所有栈对象。

0:069> !dumpstackobjects
OS Thread Id: 0x2d20 (69)
RSP/REG          Object           Name
r15              0000002031518d78 System.Threading.Thread
00000022C6EBE680 0000001db1a917a8 System.NullReferenceException
00000022C6EBE6A0 0000001db1a917a8 System.NullReferenceException
00000022C6EBE6F8 0000001db1a917a8 System.NullReferenceException
00000022C6EBE740 0000001db1a91c40 System.Threading.QueueUserWorkItemCallback
00000022C6EBE758 0000001db1a91ad0 System.Runtime.ExceptionServices.ExceptionDispatchInfo
00000022C6EBE760 0000001db1a917a8 System.NullReferenceException
00000022C6EBE780 0000002031518d78 System.Threading.Thread
00000022C6EBE788 0000001db1a91c40 System.Threading.QueueUserWorkItemCallback
00000022C6EBE790 0000001db1a917a8 System.NullReferenceException
00000022C6EBE7A8 0000001df1b95078 System.SByte[]
00000022C6EBE7B0 0000001df1392cc0 System.Threading.ContextCallback
00000022C6EBE7C0 0000001db1a917a8 System.NullReferenceException
00000022C6EBE880 0000001db1a91ad0 System.Runtime.ExceptionServices.ExceptionDispatchInfo
00000022C6EBE888 0000001db1a917a8 System.NullReferenceException
00000022C6EBE8A8 0000001db1a91c40 System.Threading.QueueUserWorkItemCallback
00000022C6EBE8B0 0000002031518d78 System.Threading.Thread
00000022C6EBE910 0000001df1392cc0 System.Threading.ContextCallback
00000022C6EBE920 0000001db1a917a8 System.NullReferenceException
00000022C6EBE928 0000001db1a91ad0 System.Runtime.ExceptionServices.ExceptionDispatchInfo
00000022C6EBE938 0000001db1a91cc8 System.Threading.ExecutionContext
00000022C6EBE940 0000001db1a91cc8 System.Threading.ExecutionContext
00000022C6EBE950 0000001df1392cc0 System.Threading.ContextCallback
00000022C6EBE990 0000002031518d78 System.Threading.Thread
00000022C6EBE9F0 0000001db1a91c40 System.Threading.QueueUserWorkItemCallback
00000022C6EBE9F8 0000001db1a91cc8 System.Threading.ExecutionContext
00000022C6EBEA60 0000001df1392c98 System.Threading.ThreadPoolWorkQueue
00000022C6EBEAB8 0000001fb114f2e8 System.Threading.TimerQueue
00000022C6EBEAD8 00000020314c0ae8 System.Threading.ThreadPoolWorkQueueThreadLocals
00000022C6EBEAE0 0000001df1392c98 System.Threading.ThreadPoolWorkQueue
00000022C6EBEB00 0000001db1a91c40 System.Threading.QueueUserWorkItemCallback
00000022C6EBEE18 0000001df19f8320 System.Web.Hosting.IIS7WorkerRequest

这里有很多我们感兴趣的对象,那源头自然要深挖栈底对象 System.Web.Hosting.IIS7WorkerRequest ,使用 !do xxx 命令。

3ee0c17b42180d991819402cee01cbc5.png

然后再挖里面的 _path_httpVerb 字段,继续使用 !do

38d4d4020f92e208bb14e4224ba279d1.png

从图中可以看到,线程的源头是从 POST /Account/Login 请求开始的。

总结

当把调查结果反馈给客户,几分钟后我收到了一封邮件,他们已经定位并解决了这个问题,据说是因为调用了第三方的 dll ,但这个 dll 并没有合理的实现 wait 所致。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 下载安装WindbgWindbg是微软官方提供的调试工具,可以在微软官网上下载。 2. 打开Dump文件:在Windbg菜单栏中选择“File”->“Open Crash Dump”,选择要分析的Dump文件。 3. 加载符号:在菜单栏中选择“File”->“Symbol File Path”,添加符号文件路径,以便Windbg能够正确解析代码。 4. 分析Dump文件:在Windbg中输入“!analyze -v”命令,分析Dump文件。Windbg会输出Dump文件的分析结果,包括错误代码、异常信息等。 5. 查看堆栈:在Windbg中输入“kb”命令,查看当前线程的堆栈信息。堆栈信息可以帮助我们定位到出现问题的代码位置。 6. 查看变量值:在Windbg中输入“dv”命令,查看当前线程中的变量值。通过查看变量值,可以帮助我们了解程序运行状态,定位问题。 7. 分析调用栈:在Windbg中输入“!analyze -v -hang”命令,分析系统卡慢的原因。Windbg会输出系统卡慢的原因,帮助我们找到问题。 8. 分析内存泄漏:在Windbg中输入“!heap -s”命令,分析应用程序中的内存泄漏。Windbg会输出应用程序中的内存泄漏情况,帮助我们优化应用程序。 9. 分析CPU使用率:在Windbg中输入“!runaway”命令,分析CPU使用率。Windbg会输出CPU使用率高的线程信息,帮助我们找到CPU使用率高的原因。 10. 分析网络问题:在Windbg中输入“!netstat”命令,分析网络问题。Windbg会输出网络连接信息,帮助我们找到网络问题的原因。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值