记一次 .NET 某金融企业 WPF 程序卡死分析

一:背景

1. 讲故事

前段时间遇到了一个难度比较高的 dump,经过几个小时的探索,终于给找出来了,在这里做一下整理,希望对大家有所帮助,对自己也是一个总结,好了,老规矩,上 WinDBG 说话。

二:WinDbg 分析

1. 为什么会卡死

既然程序卡死,那肯定是被冻住了,所以看下主线程此时在做什么。

0:000:x86> !clrstack 
OS Thread Id: 0xe20 (0)
Child SP       IP Call Site
0034d5e8 000bc4b8 [HelperMethodFrame_1OBJ: 0034d5e8] System.Threading.SynchronizationContext.WaitHelper(IntPtr[], Boolean, Int32)
0034d88c 73fd7623 System.Windows.Threading.DispatcherSynchronizationContext.Wait(IntPtr[], Boolean, Int32)
0034d8a0 713eab08 System.Threading.SynchronizationContext.InvokeWaitMethodHelper(System.Threading.SynchronizationContext, IntPtr[], Boolean, Int32)
0034dac0 72231396 [GCFrame: 0034dac0] 
0034dc04 72231396 [HelperMethodFrame_1OBJ: 0034dc04] System.Threading.Thread.JoinInternal(Int32)

从代码的 Thread.JoinInternal() 方法看,它正在等待另一个线程,接下来用 !dso 找一下这个 Thread 对象,发现标记的是托管线程 34, 信息如下:

0:000:x86> !DumpObj /d 02bef5c8
Fields:MT    Field   Offset                 Type VT     Attr    Value Name7151f6bc  40018a7       28         System.Int32  1 instance       34 m_ManagedThreadId

接下来切到 34 号线程使用 k 命令看下它正在做什么?

0:038:x86> kb# ChildEBP RetAddr      Args to Child              
00 0ee9ede0 77708dd4     0000003c 00000000 00000000 ntdll_776d0000!NtWaitForSingleObject+0x15
01 0ee9ede0 77708cb8     00000000 00000000 096346b0 ntdll_776d0000!RtlpWaitOnCriticalSection+0x13e
02 0ee9ee08 5da08101     0963c554 0963c4ec 0ee9ee34 ntdll_776d0000!RtlEnterCriticalSection+0x150
03 0ee9ee18 5db16581     0963c554 096346b0 20000000 quartz!CBlockLock<CKsOpmLib>::CBlockLock<CKsOpmLib>+0x14

从输出的 RtlEnterCriticalSection 方法看,它正在等待临界区资源,接下来使用 !cs 看下这个临界区资源到底被谁持有?

0:038:x86> !cs 0963c554
-----------------------------------------
Critical section   = 0x0963c554 (+0x963C554)
DebugInfo          = 0x0e4859e0
LOCKED
LockCount          = 0x1
WaiterWoken        = No
OwningThread       = 0x00000ee4
RecursionCount     = 0x1
LockSemaphore      = 0x3C
SpinCount          = 0x00000000

可以看到,持有这个临界区的线程是 0x00000ee4 ,接下来我们切过去看下这个线程此时正在做什么?

0:038:x86> ~~[0x00000ee4]s
ntdll_776d0000!ZwWaitForMultipleObjects+0x15:
776f014d 83c404          add     esp,40:041:x86> !clrstack 
Child SP       IP Call Site
0f4ff784 0000002b [GCFrame: 0f4ff784] 
0f4ff85c 0000002b [GCFrame: 0f4ff85c] 
0f4ff878 0000002b [HelperMethodFrame_1OBJ: 0f4ff878] System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)
0f4ff8f4 713ea287 System.Threading.Monitor.Enter(System.Object, Boolean ByRef)
...0:041:x86> !dso
OS Thread Id: 0xee4 (41)
ESP/REG  Object   Name
0F4FF7B8 028d9de8 System.Drawing.Bitmap

