日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

解密微軟中間語言:MSIL Xinsoft

系統(tǒng) 2229 0


在.Net框架中,公共語言基礎(chǔ)結(jié)構(gòu)使用公共語言規(guī)范來綁定不同的語言。通過要求不同的語言至少要實(shí)現(xiàn)公共類型系統(tǒng)(CTS)包含在公共語言規(guī)范中的部分,公共語言基礎(chǔ)結(jié)構(gòu)允許不同的語言使用.Net框架。因此在.Net框架中,所有的語言(C#,VB.Net,Effil.Net等)最后都被轉(zhuǎn)換為了一種通用語言:微軟中間語言(MSIL)。

MSIL是將.Net代碼轉(zhuǎn)化為機(jī)器語言的一個(gè)中間過程。它是一種介于高級(jí)語言和基于Intel的匯編語言的偽匯編語言。當(dāng)用戶編譯一個(gè).Net程序時(shí),編譯器將源代碼翻譯成一組可以有效地轉(zhuǎn)換為本機(jī)代碼且獨(dú)立于CPU 的指令。當(dāng)執(zhí)行這些指令時(shí),實(shí)時(shí)(JIT)編譯器將它們轉(zhuǎn)化為CPU特定的代碼。由于公共語言運(yùn)行庫(kù)支持多種實(shí)時(shí)編譯器,因此同一段MSIL代碼可以被不同的編譯器實(shí)時(shí)編譯并運(yùn)行在不同的結(jié)構(gòu)上。從理論上來說,MSIL將消除多年以來業(yè)界中不同語言之間的紛爭(zhēng)。在.Net的世界中可能出現(xiàn)下面的情況:一部分代碼可以用Effil實(shí)現(xiàn),另一部分代碼使用C#或VB完成的,但是最后這些代碼都將被轉(zhuǎn)換為中間語言。這給程序員提供了極大的靈活性,程序員可以選擇自己熟悉的語言,并且再也不用為學(xué)習(xí)不斷推出的新語言而煩惱了。

解密微軟中間語言的系列文章將通過一些簡(jiǎn)單易懂的方式來揭示中間語言的復(fù)雜原理。這些原理通過詳細(xì)的例子來闡述。
<!-- leadcode('Content823012'); //-->

解密微軟中間語言MSIL之中間語言概述 Xinsoft,2004-03-02 08:27:25

在.Net框架中,公共語言基礎(chǔ)結(jié)構(gòu)使用公共語言規(guī)范來綁定不同的語言。通過要求不同的語言至少要實(shí)現(xiàn)公共類型系統(tǒng)(CTS)包含在公共語言規(guī)范中的部分,公共語言基礎(chǔ)結(jié)構(gòu)允許不同的語言使用.Net框架。因此在.Net框架中,所有的語言(C#,VB.Net,Effil.Net等)最后都被轉(zhuǎn)換為了一種通用語言:微軟中間語言(MSIL)。

MSIL是將.Net代碼轉(zhuǎn)化為機(jī)器語言的一個(gè)中間過程。它是一種介于高級(jí)語言和基于Intel的匯編語言的偽匯編語言。當(dāng)用戶編譯一個(gè).Net程序時(shí),編譯器將源代碼翻譯成一組可以有效地轉(zhuǎn)換為本機(jī)代碼且獨(dú)立于CPU 的指令。當(dāng)執(zhí)行這些指令時(shí),實(shí)時(shí)(JIT)編譯器將它們轉(zhuǎn)化為CPU特定的代碼。由于公共語言運(yùn)行庫(kù)支持多種實(shí)時(shí)編譯器,因此同一段MSIL代碼可以被不同的編譯器實(shí)時(shí)編譯并運(yùn)行在不同的結(jié)構(gòu)上。從理論上來說,MSIL將消除多年以來業(yè)界中不同語言之間的紛爭(zhēng)。在.Net的世界中可能出現(xiàn)下面的情況:一部分代碼可以用Effil實(shí)現(xiàn),另一部分代碼使用C#或VB完成的,但是最后這些代碼都將被轉(zhuǎn)換為中間語言。這給程序員提供了極大的靈活性,程序員可以選擇自己熟悉的語言,并且再也不用為學(xué)習(xí)不斷推出的新語言而煩惱了。

解密微軟中間語言的系列文章將通過一些簡(jiǎn)單易懂的方式來揭示中間語言的復(fù)雜原理。這些原理通過詳細(xì)的例子來闡述。在一些例子中同時(shí)給出了源代碼和中間代碼,通過比較源代碼和中間代碼,我們可以更好地理解編譯器的局限性,指導(dǎo)我們編寫出更好更快的代碼。


微軟中間語言概述


1.用中間語言編寫的一個(gè)簡(jiǎn)單程序

讓我們從經(jīng)典的Hello World例子開始。首先在一個(gè)文本編輯器中輸入以下的代碼,并保存為HelloWorld.il:

.assembly HelloWorldIL {}
.method static void HelloWorld()
{
.entrypoint
ldstr "Hello World."
call void [mscorlib]System.Console::WriteLine(class System.String)
ret
}



在一個(gè)中間語言程序中,如果某一行以“.”開始,則代表這是一個(gè)傳輸給匯編工具的指令,該指令要求匯編工具執(zhí)行某些操作,例如生成一個(gè)函數(shù)或類。而沒有以“.”開始的行是中間語言的代碼。在中間語言中方法通過匯編命令method來定義,匯編命令后跟方法的返回值、名稱和參數(shù)。方法體被包含在{}中。例子中的ret代表該方法的結(jié)束。

一個(gè)中間語言文件可以包含很多函數(shù),匯編工具沒有辦法分辨應(yīng)該首先執(zhí)行哪一個(gè)方法。在諸如C#或VB這一類高級(jí)語言中,程序的入口方法通常都有特定的名稱,例如在C#中的public static void Main()。這就是上面的匯編工具發(fā)出錯(cuò)誤提示的原因。在中間語言中,第一個(gè)被執(zhí)行的方法被稱為入口函數(shù)(EntryPoint Function)。為了告訴匯編工具HelloWorld是入口函數(shù),我們需要在代碼中增加一條匯編命令entrypoint,該命令可以放在方法體中的任何位置。需要注意的是在一個(gè)程序集中只能有一個(gè)入口函數(shù)。

中間語言代碼通常被編譯成一個(gè)模塊,該模塊隸屬于一個(gè)程序集。在.Net中模塊和程序集的概念非常重要,因此開發(fā)人員需要很清楚地了解它們。在后面的文章中我們將詳細(xì)討論.Net程序的結(jié)構(gòu)。通過在代碼中加入assembly命令,可以告訴匯編工具中間代碼隸屬于那個(gè)程序集。assembly命令的格式如下:

.assembly <程序集名稱> {}



需要注意在method命令后加入了static關(guān)鍵字,這是因?yàn)槊總€(gè)入口函數(shù)必須是靜態(tài)的,例如在C#中我們將Main方法定義為public static void Main()。

接下來我們需要調(diào)用WriteLine方法將HelloWorld字符串輸出到屏幕。通過使用call指令(Instruction)我們可以達(dá)到這個(gè)目的。指令的格式如下:

call <return type> <namespace>.<class name>::<method name>



這里我們可以看到當(dāng)調(diào)用一個(gè)方法時(shí),中間語言和其他的編程語言有很大的區(qū)別。在中間語言中,如果需要調(diào)用一個(gè)方法,需要指定方法的全名,包括他的名稱域(namespace)、類名、返回值類型和參數(shù)的數(shù)據(jù)類型。這樣就保證了匯編工具能夠找到正確的方法。

在調(diào)用WriteLine方法時(shí)需要一個(gè)字符串參數(shù)。所有傳遞給方法或函數(shù)的參數(shù)都被保存在內(nèi)存的堆棧中。在中間語言中有一個(gè)指令ldstr可以從堆棧中加載一個(gè)字符串。(堆棧是內(nèi)存中的一塊區(qū)域,它被用于將參數(shù)傳輸給方法,在后面我們會(huì)詳細(xì)討論堆棧的問題)。所有的方法都從堆棧中獲取它們的參數(shù),因此ldstr指令是必不可少的。ldstr指令的格式如下所示:

ldstr <parameter string>



我們可以用ILAsm.exe來編譯這個(gè)程序。在運(yùn)行ILAsm.exe之前,首先需要確認(rèn)一下該程序已經(jīng)包含在了Windows操作系統(tǒng)的Path環(huán)境變量中。ILAsm.exe 可在下面的路徑中找到:

%windir%\Microsoft.NET\Framework\v1.0.xxxx



其中xxxx是正在使用的.NET框架的內(nèi)部版本號(hào)。例如我使用的版本號(hào)是3705,則應(yīng)該如下設(shè)置Path環(huán)境變量:

Set Path = %Path%;c:\Windows\Microsoft.NET\Framework\v1.0.3705



然后運(yùn)行cmd.exe(開始->運(yùn)行->輸入cmd->按下確認(rèn)鍵)。在彈出的命令窗口中輸入:

J:\Testcode>ilasm HelloWorld.il



匯編代碼后運(yùn)行程序就可以看到Hello World.的輸出。

通過上面的例子,我們了解了中間語言的程序結(jié)構(gòu),一些命令和指令。同時(shí)需要提醒大家的是中間語言是區(qū)分大小寫的。

2.改進(jìn)的HelloWorld例子

在.Net中的所有語言都是面向?qū)ο蟮恼Z言,但是上面的HelloWorld例子是一個(gè)結(jié)構(gòu)化的例子。下面讓我們來看一下如何將它轉(zhuǎn)化為面向?qū)ο蟮拇a。在面向?qū)ο蟮木幊讨校覀儗⒉僮鞫x在類中。為了將上面的HelloWorld例子轉(zhuǎn)化為面向?qū)ο蟮拇a,可以使用class命令:

