记一次 .NET 某教育系统 异常崩溃分析

一:背景

1. 讲故事

这篇文章起源于 搬砖队大佬 的精彩文章 WinDBg定位asp.net mvc项目异常崩溃源码位置 ,写的非常好,不过美中不足的是通览全文之后,总觉得有那么一点不过瘾,就是没有把当时抛异常前的参数给找出来。。。这一篇我就试着弥补这个遗憾????????????。

为了能够让文章行云流水,我就按照自己的侦察思路吧,首先看一下现状:iis上的应用程序崩溃, catch 不到错误,windows日志中只记录了一个 AccessViolationException 异常,如何分析?

说实话我也是第一次在托管语言 C# 中遇到这种异常,够奇葩,先看看 MSDN 上的解释。

好了,先不管奇葩不奇葩,反正有了一份 dump + AccessViolationException,还是可以挖一挖的,老规矩,上windbg说话。

二:windbg 分析

1. 寻找异常的线程

如果是在 异常崩溃 的时候抓的dump,一般来说这个异常会挂在这个执行线程上,不相信的话,可以看看dump。


0:0:037> !t
ThreadCount:      9
UnstartedThread:  0
BackgroundThread: 9
PendingThread:    0
DeadThread:       0
Hosted Runtime:   noLock  ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception8    1 2188 019da830     28220 Preemptive  10C08398:00000000 01a02bd8 0     Ukn 29    2 36b8 025d7738     2b220 Preemptive  00000000:00000000 01a02bd8 0     MTA (Finalizer) 31    3 1c6c 0260b568   102a220 Preemptive  00000000:00000000 01a02bd8 0     MTA (Threadpool Worker) 32    4 315c 02616678     21220 Preemptive  00000000:00000000 01a02bd8 0     Ukn 34    6 31c0 026180e0   1020220 Preemptive  00000000:00000000 01a02bd8 0     Ukn (Threadpool Worker) 35    7 1274 02618628   1029220 Preemptive  069745A0:00000000 01a02bd8 0     MTA (Threadpool Worker) 37    8 2484 02617108   1029220 Preemptive  0EBFFB18:00000000 01a02bd8 0     MTA (Threadpool Worker) System.AccessViolationException 0ebee9dc38    9 2234 026156a0   1029220 Preemptive  0AAED5CC:00000000 01a02bd8 0     MTA (Threadpool Worker) 39   10 3858 02617b98   1029220 Preemptive  0CB7BEE0:00000000 01a02bd8 0     MTA (Threadpool Worker) 

上面的第37号线程清楚的记录了异常 System.AccessViolationException,后面还跟了一个异常对象的地址 0ebee9dc ,接下来就可以用 !do 给打印出来。


0:0:037> !do 0ebee9dc
Name:        System.AccessViolationException
MethodTable: 6fc1bf4c
EEClass:     6f926bec
Size:        96(0x60) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:MT    Field   Offset                 Type VT     Attr    Value Name
6fc146a4  4000005       10        System.String  0 instance 0ebf02f0 _message
6fc1be98  4000006       14 ...tions.IDictionary  0 instance 00000000 _data
6fc146a4  400000c       2c        System.String  0 instance 0ebfd24c _remoteStackTraceString

这个 Exception 上面有很多的属性,比如最后一行的 _remoteStackTraceString 显示的就是异常堆栈信息,接下来我再给 do 一下。


0:0:037> !do 0ebfd24c
Name:        System.String
MethodTable: 6fc146a4
EEClass:     6f8138f0
Size:        10444(0x28cc) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String:         在 System.Data.Common.UnsafeNativeMethods.ICommandText.Execute(IntPtr pUnkOuter, Guid& riid, tagDBPARAMS pDBParams, IntPtr& pcRowsAffected, Object& ppRowset)在 System.Data.OleDb.OleDbCommand.ExecuteCommandTextForMultpleResults(tagDBPARAMS dbParams, Object& executeResult)在 System.Data.OleDb.OleDbCommand.ExecuteCommandText(Object& executeResult)在 System.Data.OleDb.OleDbCommand.ExecuteCommand(CommandBehavior behavior, Object& executeResult)在 System.Data.OleDb.OleDbCommand.ExecuteReaderInternal(CommandBehavior behavior, String method)在 System.Data.OleDb.OleDbCommand.ExecuteNonQuery()在 xxx.Model.xxx.getOneData(OleDbCommand comm)在 xxx.Model.xxx.getOtherDataSource(List`1 keys, Dictionary`2 data)在 xxx.Controllers.xxxOtherController.Post(JObject json)在 System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] methodParameters)在 System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object[] arguments)在 System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)

