寫了那麼多C#代碼,大家有沒有想過自己寫的代碼編譯後的可執行文件內部是什麼樣子,是怎樣在系統上運行的?
編譯成exe,然後雙擊exe文件運行,這中間到底發生了些什麼呢,這篇先來剖析下exe內部的樣子:
我們知道C#代碼編譯後的結果是IL(Intermedidate Language),那生成的exe文件裡面都是IL嗎,當然不會。
C#生成的exe既然是window下可執行文件,那也就是標准的PE文件,和普通win32的exe文件格式一樣。我們來看下exe文件的格式:
dll文件本質上和exe一樣,只是少了入口函數。
MS-DOC MZ Header和MS-DOS Stub是為了兼容DOS系統存在的,目的是使這個exe在DOS下執行時彈出一個提示"This program cannot be run in DOS mode"。
PE Header包含了這個文件的一些信息,如:文件創建日期,文件類型,Section的數量,Optional Header的大小等等。詳細可以參考Winnt.h裡的結構_IMAGE_FILE_HEADER。
PE Optional Header則包含了文件的版本號以及重要的基地址和AddressOfEntryPoint(RVA-Relative Virtual Address),這是程序執行的入口地址,雙擊exe後就從這裡開始執行。對C#程序來說,這裡指向的是.net的核心庫MsCorEE.dll的_CorExeMain()函數。當然這是針對XP系統的,XP以後的系統,OS Loader已經可以判斷出這個PE是否包含CLR頭來決定是否運行MsCorEE.dll的_CorExeMain()函數。
Section有很多,包括代碼節,數據節等,C#程序會把CLR頭,元數據,IL放在這裡面。
CLR是什麼呢,全稱Common Language Runtime,公共語言運行時,CLR主要是管理程序集,托管堆內存,異常處理和線程同步等等。
CLR頭具體可以參考CorHdr.h中的IMAGE_COR20_HEADER結構,如下:
typedef struct IMAGE_COR20_HEADER { // CLR版本信息 ULONG cb; USHORT MajorRuntimeVersion; USHORT MinorRuntimeVersion; IMAGE_DATA_DIRECTORY MetaData; //元數據 ULONG Flags; ULONG EntryPointToken; //入口函數Main的標識 IMAGE_DATA_DIRECTORY Resources; //資源 IMAGE_DATA_DIRECTORY StrongNameSignature; //強名稱標識 // Regular fixup and binding information IMAGE_DATA_DIRECTORY CodeManagerTable; IMAGE_DATA_DIRECTORY VTableFixups; IMAGE_DATA_DIRECTORY ExportAddressTableJumps; // Precompiled image info (internal use only - set to zero) IMAGE_DATA_DIRECTORY ManagedNativeHeader; } IMAGE_COR20_HEADER;
元數據很重要,驗證代碼類型安全,GC的對象引用跟蹤還有我們常用的反射都需要用到元數據。
元數據主要由定義表,引用表,清單表組成。
定義表包括應用所有的類型,方法,字段,屬性,參數,事件的定義,代碼裡任何的定義項都可以在這個表裡找到,反射就是靠這個表只要一個名字就能得到屬性或函數。運行時的類型安全檢查也離不開它。
引用表包括程序集,類型和成員的引用,我們知道GC在回收內存時先默認認為所有對象都是垃圾,然後通過線程棧上的根(cpu寄存器,局部變量,參數,靜態變量)找引用的對象,能找到的說明還在使用就去掉垃圾標記,這個表可以讓GC在回收內存時方便從根找到所有引用。
清單表主要是程序集,文件,資源的定義。
IL就不多說了,不了解的朋友可以參考小弟的另一篇文章:30分鐘?不需要,輕松讀懂IL
元數據和IL都可以通過工具ildasm.exe來查看。
以上就是C#生成的exe文件的主要結構,下篇再講exe文件的運行過程,謝謝。