用WinDbg探索CLR世界 [2] 線程
發(fā)表時(shí)間:2023-07-15 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要][2] 線程 在配置好WinDbg之后,我們載入一個(gè)CLR程序并執(zhí)行至CLR被載入,然后開始我們的CLR探索之旅。 首先,使用!threads命令看看當(dāng)前CLR中有哪些線程正在執(zhí)行以下為...
[2] 線程
在配置好WinDbg之后,我們載入一個(gè)CLR程序并執(zhí)行至CLR被載入,然后開始我們的CLR探索之旅。
首先,使用!threads命令看看當(dāng)前CLR中有哪些線程正在執(zhí)行
以下為引用:
0:004> !threads
ThreadCount: 2
UnstartedThread: 0
BackgroundThread: 1
PendingThread: 0
DeadThread: 0
PreEmptive GC Alloc Lock
ID ThreadOBJ State GC Context Domain Count APT Exception
0 6ec 0014e708 6020 Enabled 00000000:00000000 00148a90 0 STA
2 a68 00157618 b220 Enabled 00000000:00000000 00148a90 0 MTA (Finalizer)
前面5個(gè)計(jì)數(shù)器分別表示托管(managed)線程、未啟動(dòng)線程、后臺(tái)線程、阻塞線程和僵死線程的數(shù)量。
下面的列表是當(dāng)前托管線程的詳細(xì)信息:第一個(gè)域是WinDbg的線程編號(hào);ID是Win32線程ID;ThreadObj是線程的對(duì)象;State是一個(gè)標(biāo)志位,以后再詳細(xì)介紹;PreEmptive GC表示GC是否與此線程協(xié)作;GC Alloc Context是GC的相關(guān)信息;Domain是線程所在AppDomain;Lock Count是線程擁有鎖的計(jì)數(shù)器;APT是線程類型,沿用COM中STA/MTA/NTA(netural)的概念;最后的Exception表示線程類型,除了普通的用戶線程外還有finalizer、GC、Theadpool Worker和Threadpool Completion Port,其功能與名字相符。
我們可以在.NET Framework SDK的Tool Developers Guide\Samples\sos子目錄下找到所有sos.dll支持命令的詳細(xì)說明;在rotor的clr\src\tools\sos子目錄下找到針對(duì)rotor系統(tǒng)的sos.dll的實(shí)現(xiàn)代碼。這份源代碼在功能上實(shí)現(xiàn)了與CLR正規(guī)發(fā)行版本基本上相同的功能,也是我們下面研究的主要目標(biāo)之一。
其中Strike.cpp是sos功能命令的實(shí)現(xiàn)所在。每個(gè)sos的命令在strike.cpp中以一個(gè)函數(shù)實(shí)現(xiàn),通過DECLARE_API宏定義函數(shù)參數(shù)。
以下為引用:
#define DECLARE_API(s) \
CPPMOD VOID \
s( \
HANDLE hCurrentProcess, \
HANDLE hCurrentThread, \
ULONG dwCurrentPc, \
ULONG dwProcessor, \
PCSTR args \
)
函數(shù)參數(shù)分別傳入WinDbg正在調(diào)試的進(jìn)程句柄、當(dāng)前線程句柄、當(dāng)前指令地址、處理器和命令行參數(shù)信息。函數(shù)內(nèi)再對(duì)此信息進(jìn)行處理,輸出調(diào)試信息到WinDbg界面中。
讓我們先看看Threads命令(strike.cpp:1237)的實(shí)現(xiàn)原理。
Threads函數(shù)首先從一個(gè)全局線程存儲(chǔ)池中獲取當(dāng)前線程統(tǒng)計(jì)信息,并將之存儲(chǔ)在一個(gè)結(jié)構(gòu)并內(nèi)打印統(tǒng)計(jì)值;然后調(diào)用GetThreadList函數(shù)(sos\util.cpp:2259)獲取線程列表;對(duì)每個(gè)線程獲取線程信息,并將之存儲(chǔ)在一個(gè)結(jié)構(gòu)內(nèi)并打印線程詳細(xì)信息;在打印線程信息時(shí),會(huì)判斷此線程的類型,并打印相關(guān)信息。
首先來看看全局線程存儲(chǔ)池ThreadStore類(vm\threads.h:1998)的設(shè)計(jì)和使用思路。
CLR在啟動(dòng)時(shí),會(huì)通過 CoInitializeEE 函數(shù)(vm\ceemain.cpp:1100)初始化一個(gè)執(zhí)行引擎(Execute Engine),這兒的EE類似JVM的概念,實(shí)際上就是CLR的運(yùn)行時(shí)環(huán)境。關(guān)于CLR的詳細(xì)啟動(dòng)過程請(qǐng)參見筆者另外一篇文章《.Net平臺(tái)下CLR程序載入原理分析》。
CoInitializeEE函數(shù)使用全局變量保障每個(gè)進(jìn)程最多只有一個(gè)CLR環(huán)境;對(duì)沒有構(gòu)造CLR的進(jìn)程,調(diào)用TryEEStartup函數(shù)(vm\ceemain.cpp:500)嘗試初始化CLR。偽代碼如下:
以下為引用:
HRESULT STDMETHODCALLTYPE CoInitializeEE(DWORD fFlags)
{
if(++g_RefCount <= 1 && !g_fEEStarted && !g_fEEInit)
{
g_EEStartupStatus = TryEEStartup(fFlags);
}
return SUCCEEDED(g_EEStartupStatus) ?
(SetupThread() ? S_OK : E_OUTOFMEMORY) : g_EEStartupStatus;
}
TryEEStartup函數(shù)則以異常安全策略包裝EEStartup函數(shù)(vm\ceemain.cpp:206)完成實(shí)際的CLR啟動(dòng)工作。在EEStartup函數(shù)中會(huì)真正調(diào)用InitThreadManager函數(shù)(vm\Threads.cpp:2068)完成線程管理器的初始化工作。而InitThreadManager函數(shù)出了初始化TLS外,絕大部分工作是由實(shí)現(xiàn)ThreadStore類的Singleton模式的ThreadStore::InitThreadStore函數(shù)(vm\Threads.cpp:4345)實(shí)現(xiàn)的。其中保存全局唯一ThreadStore類實(shí)例的就是前面獲取線程統(tǒng)計(jì)信息的全局線程存儲(chǔ)池。
以下為引用:
ThreadStore *g_pThreadStore;
BOOL ThreadStore::InitThreadStore()
{
g_pThreadStore = new ThreadStore;
return (g_pThreadStore != NULL);
}
因此,ThreadStore類實(shí)際上是一個(gè)全局唯一的線程管理器,新增和終止一個(gè)CLR線程都需要在此存儲(chǔ)中更新相關(guān)信息。此線程管理器除了維護(hù)一個(gè)當(dāng)前線程列表的鏈表外,還維護(hù)了一套線程相關(guān)信息的統(tǒng)計(jì)值。前面Threads命令獲取的幾個(gè)統(tǒng)計(jì)值就是從此而來。而獲取當(dāng)前線程列表的GetThreadList函數(shù)(sos\util.cpp:2259),實(shí)際上也是直接從線程管理器的線程列表中獲取每個(gè)線程對(duì)象的入口。
最后來看看線程信息的獲取步驟。
每個(gè)線程Thread類(vm\Threads.h:544)的對(duì)象表示一個(gè)managed線程。此線程是一個(gè)邏輯上的線程,如果被啟動(dòng)則可能直接對(duì)應(yīng)于一個(gè)系統(tǒng)的物理線程。而一個(gè)物理線程則無需綁定到一個(gè)被管理的邏輯線程上,物理線程卻可以在多個(gè)AppDomain中共享以運(yùn)行被調(diào)度到的被管理線程。此外每個(gè)被管理的線程必須有一個(gè)運(yùn)行時(shí)環(huán)境(Contex),但不一定在一個(gè)確定的應(yīng)用程序域(AppDomain)中。呵呵,搞糊涂了吧 :D 這里繞的幾個(gè)彎子我以后再寫篇詳細(xì)的文章討論好了 :P
被管理的線程除了可以獲取當(dāng)前線程ID和綁定到的物理線程ID外,還有一個(gè)ThreadState狀態(tài)(vm\Threads.h:576)定義其當(dāng)前運(yùn)行情況。
對(duì)線程類型的判斷邏輯,首先將線程與FinalizerThread(Finalizer)和GcThread(GC)兩個(gè)全局變量指向的系統(tǒng)功能線程比較,判斷是否是這兩種特殊線程;然后根據(jù)線程狀態(tài)的Thread::TS_ThreadPoolThread位是否被設(shè)置來判斷是否在線程池中;如果在線程池中還要通過狀態(tài)的Thread::TS_TPWorkerThread標(biāo)志位進(jìn)一步判斷是否為工作者線程(Threadpool Worker),不是工作者線程則為完成端口線程(Threadpool Completion Port)。這幾種線程緩沖池中線程的概念,我們以后章節(jié)討論線程池時(shí)再詳細(xì)討論。