从输出信息中可以看到, 线程 0x00000ee4 正在 lock 锁上等待, lock 的对象是 Bitmap,接下来的问题是谁正在持有 lock 锁呢?可以使用 !syncblk 观察同步块表即可。

0:041:x86> !syncblk
CLR Version: 4.6.1055.0
SOS Version: 4.8.4300.0
Index         SyncBlock MonitorHeld Recursion Owning Thread Info          SyncBlock Owner
SyncBlock 856 is invalid, continuing...
-----------------------------
Total           1046
CCW             59
RCW             24
ComClassFactory 5
Free            832

从输出中可以看到,此时的 syncblk 已经损坏,也就无法知道当前是哪个线程 lock 了 Bitmap,到这里难度就骤然增大了。。。

问题还得要解决,那怎么办呢?只能试着自己把 syncblk 给恢复出来,入口就是 Bitmap 上的同步块索引。

2. 根据 索引 恢复 同步块表

了解 lock 的朋友应该知道,它在 CLR 层面是 AwareLock ,而这个 锁 就承载了绝大多数的 syncblk 信息。

0:007> dt coreclr!AwareLock+0x000 m_lockState      : AwareLock::LockState+0x004 m_Recursion      : Uint4B+0x008 m_HoldingThread  : Ptr64 Thread+0x010 m_TransientPrecious : Int4B+0x014 m_dwSyncIndex    : Uint4B+0x018 m_SemEvent       : CLREvent+0x028 m_waiterStarvationStartTimeMs : Uint4B

其中:

  1. m_HoldingThread:当前 lock 的持有线程。

  2. m_dwSyncIndex:    当前的同步块索引。

  3. m_SemEvent:       lock 底层的信号量

上面这三个值,其实我是知道两个的,一个可以从 Bitmap 头上获取 m_dwSyncIndex ,一个可以从 kb 命令的 WaitForMultipleObjectsEx 参数中提取 m_SemEvent ,输出如下:

0:041:x86> dp 028d9de8 -0x4 L1
028d9de4  080003580:041:x86> kb# ChildEBP RetAddr      Args to Child              
00 0f4ff568 77250962     00000001 0f4ff51c 00000001 ntdll_776d0000!ZwWaitForMultipleObjects+0x15
01 0f4ff568 75a41a2c     0f4ff51c 0f4ff590 00000000 KERNELBASE!WaitForMultipleObjectsEx+0x1000:041:x86> dp 0f4ff51c L1
0f4ff51c  00000ff8

可以看到 Bitmap 的索引号为 0x358,接下来可以全内存搜索,全称为:80000358 ,记得这里是 80000358 而不是 08000358