.class HelloWorld
{
}



class命令后緊跟的是類的名稱。類的名稱在中間語言中是可選的。同時(shí)我們還需要為該指令添加一些屬性,例如存取控制類在內(nèi)存中的布局和互用性等。這樣代碼就變成了:

.assembly HelloWorldIL {}
.class public auto ansi HelloWorld extends [mscorlib]System.Object
{
.method public hidebysig static void HelloWorld() cil managed
{
.entrypoint
ldstr "Hello World."
call void [mscorlib]System.Console::WriteLine(class System.String)
ret
}
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
ldarg.0
call instance void [mscorlib]System.Object::.ctor()
ret
}
}



在代碼中用到了三個(gè)屬性:

· public:public是訪問控制屬性,它表明了對(duì)于訪問該類的成員沒有限制。

· auto:auto屬性表明了當(dāng)類被加載到內(nèi)存中時(shí),在內(nèi)存中的布局是由公共運(yùn)行庫(kù)而不是程序決定的。

· ansi:指定ansi屬性是為了在沒有被管理和被管理的代碼之間實(shí)現(xiàn)無縫的轉(zhuǎn)化。在.Net中,那些不可直接應(yīng)用在公共語言基礎(chǔ)設(shè)施之上的代碼被稱為沒有被管理的代碼,例如C、C++和VB6的代碼。我們需要一個(gè)屬性來處理被管理的代碼和沒有被管理的代碼之間的互用性。在被管理的代碼中,字符串用雙字節(jié)的Unicode字符表示,而在被管理的代碼中,字符串有可能用單字節(jié)的ANSI字符表示。指定了ansi屬性就可以在不同的代碼間轉(zhuǎn)化字符串了。

