處理器不能直接解釋程序集。程序集用的是另一種語(yǔ)言,即公共中間語(yǔ)言(Common Intermediate Language,CIL),或稱(chēng)為中間語(yǔ)言(IL)[1]。
C# 編譯器將 C# 源代碼文件轉(zhuǎn)換成中間語(yǔ)言。為了將 CIL 代碼轉(zhuǎn)換成處理器能理解的機(jī)器碼,還要完成一個(gè)額外的步驟(通常在運(yùn)行時(shí)進(jìn)行)。該步驟涉及 C# 程序執(zhí)行的一個(gè)重要元素:VES(Virtual Execution System,虛擬執(zhí)行系統(tǒng))。VES 也稱(chēng)為運(yùn)行時(shí)(runtime)。
它根據(jù)需要編譯 CIL 代碼,這個(gè)過(guò)程稱(chēng)為即時(shí)編譯或 JIT 編譯(just-in-time compilation)。如代碼在像“運(yùn)行時(shí)”這樣的一個(gè)“代理” 的上下文中執(zhí)行,就稱(chēng)為托管代碼(managed code),在“運(yùn)行時(shí)”的控制下執(zhí)行的過(guò)程則稱(chēng)為托管執(zhí)行(managed execution)。
之所以 稱(chēng)為“托管”,是因?yàn)椤斑\(yùn)行時(shí)”管理著諸如內(nèi)存分配、安全性和 JIT 編譯等方面,從而控制了主要的程序行為。執(zhí)行時(shí)不需要“運(yùn)行時(shí)”的代碼稱(chēng)為本機(jī)代碼(native code)或非托管代碼(unmanaged code)。
說(shuō)明:“運(yùn)行時(shí)”既可能指“程序執(zhí)行的時(shí)候”,也可能指“虛擬執(zhí)行系統(tǒng)”。為明確起見(jiàn),用“執(zhí)行時(shí)”表示“程序執(zhí)行的時(shí)候”,用“運(yùn)行時(shí)”表示負(fù)責(zé)管理 C# 程序執(zhí)行的代理。[2]
“運(yùn)行時(shí)”規(guī)范包含在一個(gè)包容面更廣的規(guī)范中,即 CLI(Common Language Infrastructure,公共語(yǔ)言基礎(chǔ)結(jié)構(gòu))規(guī)范。作為國(guó)際標(biāo)準(zhǔn),CLI 包含了以下幾方面的規(guī)范:
VES 或“運(yùn)行時(shí)”。
CIL。
支持語(yǔ)言互操作性的類(lèi)型系統(tǒng),稱(chēng)為 CTS(Common Type System,公共類(lèi)型系統(tǒng))。
如何編寫(xiě)通過(guò) CLI 兼容語(yǔ)言訪問(wèn)的庫(kù)的指導(dǎo)原則,這部分內(nèi)容具體放在公共語(yǔ)言規(guī)范(Common Language Specification,CLS)中。
使各種服務(wù)能被 CLI 識(shí)別的元數(shù)據(jù)(包括程序集的布局或文件格式規(guī)范)。
在“運(yùn)行時(shí)”執(zhí)行引擎的上下文中運(yùn)行,程序員不需要直接寫(xiě)代碼就能使用幾種服務(wù)和功能,包括:
語(yǔ)言互操作性:不同源語(yǔ)言間的互操作性。語(yǔ)言編譯器將每種源語(yǔ)言轉(zhuǎn)換成相同中間語(yǔ)言(CIL)來(lái)實(shí)現(xiàn)這種互操作性。
類(lèi)型安全:檢查類(lèi)型間轉(zhuǎn)換,確保兼容的類(lèi)型才能相互轉(zhuǎn)換。這有助于防范緩沖區(qū)溢出(這是產(chǎn)生安全隱患的主要原因)。
代碼訪問(wèn)安全性:程序集開(kāi)發(fā)者的代碼有權(quán)在計(jì)算機(jī)上執(zhí)行的證明。
垃圾回收:一種內(nèi)存管理機(jī)制,自動(dòng)釋放“運(yùn)行時(shí)”為數(shù)據(jù)分配的空間。
平臺(tái)可移植性:同一程序集可在多種操作系統(tǒng)上運(yùn)行。要實(shí)現(xiàn)這一點(diǎn),一個(gè)顯而易見(jiàn)的限制就是不能使用平臺(tái)特有的庫(kù)。所以平臺(tái)依賴(lài)問(wèn)題需單獨(dú)解決。
BCL(基類(lèi)庫(kù)):提供開(kāi)發(fā)者能(在所有 .NET 框架中)依賴(lài)的大型代碼庫(kù),使其不必親自寫(xiě)這些代碼。
注意: 本篇只是簡(jiǎn)單介紹了 CLI,目的是讓讀者熟悉 C# 程序的執(zhí)行環(huán)境。此外,本篇還提及了本系列博文后面才會(huì)用到的一些術(shù)語(yǔ)。在時(shí)機(jī)合適的時(shí)候,我會(huì)專(zhuān)門(mén)總結(jié) CLI 及其與 C# 的關(guān)系。
前面說(shuō)過(guò),C# 編譯器將 C# 代碼轉(zhuǎn)換成 CIL 代碼而不是機(jī)器碼。處理器只理解機(jī)器碼,所以 CIL 代碼必須先轉(zhuǎn)換成機(jī)器碼才能由處理器執(zhí)行??捎?CIL 反匯編程序?qū)⒊绦蚣鈽?gòu)為 CIL。通常使用 Microsoft 特有的文件名 ILDASM 來(lái)稱(chēng)呼這種 CIL 反匯編程序(ILDASM 是 IL Disassembler 的簡(jiǎn)稱(chēng)),它能對(duì)程序集執(zhí)行反匯編,提取 C# 編譯器生成的CIL。
反匯編 .NET 程序集的結(jié)果比機(jī)器碼更易理解。許多開(kāi)發(fā)人員害怕即使別人沒(méi)有拿到源代碼,程序也容易被反匯編并曝光其算法。其實(shí)無(wú)論是否基于 CLI,任何程序防止反編譯唯一安全的方法就是禁止訪問(wèn)編譯好的程序(例如只在網(wǎng)站上存放程序,不把它分發(fā)到用戶(hù)機(jī)器)。
但假如目的只是減小別人獲得源代碼的可能性,可考慮使用一些混淆器(obfuscator)產(chǎn)品。這種產(chǎn)品會(huì)打開(kāi) IL 代碼,轉(zhuǎn)換成一種功能不變但更難理解的形式。這可以防止普通開(kāi)發(fā)者訪問(wèn)代碼,使程序集難以被反編譯成容易理解的代碼。除非程序需要對(duì)算法進(jìn)行高級(jí)安全防護(hù),否則混淆器足矣。
myApp.dll 程序請(qǐng)參考這篇文章:https://www.vinanysoft.com/c-sharp-basics/introducing/start-with-hello-world/
安裝了 Visual Studio 之后 ILDASM 會(huì)默認(rèn)被安裝,位置是:C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A(隨便一個(gè)目錄)\bin\NETFX 4.8 Tools(隨便一個(gè)目錄)\x64
。
雙擊運(yùn)行 ildasm.exe,把 myApp.dll 拖進(jìn)去,如下圖:
雙擊即可查看到 IL 代碼,下面是 MANIFEST 的 IL 代碼
// Metadata version: v4.0.30319.assembly extern System.Runtime{ .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: .ver 4:2:1:0}.assembly extern System.Console{ .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: .ver 4:1:1:0}.assembly myApp{ .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) .custom instance void [System.Runtime]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows. // --- 下列自定義特性會(huì)自動(dòng)添加,不要取消注釋 ------- // .custom instance void [System.Runtime]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 ) .custom instance void [System.Runtime]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 18 2E 4E 45 54 43 6F 72 65 41 70 70 2C 56 // ....NETCoreApp,V 65 72 73 69 6F 6E 3D 76 33 2E 30 01 00 54 0E 14 // ersion=v3.0..T.. 46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C 61 79 // FrameworkDisplay 4E 61 6D 65 00 ) // Name. .custom instance void [System.Runtime]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 05 6D 79 41 70 70 00 00 ) // ...myApp.. .custom instance void [System.Runtime]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 05 44 65 62 75 67 00 00 ) // ...Debug.. .custom instance void [System.Runtime]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 31 2E 30 2E 30 2E 30 00 00 ) // ...1.0.0.0.. .custom instance void [System.Runtime]System.Reflection.AssemblyInformationalVersionAttribute::.ctor(string) = ( 01 00 05 31 2E 30 2E 30 00 00 ) // ...1.0.0.. .custom instance void [System.Runtime]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 05 6D 79 41 70 70 00 00 ) // ...myApp.. .custom instance void [System.Runtime]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 05 6D 79 41 70 70 00 00 ) // ...myApp.. .hash algorithm 0x00008004 .ver 1:0:0:0}.module myApp.dll// MVID: {29FC93A2-9A52-445C-A581-09AA5BCC11C7}.imagebase 0x00400000.file alignment 0x00000200.stackreserve 0x00100000.subsystem 0x0003 // WINDOWS_CUI.corflags 0x00000001 // ILONLY// Image base: 0x000002015D090000
.class private auto ansi beforefieldint
.class private auto ansi beforefieldinit myApp.Program extends [System.Runtime]System.Object{} // end of class myApp.Program
.ctor:void()
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed{ // 代碼大小 8 (0x8) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [System.Runtime]System.Object::.ctor() IL_0006: nop IL_0007: ret} // end of method Program::.ctor
Main:void(string[])
.method private hidebysig static void Main(string[] args) cil managed{ .entrypoint // 代碼大小 42 (0x2a) .maxstack 2 .locals init (valuetype [System.Runtime]System.DateTime V_0) IL_0000: nop IL_0001: ldstr "Hello World!" IL_0006: call void [System.Console]System.Console::WriteLine(string) IL_000b: nop IL_000c: ldstr "The current time is " IL_0011: call valuetype [System.Runtime]System.DateTime [System.Runtime]System.DateTime::get_Now() IL_0016: stloc.0 IL_0017: ldloca.s V_0 IL_0019: call instance string [System.Runtime]System.DateTime::ToString() IL_001e: call string [System.Runtime]System.String::Concat(string, string) IL_0023: call void [System.Console]System.Console::WriteLine(string) IL_0028: nop IL_0029: ret} // end of method Program::Main
最開(kāi)頭是清單(manifest)信息。其中不僅包括被反編譯的模塊的全名(myApp),還包括它依賴(lài)的所有模塊和程序集及其版本信息。
基于這樣的一個(gè) CIL 代碼清單,最有趣的可能就是能相對(duì)比較容易地理解程序所做的事情,這比閱讀并理解機(jī)器碼(匯編程序)容易多了。
上述代碼出現(xiàn)了對(duì) System.Console.WriteLine()
的顯式引用。 CIL 代碼清單包含許多外圍信息,但如果開(kāi)發(fā)者想要理解 C# 模塊(或任何基于 CLI 的程序)的內(nèi)部工作原理,但又拿不到源代碼,只要作者沒(méi)有使用混淆器,理解這樣的 CIL 代碼清單還是比較容易的。
事實(shí)上,一些免費(fèi)工具(比如 Red Gate Reflector,ILSpy,JustDecompile,dotPeek 和 CodeReflect)都能將 CIL 自動(dòng)反編譯成 C#。
ILSpy 的地址:https://github.com/icsharpcode/ILSpy
雙擊運(yùn)行 ILSpy.exe,把 myApp.dll 拖進(jìn)去,如下圖:
注意: 反匯編(disassemble)和反編譯(decompile)的區(qū)別。反匯編得到的是匯編代碼,反編譯得到的是所用語(yǔ)言的源代碼。
聯(lián)系客服