CLR探索系列:托管PE/COFF文件格式側窺
一直都想寫篇文章來說說那些塵封在PE/Coff文件格式下的那些事,還有Metadata和EEClass是如何表現了一個靜態的PE格式文件在內存中的映射結構。
在這篇文章里,我不去介紹windows下PE文件的具體格式,也不去介紹一個托管或者是非托管PE文件的加載運行方式,更加不去介紹一個PE文件里面的各個頭部以及整體結構的各個部分的含義。
而是側重于介紹,基于托管環境下,DotNet對基本的PE/CoFF文件格式做了那些擴充,CLR頭部介紹,以及元數據和IL代碼詳析解析。主要側重從靜態文件的角度,來剖析DotNet下最基本的模塊的結構,以及這樣的結構如何適應一個托管的環境。
擬把PE文件格式里里外外從上到下一點一點的完全解剖一遍,當然,不可能做到很全面,不然,如果想知道PE文件的各個方面的具體的細節,可以參閱文章底部推薦的那個白皮書文檔,這份文檔相當詳盡介紹了PE文件格式的點點滴滴。
首先,還是從一段C#代碼開始:
class Program
{
public const int conField=122*1119;
public readonly int roField;
private int _property;
public int Property
{
get {return _property; }
set{_property = value; }
}
static void
{
(new Program()).Method();
}
public void Method()
{
System.Console.ReadLine();
}
}
之所以定義這么多類型和字段,主要是為了在解說托管PE文件格式的時候,元數據表中相關的表都會出現相關記錄。
編譯之后,得到一個叫做TestConsoleApp.exe的托管PE文件。在繼續下面的敘述之間,首先先概括的說一下Metadata。不說IL語言是因為,在我以前的博文中,已經有相關的介紹。
一個托管PE文件,粗略的講,由4個部分組成。PE32(+)的頭部,CLR頭部,Metadata以及IL。
首先,我們來說說CLR的頭部。這個東西,是托管的PE文件所特有的東西。
我們打開DotNet Framework里面的include文件夾里面一個叫做CorHdr.h的文件,找到一個叫做IMAGE_COR20_HEADER這個數據結構的定義。這個數據結構,定義的就是CLR的header里面內容。下面對其的定義:
// COM+ 2.0 header structure.
typedef struct IMAGE_COR20_HEADER
{
// Header versioning
DWORD cb;
//CLR的主版本號
WORD MajorRuntimeVersion;
//CLR的副版本號
WORD MinorRuntimeVersion;
// Symbol table and startup information
//標識元數據在這個PE文件里面起始位置。
IMAGE_DATA_DIRECTORY MetaData;
//標識這個runtime的Flags
DWORD Flags;
// DDBLD - Added next section to replace following lin
// DDBLD - Still verifying, since not in NT SDK
// DWORD EntryPointToken;
// If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT is not set, EntryPointToken represents a managed entrypoint.
// If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT is set, EntryPointRVA represents an RVA to a native entrypoint.
//EnterPoint Token,這個定義的是這個image的MethodDef的入口點。
union {
DWORD EntryPointToken;
DWORD EntryPointRVA;
};
// DDBLD - End of Added Area
// Binding information
//標識CLI資源的目錄
IMAGE_DATA_DIRECTORY Resources;
//強命名的簽名文件。標識對這個PE文件計算得到的一個Hash文件的地址。這個是在CLI的loader在加載一個PE文件的時候,驗證版本和加載的時候需要使用的。可以為空。
IMAGE_DATA_DIRECTORY StrongNameSignature;
// Regular fixup and binding information
//代碼管理表的地址
IMAGE_DATA_DIRECTORY CodeManagerTable;
//這個module里面的一個包含地址的數組,數組的每一項,都包含了對一個founction的指針。
IMAGE_DATA_DIRECTORY VTableFixups;
//這個也是包含的一個數組,數組里面都是方法需要jump的地址。
IMAGE_DATA_DIRECTORY ExportAddressTableJumps;
// Precompiled image info (internal use only - set to zero)
//這個地址,保存的是這個Module對應的在本機上面的Jit過后了的本地代碼的目錄。
IMAGE_DATA_DIRECTORY ManagedNativeHeadesr;
} IMAGE_COR20_HEADER, *PIMAGE_COR20_HEADER;
接下來,我們就直接打開上面編譯好的那個托管模塊的CLR頭部,看看里面有些什么:
----- CLR Header:
Header size: 0x00000048
Major runtime version: 0x0002
Minor runtime version: 0x0005
0x00002094 [0x00000660] address [size] of Metadata Directory:
Flags: 0x00000001
Entry point token: 0x06000003
0x00000000 [0x00000000] address [size] of Resources Directory:
0x00000000 [0x00000000] address [size] of Strong Name Signature:
0x00000000 [0x00000000] address [size] of CodeManager Table:
0x00000000 [0x00000000] address [size] of VTableFixups Directory:
0x00000000 [0x00000000] address [size] of Export Address Table:
0x00000000 [0x00000000] address [size] of Precompile Header:
從上面的這個托管模塊的頭部可以看到,這個頭部里面包含的內容,和這個頭部的結構體所定義的東西,是完全一致的。
幾個需要說明的,一個是Major runtime version,以及Minor runtime version標識的是不同的runtime的版本。在上面的頭部中,主要是一個文件的偏移的offset,就是Entry point token的地址。
這里要特別提出來一點,這里的Entry point token表示的是入口點,是MethodDef的入口點。
而不是整個托管PE文件的入口點。整個PE文件的入口點,在這里,用PEID打開可以看到,是
Addr. of entry point: 0x000027be
這個EnterPoint是在一個32位的PE Optional Header里面定義的。這個入口點,才是整個應用程序的入口點。
這個入口點里面,我們使用IDA之類的逆向工程工具可以看到,托管PE模塊在這個地址上面的代碼:
004027BE: FF 25 00 20 40 00 jmp dword ptr ds:[402000] ; _CorExeMain
在這里,我們看到了熟悉的CorExeMain這個入口函數 ^_^ 關于這個函數,我就不多說了,在前面的博文里面有詳析的分析。參見那篇探索托管模塊加載過程的文章。
接下來,我們介紹下元數據表,以及一些對這個PE文件的統計信息,首先查看這個托管PE文件的統計信息,使用的還是ildasm工具,我的最愛:
File size : 16384
PE header size : 4096 (496 used) (25.00%)
PE additional info : 1075 ( 6.56%)
Num.of PE sections : 3
CLR header size : 72 ( 0.44%)
CLR meta-data size : 1632 ( 9.96%)
CLR additional info : 0 ( 0.00%)
CLR method headers : 16 ( 0.10%)
Managed code : 49 ( 0.30%)
Data : 8192 (50.00%)
Unaccounted : 1252 ( 7.64%)
Num.of PE sections : 3
.text - 4096
.rsrc - 4096
.reloc - 4096
CLR meta-data size : 1632
Module - 1 (10 bytes)
TypeDef - 2 (28 bytes) 0 interfaces, 0 explicit layout
TypeRef - 18 (108 bytes)
MethodDef - 5 (70 bytes) 0 abstract, 0 native, 5 bodies
FieldDef - 3 (18 bytes) 0 constant
MemberRef - 17 (102 bytes)
ParamDef - 2 (12 bytes)
Constant - 1 (6 bytes)
CustomAttribute- 13 (78 bytes)
StandAloneSig - 1 (2 bytes)
PropertyMap - 1 (4 bytes)
Property - 1 (6 bytes)
MethodSemantic- 2 (12 bytes)
Assembly - 1 (22 bytes)
AssemblyRef - 1 (20 bytes)
Strings - 680 bytes
Blobs - 236 bytes
UserStrings - 8 bytes
Guids - 16 bytes
Uncategorized - 194 bytes
CLR method headers : 16
Num.of method bodies - 5
Num.of fat headers - 1
Num.of tiny headers - 4
Managed code : 49
Ave method size - 9
可以看到,在上面的統計信息中,顯示了一個托管的PE模塊的各個部分的組成。從各個部分的統計的大小里面,PE Header和CLR的Metadata占據了相當大的比例,而IL代碼,僅僅占據了整個托管模塊大小的0.3%。只有49個字節。
順便提一下,如果Unaccounted顯示的是負數,是不能相信的,那是以前的版本存在的一個bug。
就寫到這里吧,如果覺得看了上面的東西還是不知所云或者覺得不完整,那我推薦一本MS的白皮技術文檔:Visual Studio, Microsoft Portable Executable and Common Object File Format Specification
可以在msdn上面下載到,它完整的講述了PE文件格式的各個部分的細節。
接下來的一篇博文,就說說元數據以及元數據表的內存結構,邏輯結構和在SSCLI中的設計和實現。
最后做個廣告:
歡迎園子里面的朋友加入SSCLI團隊,這里,我們致力于對.Net底層核心技術及其實現的研究。如果你想真正的了解.Net最核心的實現,我們熱忱的歡迎你的加入:
圈子地址:http://sscli.cnblogs.com
加入地址:http://www.rzrgm.cn/lbq1221119/archive/2008/03/10/1097627.html
圈子剛剛建立,希望園子里的朋友多多支持,:)
posted on 2008-03-10 10:02 lbq1221119 閱讀(2296) 評論(3) 收藏 舉報
浙公網安備 33010602011771號