我們知道在.Net框架中,所有的類都直接或間接地繼承了System.Object類。在代碼中我們明確指定了HelloWorld繼承了System.Object。

在HelloWorld方法中加入了public、hidebysig、cil managed屬性,下面是對(duì)這些屬性的解釋:

· public:在C#或VB.Net中,當(dāng)我們定義一個(gè)方法時(shí),需要指定方法的訪問修飾符。訪問修飾符可以是public、protected、internal或private 。

· hidebysig:一個(gè)類可以繼承其他的類,hidebysig屬性保證當(dāng)前類中的方法在作為父類時(shí)不會(huì)被子類繼承。例如如果HelloWorldChild類繼承了HelloWorld類,在HelloWorldChild中不會(huì)看到HelloWorld方法。

· cil managed:該屬性將在后面討論。

在高級(jí)語言中(C#,VB.Net等),每個(gè)類必須有構(gòu)造函數(shù),而且構(gòu)造函數(shù)的第一行需要調(diào)用基類的構(gòu)造函數(shù)。如果類中沒有構(gòu)造函數(shù),基類的構(gòu)造函數(shù)將被自動(dòng)調(diào)用。通常這是由編譯器自動(dòng)完成的,現(xiàn)在我們要在的代碼中加入構(gòu)造函數(shù),該構(gòu)造函數(shù)通過.ctor命令調(diào)用基類的構(gòu)造函數(shù)。


小結(jié)


本文我們從經(jīng)典的Hello World例子開始,通過實(shí)例了解了微軟中間語言的基本語法規(guī)則以及中間語言與其他開發(fā)語言的關(guān)系。在下一篇文章中,我們將在此基礎(chǔ)上,運(yùn)用實(shí)例程序講述.net應(yīng)用程序的格式和結(jié)構(gòu)等內(nèi)容。
<!-- leadcode('Content823014'); //-->

解密微軟中間語言MSIL之解析.Net應(yīng)用程序 Xinsoft,2004-03-02 08:29:37

.Net應(yīng)用程序由一個(gè)或多個(gè)可執(zhí)行程序組成,每個(gè)可執(zhí)行程序中都有元數(shù)據(jù)和可管理的代碼。.Net應(yīng)用程序通常被稱為程序集。一個(gè)程序集由一個(gè)或多個(gè)部署在一起的文件組成,它通常保存一份清單,該清單確定程序集標(biāo)識(shí),指定組成程序集實(shí)現(xiàn)的文件,指定組成程序集的類型和資源,列舉對(duì)其他程序集的編譯時(shí)依賴項(xiàng),并指定為保證程序集正確運(yùn)行所需要的權(quán)限集。在運(yùn)行時(shí)使用此信息來解析引用,強(qiáng)制版本綁定策略,并驗(yàn)證已加載的程序集的完整性。

不含程序集清單的中間語言文件被稱為模塊。程序集可以是單模塊的,也可以是多模塊的。每個(gè)程序集只能夠有一個(gè)清單,該清單駐留在擁有入口函數(shù)的模塊中。圖一顯示了一個(gè)單模塊程序集的結(jié)構(gòu):



圖一 單模塊程序集的結(jié)構(gòu)


從圖一中我們可以看到程序集中包含了程序集標(biāo)識(shí)段,元數(shù)據(jù)段和中間語言代碼段。讓我們來看一下HelloWorld中的代碼,其中的assembly命令代表是程序集標(biāo)識(shí)段,但是在其中沒有包含版本、名稱、區(qū)域性、安全性和模塊信息。讓我們?cè)诖a中加入下面的行(代碼重用黑體標(biāo)出):

