目錄
1、問題描述
2、使用.effmach x86命令切換到32位上下文
3、切換到UI線程,發現UI線程死鎖了
4、使用!locks命令查看臨界區鎖的詳細信息,遇到了問題
5、使用dt命令查看臨界區對象信息,找到發生死鎖的多個線程
6、用戶態鎖與內核態鎖
7、最后
C++軟件異常排查從入門到精通系列教程(核心精品專欄,訂閱量已達8000多個,歡迎訂閱,持續更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++實戰專欄(重點專欄,專欄文章已更新500多篇,訂閱量已達6000多個,歡迎訂閱,持續更新中...)
https://blog.csdn.net/chenlycly/article/details/140824370C++ 軟件開發從入門到實戰(重點專欄,專欄文章已更新300多篇,歡迎訂閱,持續更新中...)
https://blog.csdn.net/chenlycly/category_12695902.htmlVC++常用功能開發匯總(專欄文章列表,歡迎訂閱,持續更新...)
https://blog.csdn.net/chenlycly/article/details/124272585C++軟件分析工具從入門到精通案例集錦(專欄文章,持續更新中...)
https://blog.csdn.net/chenlycly/article/details/131405795開源組件及數據庫技術(專欄文章,持續更新中...)
https://blog.csdn.net/chenlycly/category_12458859.html網絡編程與網絡問題分享(專欄文章,持續更新中...)
https://blog.csdn.net/chenlycly/category_2276111.html? ? ? ?最近項目中又遇到了多線程死鎖的問題,導出了dump文件,事后用Windbg詳細分析了一下。本文詳細講述一下這個多線程死鎖問題的完整分析與排查過程,以供借鑒或參考。
1、問題描述
? ? ? ?測試同事在測試新版本軟件(新需求迭代,開發了新版本),問題軟件終端以SIP協議入會,會議中發起桌面共享,然后測試同事去忙其他事情,20分鐘后點擊停止發送桌面共享,然后UI界面不能正常顯示,且窗口不可點擊,估計是UI線程卡死了。
? ? ? ?將Windbg附加到軟件進程上,切換到0號UI線程,查看函數調用堆棧,顯示線程卡在獲取臨界區鎖的接口上,所以可以確定是UI線程和其他線程發生死鎖卡死了。因為調試中的Windbg中輸入命令很卡,所以直接到Windows任務管理中導出了軟件進程的dump轉儲文件。因為當前軟件不是崩潰閃退,是多線程死鎖引發卡死,所以進程還在的,可以從任務管理器中導出dump文件。
? ? ? ?從Windows任務管理器中導出dump文件,是生成dump文件的方式之一,生成dump文件有多種方式。關于dump文件的分類以及生成dump文件的多種方式,可以查看我的文章:
【C++軟件調試技術】dump文件類型與dump文件生成方法詳解https://blog.csdn.net/chenlycly/article/details/144424512
2、使用.effmach x86命令切換到32位上下文
? ? ? ? 從任務管理器中導出的dump文件,用Windbg打開,默認是64位上下文,查看線程函數調用堆棧,顯示的堆棧不是正常的堆棧(堆棧中顯示的函數不是代碼中的函數接口),如下:
而我們的軟件是32位的,需要使用.effmach x86命令切換到32位上下文。切換后就可以看到正常的函數調用堆棧,可以看到代碼中調用的接口。
? ? ? ?關于使用Windbg分析dump文件的方法與一般步驟,此處不再贅述,可以查看我之前寫的文章:
使用Windbg分析dump文件定位軟件異常的方法與操作步驟https://blog.csdn.net/chenlycly/article/details/146005441? ? ? ?有時我們需要將Windbg附加到目標進程上進行動態調試,如何使用Windbg進行動態調試,可以查看我之前寫的文章:
使用Windbg調試目標進程排查C++軟件異常的一般步驟與要點分享https://blog.csdn.net/chenlycly/article/details/145826705
3、切換到UI線程,發現UI線程死鎖了
? ? ? ?因為UI界面卡死堵塞了,所以初步懷疑可能是UI線程發生死鎖了。當然導致線程卡死,也可能是代碼中發生死循環導致函數一直不返回引起的。于是使用~0s命令切換到UI線程,輸入kn查看UI線程的函數調用堆棧,如下:
從堆棧中可以看出,UI線程調用了RtlEnterCriticalSection要獲取臨界區鎖,然后最終卡在等待臨界區鎖的接口NtWaitForAlertByThreadId上了。
? ? ? ?在這里,給大家重點推薦一下我的幾個熱門暢銷專欄,歡迎訂閱:(博客主頁還有其他專欄,可以去查看)
專欄1:(該精品技術專欄的訂閱量已達到10000多個,專欄中包含大量項目實戰分析案例,有很強的實戰參考價值,廣受好評!專欄文章持續更新中,已經更新到210篇以上!歡迎訂閱!)
C++軟件調試與異常排查從入門到精通系列文章匯總https://blog.csdn.net/chenlycly/article/details/125529931
本專欄根據多年C++軟件異常排查的項目實踐,系統地總結了引發C++軟件異常的常見原因以及排查C++軟件異常的常用思路與方法,詳細講述了C++軟件的調試方法與手段,詳細介紹分析C++軟件問題的常用分析工具,以圖文并茂的方式給出具體的項目問題實戰分析實例(詳細講述分析排查過程,很有實戰參考價值),帶領大家逐步掌握C++軟件調試與異常排查的相關技術,適合基礎進階和想做技術提升的相關C++開發人員!
考察一個開發人員的水平,一是看其編碼及設計能力,二是要看其軟件調試能力!所以軟件調試能力(排查軟件異常的能力)很重要,必須重視起來!能解決一般人解決不了的問題,既能提升個人能力及價值,也能體現對團隊及公司的貢獻!
專欄中的文章都是通過項目實戰總結出來的,包含大量項目問題實戰分析案例,有很強的實戰參考價值!專欄文章還在持續更新中,預計文章篇數能更新到300篇以上!
專欄2:(本專欄涵蓋了C++多方面的內容,是當前重點打造的專欄,訂閱量已達8000多個,專欄文章已經更新到500多篇,持續更新中...)
C/C++實戰進階(專欄文章,持續更新中...)https://blog.csdn.net/chenlycly/category_11931267.html
以多年的開發實戰為基礎,總結并講解一些的C/C++基礎與項目實戰進階內容,以圖文并茂的方式對相關知識點進行詳細地展開與闡述!專欄涉及了C/C++領域多個方面的內容,包括C++基礎及編程要點(模版泛型編程、STL容器及算法函數的使用等)、數據結構與算法、C++11及以上新特性(開源代碼中可能會用到很多新特性(比如WebRTC開源庫),日常編碼中也會用到部分新特性,面試時也會頻繁地涉及到,學習新特性很有必要)、常用C++開源庫的介紹與使用(比如SQLite、libcurl、libwebsockets、libevent、jsoncpp/RapidJson、Redis、RabbitMQ、MongoDB、MQTT、ZooKeeper、OpenCV、FFmpeg、SDL、GStreamer、Live555、ReactOS等)、代碼分享(調用系統API、使用開源庫)、常用編程技術(動態庫、多線程、多進程、數據庫及網絡編程等)、軟件UI編程(Win32/duilib/QT/MFC)、C++軟件調試技術(引發C++軟件異常的常見原因分析與總結、排查C++軟件異常的手段與方法、分析C++軟件異常的基礎知識、使用常用軟件分析工具分析C++軟件問題、多個項目實戰問題分析案例分享等)、設計模式(單例模式、工廠模式、觀察者模式、狀態模式等)、網絡基礎知識與網絡問題分析進階內容(實戰問題分析實例分享)等。本專欄的內容都是建立在項目實踐的基礎上,來源于項目實戰,服務于項目實戰,很有實戰參考價值!
專欄3:??
C++常用軟件分析工具從入門到精通案例集錦匯總(專欄文章,持續更新中...)https://blog.csdn.net/chenlycly/article/details/131405795
常用的C++軟件輔助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro等,本專欄詳細介紹如何使用這些工具去巧妙地分析和解決日常工作中遇到的問題,很有實戰參考價值!
專欄4:???
VC++常用功能開發匯總(專欄文章,持續更新中...)https://blog.csdn.net/chenlycly/article/details/124272585
將10多年C++開發實踐中常用的功能,以高質量的代碼展現出來。這些常用的高質量規范代碼,可以直接拿到項目中使用,能有效地解決軟件開發過程中遇到的問題。
專欄5:?
C++ 軟件開發從入門到精通(專欄文章,持續更新中...)https://blog.csdn.net/chenlycly/category_12695902.html
根據多年C++軟件開發實踐,詳細地總結了C/C++軟件開發相關技術實現細節,分享了大量的實戰案例,很有實戰參考價值。
4、使用!locks命令查看臨界區鎖的詳細信息,遇到了問題
? ? ? ?輸入kv命令,將堆棧中傳入的參數值打印出來,如下所示:
對于臨界區對象鎖,調用的是RtlEnterCriticalSection,傳入的是臨界區結構體CRITICAL_SECTION對象地址,所以UI線程等待的臨界區對象地址就是堆棧中的參數值087d89f0。
? ? ? ?于是輸入命令!locks,查看當前臨界區對象信息,看看087d89f0臨界區鎖被哪個線程占用了。輸入后提示:
提示沒法解析ntdll.dll庫中的RtlCriticalSectionList接口,應該是沒有設置系統pdb下載地址的問題。于是將微軟系統庫pdb在線下載地址srv*f:\mss0616*http://msdl.microsoft.com/download/symbols設置到windbg中,重新輸入!locks,結果還是提示有問題:
沒法讀取dump文件中的內存,當前dump文件中的內存數據不全?奇怪了,當前的dump文件是從資源管理器中導出的,應該是全dump文件,咋還沒法讀取內存呢?難道從正在調試的Windbg中導出的dump文件才是包含所有內存的dump文件?讓測試同事那邊把Windbg附加到程序進程中重新復現一下,然后從正在調試的Windbg中導出dump文件再分析一下?
? ? ? ?關于pdb符號文件以及如何使用pdb符號文件,可以查看我之前寫的文章:
【C++軟件調試技術】什么是pdb文件?如何使用pdb文件?哪些工具需要使用pdb文件?https://blog.csdn.net/chenlycly/article/details/140742876
5、使用dt命令查看臨界區對象信息,找到發生死鎖的多個線程
? ? ? ?既然!locks命令沒法查看到當前軟件進程中的臨界區對象信息,我們可以嘗試使用dt命令看一下。我們上面已經知道UI線程在等待臨界區鎖087d89f0(臨界區結構體CRITICAL_SECTION對象地址),可以使用“dt RTL_CRITICAL_SECTION 087d89f0”命令,把地址087d89f0當成RTL_CRITICAL_SECTION結構體對象的首地址,從而獲取到該地址對應的結構體對象的字段值,如下所示:
其中的OwningThread字段就是當前臨界區鎖被哪個線程占用了(還沒釋放)。因為占用該臨界區鎖的線程沒有釋放鎖,導致UI線程一直獲取不到鎖,一直在等待,產生死鎖。
? ? ? ?OwningThread字段值 0x00003bc8就是線程id,于是使用~*命令,將當前進程中的所有信息打印出來:
找到線程id為0x00003bc8的線程對應的線程號,對應38號線程。
? ? ? ?于是使用~38s命令,切換到38號線程,使用kn命令將該線程的函數調用堆棧打印出來,看看為什么占用了臨界區鎖087d89f0沒有釋放。38號線程的函數調用堆棧如下:
38號線程也卡住了,卡在NtWaitForSingleObject接口上,沿著調用堆棧向上看,是在等待mutex互斥量鎖。38號線占用了臨界區鎖087d89f0,因為38號線程在等待另一個mutex互斥量鎖,所以沒有釋放臨界區鎖087d89f0,所以導致UI線程一直拿不到臨界區鎖087d89f0產生了堵塞卡死。至于38號線一直在等待mutex互斥量鎖,肯定這個互斥量鎖被第三個線程占用了,導致38號線程卡死了。
6、用戶態鎖與內核態鎖
? ? ? ?本案例中涉及到的鎖有臨界區和mutex互斥鎖,前者是用戶態鎖,后者是內核態鎖。
? ? ? ?用于多線程之間同步的常用鎖有臨界區、事件、信號量和互斥量等,其中臨界區是Windows系統獨有的,是用戶態鎖,事件、信號量和互斥量則是內核態鎖。用戶態鎖在訪問時不需要進行用戶態與內核態的切換,效率比較高。對于內核態鎖,訪問時要進行用戶態與內核態之間的切換,相對于用戶態鎖,訪問效率要低一些。我們的業務代碼運行在用戶態,當調用系統API訪問內核態鎖時,API函數底層需要從用戶態切換到內核態,訪問完后,再從內核態返回到用戶態,這就是用戶態與內核態之間的切換。
? ? ? ?出于訪問鎖的效率問題,一般在Windows平臺會優先使用臨界區鎖。在很多支持跨平臺的開源代碼中,封裝的鎖在Windows平臺上都被定義成臨界區鎖。
7、最后
? ? ? ? 所以本案例中應該是3個線程之間發生了死鎖。因為當前發生死鎖的多個線程屬于底層的音視頻編解碼模塊,所以這個問題的后續排查只能交給該模塊的開發團隊了,我這邊的排查工作就基本完成了。