我去,原来是执行数据库的时候抛出的 AccessViolationException,哈哈,有点意思,究竟是个什么样的神操作能搞出这个异常?好,接下来我就来挖一下 getOneData() 方法到底干了什么?

2.寻找问题代码 getOneData()

要想找到 getOneData() 的源码,还是老规矩,使用 !name2ee + !savemodule 导出。


0:0:037> !name2ee *!xxx.Model.xxx.getOneData
--------------------------------------
Module:      1b9679c0
Assembly:    xxx.dll
Token:       06000813
MethodDesc:  0149faec
Name:        xxx.Model.xxx.getOneData(System.Data.OleDb.OleDbCommand)
JITTED Code Address: 1ede0050
--------------------------------------0:0:037> !savemodule 1b9679c0 E:\dumps\2.dll
3 ps in file
p 0 - VA=2000, VASize=d8d74, FileAddr=200, FileSize=d8e00
p 1 - VA=dc000, VASize=318, FileAddr=d9000, FileSize=400
p 2 - VA=de000, VASize=c, FileAddr=d9400, FileSize=200

有了 2.dll ,接下来就可以用 ILSPY 看一看源码。

从源码上看也都是一些中规中矩的操作,没啥特别的地方,既然写法上没问题,我也只能怀疑是某些数据方面出了问题,接下来准备挖一挖 OleDbCommand

3. 从线程栈上提取 OleDbCommand 对象

玩过 ADO.NET 的都知道,最后的 sql + parameters 都是藏在 OleDbCommand 上的,参考代码如下:


public sealed class OleDbCommand : DbCommand, ICloneable, IDbCommand, IDisposable
{public override string CommandText { get; set; }public new OleDbParameterCollection Parameters{get{OleDbParameterCollection oleDbParameterCollection = _parameters;if (oleDbParameterCollection == null){oleDbParameterCollection = (_parameters = new OleDbParameterCollection());}return oleDbParameterCollection;}}
}

所以目标很明确,就是把 CommandText + Parameters 给挖出来,说干就干,用 !clrstack -a 提取线程栈上的所有参数,如下图所示:

真是悲剧,由于异常的抛出捣毁了线程调用栈,尼玛,也就是说调用栈上的 局部变量 + 方法参数 都被销毁了,这该如何是好呀?好想哭????????????。

在迷茫了一段时间后,突然灵光一现,对,虽然调用栈被捣毁了,但 OleDbCommand 是引用类型啊,栈地址没了就没了,OleDbCommand 本尊肯定还是在热乎的 gen0 上,毕竟也是刚抛出来的异常,这时候 GC 还在打呼噜,肯定不会回收它的,哈哈,突然又充满能量了。

4. 从托管堆中寻找 OleDbCommand

要想在托管堆上找 OleDbCommand 的话,使用如下命令:!dumpheap -type OleDbCommand 即可。


||0:0:037> !dumpheap -type OleDbCommand Address       MT     Size
02a8393c 6c74a6a8       84     
02bc280c 6c74a6a8       84     
02bd98dc 6c74a6a8       84     
02be1d74 6c74a6a8       84     
02be3c68 6c74a6a8       84     
02be5b3c 6c74a6a8       84     
0696f978 6c74a6a8       84     
0a94ea54 6c74a6a8       84     
0a9678b8 6c74a6a8       84     
0a96a5a0 6c74a6a8       84     
0aabefe4 6c74a6a8       84     
0eb10e08 6c74a6a8       84     Statistics:MT    Count    TotalSize Class Name
6c74a6a8       12         1008 System.Data.OleDb.OleDbCommand
Total 12 objects

还不错,托管堆上只有 12 个 OleDbCommand,说明这程序也是刚起来没溜两圈就挂掉了,接下来要做的事就是逐个排查里面的 Sql + Parameter 是否有异常,用人肉去检查,能把眼睛给弄瞎,所以得把这脏活累活留给 script 去实现,为此我花了一个小时写了一个脚本,都差点写睡着了????????????。