.assembly DemystifyingILChapter1
{
.hash algorithm 0x00008004
.ver 1:0:0:0}
.class public auto ansi HelloWorld extends [mscorlib]System.Object
{

}



上面的代碼擴(kuò)充了assembly命令的內(nèi)容。事實(shí)上assembly命令可以包含很多其它的命令,在上面的代碼中使用了hash和ver命令。

· hash:該命令告訴VSE實(shí)現(xiàn)安全性所使用的哈希算法。數(shù)字0x00008004表示使用SHA1,這也是系統(tǒng)的缺省設(shè)置。

· ver:程序集的版本號(hào),由四個(gè)32位整數(shù)組成。

前面在討論.Net應(yīng)用程序的格式的時(shí)候,曾提到在一個(gè)可執(zhí)行的應(yīng)用程序中可以包含對(duì)其它模塊的引用。到目前為止我們還沒有使用任何命令來告知程序集應(yīng)該生成哪一個(gè)模塊。在HelloWorld例子中我們引用了一個(gè)外部程序mscorlib。那么匯編工具正確編譯了代碼嗎?答案是肯定的。ILAsm能夠自動(dòng)將HelloWorld中的代碼定義為基本模塊,并在其中引用mscorlib程序集。在下面的代碼中我們將使用命令告訴編譯器如何集成模塊。我們使用的命令仍然是assembly,不過現(xiàn)在帶上了extern屬性。為了正確地引用一個(gè)程序集,至少需要?jiǎng)?chuàng)作者的公鑰或公鑰Token以及程序集的版本信息。公鑰Token是SHA1哈希碼的低八位字節(jié),它能夠唯一確定一個(gè)程序集。我們可以在C:\Winnt\assembly目錄下找到程序集的相關(guān)信息(如圖二所示)。

圖二 程序集的相關(guān)信息


在C:\Winnt\assembly中可以看到計(jì)算機(jī)上安裝的mscorlib版本是1.0.3300.0。公鑰Token是B77A5C561934E089。也許你計(jì)算機(jī)上安裝了不同版本的mscorlib。在下面的代碼中你需要用正確的版本號(hào)替代1.0.3300.0。

.module Hello.exe
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89)
.ver 1:0:3300:0
}
.assembly DemystifyingILChapter1
{

}
.class public auto ansi HelloWorld extends [mscorlib]System.Object
{

}



現(xiàn)在我們擁有了一個(gè)正確的.Net應(yīng)用程序,該程序中提供了.Net框架所有必要的信息。下面我們將用C#和VB.Net中編寫HelloWorld程序,并將它們編譯成中間代碼。


在高級(jí)語言中實(shí)現(xiàn)HelloWorld例子程序


我們將在C#和VB.Net中編寫HelloWorld程序,將它們編譯成可執(zhí)行程序,然后用反匯編工具ildasm.exe將執(zhí)行程序反匯編為中間代碼,把它們和上面的例子進(jìn)行比較。

