记一次 .NET 某工控视觉系统 卡死分析

这篇具有很好参考价值的文章主要介绍了记一次 .NET 某工控视觉系统 卡死分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一:背景

1. 讲故事

前段时间有位朋友找到我,说他们的工业视觉软件僵死了,让我帮忙看下到底是什么情况,哈哈,其实卡死的问题相对好定位,无非就是看主线程栈嘛,然后就是具体问题具体分析,当然难度大小就看运气了。

前几天看一篇文章说现在的 .NET程序员 不需要学习WinDbg ,理由就是有很多好的分析工具诸如 VS,DnSpy,PerfView 可以替代,我也只能笑笑,在他们的认知中可能 .NET程序 是不需要和其他语言交互而独成一体的。

话不多说,回到主题,上 WinDbg 说话。

二:为什么会卡死

1. 主线程在做什么

刚才也说到了,卡死是比较好定位的,切到主线程看线程栈即可,简化输出如下:


0:000> ~0s;k
ntdll!NtDelayExecution+0x14:
00007ffc`7d45fcf4 c3              ret
 # Child-SP          RetAddr               Call Site
00 00000000`007fd628 00007ffc`79a15631     ntdll!NtDelayExecution+0x14
01 00000000`007fd630 00007ffc`40b7b116     KERNELBASE!SleepEx+0xa1
02 00000000`007fd6d0 00007ffc`40b7372e     cogxstd+0x13b116
03 00000000`007fd700 00007ffc`40b73ece     cogxstd+0x13372e
...
09 00000000`007fd9b0 00007ffc`7d1c77e3     CogDisplay!DllUnregisterServer+0x1833f
0a 00000000`007fdab0 00007ffc`7d16436c     rpcrt4!Invoke+0x73
0b 00000000`007fdb00 00007ffc`7cdbc473     rpcrt4!NdrStubCall2+0x42c
0c 00000000`007fe130 00007ffc`7c451bf0     combase!CStdStubBuffer_Invoke+0x73 [onecore\com\combase\ndr\ndrole\stub.cxx @ 1446] 
...
11 00000000`007fe230 00007ffc`7cdc2df6     combase!DefaultStubInvoke+0x1c4 [onecore\com\combase\dcomrem\channelb.cxx @ 1769] 
12 (Inline Function) --------`--------     combase!SyncStubCall::Invoke+0x22 [onecore\com\combase\dcomrem\channelb.cxx @ 1826] 
13 00000000`007fe380 00007ffc`7cd62e55     combase!SyncServerCall::StubInvoke+0x26 [onecore\com\combase\dcomrem\servercall.hpp @ 825] 
14 (Inline Function) --------`--------     combase!StubInvoke+0x265 [onecore\com\combase\dcomrem\channelb.cxx @ 2052] 
15 00000000`007fe3c0 00007ffc`7cd8ded2     combase!ServerCall::ContextInvoke+0x435 [onecore\com\combase\dcomrem\ctxchnl.cxx @ 1532] 
...
31 00000000`007fff60 00000000`00000000     ntdll!RtlUserThreadStart+0x21

从卦中看当前主线程正在 Sleep,这就很奇葩了,并且还是康耐视的 cogxstd 动态链接库的逻辑,这里我敢相信它不会有这么低级的错误,接下来我们洞察下到底 Sleep 了多久,仔细观察汇编代码,精简后如下:


    ntdll!NtDelayExecution:
00007ffc`7d45fce0 4c8bd1           mov     r10, rcx
00007ffc`7d45fce3 b834000000       mov     eax, 34h
00007ffc`7d45fce8 f604250803fe7f01 test    byte ptr [7FFE0308h], 1
00007ffc`7d45fcf0 7503             jne     ntdll!NtDelayExecution+0x15 (7ffc7d45fcf5)
00007ffc`7d45fcf2 0f05             syscall 
00007ffc`7d45fcf4 c3               ret     
00007ffc`7d45fcf5 cd2e             int     2Eh
00007ffc`7d45fcf7 c3               ret     
00007ffc`7d45fcf8 0f1f840000000000 nop     dword ptr [rax+rax]

    KERNELBASE!SleepEx:
00007ffc`79a15590 89542410         mov     dword ptr [rsp+10h], edx
00007ffc`79a15594 4c8bdc           mov     r11, rsp
00007ffc`79a15597 53               push    rbx
00007ffc`79a15598 56               push    rsi
00007ffc`79a15599 57               push    rdi
00007ffc`79a1559a 4881ec80000000   sub     rsp, 80h
00007ffc`79a155a1 8bda             mov     ebx, edx
00007ffc`79a155a3 8bf9             mov     edi, ecx
...
00007ffc`79a155f4 488b9424b8000000 mov     rdx, qword ptr [rsp+0B8h]
00007ffc`79a155fc 85db             test    ebx, ebx
00007ffc`79a155fe 0f8592000000     jne     KERNELBASE!SleepEx+0x106 (7ffc79a15696)
00007ffc`79a15604 83ffff           cmp     edi, 0FFFFFFFFh
00007ffc`79a15607 7443             je      KERNELBASE!SleepEx+0xbc (7ffc79a1564c)
00007ffc`79a15609 4869cf10270000   imul    rcx, rdi, 2710h
00007ffc`79a15610 48894c2420       mov     qword ptr [rsp+20h], rcx
00007ffc`79a15615 48f7d9           neg     rcx
...
00007ffc`79a15622 488d542420       lea     rdx, [rsp+20h]
00007ffc`79a15627 0fb6cb           movzx   ecx, bl
00007ffc`79a1562a 48ff15ef641400   call    qword ptr [KERNELBASE!__imp_NtDelayExecution (7ffc79b5bb20)]

再上一段 reactos 的 C++ 方法签名。


DWORD
WINAPI
SleepEx(IN DWORD dwMilliseconds,
        IN BOOL bAlertable)
{}

NTSTATUS
NTAPI
NtDelayExecution(IN BOOLEAN Alertable,
                 IN PLARGE_INTEGER DelayInterval)
{}

我们要重点观察 NtDelayExecution 方法中 rdx 参数是怎么计算的,重点就是下面的两句汇编。


imul    rcx, rdi, 2710h
neg     rcx

这两句汇编是什么意思呢? 转成 C++ 代码就是


interval = - (milliseconds * 0x2710);

在汇编中我们是知道 interval 的,它相当于是 milliseconds 计算后的补码,即下面的 Binary: 列。


0:000> r
rax=0000000000000034 rbx=0000000000000000 rcx=0000000000000000
rdx=00000000007fd650 rsi=0000000000000000 rdi=0000000000000001
rip=00007ffc7d45fcf4 rsp=00000000007fd628 rbp=00000000bf1efcf8
 r8=00000000007fd628  r9=00000000bf1efcf8 r10=0000000000000000
r11=0000000000000246 r12=0000000000000000 r13=0000000000000798
r14=000000003bd064b0 r15=00000000bf1efce0

0:000> dp 00000000007fd650 L1
00000000`007fd650  ffffffff`ffffd8f0

0:000> .formats ffffffff`ffffd8f0
Evaluate expression:
  Hex:     ffffffff`ffffd8f0
  Binary:  11111111 11111111 11111111 11111111 11111111 11111111 11011000 11110000
  ...

那怎么求 milliseconds 呢? 其实 补码的补码 就是原码,然后再除以 0x2710 就可以获取到 milliseconds 了哈。

  • 补码:11111111 11111111 11111111 11111111 11111111 11111111 11011000 11110000
  • 反码:00000000 00000000 00000000 00000000 00000000 00000000 00100111 00001111
  • 补补:00000000 00000000 00000000 00000000 00000000 00000000 00100111 00010000

0:000> .formats 0y0000000000000000000000000000000000000000000000000010011100010000
Evaluate expression:
  Hex:     00000000`00002710
  Decimal: 10000
  Decimal (unsigned) : 10000
  Octal:   0000000000000000023420
  Binary:  00000000 00000000 00000000 00000000 00000000 00000000 00100111 00010000

