CLR探索系列(上):Windbg+SOS剖析揭示域世界

在CLR的世界中,有一系列的令人Amazing的技術和架構。其中,CLR對應用程序在內存中內存分配,執行模型,程序之間的交互等一系列的技術,值得每一個致力於DotNet平臺的技術人員深究。
編程人員在開發的過程中,如果把程序集的加載(Assemblies Load)反射(Reflection)寄宿(Hosting)應用程序域(AppDomain),這四種技術結合起來使用的話,不僅能更好的使用CLR這個平臺提供的強大的功能,而且能夠構建更安全,更健壯的應用程序代碼。

這篇博文裏,就是使用託管代碼的動態調試工具,來研究一下CLR內部AppDomain的世界。

首先,從一個C#程序開始:
    class Program
    {
        static void Main(string[] args)
        {
            Program b = new Program();
            b.test();
            System.Console.ReadLine();
        }

        public void test()
        {
            int i = 67;
            System.Console.WriteLine((char)i);
            System.Console.WriteLine((char)67);
            i = 1;
        }
    }

運行了這個應用程序以後,我們打開windbg,attach到這個託管進程。
.load SOS
加載SOS擴展調試模塊,可以使用.chain指令查看加載是否正確。
0:003> lm
start    end        module name
00400000 00408000   TestConcoleApp   (deferred)            
76990000 76acd000   ole32      (deferred)            
77be0000 77c38000   msvcrt     (deferred)             
省略若干
77fc0000 77fd1000   Secur32    (deferred)            
78130000 781cb000   MSVCR80    (deferred)            
79000000 79045000   mscoree    (deferred)            
79060000 790b3000   mscorjit   (deferred)            
790c0000 79b90000   mscorlib_ni   (deferred)            
79e70000 7a3d6000   mscorwks   (deferred)            
查看下意境加載了的模塊,然後使用ld命令把我們的調試符號文件載入。VS在編譯生成一個Console App的時候,在debug模式的時候會在debug的bin目錄下生成一個和應用程序同名的pdb文件。我們要做的,就是載入這個文件:

0:003> ld TestConcoleApp
*** WARNING: Unable to verify checksum for G:/Projects/TestConcoleApp/TestConcoleApp/bin/Debug/TestConcoleApp.exe
Symbols loaded for TestConcoleApp

有一個警告,咋這裏先不管,再使用lm查看已經加載了的模塊的時候,可以看到這個module的調試符號文件已經被加載上了。

此 時,我們可以查看下Excute Engine (CLR)的堆裏面都有些什麼東西,我們可以使用!EEHeap命令,EE的意思,就是CLI的執行引擎,也就是我們常說的CLR。這個命令可以查看到一 個託管進程裏面的garbage-collected 和 Loader heaps相關信息。
0:003> !eeheap
PDB symbol for mscorwks.dll not loaded
Loader Heap:
--------------------------------------
System Domain: 7a38f918
LowFrequencyHeap: Size: 0x0(0)bytes.
HighFrequencyHeap: 00a62000(8000:1000) Size: 0x1000(4096)bytes.
StubHeap: 00a6a000(2000:1000) Size: 0x1000(4096)bytes.
Virtual Call Stub Heap:
  IndcellHeap: Size: 0x0(0)bytes.
  LookupHeap: Size: 0x0(0)bytes.
  ResolveHeap: Size: 0x0(0)bytes.
  DispatchHeap: Size: 0x0(0)bytes.
  CacheEntryHeap: Size: 0x0(0)bytes.
Total size: 0x2000(8192)bytes
/**********************************************
Loader Heap 中的系統域。這個域和下面的Shared Domian一起,是對託管的宿主程序,以及託管代碼不可見的。這個域加載了兩個CLR執行中十分重要的Module,MSCorEE.dll和 MScorwks.dll。MSCorEE.dll這個文件就是大家熟悉的shim,墊片。在CLR加載中起到的重要作用。這裏就不分析了,大家可以參考 別的文獻的介紹。
對於每個應用程序域,都會有自己的安全描述符,安全上下文以及默認的上下文。這三個部分可以支持一個應用程序域來自定義一個單獨實施的安全策略,譬如,可以用來確保宿主程序在加載託管代碼的時候不會對這些重要的數據結構造成破壞。
**********************************************/

