《Inside Microsoft IL Assembler》學習筆記4:.net中PE檔案的結構

來源:互聯網
上載者:User

前文索引:
(1)《Inside Microsoft IL Assembler》學習筆記1:初步認識IL代碼 
(2)《Inside Microsoft IL Assembler》學習筆記2:讓IL代碼簡短些 
(3)《Inside Microsoft IL Assembler》學習筆記3:簡述PE檔案 

一.摘要
  在我們使用了任何支援CLR的語言來建立了原始碼檔案之後,無論使用什麼編譯器,編譯出的檔案都是一個託管模組(managed module),這個託管模組可以在CLR上運行。所以,我們把這種檔案稱為託管可執行檔(Managed Executable File)。關於通用PE檔案的格式已經在筆記三中間記錄了,這裡只記錄一些託管檔案自身比較有特色的部分。

二.託管執行檔案的重要組成部分
1.CLR Header
  我們知道,在PE頭中,包含一個資料目錄表(Data Directory Table),這個表的第15項就是一個包含了CLR頭的RVA和大小,可以通過它找到CLR Header。簡單記錄一下CLR Header中的幾個主要的欄位:
(1)Flags
  這是一個二進位的標記,其可選值包括:
A.COMIMAGE_FLAGS_ILONLY (0x00000001)  檔案中只包含純的IL代碼,未嵌入任何Native Code(除了Dos Stub,這個stub會被所有能夠識別CLR的系統忽略掉)。
B.COMIMAGE_FLAGS_32BITREQUIRED (0x00000002)  檔案只能被載入到32位的進程空間中。
C.COMIMAGE_FLAGS_STRONGNAMESIGNED (0x00000008) 檔案受到強式名稱簽名的保護
(2)EntryPointToken
  這是一個指明了此PE檔案進入點的中繼資料標誌符(MeteData Identifier),它指向一個方法定義或者檔案引用,而指向檔案引用的唯一可能是:一個多模組程式集的進入點不在其主模組中,那麼主模組裡的進入點標記指向包含進入點的模組檔案。進入點只能出現在可執行檔裡,如果你的代碼裡不包含進入點,IL彙編器會拒絕為你編譯EXE檔案。但對於非可執行檔,卻分為兩種情況:如果程式是一個純的託管程式,則不需要進入點;如果程式中即包含IL代碼又包含機器碼(由MC++編譯器和連結器產生),則必須把DLLMain()作為進入點函數,它要對程式集裡的Unmanaged 程式碼進行一些必要的初始化操作。
(3)VTableFixups
  要理解這個欄位,首先要理解v-table的概念。這是我摘了一個老外的描述,應該很精確,“Certain languages, which choose not to follow the common type system runtime model, may have virtual functions which need to be represented in a v-table. These v-tables are laid out by the compiler, not by the runtime. Finding the correct v-table slot and calling indirectly through the value held in that slot is also done by the compiler”。v-table是為虛方法調用服務的,而VTableFixups包含了一組v-table的地址和大小。
  根據筆者當前的認識,v-table只有在此託管檔案需要和非託管環境互動的時候才有用(比如這個Managed 程式碼要被一個非託管環境調用),在純的託管環境下可能是沒用的。當然,大家都知道C#作為典型的OO語言,也有虛函數的概念,但它的虛函數實現機制和C++中的v-table的實現方式有什麼不同,我暫時還不清楚。

2.文本段(.Text section)
  整個CLR Header是放置在一個文本段中的。這是一個唯讀段,包含了中繼資料表、IL代碼、引入表等,整個結構如所示:

這個段裡的各個部分並非同時產生的,用帶序號的方框來提示這一點,序號低的先產生。

3.資源
  在託管檔案中可以嵌入兩種不同類型的資源:非託管的平台相關的資源,或者託管資源。這兩種資源儲存在PE檔案的不同section裡(其中託管資源已經在上面的文本段內出現過了,不是嗎?),其中非託管資源被放在一個單獨的.rsrc段裡,而託管資源放在文本段裡。
  需要注意的是,IL彙編器在每個託管可執行檔中只能嵌入一個資源檔(.RES)。IL反組譯碼器會定位到這個section,然後把section中的所有內容作為一個.RES檔案釋放出來。