"use strict";function initializeScript() {return [new host.apiVersionSupport(1, 7)];
}function invokeScript() {//获取所有 oledbComamand 对象var output = exec("!dumpheap -type System.Data.OleDb.OleDbCommand -short");for (var line of output) {showOleDb(line);log("------------------------------------------------------------------------");}
}//遍历oledb
function showOleDb(oledb) {log("oledb:       " + oledb);showsql(oledb);showparameters(oledb);
}//show sql
function showsql(oledb) {var command = "!do -nofields poi(" + oledb + "+0x10)";var output = exec(command).Skip(5);for (var line of output) {log(line);}
}//show parameters
function showparameters(oledb) {var address = "poi(poi(poi(" + oledb + "+0x1c)+0x8)+0x4)"var arrlen = "poi(" + address + "+0x4)";var command = "!da -nofields -details " + address;//var str = "";var output = exec(command).Where(k => k.indexOf("[") == 0).Select(k => k.split(' ')[1]).Where(k => k != "null").Select(k => k);for (var line of output) {var name = showparamname(line);var value = showparamvalue(line);log(name + " -> " + value);}
}//show parametername
function showparamname(param) {var command = "!do -nofields poi(" + param + "+0xc)";var output = exec(command);output = output.Skip(5).First().replace("String:      ", "");return output;
}//show paramtervalue
function showparamvalue(param, offset) {//第一步: 判断是否为引用类型var address = "poi(" + param + "+0x14)";var isGtZero = parseInt(exec(".printf \"%d\"," + address).First()) > 0;if (!isGtZero) return "0";var command = "!do -nofields " + address;var output = exec(command);//第二步: 判断是否为 System.DateTimevar isDateTime = output.First().indexOf("System.DateTime") > -1;if (isDateTime) return getFormatDate(address);output = output.Skip(5).First().replace("String:      ", "");return output;
}function getFormatDate(address) {//16hexvar dtstr = ".printf \"%02X%02X\",poi(" + address + "+0x8),poi(" + address + "+0x4);";//10hexvar num = parseInt("0x" + exec(dtstr).First(), 16);var command = "!filetime ((0n" + num + " & 0x3fffffffffffffff) - 0n504911519999995142)";var time = exec(command).First().split("(")[0].trim();return time;
}function log(instr) {host.diagnostics.debugLog("\n" + instr + "\n");
}function exec(str) {return host.namespace.Debugger.Utility.Control.ExecuteCommand(str);
}

简单说一下,上面的 poi 表示取地址上的值,这个值可能是数字,也可能是引用地址,接下来把脚本跑起来, 由于这信息太敏感了,只能虚拟化了哈。


------------------------------------------------------------------------oledb:       0eb10e08String:      update xxx  set a=:a, b=:b, c=:c where info_id = :info_ida -> 'xxx'b -> 'yyy'c -> File:        C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\collegeappxy\e05a2cb1\4405de9e\assembly\dl3\d914f432\c1375f08_c05cd201\Newtonsoft.Json.dllinfo_id -> 1

在 1s 的等待后,终于发现上面这条 sql 的参数化 c 出了问题,因为它是一个 Newtonsoft.Json.dll 的 File,真奇葩,稍微修改一下脚本把这个参数的 address 找出来。


||0:0:037> !do -nofields poi(0eb9ba40+0x14)
Name:        Newtonsoft.Json.Linq.JObject
MethodTable: 1c600d98
EEClass:     1c5f31d0
CCW:         1bbd0020
Size:        68(0x44) bytes
File:        C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\collegeappxy\e05a2cb1\4405de9e\assembly\dl3\d914f432\c1375f08_c05cd201\Newtonsoft.Json.dll

到此基本确定是因为把 JObject 放入了参数化导致了异常的发生,为此我还特意查了下 JObject ,一个挺有意思的玩意,将它 ToString() 之后居然是以格式化方式显示的,如下图所示:

如果想要去掉这种格式化,需要在 ToString() 中配一个 None 枚举,哈哈,就是这么出乎意料 ???????????? 。

三:总结

总的来说,我觉得这是 OleDbCommand 的一个bug,既然是做参数化,就算我把 ???? 投下去了,你也要给我正确入库,不是嘛?其次从分析结果看,知道了这种异常的调用堆栈,解决起来也是非常容易的,使用日志记录下当时的 OleDbCommand 就可以了,使用 script 暴力搜索那也是万不得已的事情????????????, 最后感谢 搬砖队大佬 的精彩文章和dump。

END

工作中的你,是否已遇到 ... 