C#
public class HelloWorld
{
public static void Main()
{
System.Console.WriteLine("Hello World.");
}
}



當(dāng)生成可執(zhí)行文件后,用ildasm.exe來反匯編才生成的HelloWorld.exe。在命令行中輸入:

ildasm /out=HelloWorld.txt HelloWorld.exe



查看生成的HelloWorld.txt文件,我們可以看到:

// Microsoft (R) .NET Framework IL Disassembler. Version 1.0.3705.0
// Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 1:0:3300:0
}
.assembly HelloWorld
{
// --- The following custom attribute is added automatically, ---
// --- do not uncomment -------
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::
// .ctor(bool, bool) = ( 01 00 00 01 00 00 )
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module HelloWorld.exe
// MVID: {E63F9CA9-D4C4-4826-9BE1-2B0EE3694289}
.imagebase 0x00400000
.subsystem 0x00000003
.file alignment 512
.corflags 0x00000001
// Image base: 0x03000000
//
// ============== CLASS STRUCTURE DECLARATION ==================
//
.class public auto ansi beforefieldinit HelloWorld
extends [mscorlib]System.Object
{
} // end of class HelloWorld
// =============== CLASS MEMBERS DECLARATION ===================
// note that class flags, 'extends' and 'implements' clauses
// are provided here for information only

.class public auto ansi beforefieldinit HelloWorld
extends [mscorlib]System.Object
{
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 11 (0xb)
.maxstack 1
IL_0000: ldstr "Hello World."
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method HelloWorld::Main
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 7 (0x7)
.maxstack 1
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method HelloWorld::.ctor
} // end of class HelloWorld
// =============================================================
//*********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file HelloWorld.res



如果仔細(xì)察看一下上面的文件,我們會(huì)發(fā)現(xiàn)其中大部分的命令和指令在前面已經(jīng)作了闡述。如果用VB.Net來編寫HelloWorld程序,編譯器的輸出基本上和C#一樣。因此雖然使用的語言不一樣,但是源代碼最終會(huì)編譯成相同的中間代碼,因此由于語言之間的差別產(chǎn)生的種種問題在.Net中都不足為道了。


小結(jié)


本文我們延續(xù)使用了《解密微軟中間語言MSIL之中間語言概述 》Hello World例子程序,詳細(xì)分析了.net應(yīng)用程序的格式和結(jié)構(gòu)。接下來,我們將在下一篇文章中完成對(duì)程序的調(diào)試工作。
<!-- leadcode('Content823016'); //-->

解密微軟中間語言MSIL之調(diào)試程序(1) Xinsoft,2004-03-02 08:31:39

沒有程序員敢保證沒有經(jīng)過調(diào)試的代碼絕對(duì)沒有錯(cuò)誤,無論他/她智商多么高,開發(fā)出來的代碼總是或多或少帶有一些錯(cuò)誤(當(dāng)然是無意的:-))。這些錯(cuò)誤可能是簡(jiǎn)單的語法錯(cuò)誤或者復(fù)雜的邏輯錯(cuò)誤。因此和其他語言一樣,我們需要中間語言的調(diào)試工具/方法。由于中間語言是比較底層的語言,因此調(diào)試工具/方法對(duì)于程序員來說更加重要。

最簡(jiǎn)單的調(diào)試方法莫過于在程序中加入WriteLine方法,但是在中間語言中使用這種方法非常繁瑣,因?yàn)檎{(diào)用WriteLine方法需要三行代碼:

ldstr "Hello World"
call void [mscorlib]System.Console::WriteLine(string)
ret



如果你需要調(diào)試一個(gè)比較大的應(yīng)用程序,顯然上面的方法行不通。幸運(yùn)的是,微軟在.Net中提供了兩個(gè)調(diào)試工具用于調(diào)試.Net的程序集。


調(diào)試工具


.Net提供了兩個(gè)非常好的調(diào)試工具,分別是CLR調(diào)試器和運(yùn)行庫(kù)調(diào)試器:

· CLR調(diào)試器(DbgCLR.exe):提供圖形界面幫助開發(fā)者調(diào)試程序。

· 運(yùn)行庫(kù)調(diào)試器(Cordbg.exe):使用運(yùn)行庫(kù)調(diào)試API,通過命令行對(duì)程序進(jìn)行調(diào)試。

初看這兩個(gè)工具提供的是相同的功能。但是事實(shí)上它們的功能還是有所區(qū)別的。DbgCLR.exe是一個(gè)Windows應(yīng)用程序,提供了用戶界面,并且很容易定義斷點(diǎn)和即時(shí)窗口;而Cordbg.exe使一個(gè)命令行工具,它允許開發(fā)人員通過調(diào)試腳本的方式來調(diào)試程序。本文側(cè)重于介紹DgbCLR.exe。


CLR調(diào)試器


為了展示CLR調(diào)試器的功能,我們需要編寫一個(gè)帶有錯(cuò)誤的程序。下面是一個(gè)C#的程序,編譯后我們將通過利用ildasm.exe獲得它的中間語言代碼。

using System;
namespace ErrorneousApp
{
class ErrorneousClass
{
[STAThread]
static void Main(string[] args)
{
int operand1;
int operand2;
int sum;
operand1 = int.Parse(args[0]);
operand2 = int.Parse(args[1]);
sum = Add(operand1 , operand2);
Console.WriteLine(sum);
}
private static int Add(int op1, int op2)
{
// 很明顯的邏輯錯(cuò)誤 :-)
return 5 * (op1 + op2);
}
}
}



我們可以看到在這段代碼中存在兩個(gè)錯(cuò)誤:

· 邏輯錯(cuò)誤:Add方法返回了不正確的值

· 方法錯(cuò)誤:如果調(diào)用方法是沒有提供兩個(gè)參數(shù),程序?qū)?bào)錯(cuò)。