0:000:x86> s-d 0 L?0xffffffff 0x80000358
051ed11c  80000358 00000002 80000370 00000003  X.......p.......
051f3fcc  80000358 0000008e 80000370 00000090  X.......p.......
05229980  80000358 80000038 00000005 80000050  X...8.......P...
052b5c00  80000358 80000028 00000006 80000040  X...(.......@...
0531c28c  80000358 00000010 800004f0 00000000  X...............
0535ed54  80000358 0000014d 80000370 000004c5  X...M...p.......
05432f0c  80000358 0000a424 80000370 00000000  X...$...p.......
109e0284  80000358 00000680 80000370 00000730  X.......p...0...
192e6690  80000358 ffffffff 00000000 192e8848  X...........H...
192ee1b4  80000358 00000ff8 0000000d 00000000  X...............
558d209c  80000358 00000067 80000370 00000071  X...g...p...q...
5d791104  80000358 00000012 80000370 00000013  X.......p.......
6d331104  80000358 0000038a 80000370 0000038b  X.......p.......
758a004c  80000358 00000010 800003d0 00000018  X...............

搜出来有很多,但不要慌,根据 AwareLock 的偏移,已知的两个值 80000358 00000ff8 会是有序排列的,所以正确的地址应该是 192ee1b4,现在我们可以向前偏移 0x10 个位置就能找到 AwareLock 的首地址。

0:000:x86> dp 192ee1b4-0x10 L8
192ee1a4  00000003 00000029 192ebf00 00000001
192ee1b4  80000358 00000ff8 0000000d 000000000:007> dt coreclr!AwareLock+0x000 m_lockState      : AwareLock::LockState+0x004 m_Recursion      : Uint4B+0x008 m_HoldingThread  : Ptr64 Thread+0x010 m_TransientPrecious : Int4B+0x014 m_dwSyncIndex    : Uint4B+0x018 m_SemEvent       : CLREvent+0x028 m_waiterStarvationStartTimeMs : Uint4B

再对应刚才的结构,可以看到 192ebf00 其实就是我们要找的 m_HoldingThread 线程,但这个线程只是 CLR Thread 类,接下来再找下它关联的是哪一个托管线程ID。

0:000:x86> dp 192ebf00 L8
192ebf00  722c0002 01039820 00000000 ffffffff
192ebf10  00000000 00000000 00000000 0000000c
0:000:x86> ? 0000000c
Evaluate expression: 12 = 0000000c

终于我们找到了,原来是托管线程ID=12,接下来用 !t 显示出所有线程,观察下到底怎么回事。

0:000:x86> !tID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception0    1  e20 006f0690   2026020 Preemptive  02CCEC04:00000000 006e94f0 0     STA 2    2  e2c 006fe5d8     2b220 Preemptive  02CB6AB0:00000000 006e94f0 0     MTA (Finalizer) 5    5  e68 0971d0f8     2b220 Preemptive  02D091BC:00000000 006e94f0 0     MTA 7    6  e7c 0e379d50   3029220 Preemptive  00000000:00000000 006e94f0 0     MTA (Threadpool Worker) 
XXXX    7    0 0e384a70   1039820 Preemptive  00000000:00000000 006e94f0 0     Ukn (Threadpool Worker) 8    9  e8c 0e376d18   102a220 Preemptive  00000000:00000000 006e94f0 0     MTA (Threadpool Worker) 11   11  ea8 0e3b8250   1020220 Preemptive  00000000:00000000 006e94f0 0     Ukn (Threadpool Worker) 12   15  f4c 0e487a90   202b220 Preemptive  00000000:00000000 006e94f0 0     MTA 13   16  f54 0e488f78   202b220 Preemptive  00000000:00000000 006e94f0 0     MTA

上面的 ID 列就是 托管线程的标号,但很可惜,这个线程已经消失了,而且搜索托管堆上的所有 Thread,都没有这个ID号,说明这个线程已经被 GC 回收掉了。

3. 真相大白

由于代码比较隐私,这里就绘制个模型吧,截图如下:

3fc5e497fc28053e0ddf6f56aba4823b.jpeg

这里有两点信息:

  1. TestEvent 会被 C++ 触发。

  2. lock 中会执行 C++ 逻辑。

当 tid=12 进入了 lock 锁时,由于某种原因, 1 或者 2 处的 C++ 代码执行了类似 Thread.Abort 的逻辑,这就导致 托管ID 和 OS 线程ID 断了联系,后续就被 GC 给回收了,底层逻辑大概就是这样。

三:总结

是不是有点颠覆三观,你认为 lock 能 100% 的实现原子化,其实也不一定,而且还让程序遭受着严重的后果。

在《.NET 高级调试》这本书中也有类似的讲述,感兴趣的朋友可以看一下。

abab1584f5bde1e07649955c3067de1d.jpeg

最后的修复方法就是:不要在 TestEvent 中处理 C++ 逻辑,因为这块处理比较慢,将其提到单独线程中处理,也让 TestEvent 可以快速结束。

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

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

相关文章

如何将图片放到mysql_怎么将图片添加到mysql中

将图片添加到mysql中的方法&#xff1a;首先将数据库存储图片的字段类型设置为blob二进制大对象类型&#xff1b;然后将图片流转化为二进制&#xff1b;最后将图片插入数据库即可。正常的图片储存要么放进本地磁盘&#xff0c;要么就存进数据库。存入本地很简单&#xff0c;现在…

Java线程与Linux内核线程的映射关系

http://blog.sina.com.cn/s/blog_605f5b4f010198b5.html Linux从内核2.6開始使用NPTL &#xff08;Native POSIX Thread Library&#xff09;支持&#xff0c;但这时线程本质上还轻量级进程。Java里的线程是由JVM来管理的。它怎样相应到操作系统的线程是由JVM的实现来确定的。L…

YoursLC 有源 低代码 项目介绍

YoursLC 是我们独立研发的一款低代码产品&#xff0c;YoursLC-yours你们的、LC是低代码low-code的缩写&#xff0c;中文名称&#xff1a;有源低代码&#xff0c; 是一套双输出的低代码产品&#xff1a;既能完整输出功能又能100%输出源码。满足用户高效率、低成本和个性化的需求…

stm32电机控制定时器1_STM32通过PWM控制电机速度

做STM32智能小车的实验中会用到定时器PWM输出&#xff0c;来改变直流电机的转速。分享本文了解如何通过PWM实现对电机速度的控制。PWM控制电机速度的基本原理PWM(Pulse Width Modulation)&#xff0c;也就是脉冲宽度调制。PWM中有一个比较重要的概念&#xff0c;占空比&#xf…

走向无后端的系统开发实践:CRUD自动化与强约定的REST接口

2019独角兽企业重金招聘Python工程师标准>>> ttp://mp.weixin.qq.com/s?__bizMzAwMDU1MTE1OQ&idx1&mid2653548079&sn2377b625db58b2ea93c3ef2d87e4c395 转载于:https://my.oschina.net/yunjie/blog/806130

mysql char varchar 性能_Mysql小细节:varchar与char在性能上的特点

varchar与char的一个主要区别是存储方式的不同varchar 是变长存储占用的存储空间 存储内容实际大小 长度记录位char 是定长存储占用的存储空间 字段声明的宽度存储方式对性能是有影响的例如分别使用 varchar(10) 与 varchar(255) 定义一个字段&#xff0c;实际存储的字符串为…

Dubbo源码解析之Zookeeper连接

2019独角兽企业重金招聘Python工程师标准>>> 注&#xff1a;Dubbo的版本是2.5.7。 图1 RegistryProtocol的export时序图 注册中心有Zookeeper、Redis、Dubbo&#xff0c;分别对应ZookeeperRegistry、RedisRegistry、MulticastRegistry。 连接Dubbo的客户端有俩种&am…

SHELL 脚本小技巧

脚本很简单&#xff0c;直接上功能介绍及脚本&#xff0c;可以做模板使用&#xff1a; 记录日志,记录脚本开始执行时间、结束时间usage 函数&#xff0c;脚本需接参数执行&#xff0c;避免误执行&#xff0c;告诉用户&#xff0c;这个脚本的使用方法加锁&#xff0c;创建锁文件…

WinForm(十)项目框架结构

看到下面的项目结构&#xff0c;是否曾经相识&#xff1f;不要笑&#xff0c;这也是一种项目结构&#xff0c;极简主义。项目结构没有对错&#xff0c;合适就好&#xff0c;但也要有几个要求&#xff0c;至少要做到结构明确&#xff0c;清晰&#xff0c;当然上图的结构清晰&…

mysql索引有哪些了解_Mysql索引(简单了解)

Mysql的存储引擎&#xff0c;可以针对不同的表使用不同的存储引擎MyISAM&#xff1a;插入&#xff0c;查血速度快&#xff0c;但是不支持事物&#xff0c;所以适用于数据仓库&#xff0c;Web等InnoDB&#xff1a;支持事物&#xff0c;所以适合于事物型数据库Memory&#xff1a;…

使用Spring发送带附件的电子邮件(站内和站外传送)

1.使用Spring发送带附件的电子邮件 <?xml version"1.0" encoding"UTF-8"?> <beansxmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xmlns:p"http://www.spr…

vmware 克隆后Linux没有eth网卡只有lo

想试着在虚拟机上搭下主从&#xff0c;&#xff0c;&#xff0c;结果&#xff0c;克隆出来的虚拟机没有网卡。。。只有lo 于是跟着别人的来添加一个网卡 第一步&#xff1a;打开克隆的虚拟机 第二步&#xff1a; 第三步 第四步&#xff1a; 最后&#xff1a; 之后执行reboot重启…

聊一聊 C++ 中的 namespace

一&#xff1a;背景 相信大家在分析 dump 时&#xff0c;经常会看到 WKS 和 SRV 这样的字眼&#xff0c;如下代码所示&#xff1a;00007ffa778a07b8 coreclr!WKS::gc_heap::segment_standby_list 0x0000000000000000 00007ffa778a3870 coreclr!WKS::qpf 0x989680 00007ffa778…

会议会展活动从业技能之会销流程的16个环节

会议营销是透过相对大型活动来实现销售。会议营销是细节营销&#xff0c;一般操作流程有会前、会中、会后三个阶段共计16个环节&#xff0c;每个环节都做到位了&#xff0c;衔接好了&#xff0c;会议营销的最终效果就会得到保证。一般而言&#xff0c;会议营销分为会前营销、会…

haproxy详细介绍

Haproxy是既可以工作在7层也能工作在4层的反代工具.Haproxy的功能:路由HTTP请求到后端服务器,基于cookie作会话绑定.能够将多个请求反代至后端主机完成负载均衡的效果.主服务器失败时能自动切换到备服务器上.接受特殊的端口连接完成服务监控拒绝新连接时不会关闭已经连接的请求…

《ASP.NET Core 6框架揭秘》实例演示[17]:利用IHttpClientFactory工厂来创建HttpClient

在一个采用依赖注入框架的应用中&#xff0c;我们一般不太推荐利用手工创建的HttpClient对象来进行HTTP调用&#xff0c;使用的HttpClient对象最好利用注入的IHttpClientFactory工厂来创建。前者引起的问题&#xff0c;以及后者带来的好处&#xff0c;将通过如下这几个演示程序…

Hadoop部署方式-高可用集群部署(High Availability)

Hadoop部署方式-高可用集群部署(High Availability) 作者&#xff1a;尹正杰 版权声明&#xff1a;原创作品&#xff0c;谢绝转载&#xff01;否则将追究法律责任。 本篇博客的高可用集群是建立在完全分布式基础之上的&#xff0c;详情请参考&#xff1a;https://www.cnblogs.c…

sybase 连接mysql_如何连接到Sybase SQL Anywhere数据库

Sybase SQL Anywhere数据库具有许多非常有用的功能&#xff0c;与竞争对手相比&#xff0c;它具有很高的竞争力。首先&#xff0c;它允许您处理大量数据。其次&#xff0c;它具有很高的生产率&#xff0c;也就是说&#xff0c;可以快速提供大量数据。第三&#xff0c;它需要最少…

【汇编语言】DEBUG的使用

在masm for windows中&#xff0c;需要先生存exe文件&#xff0c;然后再点调试按钮。 常用的命令有&#xff1a; R命令&#xff1a;查看、改变CPU寄存器的内容&#xff1b;如果要修改某个寄存器的内容&#xff0c;可以在r的后面接上空格和寄存器名。如&#xff1a;-r ax&#x…

Leetcode 动态规划 Trapping Rain Water

本文为senlie原创。转载请保留此地址&#xff1a;http://blog.csdn.net/zhengsenlie Trapping Rain Water Total Accepted: 14568 Total Submissions: 50810My SubmissionsGiven n non-negative integers representing an elevation map where the width of each bar is 1, co…