1. CPU爆高

2. 内存暴涨

3. 资源泄漏

4. 崩溃死锁

5. 程序呆滞

等紧急事件,全公司都指望着你能解决...  危难时刻才能展现你的技术价值,作为专注于.NET高级调试的技术博主,欢迎微信搜索: 一线码农聊技术,免费协助你分析Dump文件,希望我能将你的踩坑经验分享给更多的人。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/302735.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Json.Net系列教程 3.Json.Net序列化和反序列化设置

上节补充首先补充一点,Json.Net是支持序列化和反序列化DataTable,DataSet,Entity Framework和NHibernate的.我举例说明DataTable的序列化和反序列化.创建一个DataTable对象,如下: DataTable dt new DataTable();DataColumn dcName new DataColumn("Name");DataColu…

5道谷歌面试题:即使是天才也要怀疑自己能力了(附答案)

全世界只有3.14 % 的人关注了数据与算法之美谷歌&#xff0c;美国的跨国科技企业&#xff0c;致力于互联网搜索、云计算、广告技术等领域&#xff0c;开发并提供大量基于互联网的产品与服务。这样一家实力雄厚前景无量的公司是众多求职者梦寐以求的地方&#xff0c;然而&#x…

[Abp vNext 源码分析] - 18. 单元测试

简介ABP vNext 框架使用 xUnit 作为单元测试组件&#xff0c;官方的所有模块都编写了大量的 单元/集成测试 确保功能正常。由于 ABP vNext 模块化系统的原因&#xff0c;开发人员在建立单元测试项目的时候需要集成 Volo.Abp.UnitTest 项目&#xff0c;这样在执行单元测试的时候…

php表格单元格怎么实现排序,javascript实现对表格元素进行排序操作

我们在上网中都能看到很多能够排序的&#xff0c;如大小、时间、价格等现在我们也试一下排序功能&#xff1a;排序的函数代码&#xff1a;里面含有点击之后排序--还原&#xff0c;和排升序和降序。function sortAge(){//对年龄进行排序&#xff0c;要先进行获得每一行对象&…

【10.29周一电商,已好】中国日历的至高境界,377张震撼级插画,美到爆!

每段时光都有属于每段时光的回忆它们是童年的纸飞机是校园时代的试卷与课本是第一次离开家乡时的兴奋与忐忑是跟某个人眼神交汇时的慌乱...回忆如此珍贵&#xff0c;以致于令我们频频回想&#xff0c;渴望着回到过去&#xff0c;与美好再度相逢。还记得文先生给大家推荐过的新一…

设置润乾报表鼠标移到格子上就显示提示内容

为了达到一定的交互性和易用性,我们一般喜欢将鼠标移动到格子上就能显示出一定的提示信息,比方说这个格子大小固定了但是里面内容超出格子了,这样我们希望鼠标移动过去后能自动提示所有的内容。用润乾报表可以这样设计&#xff1a; 比方说一个格子里面有如下内容 “这是一个很长…

ASP.NET Core 单元测试:如何Mock Url.Page()

点击上方蓝字关注“汪宇杰博客”导语在 ASP.NET Core 中&#xff0c;当你在 UrlHelperExtensions 类上使用扩展方法时&#xff0c;很难在单元测试中编写Mock。因为Moq框架不支持模拟扩展方法。问题例如&#xff0c;我的博客代码中使用了 Url.Page() 方法&#xff1a;var callba…

“甜橙金融杯”数据建模大赛发布,8万重金寻找大数据金融人才!

全世界有3.14 % 的人已经关注了数据与算法之美随着互联网概念不断发展&#xff0c;越来越多的商家进入这一市场。为了在竞争中拉取新用户&#xff0c;培养用户的消费习惯&#xff0c;各种类型的营销和补贴活动层出不穷。为正常用户带来福利的同时&#xff0c;也催生了一批“羊毛…

常用加解密工具集合|视频图片加解密方案

最近工作需要做视频加密解密&#xff0c;大概需求就是摄像头录制好的视频实时加密存储到本地&#xff0c;防止别人拔掉存储卡把视频拷贝走。大胆设想一下&#xff0c;假如现在很多网约车车内都有摄像头&#xff0c;这些对着乘客和司机的车内摄像头都是实时录制视频并存储到本地…

修炼九阴真经Windows Phone开发 (7):本地化应用程序栏Localizing an Application Bar 下...