需要提醒大家的是,CLR調(diào)試器能夠調(diào)試的錯(cuò)誤遠(yuǎn)遠(yuǎn)不止以上兩種。現(xiàn)在編譯這段代碼,生成可執(zhí)行文件,然后用反匯編工具生成中間語言,其中去除了一些不重要的代碼:

.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89) .ver 1:0:3300:0
}
.assembly ErrorneousApp
{
.ver 1:0:1026:17140
}
.module ConsoleApplication1.exe
.namespace ErrorneousApp
{
.class private auto ansi beforefieldinit ErrorneousClass
extends [mscorlib]System.Object
{
.method private hidebysig static void
Main(string[] args) cil managed
{
.entrypoint
.locals init ([0] int32 operand1, [1] int32 operand2, [2] int32 sum)
ldarg.0
ldc.i4.0
ldelem.ref
call int32 [mscorlib]System.Int32::Parse(string)
stloc.0
ldarg.0
ldc.i4.1
ldelem.ref
call int32 [mscorlib]System.Int32::Parse(string)
stloc.1
ldc.i4.5
ldloc.0
ldloc.1
add
mul
stloc.2
ldloc.2
call void [mscorlib]System.Console::WriteLine(int32)
ret
} // end of method ErrorneousClass::Main

.method private hidebysig static int32
Add(int32 op1, int32 op2) cil managed
{
.locals init ([0] int32 CS$00000003$00000000)
ldc.i4.5
ldarg.0
ldarg.1
add
mul
stloc.0
ldloc.0
ret
} // end of method ErrorneousClass::Add
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
ldarg.0
call instance void [mscorlib]System.Object::.ctor()
ret
} // end of method ErrorneousClass::.ctor
} // end of class ErrorneousClass
} // end of namespace ErrorneousApp



在使用代碼以前先解釋一下代碼。我們使用了Consle.WriteLine和Int.Parse方法,這兩個(gè)方法定義在外部程序集mscorlib中,因此我們需要?jiǎng)?chuàng)建一個(gè)對(duì)它們的引用。通過使用帶external參數(shù)的assembly命令我們可以達(dá)到這個(gè)目的。

然后代碼中通過class命令定義了ErrorneousClass類,并在該類中用method命令創(chuàng)建了Main方法,該方法是程序的入口方法。接著用local命令初始化了三個(gè)本地變量:

.locals init ([0] int32 operand1, [1] int32 operand2, [2] int32 sum)



接下來需要給這些本地變量賦值。代碼通過ldelm命令從程序的參數(shù)數(shù)組中提取相應(yīng)的值賦給變量,但是在賦值之前需要用ldarg將參數(shù)(由指定索引值引用)加載到堆棧上,然后ldc命令將真正的值推送到計(jì)算堆棧上。ldc.i4.0的表示將0作為int32類型推送到計(jì)算堆棧上。接下來代碼調(diào)用System.Int32.Parse方法將字符串轉(zhuǎn)換為整數(shù)。當(dāng)所有的變量都完成初始化后,代碼調(diào)用Add方法計(jì)算operand1和operand2的和。最后通過調(diào)用System.Console.WriteLine方法來顯示計(jì)算結(jié)果。