--------------------------------------
Shared Domain: 7a38fef0
LowFrequencyHeap: 00a90000(2000:1000) Size: 0x1000(4096)bytes.
HighFrequencyHeap: Size: 0x0(0)bytes.
StubHeap: 00a9a000(2000:1000) Size: 0x1000(4096)bytes.
Virtual Call Stub Heap:
  IndcellHeap: Size: 0x0(0)bytes.
  LookupHeap: Size: 0x0(0)bytes.
  ResolveHeap: 00aab000(5000:1000) Size: 0x1000(4096)bytes.
  DispatchHeap: 00aa7000(4000:1000) Size: 0x1000(4096)bytes.
  CacheEntryHeap: Size: 0x0(0)bytes.
Total size: 0x4000(16384)bytes
/**********************************************
在共享域中,加載所有的應用程序域中都要使用到的assemblies,譬如MScorlib.dll。裝載了System.Object,System.ValueType這樣的基礎類。
**********************************************/

--------------------------------------
Domain 1: 154250
LowFrequencyHeap: 00a70000(2000:2000) Size: 0x2000(8192)bytes.
HighFrequencyHeap: 00a72000(8000:2000) Size: 0x2000(8192)bytes.
StubHeap: Size: 0x0(0)bytes.
Virtual Call Stub Heap:
  IndcellHeap: Size: 0x0(0)bytes.
  LookupHeap: Size: 0x0(0)bytes.
  ResolveHeap: Size: 0x0(0)bytes.
  DispatchHeap: Size: 0x0(0)bytes.
  CacheEntryHeap: Size: 0x0(0)bytes.
Total size: 0x4000(16384)bytes
/**********************************************
對於特定的寄宿程序,可以根據需要創建多個應用的默認域。例如IE,Asp.Net,或者是SqlServer,可以創建一個或者是多個默認的域。域名默認情況下的name就是module的名稱。
在默認域中,應用程序執行的時候需要裝載經來的assemblies可以被加載到這裏。
在每個應用程序域中,代碼創建的對象不能直接訪問另外的應用程序域中的代碼。如果要訪問這些代碼,可以採用靜態的委託,或者是appDomain的自己的方法來實現。
**********************************************/

--------------------------------------
Jit code heap:
LoaderCodeHeap: 00db0000(10000:1000) Size: 0x1000(4096)bytes.
Total size: 0x1000(4096)bytes
對 於託管的應用程序,有兩種把IL代碼編譯成本地代碼的方式。一種是在第一次運行的時候,調用JIT模塊來實時編譯,編譯好了的本地代碼就放到這裏。在PE 文件中的相應的代碼處,就用一個指針指引CLR到這裏來找相關的編譯好了的本地代碼。第二中是安裝的時候就編譯成爲本地代碼。
同時可以看到,Jit Heap佔用很少的內存空間。
--------------------------------------
Module Thunk heaps:
Module 790c2000: Size: 0x0(0)bytes.
Module 00a72c24: Size: 0x0(0)bytes.
Total size: 0x0(0)bytes
--------------------------------------
Module Lookup Table heaps:
Module 790c2000: Size: 0x0(0)bytes.
Module 00a72c24: Size: 0x0(0)bytes.
Total size: 0x0(0)bytes

