當前位置: 妍妍網 > 碼農

記一次 .NET某工控視覺自動化系統 卡死分析

2024-06-07碼農

一:背景

1. 講故事

今天分享的dump是訓練營裏一位學員的,從一個啥也不會到現在分析的有模有樣,真的是看他成長起來的,偵錯技術學會了就是真真實實自己的,話不多說,上windbg說話。

二:WinDbg 分析

1. 為什麽會卡死

這位學員是從事工控大類下的視覺自動化,也是目前.NET的主戰場,這個場景下大多都是WPF或者WinForm程式,不管是什麽程式,先用命令 k 開路。


0:000> ~0s
ntdll!NtWaitForMultipleObjects+0x14:
00007ff8`d825cc14 c3 ret
0:000> k
# Child-SP RetAddr Call Site
00000000e4`c0cf87e8 00007ff8`d54f7ff7 ntdll!NtWaitForMultipleObjects+0x14
01000000e4`c0cf87f0 00007ff8`83aa7585 KERNELBASE!WaitForMultipleObjectsEx+0x107
02000000e4`c0cf8af0 00007ff8`83aa76fa PylonBase_v5_1!Pylon::CInstantCameraArray::DestroyInstantCamera+0x78da1
03000000e4`c0cf8bb0 00007ff8`83a188bc PylonBase_v5_1!Pylon::CInstantCameraArray::DestroyInstantCamera+0x78f16
04000000e4`c0cf8c30 00007ff8`83a22a70 PylonBase_v5_1!Pylon::CGrabResultPtr::IsUnique+0x16ec
05000000e4`c0cf8cd0 00007ff8`41fee2dd PylonBase_v5_1!Pylon::CGrabResultPtr::IsUnique+0xb8a0
06000000e4`c0cf8d40 00007ff8`4218711b0x00007ff8`41fee2dd
...

從卦象看真的很不吉利,因為這個等待是一個第三方的SDK庫,從 DestroyInstantCamera 名字看就是 立即銷毀相機 ,接下來我們看下 PylonBase_v5_1 是何方聖神?


0:000> lmvm PylonBase_v5_1
Browse full module list
start end module name
00007ff8`839e000000007ff8`83b5b000 PylonBase_v5_1 (export symbols) PylonBase_v5_1.dll
Loaded symbol image file: PylonBase_v5_1.dll
Image path: C:\Program Files\Basler\pylon 5\Runtime\x64\PylonBase_v5_1.dll
Image name: PylonBase_v5_1.dll
Browse all global symbols functions data
Timestamp: Fri Aug 24 20:41:55 2018 (5B7FFD13)
CheckSum: 0017E66C
ImageSize: 0017B000
File version: 5.1.0.12681
Product version: 5.1.0.12681
File flags: 0 (Mask 3F)
File OS: 40004 NT Win32
File type: 2.0 Dll
File date: 00000000.00000000
Translations: 0000.04b0
Information from resource tables:
CompanyName: Basler
ProductName: Basler pylon
InternalName: PylonBase
OriginalFilename: PylonBase.dll
ProductVersion: 5.1.0.12681 
FileVersion: 5.1.0.12681
PrivateBuild:
SpecialBuild: 0
FileDescription: PylonBase Module
LegalCopyright: Copyright (c) 2006-2018 Basler AG - All rights reserved.
LegalTrademarks:
Comments: 1d4ccf9b36037580c4655fde004335702d90d3e8

由於我是行外人,所以我好奇的查一下 Basler 是什麽公司,🐂👃哈,截圖如下:

再回過頭來看,為什麽會在這裏被卡呢?這個庫是商業產品沒有pdb的,看組譯很難推進,索性就從執行緒棧中逆向推測,即從 NtWaitForMultipleObjects 方法入手。

2. 如何解讀 NtWaitForMultipleObjects

這個方法是微軟公開的方法,和C#的 Task.WaitAny 功能等價,在 MSDN 上可以看到它的簽名資訊。