三.中繼資料的結構

  使用過.net的Reflection功能的人對中繼資料可能多少都有點概念,它是對整個託管模組的邏輯結構的完整描述,包含了所有在模組中聲明和引用的元素。從結構上來說,所有中繼資料類似於一個關聯式資料庫,裡面的資料體現為一組交叉引用的表(而不是樹或者什麼其他資料結構),並且任何資料都只有一份(其他用到這個資料的位置都將包含一個指向此資料的引用)。從用途上來說,這些表分為三類:定義表(definition table)、參考資料表(reference table)和清單表(manifest table)。
  整個中繼資料是一個二進位的資料區塊,你只能通過工具來查看已產生程式集的中繼資料資訊,如ildasm.exe(你可以在“視圖”菜單裡找到關於中繼資料資訊顯示的命令)。
(1)中繼資料結構概覽
  下面貼出來的顯示資訊,是我用ildasm.exe統計了自己寫的一個很小的Demo程式中的中繼資料資訊,先放在這裡,可以和後面提到的內容相互參考:

CLR meta-data size  : 1260
   ---------這些是中繼資料中的table stream-----------------
   Module        -    1 (10 bytes)
   TypeDef       -    3 (42 bytes)      0 interfaces, 0 explicit layout
   TypeRef       -    8 (48 bytes)
   MethodDef     -    8 (112 bytes)     0 abstract, 0 native, 4 bodies
   FieldDef      -    1 (6 bytes)       0 constant
   MemberRef     -    5 (30 bytes)
   ParamDef      -    9 (54 bytes)
   CustomAttribute-    4 (24 bytes)
   StandAloneSig -    1 (2 bytes)
   Assembly      -    1 (22 bytes)
   AssemblyRef   -    1 (20 bytes)
   -----------table stream end--------------------------
   
   -----------這些是中繼資料中的Heap stream-------------------
   Strings       -   385 bytes
   Blobs         -   108 bytes   //
   UserStrings   -   200 bytes
   Guids         -    16 bytes
   -----------heap stream end---------------------------

   Uncategorized -   181 bytes

可以看到,在中繼資料中除了上面我們提到的那些表以外,還有一些用於記錄UserString,Guid的堆資料,後面會提到。

(2)中繼資料中的父子關係
  中繼資料中包含很多“父子關係”,如“類--方法”、“方法--參數”等等。如果你想找到和某個父資料對應的所有子資料,遍曆這個子資料所在的表可是個糟糕的選擇。事實上,對於這種一對多關聯性,彙編器在構造中繼資料表的時候,並不僅僅使用資料間的參考關聯性,而且使用了資料的排列順序來協助定位。每個父資料都只含有一個指向其第一個子資料的引用,其子資料的結尾靠下一個父資料的起始引用來定位。這就要求子資料要依照他們的父資料來排序(符合這種條件的中繼資料被稱為“最佳化的”、“壓縮的”中繼資料,而IL彙編器一般都是產生這種中繼資料)。是書中給出的class-method父子關係的中繼資料表:

(3)中繼資料結構
   前面曾經提到過,中繼資料其實就是一個二進位的資料區塊,所以中繼資料的內部,就是一個個的named stream。這些stream又分為兩種類型,除了前面提到過的Table,還有一類是以Heap的形式體現(見上文程式碼片段中的注釋)。是書中給出的一個完整的中繼資料結構圖:

上面以#開頭的就是中繼資料中可能出現的6個命名流,他們的用途分別是
 #Strings:用來儲存中繼資料項的名字,如類名、方法名等  //Heap Stream
 #Blob: 用來儲存一些內部的對象執行個體,如預設值什麼的 //Heap Stream
 #US: 使用者定義的字串常量 //Heap Stream
 #GUID: 包含各種全域統一標誌符  //Heap Stream
 #~: “最佳化的”、“壓縮的”中繼資料,裡面的中繼資料表以最佳化方式儲存(我們在父子關係中剛剛提到過的)  //Table Stream
 #-: 非最佳化的中繼資料(和#~不能共存) //Table Stream
 其中(#~或#-)、#GUID、#Strings是必不可少的。
 另外,中繼資料既然被儲存在很多表裡,那麼CLR是如何定位一個中繼資料項的呢(怎麼樣定位到某個表的某一行)?這裡,它使用了一種叫做Token的機制,每個token佔4個位元組,其中高位位元組表示表序號,剩餘三個位元組表示表內的行序號。具體這些序號被稱為RID(record index),其中行序號從1開始,表序號從0開始。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.