0:000> ? 00002710/ 2710
Evaluate expression: 1 = 00000000`00000001

从卦中看当前也就暂停了 1ms,如果想验证对不对的话,仔细看mov edi, ecx 会发现做了一次备份,但不管怎么说 Thread.Sleep(1) 应该问题不大,那问题在哪里呢?

2. 问题到底在哪里

既然问题不在 Sleep(1) 上那到底在哪里呢?仔细观察线程栈会发现底层做了一个 RPC 通讯,从 combase!SyncServerCall::StubInvokerpcrt4!NdrStubCall2 方法来看,它是 RPC 的 Server 端,既然是 Server 端就必然有 Client 端,根据经验这个 RPC 应该是 命令管道 的方式,没开 Windows 的RPC诊断所以不能100%确认。

接下来看下其他线程有没有 RPC 的 rpcrt4!NdrpClientCall 请求,抱着试试看的态度搜一搜,我去,还真有10几个,截图如下:

记一次 .NET 某工控视觉系统 卡死分析

仔细分析这 12 个 Reqeust,发现其中的 Cognex.VisionPro.Display.CogDisplay.set_Image 比较可疑,毕竟 Image 运作起来肯定是费时费力的。


0:543> k
 # Child-SP          RetAddr               Call Site
00 00000000`fc65def8 00007ffc`79a1c2ce     ntdll!NtWaitForMultipleObjects+0x14
...
04 (Inline Function) --------`--------     combase!CSyncClientCall::SwitchAptAndDispatchCall+0x34a
05 00000000`fc65e290 00007ffc`7cd9b015     combase!CSyncClientCall::SendReceive2+0x42c
06 (Inline Function) --------`--------     combase!SyncClientCallRetryContext::SendReceiveWithRetry+0x25 
07 (Inline Function) --------`--------     combase!CSyncClientCall::SendReceiveInRetryContext+0x25 
08 00000000`fc65e480 00007ffc`7cd8c55d     combase!DefaultSendReceive+0x65
09 00000000`fc65e4e0 00007ffc`7cd60a54     combase!CSyncClientCall::SendReceive+0x12d 
0a 00000000`fc65e710 00007ffc`7cdbc54e     combase!CClientChannel::SendReceive+0x84 
0b 00000000`fc65e780 00007ffc`7d151e93     combase!NdrExtpProxySendReceive+0x4e 
0c 00000000`fc65e7b0 00007ffc`7cdbae17     rpcrt4!NdrpClientCall2+0x463
0d 00000000`fc65edf0 00007ffc`7ce2ce92     combase!ObjectStublessClient+0x1d7 
0e 00000000`fc65f180 00007ffb`f1321db8     combase!ObjectStubless+0x42
0f 00000000`fc65f1d0 00007ffc`4002c906     0x00007ffb`f1321db8
10 00000000`fc65f2c0 00007ffb`f131d541     Cognex_VisionPro_Display_Controls_ni!Cognex.VisionPro.Display.CogDisplay.set_Image+0xb6

0:543> !clrstack
OS Thread Id: 0x2bbc (543)
        Child SP               IP Call Site
...
00000000fc65f208 00007ffbf1321db8 [InlinedCallFrame: 00000000fc65f208] Cognex.VisionPro.Interop.CogDisplayClass.set_Image(Cognex.VisionPro.Interop.ICogImage)
00000000fc65f1d0 00007ffbf1321db8 DomainBoundILStubClass.IL_STUB_CLRtoCOM(Cognex.VisionPro.Interop.ICogImage)
00000000fc65f2c0 00007ffc4002c906 Cognex.VisionPro.Display.CogDisplay.set_Image(Cognex.VisionPro.ICogImage)
00000000fc65f310 00007ffbf131d541 xxxx.SetDefaultRecord()
...
00000000fc65f680 00007ffc4bc17e46 System.Threading.ThreadPoolWorkQueue.Dispatch()
00000000fc65fb20 00007ffc4d706c93 [DebuggerU2MCatchHandlerFrame: 00000000fc65fb20] 

根据卦中的托管方法 xxxx.SetDefaultRecord() ,让朋友不要做 Image 赋值观察下效果,朋友反馈说,这个 Image 不赋值问题就没有了。

既然去掉就好了,到这里只能推测当前主线程不是卡死,而是 RPC 请求过多Size过大,导致主线程一直忙碌中,具体为什么会忙碌,这就需要逆向 cogxstd 来滤清业务逻辑了,这个就太费时费力了,还是先绕过去为好。

三:总结

还是回到文章开头的那句话,这种 dump 问题,你能用 DnSpy,VS 调试出来吗?说实话很难,虽然以 .NET 程序为出口,但考察了你很多基础知识,诸如 RPC,COM,汇编,没有这些基础沉淀,这类dump很难摸清来龙去脉。文章来源地址https://www.toymoban.com/news/detail-518207.html

记一次 .NET 某工控视觉系统 卡死分析

到了这里,关于记一次 .NET 某工控视觉系统 卡死分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 记一次 .NET某工控 宇宙射线 导致程序崩溃分析

    为什么要提 宇宙射线 , 太阳耀斑 导致的程序崩溃呢?主要是昨天在知乎上看了这篇文章:莫非我遇到了传说中的bug? ,由于 rip 中的0x41变成了0x61出现了bit位翻转导致程序崩溃,截图如下: 下面的评论大多是说由于 宇宙射线 ,这个太玄乎了,说实话看到这个 传说bug 的提法

    2024年02月04日
    浏览(29)
  • 记一次 .NET 某医院门诊软件 卡死分析

    前几天有位朋友找到我,说他们的软件在客户那边卡死了,让我帮忙看下是怎么回事?我就让朋友在程序卡死的时候通过 任务管理器 抓一个 dump 下来,虽然默认抓的是 wow64 ,不过用 soswow64.dll 转还是可以的,参考命令如下: 接下来就可以分析了哈。 首先用 !t 简单看一下主

    2024年02月04日
    浏览(35)
  • 记一次 .NET 某拍摄监控软件 卡死分析

    今天本来想写一篇 非托管泄露 的生产事故分析,但想着昨天就上了一篇非托管文章,连着写也没什么意思,换个口味吧,刚好前些天有位朋友也找到我,说他们的拍摄监控软件卡死了,让我帮忙分析下为什么会卡死,听到这种软件,让我不禁想起了前些天 在程序员桌子上安

    2024年02月08日
    浏览(29)
  • 记一次 .NET某MES自动化桌面程序 卡死分析

    前些天有位朋友在微信上找到我,说他们的客户端程序卡死了,让我帮忙看下是什么原因导致的?dump也拿到了手,既然有了dump就开始正式分析吧。 客户端的程序卡死比较好找原因,入手点就是主线程,看下它此时正在做什么,可以用 k 命令。 从卦中信息看,代码正在托管层

    2024年01月16日
    浏览(32)
  • 记一次 .NET 某企业内部系统 崩溃分析

    前些天有位朋友找到我,说他的程序跑着跑着就崩溃了,让我看下怎么回事,其实没怎么回事,抓它的 crash dump 就好,具体怎么抓也是被问到的一个高频问题,这里再补一下链接: [.NET程序崩溃了怎么抓 Dump ? 我总结了三种方案] https://www.cnblogs.com/huangxincheng/p/14811953.html ,采用

    2024年02月10日
    浏览(38)
  • 记一次 .NET某防伪验证系统 崩溃分析

    昨晚给训练营里面的一位朋友分析了一个程序崩溃的故障,因为看小伙子昨天在群里问了一天也没搞定,干脆自己亲自上阵吧,抓取的dump也是我极力推荐的用 procdump 注册 AEDebug 的方式,省去了很多沟通成本。 windbg有一个非常强大的点就是当你双击打开后,会自动帮你切换到

    2024年03月28日
    浏览(50)
  • 记一次 Visual Studio 2022 卡死分析

    最近不知道咋了,各种程序有问题都寻上我了,你说 .NET 程序有问题找我能理解,Windows 崩溃找我,我也可以试试看,毕竟对 Windows 内核也知道一丢丢,那 Visual Studio 有问题找我就说不过去了,但又不好拒绝,就让朋友发下卡死的 dump 我看一看。 因为 VS 是窗体程序,所以在卡

    2024年02月05日
    浏览(40)
  • 记一次 .NET某列控连锁系统 崩溃分析

    过年喝了不少酒,脑子不灵光了,停了将近一个月没写博客,今天就当新年开工写一篇吧。 去年年初有位朋友找到我,说他们的系统会偶发性崩溃,在网上也发了不少帖子求助,没找到自己满意的答案,让我看看有没有什么线索,看样子这是一个牛皮藓的问题,既然对方有了

    2024年02月21日
    浏览(41)
  • 记一次 .NET 某电力系统 内存暴涨分析

    前些天有位朋友找到我,说他生产上的程序有内存暴涨情况,让我帮忙看下怎么回事,最简单粗暴的方法就是让朋友在内存暴涨的时候抓一个dump下来,看一看大概就知道咋回事了。 这个问题说的再多也不为过,一定要看清楚这个程序是如何个性化发展的,可以使用 !address

    2024年02月08日
    浏览(34)
  • 记一次 .NET 某埋线管理系统 崩溃分析

    经常有朋友跟我反馈,说看你的文章就像看天书一样,有没有一些简单入手的dump 让我们先找找感觉,哈哈,今天就给大家带来一篇入门级的案例,这里的入门是从 WinDbg 的角度来阐述的,这个问题如果你通过 记日志,分析代码 的方式,可能真的无法解决,不信的话继续往下

    2024年02月11日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包