DWORD WaitForMultipleObjects(
[in] DWORD nCount,
[inconst HANDLE *lpHandles,
[in] BOOL bWaitAll,
[in] DWORD dwMilliseconds
)
;

接下來就是提取 lpHandles 中的 handle 值,看下這個 handle 到底是什麽型別,再圖後續方向,根據x64呼叫協定,只需要提取 rdx 參數即可。


0:000> r
rax=000000000000005b rbx=0000000000000001 rcx=0000000000000001
rdx=000000e4c0cf8b58 rsi=0000000000000000 rdi=0000000000000001
rip=00007ff8d825cc14 rsp=000000e4c0cf87e8 rbp=000000e4c0cf8c00
 r8=000002aedcc62701 r9=ffffffffffffffe8 r10=000002ae86a12580
r11=000000e4c0cf8680 r12=00000000fffffffe r13=000000e4c0cf8b58
r14=000000e4c0cf8840 r15=0000000000000000
iopl=0 nv up ei pl zr na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
ntdll!NtWaitForMultipleObjects+0x14:
00007ff8`d825cc14 c3 ret
0:000> !handle poi(000000e4c0cf8b58) f
Handle 0000000000001790
Type Thread
Attributes 0
GrantedAccess 0x1fffff:
Delete,ReadControl,WriteDac,WriteOwner,Synch
Terminate,Suspend,Alert,GetContext,SetContext,SetInfo,QueryInfo,SetToken,Impersonate,DirectImpersonate
HandleCount 6
PointerCount 201480
Name <none>
Object specific information
Thread Id 4714.ff4
Priority 10
Base Priority 0

從卦中數據看,居然是一個執行緒控制代碼,而且資訊 Thread Id 4714.ff4 也標出來了,真tmd的是 山重水復疑無路,柳暗花明又一村 。。。

3. ff4號執行緒正在做什麽

有了線索之後,後面就是順藤摸瓜了,先切到 ff4 號執行緒。


0:137> !clrstack
OS Thread Id: 0xff4 (137)
Child SP IP Call Site
000000e4c53fd418 00007ff8d825cc14 [GCFrame: 000000e4c53fd418] 
000000e4c53fd610 00007ff8d825cc14 [GCFrame: 000000e4c53fd610] 
000000e4c53fd668 00007ff8d825cc14 [HelperMethodFrame: 000000e4c53fd668] System.Threading.Monitor.Enter(System.Object)
000000e4c53fd760 00007ff84218d827 Basler.xxx.OnImageGrabbed(System.Object, Basler.Pylon.ImageGrabbedEventArgs)
...

從卦象看,這個執行緒正在 lock鎖上等待,那這個lock鎖被誰持有著呢?這個就比較簡單了,檢視下同步塊索引即可。


0:137> !syncblk
Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner
321000002ae81012218 71000002ae83701520 2e7457000002aedea354e8 System.Object

卦中的 2e74號執行緒正是持有鎖,接下來就是探究下 2e74號執行緒此時正在幹什麽?

4. 2e74號執行緒正在幹什麽

一路摸瓜,有種預感馬上就能看到光了,切到這個執行緒繼續觀察。


0:057> !clrstack
OS Thread Id: 0x2e74 (57)
Child SP IP Call Site
000000e4c4efcdb8 00007ff8d825cc14 [HelperMethodFrame_1OBJ: 000000e4c4efcdb8] System.Threading.WaitHandle.WaitOneNative(System.Runtime.InteropServices.SafeHandle, UInt32, Boolean, Boolean)
000000e4c4efcee0 00007ff89ed99ccc System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle, Int64, Boolean, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\waithandle.cs @ 243]
000000e4c4efcf10 00007ff89ed99c9f System.Threading.WaitHandle.WaitOne(Int32, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\waithandle.cs @ 194]
000000e4c4efcf50 00007ff89af8ac84 System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle)
000000e4c4efcfc0 00007ff89a7f3264 System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control, System.Delegate, System.Object[], Boolean)
000000e4c4efd100 00007ff89af8e4a4 System.Windows.Forms.Control.Invoke(System.Delegate, System.Object[])
000000e4c4efd170 00007ff84219c223 xxx.MCamera_RetrieveImageCompleted1(System.Object, System.EventArgs)
...

從卦中看這個執行緒正在用 Invoke 給主執行緒的 Queue 塞數據,並等待主執行緒的提取喚醒,所以這是一個經典的 三角迴圈死結

有了完整的前因後果之後,改動方案就比較簡單了。

  • Invoke 改成 BeginInvoke

  • 縮小 lock 的粒度

  • 三:總結

    這個dump所呈現的 三角迴圈死結 還是非常經典的,更開心的是這位學員的分析能力已經出了新手村。。。