Add方法的實(shí)現(xiàn)也很簡(jiǎn)單。需要提醒大家的是由于MSIL工作在基于堆棧的內(nèi)存結(jié)構(gòu)上,因此最后使用的變量需要最先保存。由于算法是將兩個(gè)數(shù)相加在乘以5,因此需要先用ldc.i4.5命令將5放入堆棧中,然后加載兩個(gè)被操作的數(shù),使用add命令計(jì)算它們的和(add命令自動(dòng)將堆棧中最頂層的兩個(gè)數(shù)相加),將得到的結(jié)果放回堆棧中。最后代碼調(diào)用mul命令將兩個(gè)數(shù)相乘。

如果仔細(xì)察看中間代碼,我們會(huì)發(fā)現(xiàn)在Main方法中并沒有直接調(diào)用Add方法。這是因?yàn)镃#編譯器用內(nèi)嵌代碼替代了對(duì)靜態(tài)方法的調(diào)用。


調(diào)試MSIL代碼


調(diào)試中間代碼的同時(shí)我們需要程序數(shù)據(jù)庫(kù)文件ErrorneuosApp.pdb,在該文件中包含了調(diào)試和工程狀態(tài)信息。我們可以利用ilasm工具來獲得該文件。

ilasm errorneousApp.il /debug



現(xiàn)在運(yùn)行DbgClr.exe并打開ErroneousApp.il文件(如圖一所示),然后設(shè)定需要調(diào)試的可執(zhí)行文件(如圖二所示)。現(xiàn)在就可以調(diào)試程序了。開發(fā)人員可以設(shè)定斷點(diǎn),查看寄存器和內(nèi)存中的值等。下面讓我們一一了解這些功能。


圖一 CLR調(diào)試器



圖二 選擇要調(diào)試的程序
<!-- leadcode('Content823018'); //-->

解密微軟中間語言MSIL之調(diào)試程序(2) Xinsoft,2004-03-02 08:34:14

中斷程序的執(zhí)行

CLR調(diào)試器最基本的目的是顯示被調(diào)試程序的狀態(tài)信息。有很多工具可以監(jiān)視和修改程序的狀態(tài),但是大部分的工具在程序中斷時(shí)才能夠使用。調(diào)試器在程序運(yùn)行到一個(gè)斷點(diǎn)或遇到錯(cuò)誤時(shí)將中斷程序的運(yùn)行。

開發(fā)人員可以點(diǎn)擊中間代碼中任何可執(zhí)行的行左邊的空白處就可以設(shè)置位置斷點(diǎn)了;也可以通過菜單上調(diào)試->新斷點(diǎn)來設(shè)置位置斷點(diǎn)(如圖三所示)。第二種方法允許開發(fā)人員設(shè)定帶有條件的位置斷點(diǎn),開發(fā)人員可以設(shè)定三種類型的斷點(diǎn):

· "函數(shù)斷點(diǎn)"使程序在執(zhí)行到達(dá)指定函數(shù)內(nèi)的指定位置時(shí)中斷。

· "文件斷點(diǎn)"使程序在執(zhí)行到達(dá)指定文件內(nèi)的指定位置時(shí)中斷。

· "地址斷點(diǎn)"使程序在執(zhí)行到達(dá)指定的內(nèi)存地址時(shí)中斷。


圖三 設(shè)置新斷點(diǎn)

開發(fā)人員還可以根據(jù)設(shè)定條件判斷是否需中斷程序,或者根據(jù)斷點(diǎn)的命中次數(shù)來確定是否在斷點(diǎn)中斷程序(點(diǎn)擊次數(shù)是斷點(diǎn)被點(diǎn)擊的次數(shù)。對(duì)于位置斷點(diǎn),它是程序執(zhí)行達(dá)到指定位置并滿足可能有的斷點(diǎn)條件的次數(shù),命中次數(shù)決定執(zhí)行中斷前點(diǎn)擊發(fā)生的次數(shù))。當(dāng)程序執(zhí)行到斷點(diǎn)位置時(shí),調(diào)試器就會(huì)就會(huì)計(jì)算實(shí)現(xiàn)設(shè)定好的表達(dá)式。如果表達(dá)式的結(jié)果為真(在"斷點(diǎn)條件"對(duì)話框中是"為真")或者表達(dá)式的值更改時(shí)(在"斷點(diǎn)條件"對(duì)話框中為"已更改")則調(diào)試器點(diǎn)擊斷點(diǎn),如果點(diǎn)擊斷點(diǎn)并且點(diǎn)擊次數(shù)正確,調(diào)試器將中斷程序執(zhí)行。

局部變量窗口