本节介绍另一个本地化的方法&#xff1a; 在项目中添加资源文件&#xff1a;&#xff08;这个文件将包含应用程序的默认语言的资源&#xff09; 将要名称和值添加进去。&#xff08;作为应用程序中向用户显示字符串值&#xff09;. 重复上面的方法&#xff0c;向项目中添加更多…

统治世界的十大算法

全世界有3.14 % 的人已经关注了数据与算法之美软件正在统治世界。而软件的核心则是算法。算法千千万万&#xff0c;又有哪些算法属于“皇冠上的珍珠”呢&#xff1f;Marcos Otero 给出了他的看法。什么是算法&#xff1f;通俗而言&#xff0c;算法是一个定义明确的计算过程&…

Hosting in .NET Core

在.NET Core中&#xff0c;Host负责应用程序的启动和生命周期管理。除此之外&#xff0c;在Host中还可以设置日志(Logging)、配置(Configuration)和依赖关系注入(Dependency Injection)等。Host将一个常规的控制台应用程序(Console Application)变成了一个可以长时间运行的服务…

如何用大数据找女朋友?

全世界有3.14 % 的人已经关注了数据与算法之美导读找女朋友不仅需要好眼力&#xff0c;还需要一些技术含量。比如眼下正热的大数据&#xff0c;可以认真钻研&#xff0c;用数据分析来实现自己的“脱单计划”。小猿25岁&#xff0c;单身男&#xff0c;热衷大数据&#xff0c;并决…

ASP.NET Core 单元测试:如何 Mock HttpContext.Features.Get()

点击上方蓝字关注“汪宇杰博客”导语在 ASP.NET Core 里&#xff0c;如果你想单元测试 HttpContext.Features.Get<SomeType>()&#xff0c;这个技巧一定不要错过。问题我有个 Error 页面&#xff0c;需要取得异常的详细信息。我使用 HttpContext.Features.Get<IExcept…

mini api

大部分主流语言都支持web框架&#xff0c;并且实现起来相对轻便&#xff0c;简捷&#xff0c;比如&#xff1a;go的gin包package main import "github.com/gin-gonic/gin" func main() {r : gin.Default()r.GET("/ping", func(c *gin.Context) {c.JSON(200…

edge robert matlab,哪位熟悉matlab的大神路过瞄一眼哈

cxfx(believe truth believe me)UID240430帖子100精华积分1755蛋蛋币1755 枚威望0BT积分0阅读权限60性别男在线时间125 小时注册时间2013-3-27鸵鸟蛋主楼大中小发表于 2013-5-13 21:30 只看该作者哪位熟悉matlab的大神路过瞄一眼哈求大神指点迷津那&#xff01;谁来帮着看一下这…

php 图片 3d旋转图片,html5实现图片的3D旋转效果

我们先来看一下实现效果&#xff1a;(学习视频分享&#xff1a;html视频教程)H5旋转3D相册&#xff0c;鼠标放置暂停&#xff0c;图片灰度级为0&#xff0c;有放大效果。该实例运用H5和CSS3动画效果&#xff0c;未用javascript。提高了本人对CSS3 新属性的了解及掌握。完整代码…

数据这么多,且看R语言怎么处理!

随着科技的不断进步&#xff0c;数据处理量的不断增大&#xff0c;对数据进行处理、分析、统计建模、数据挖掘以及可视化的重要性日渐突出。如果说有一门简单易学、通俗易懂并且集上述功能为一体的编程语言让科研人员从中解脱出来&#xff0c;R语言当仁不让。作为一种统计分析软…

乘风破浪,.Net Core遇见Dapr,为云原生而生的分布式应用运行时

Dapr是一个由微软主导的云原生开源项目&#xff0c;国内云计算巨头阿里云也积极参与其中&#xff0c;2019年10月首次发布&#xff0c;到今年2月正式发布V1.0版本。在不到一年半的时间内&#xff0c;github star数达到了1.2万&#xff0c;超过同期的kubernetes、istio、knative等…

催人泪下!一个程序员的悲惨故事

全世界有3.14 % 的人已经关注了数据与算法之美编辑&#xff1a;大数据二狗如果你喜欢这篇文章&#xff0c;就把它发给朋友看吧~精品课程推荐&#xff1a;选购数学科普正版读物严选“数学思维好物”送给孩子的益智礼物 | 办公室神器算法工程师成长阅读 | 居家高科技理工…