--------------------------------------
Total LoaderHeap size: 0xb000(45056)bytes
總共的loader Heap的大小大概在45kb左右。
=======================================
Number of GC Heaps: 1
generation 0 starts at 0x013b1018
generation 1 starts at 0x013b100c
generation 2 starts at 0x013b1000
ephemeral segment allocation context: none
下 面的這兩個segment對於應用程序域的其他代碼來說是read only的,所以,這兩部分的空間比較小。這塊經常保存的是小的segement片段。除非你是很長很長的字符串。而大的object,則保持在LOH 中。GC Heap,可以有多個。每個GC Heap中,都可以有一個LOH。
而每個GC Heap的總大小=Segment佔用的空間+LOH
 segment    begin allocated     size
0014d720 790d5588  790f4b38 0x0001f5b0(128432)
013b0000 013b1000  013b3ff4 0x00002ff4(12276)
Large object heap starts at 0x023b1000
 segment    begin allocated     size
023b0000 023b1000  023b3250 0x00002250(8784)
Total Size   0x247f4(149492)
------------------------------
GC Heap Size   0x247f4(149492)
總共的GC堆大概150kb。
---------------------------------------------------------------------------------------------------------
託管線程的內存結構:
這 裏,簡單的交代一下一個託管進程的內存結構。在創建了一個託管的應用程序的線程以後,首先默認情況下創建了最少3個應用程序域,就是系統域,共享域,和默 認域。前兩個對託管的用戶代碼來說是不可見的。當時,可以調用共享與中的assemblies。用戶的託管代碼,和模塊被load到默認域中。一個託管的 宿主可以根據需要創建一個或者是多個默認域。

託管進程,線程(hard thread,soft thread),應用程序域,程序集,模塊的關係
託 管進程,應用程序域,程序集,模塊,這四個概念從左到右是一對多的關係。及一個託管可以對應多個應用程序域,一個appdomain可以對應多個 assemblies,一個assembly可以對應多個modules。modules就是我們經常看的.exe或者是.dll。exe文件是 windows下對PE文件格式擴展了的託管模塊。modules也可以是託管的動態鏈接庫文件。
對於線程,情況有點特殊。這裏,首先要區別一個 概念,操作系統的進程創建的線程和System.Threading.Thread這個類表示的線程。這裏,把操作系統創建的進程叫做hard thread,System.Threading.Thread這個類表示的線程叫做soft Thread。hard Thread和應用程序域的關係,是多堆多的關係。就是一個應用程序域中可以存在多個hard Thread。而一個hard Thread,也可以存在於多個應用程序域裏面。而soft Thread,是由應用程序域中的assemblies創建,所以,它只存在於相應的應用程序域中。
當一個系統的hard thread進入某個應用程序域中進行操作的時候,這個應用程序域就會實例化一個System.Threading.Thread類來完成這個線程對應的工作。

應用程序域的環境變量屬性:
對於每個應用程序域,有一系列的Environment屬性可以設置,通過設置這些屬性,可以配置一個應用程序域的特性,來滿足各種對於安全,性能等許多方面特別的需求。
可以參考MSDN的這裏:
http://msdn2.microsoft.com/en-us/library/system.appdomain_properties.aspx
獲知應用程序域相關的所有的Properties。
特別說明:應用程序域的動態目錄:
對於應用程序域的所有的屬性,需要特別提到一個叫做DynamicDirectory 的屬性。
這個屬性的生成,有兩個部分,譬如我本機上面的一個asp.net宿主進程的緩存文件夾:
C:/WINDOWS/Microsoft.NET/Framework/v2.0.50727/Temporary ASP.NET Files/mesapplication/8a8504fd/e1680364
C:/WINDOWS/Microsoft.NET/Framework/v2.0.50727/Temporary ASP.NET Files/mesapplication這一部分,由一個叫做DYNAMIC_BASE的屬性來定義。同時包含了這個應用程序域中加載的項目文件信息。
後面的兩部分8a8504fd/e1680364,根據項目文件的不同,由應用程序的APP_NAME這個屬性來決定。
這樣何在一起,就構成了在調試的過程中,進程在本機的緩存文件夾。

在接下來的下篇中,講從如何實現的角度,以實例源代碼來分析AppDomain的運行機制。
發佈了6 篇原創文章 · 獲贊 3 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章