在局部變量窗口中顯示了在當(dāng)前上下文關(guān)系中的局部變量變量和它們的值。開發(fā)人員可以通過選擇調(diào)用堆棧或線程來切換上下文。使用局部變量窗口的好處在于能夠在程序運(yùn)行的時(shí)候改變某個(gè)變量的值,開發(fā)人員只需要雙擊變量的值,輸入新的值就可以了。


圖四 局部變量窗口


快速監(jiān)視窗口

開發(fā)人員可以通過快速監(jiān)視窗口獲得一個(gè)變量或者表達(dá)式的值。

監(jiān)視窗口

監(jiān)視窗口同快速監(jiān)視窗口的功能類似,唯一不同的是快速監(jiān)視窗口是模態(tài)窗口。

寄存器窗口

寄存器窗口中顯示當(dāng)前寄存器中的狀態(tài)。最近被改變了的寄存器值用紅色顯示。

圖五 寄存器窗口


調(diào)用堆棧窗口

通過調(diào)用堆棧窗口,開發(fā)人員可以看到當(dāng)前在堆棧中的方法或函數(shù)。除了方法或函數(shù)的名稱外,窗口中還提供了諸如參數(shù)名稱和類型、參數(shù)值、行號(hào)和偏移量等信息。


圖六 調(diào)用堆棧窗口


內(nèi)存窗口

開發(fā)人員可以用內(nèi)存窗口來監(jiān)視大的緩沖區(qū)、字符串或者其他在監(jiān)視窗口中無法完全顯示的數(shù)據(jù)。如果開發(fā)人員希望跳轉(zhuǎn)到內(nèi)存的某個(gè)地址,只需要在地址欄填入相應(yīng)的地址或者表達(dá)式即可。缺省情況下,內(nèi)存窗口動(dòng)態(tài)監(jiān)視表達(dá)式,當(dāng)表達(dá)式的結(jié)果發(fā)生變化時(shí),地址窗口也會(huì)相應(yīng)發(fā)生變化。

調(diào)試程序

現(xiàn)在我們有了這些調(diào)試工具,調(diào)試中間代碼程序就變得很容易。首先打開局部變量窗口監(jiān)視局部變量。如果我們輸入的參數(shù)是3和4,當(dāng)單步執(zhí)行到sum的值從0變?yōu)?5時(shí),可以注意到代碼中不僅有加法,而其還有乘法。我們可以去掉乘法來修正這個(gè)錯(cuò)誤。修改的代碼如下所示:

.method private hidebysig static void
Main(string[] args) cil managed
{

//ldc.i4.5
ldloc.0
ldloc.1
add
//mul
stloc.2

} // end of method ErrorneousClass::Main



調(diào)試庫(kù)文件

在CRL調(diào)試器中,開發(fā)人員可以逐語句執(zhí)行庫(kù)文件。如果需要調(diào)試庫(kù)文件的話,開發(fā)人員需要一個(gè)測(cè)試裝置(Test Harness)文件,引用了庫(kù)中的類或方法的可執(zhí)行文件,以及可執(zhí)行文件和庫(kù)文件的程序數(shù)據(jù)庫(kù)(.pdb)文件。開發(fā)人員需要在CLR調(diào)試器中打開測(cè)試裝置文件,然后在調(diào)試時(shí)就可以進(jìn)入需要調(diào)試的外部庫(kù)的代碼行。


小結(jié)


在這篇文章中我們介紹了用與調(diào)試中間語言程序的調(diào)試工具。盡管中間語言平時(shí)不會(huì)經(jīng)常使用到,但是對(duì)中間語言的熟悉可以幫助程序員深刻理解.Net程序是如何工作的,并且根據(jù).Net程序的特點(diǎn)編寫出快速高效的代碼。同時(shí)中間語言也可以應(yīng)用到反向工程中,當(dāng)然這涉及到知識(shí)產(chǎn)權(quán)的問題。

解密微軟中間語言:MSIL Xinsoft


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 平山县| 克拉玛依市| 枣阳市| 贡山| 耿马| 始兴县| 长丰县| 桐梓县| 平乐县| 邛崃市| 缙云县| 苗栗县| 团风县| 行唐县| 津市市| 陵川县| 沁阳市| 玛曲县| 石楼县| 仙桃市| 弥渡县| 永春县| 旅游| 金平| 墨江| 容城县| 镇赉县| 滨州市| 桂东县| 乐业县| 临沭县| 巩义市| 文成县| 兖州市| 龙山县| 渑池县| 基隆市| 忻城县| 柳林县| 汉中市| 邓州市|