記一次 .NET某機械臂上位系統 卡死分析

来源:https://www.cnblogs.com/huangxincheng/p/18253859
-Advertisement-
Play Games

一:背景 1. 講故事 前些天有位朋友找到我,說他們的程式會偶發性的卡死一段時間,然後又好了,讓我幫忙看下怎麼回事?窗體類的程式解決起來相對來說比較簡單,讓朋友用procdump自動抓一個卡死時的dump,拿到dump之後,上 windbg 說話。 二:WinDbg 分析 1. 主線程在做什麼 要想 ...


一:背景

1. 講故事

前些天有位朋友找到我,說他們的程式會偶發性的卡死一段時間,然後又好了,讓我幫忙看下怎麼回事?窗體類的程式解決起來相對來說比較簡單,讓朋友用procdump自動抓一個卡死時的dump,拿到dump之後,上 windbg 說話。

二:WinDbg 分析

1. 主線程在做什麼

要想看主線程在做什麼,很顯然用 k 命令觀察非托管棧即可。


0:000> k
 # Child-SP          RetAddr               Call Site
00 000000ef`11d1cb70 00007ffc`e65ddc4a     ntdll!RtlSetLastWin32Error+0x38
01 000000ef`11d1cbc0 00007ffc`e660e1a4     clr!JIT_RareDisableHelperWorker+0xca
02 000000ef`11d1cd00 00007ffc`b5c4ea25     clr!JIT_RareDisableHelper+0x14
03 000000ef`11d1cd40 00007ffc`b5c41d35     System_Drawing_ni+0x6ea25
04 000000ef`11d1ce00 00007ffc`87948876     System_Drawing_ni!System.Drawing.StringFormat..ctor+0x15
....
10 000000ef`11d1d8b0 00007ffc`881fc86f     xxx!xxx.AutoResizeColumns+0x106
...

從卦中數據看,托管的棧頂上有一個 RtlSetLastWin32Error 函數,看樣子 JIT_RareDisableHelperWorker 方法中某一個函數返回錯誤碼了,那這個錯誤碼是多少呢?要知道這個答案,先要知道它的簽名是什麼樣的,參考鏈接: https://source.winehq.org/WineAPI/RtlSetLastWin32Error.html


void RtlSetLastWin32Error
(
    DWORD err
)

從簽名可以看到,這個 err 是一個 int ,接下來觀察 RtlSetLastWin32Error 方法的 rcx 寄存器,有沒有存到 線程棧上,如果有的話直接提取即可。


0:000> uf ntdll!RtlSetLastWin32Error
ntdll!RtlSetLastWin32Error:
00007ffd`01a00780 894c2408        mov     dword ptr [rsp+8],ecx
00007ffd`01a00784 4883ec48        sub     rsp,48h
00007ffd`01a00788 488b05813d1300  mov     rax,qword ptr [ntdll!_security_cookie (00007ffd`01b34510)]
00007ffd`01a0078f 4833c4          xor     rax,rsp
....

0:000> k
 # Child-SP          RetAddr               Call Site
00 000000ef`11d1cb70 00007ffc`e65ddc4a     ntdll!RtlSetLastWin32Error+0x38
01 000000ef`11d1cbc0 00007ffc`e660e1a4     clr!JIT_RareDisableHelperWorker+0xca

0:000> dd 000000ef`11d1cbc0 L1
000000ef`11d1cbc0  00000006

從卦中看這個 err=6 ,那這個錯誤碼是什麼意思呢?繼續查 MSDN: https://learn.microsoft.com/zh-cn/windows/win32/debug/system-error-codes--0-499-

從卦中可以清晰的看到,原來是 無效的句柄 導致的,那這個錯誤會導致程式的卡死嗎?

2. 無效的句柄會卡程式嗎

按照我的過往經驗沒有這麼一說,其實 win32api 不像編程語言直接用 try catch 與 SEH 集成,返回錯誤碼的這種方式編碼起來雖然麻煩,但性能是最高的,所以這玩意導致程式卡死基本上是不可能的,那接下來的分析方向在哪裡呢? 其實在這種場景下抓多dump就尤為重要了,畢竟多個dump之間可以相互參考來觀察程式的走勢,目前是沒有這個條件的,那就從其他的路子上探究吧。

接下來我們試探性的觀察所有的托管線程棧,看看他們此時都在做什麼。


0:000> ~*e !clrstack
OS Thread Id: 0x555c (98)
        Child SP               IP Call Site
000000ef180fd0b8 00007ffd01a4dc04 [HelperMethodFrame_1OBJ: 000000ef180fd0b8] System.Threading.WaitHandle.WaitOneNative(System.Runtime.InteropServices.SafeHandle, UInt32, Boolean, Boolean)
000000ef180fd1e0 00007ffce4e2ddfc System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle, Int64, Boolean, Boolean)
000000ef180fd210 00007ffce4e2ddcf System.Threading.WaitHandle.WaitOne(Int32, Boolean)
000000ef180fd250 00007ffcb5573d74 System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle)
000000ef180fd2c0 00007ffcb4dc0d54 System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control, System.Delegate, System.Object[], Boolean)
000000ef180fd400 00007ffcb5577674 System.Windows.Forms.Control.Invoke(System.Delegate, System.Object[])
000000ef180fd470 00007ffc882040d4 xxxx.backgroundWorker_ProgressChanged(System.Object, System.ComponentModel.ProgressChangedEventArgs)
000000ef180fe060 00007ffce4e1ae56 System.Threading.ThreadPoolWorkQueue.Dispatch() 

OS Thread Id: 0x4528 (101)
        Child SP               IP Call Site
000000ef183fe1d8 00007ffd01a4dc04 [HelperMethodFrame_1OBJ: 000000ef183fe1d8] System.Threading.WaitHandle.WaitOneNative(System.Runtime.InteropServices.SafeHandle, UInt32, Boolean, Boolean)
000000ef183fe300 00007ffce4e2ddfc System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle, Int64, Boolean, Boolean)
000000ef183fe330 00007ffce4e2ddcf System.Threading.WaitHandle.WaitOne(Int32, Boolean)
000000ef183fe370 00007ffcb5573d74 System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle)
000000ef183fe3e0 00007ffcb4dc0d54 System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control, System.Delegate, System.Object[], Boolean)
000000ef183fe520 00007ffcb5577674 System.Windows.Forms.Control.Invoke(System.Delegate, System.Object[])
000000ef183fe590 00007ffc882040d4 xxxx.backgroundWorker_ProgressChanged(System.Object, System.ComponentModel.
000000ef183ff180 00007ffce4e1ae56 System.Threading.ThreadPoolWorkQueue.Dispatch() 
000000ef183ff608 00007ffce66112c3 [DebuggerU2MCatchHandlerFrame: 000000ef183ff608] 
...

觀察上面的線程棧之後,發現有兩個線程在 MarshaledInvoke 上等待,而且都是 backgroundWorker_ProgressChanged 方法,看樣子有一個 backgroundWorker 控制項在這裡,其實這個信息還是值得警惕的,為什麼這麼說呢? 因為它往往會預示著這個 Control 的 Queue 隊列可能有很多的數據積壓,那就往這個方向走。

3. Queue 隊列有積壓嗎

要找到這個答案,需要觀察主線程的線程棧上是否有 Queue 隊列。


0:000> !dso
OS Thread Id: 0x918 (0)
RSP/REG          Object           Name
r13              000002a70660a860 System.Drawing.StringFormat
...
000000EF11D1D9B0 000002a7008b5a18 System.ComponentModel.BackgroundWorker
...
000000EF11D1E328 000002a7004ea8b8 System.Collections.Queue
...
0:000> !do 000002a7004ea8b8
Name:        System.Collections.Queue
MethodTable: 00007ffce48cd9d0
EEClass:     00007ffce49f5fd0
Size:        56(0x38) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffce48d0ba0  40018c3        8      System.Object[]  0 instance 000002a7039d3278 _array
00007ffce48d32c0  40018c4       18         System.Int32  1 instance              103 _head
00007ffce48d32c0  40018c5       1c         System.Int32  1 instance               74 _tail
00007ffce48d32c0  40018c6       20         System.Int32  1 instance              227 _size
00007ffce48d32c0  40018c7       24         System.Int32  1 instance              200 _growFactor
00007ffce48d32c0  40018c8       28         System.Int32  1 instance             1366 _version
00007ffce48d0b08  40018c9       10        System.Object  0 instance 0000000000000000 _syncRoot

從卦中數據看,這個 BackgroundWorker.Queue 當前有 227 個任務在隊列積壓,這說明主線程是沒有問題的,只不過是在忙碌的處理任務而已,再回答最後一個問題,為什麼會卡一陣子?

4. 為什麼會卡一陣子

這是朋友提到的一個疑問,要想找到這個問題的答案,我們再回頭看下主線程,看下它是如何從 Queue 中取數據的。


0:000> !clrstack
OS Thread Id: 0x918 (0)
        Child SP               IP Call Site
000000ef11d1cd70 00007ffd01a007b8 [InlinedCallFrame: 000000ef11d1cd70] System.Drawing.SafeNativeMethods+Gdip.GdipCreateStringFormat(System.Drawing.StringFormatFlags, Int32, IntPtr ByRef)
...
000000ef11d1d970 00007ffc87911aac xxx.backgroundWorker_ProgressChanged(System.Object, System.ComponentModel.ProgressChangedEventArgs)
000000ef11d1d9e0 00007ffcdeab652b System.ComponentModel.BackgroundWorker.OnProgressChanged(System.ComponentModel.ProgressChangedEventArgs)
000000ef11d1dc30 00007ffce66112c3 [DebuggerU2MCatchHandlerFrame: 000000ef11d1dc30] 
000000ef11d1dea8 00007ffce66112c3 [HelperMethodFrame_PROTECTOBJ: 000000ef11d1dea8] System.RuntimeMethodHandle.InvokeMethod(System.Object, System.Object[], System.Signature, Boolean)
000000ef11d1e020 00007ffce4dfcf58 System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(System.Object, System.Object[], System.Object[])
000000ef11d1e080 00007ffce4dcbd20 System.Delegate.DynamicInvokeImpl(System.Object[])
000000ef11d1e0d0 00007ffcb4dc702d System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry)
000000ef11d1e110 00007ffcb4dc6f49 System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(System.Object)
000000ef11d1e160 00007ffce4ddfbe8 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
000000ef11d1e230 00007ffce4ddfad5 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
000000ef11d1e260 00007ffce4ddfaa5 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
000000ef11d1e2b0 00007ffcb4dc6ecc System.Windows.Forms.Control.InvokeMarshaledCallback(ThreadMethodEntry)
000000ef11d1e300 00007ffcb4dc6c36 System.Windows.Forms.Control.InvokeMarshaledCallbacks()
000000ef11d1e370 00007ffcb4db06fb System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
000000ef11d1e430 00007ffcb4dafa72 System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)
000000ef11d1e4d0 00007ffcb553d682 DomainBoundILStubClass.IL_STUB_ReversePInvoke(Int64, Int32, Int64, Int64)
000000ef11d1e810 00007ffce660fc9e [InlinedCallFrame: 000000ef11d1e810] System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG ByRef)
...
000000ef11d1ea30 00007ffcb4dc5982 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
000000ef11d1ecd0 00007ffc86e308e0 xxx.Program.Main(System.String[])
000000ef11d1ef08 00007ffce66112c3 [GCFrame: 000000ef11d1ef08] 

從卦中看,線程棧上的 InvokeMarshaledCallback 方法就是取數據的函數,接下來用 ILSpy 反編譯下這段代碼,簡化後如下:


private void InvokeMarshaledCallbacks()
{
	ThreadMethodEntry threadMethodEntry = null;
	lock(threadCallbackList)
	{
		if (threadCallbackList.Count > 0)
		{
			threadMethodEntry = (ThreadMethodEntry)threadCallbackList.Dequeue();
		}
	}
	while (threadMethodEntry != null)
	{
		try
		{
			InvokeMarshaledCallback(threadMethodEntry);
		}
		catch (Exception ex)
		{
			threadMethodEntry.exception = ex.GetBaseException();
		}

		lock(threadCallbackList)
		{
			threadMethodEntry = ((threadCallbackList.Count <= 0) ? null : ((ThreadMethodEntry)threadCallbackList.Dequeue()));
		}
	}
}

從代碼中的while true來看,這方法真的很軸,不懂得變通,要麼不取,要麼就是一次性的取完,當 Queue=threadCallbackList 中的數據較多時,主線程就會非常的忙碌,所以這就是卡死一陣子的真正底層原因。

有了前因後果之後,建議朋友做如下兩點修改:

  1. Invoke 的邏輯儘量簡單,讓 UI 可以秒殺。
  2. Invoke 的邏輯是否可以批量化,來減少 Queue 的積壓。

三:總結

這次朋友的生產事故,對我們做分析的人來說還是有很大的教訓意義,有時候主線程的一些拋錯或者阻塞假象會誘導我們陷入分析誤區,這就需要調試人員具有一雙慧眼識別,及時的浪子回頭。
圖片名稱


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一:背景 1. 講故事 在dump分析的過程中經常會看到很多線程卡在Monitor.Wait方法上,曾經也有不少人問我為什麼用 !syncblk 看不到 Monitor.Wait 上的鎖信息,剛好昨天有時間我就來研究一下。 二:Monitor.Wait 底層怎麼玩的 1. 案例演示 為了方便講述,先 ...
  • 在以前我做程式的時候,一般在登錄視窗裡面顯示程式名稱,登錄視窗一般設置一張背景圖片,由於程式的名稱一般都是確定的,所以也不存在太大的問題,不過如果客戶定製不同的系統的時候,需要使用Photoshop修改下圖層的文字,再生成圖片,然後替換一下也可以了。不過本著減少客戶使用繁瑣性,也可以使用空白名稱的通... ...
  • .Net 中提供了一系列的管理對象集合的類型,數組、可變列表、字典等。從類型安全上集合分為兩類,泛型集合 和 非泛型集合,傳統的非泛型集合存儲為Object,需要類型轉。而泛型集合提供了更好的性能、編譯時類型安全,推薦使用。 ...
  • 今天在技術群里,石頭哥向大家提了個問題:"如何在一個以System身份運行的.NET程式(Windows Services)中,以其它活動的用戶身份啟動可互動式進程(桌面應用程式、控制台程式、等帶有UI和互動式體驗的程式)"? 我以前有過類似的需求,是在GitLab流水線中運行帶有UI的自動化測試程 ...
  • OPC基金會提供了OPC UA .NET標準庫以及示常式序,但官方文檔過於簡單,光看官方文檔和示常式序很難弄懂OPC UA .NET標準庫怎麼用,花了不少時間摸索才略微弄懂如何使用,以下記錄如何從一個控制台程式開發一個OPC UA伺服器。 安裝Nuget包 安裝OPCFoundation.NetSt ...
  • 前言 隨著 DEV24.1.3 的發佈,XAF Blazor 中的屬性編輯器(PropertyEditor)也進行了很大的改動,在使用體驗上也更接近 WinForm 了,由於進行了大量的封裝,理解上沒有 WinForm 直觀,所以本文通過對屬性編輯器的原理進行解析,並對比新舊版本中的變化,使大家能夠 ...
  • 由於.NET Framework 4.0 是比較古老的版本,只有New Relic 7.0以下的版本才會支持.NET Framework 4.0的引用程式。 Technical support for .NET Framework 4.0 or lower 你可以參考這個官方Install New ...
  • 功能說明 使用ListView時,希望可以在單元格顯示圖片或其他控制項,發現原生的ListView不支持,於是通過拓展,實現ListView可以顯示任意控制項的功能,效果如下: 實現方法 本來想著在單元格裡面實現控制項的自繪的,但是沒找到辦法,最後是通過在單元格的錶面顯示對應控制項的,浮於錶面達到目的。 實 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...