.NET編譯技術(shù)內(nèi)幕(1)
2024-07-10 13:02:11
供稿:網(wǎng)友
通過msil了解clr的運(yùn)行原理
作者: wednesday, april 17 2002 2:12 pm
作為.net最低層次的公共基礎(chǔ),微軟中介語言(msil或il)對一般開發(fā)者具有非常重要的意義。除了好奇心以外,仔細(xì)研究應(yīng)用程序的il能讓你更為清楚地了解到公共語言運(yùn)行時(clr)執(zhí)行高級c#或vb.net代碼的基本原理,從而有助于你發(fā)現(xiàn)和解決一些比較細(xì)微的問題。
在這篇文章里,我將引領(lǐng)讀者了解il,學(xué)習(xí)有關(guān)的一些關(guān)鍵指令,同時對clr的操作機(jī)理做一點(diǎn)基礎(chǔ)性解釋。我不打算教你用il編程,而是分析一些il語法和語句使你對il有更多了解。
ildasm簡介
微軟的il拆卸實(shí)用程序ildasm.exe(通常位于/program files/microsoft.net/frameworksdk/bin目錄下)可以析構(gòu).net assembly(裝配)、根據(jù)你的要求從程序中抽取il代碼。對某一assembly調(diào)用該使用程序后,ildasm會給出該assembly中所有類和名稱空間的一個視圖,如圖a所示:
" target=_blank>http://www.zdnet.com.cn/i/developer/story/39032789/image001.gif
圖a
ildasm瀏覽assembly
當(dāng)你進(jìn)到某個類的成員或其方法,ildasm就會為你顯示該成員的il代碼。如果之前你曾經(jīng)看到過匯編器或j++字節(jié)碼,那么il可能在你看來會覺得有點(diǎn)眼熟。在另一方面,如果你僅對抽象的高級程序語言有所了解,那么il看起來更像是胡言亂語。
好,現(xiàn)在你知道如何窺視assembly的il代碼了,但這些代碼都意味著什么呢?在回答這個問題之前,首先讓我們先來了解下clr的有關(guān)知識。
虛擬cpu
對.net程序來說,.net clr在功能上就如同一塊虛擬的cpu,它執(zhí)行il代碼、操作數(shù)據(jù)。clr和真實(shí)的cpu類似之處在于它們都不直接操作內(nèi)存中的變量而是使用程序變量的臨時拷貝,clr把這些程序變量存放在堆棧上。從內(nèi)存拷貝某個變量到堆棧的行為稱做裝載(loading),而從堆棧拷回某個變量到內(nèi)存的行為則被稱做存儲(storing)。
所以把兩個數(shù)字相加的過程應(yīng)該是這樣的:
1.裝載第1個數(shù)字并把它推入堆棧。
2.裝載第2個數(shù)字并把它推入堆棧。
3.從堆棧中取出這兩個數(shù)字并把它們相加。
4.把結(jié)果存儲到內(nèi)存。
什么是堆棧?
理解il的關(guān)鍵是知道堆棧的工作原理。堆棧是一種抽象數(shù)據(jù)結(jié)構(gòu),其操作機(jī)理是后進(jìn)先出。當(dāng)你把新條目推進(jìn)堆棧時,已經(jīng)在堆棧內(nèi)的任何條目都會壓到堆棧的深處。同樣的,把一個條目從堆棧移出則會讓堆棧內(nèi)的其他條目都向堆棧的頂部移動。只有堆棧最頂端的條目能從堆棧中取出,條目離開堆棧的順序和它們被推進(jìn)堆棧的順序一樣。你不妨回想下自動售貨機(jī)的裝貨和取貨過程就明白了。
重要的il語句
既然你已經(jīng)明白了clr操作的基礎(chǔ)知識,下面我們就接著討論你面前的那些代碼。怎么?沒有看到什么代碼?那么請你看看這里列出的il代碼。
sidebar a: sample il listing
.method private instance void listen() cil managed
{
// code size 169 (0xa9)
.maxstack 4
.locals init ([0] unsigned int8[] buff,
[1] class [system]system.net.sockets.networkstream chatstream,
[2] class [system]system.net.sockets.tcpclient remoteclient,
[3] string strhandle,
[4] class chat.chatserver/textrelayer 'handler',
[5] class [mscorlib]system.threading.thread t)
il_0000: nop
il_0001: nop
.try
{
il_0002: br il_0091
il_0007: ldarg.0
il_0008: ldfld class [system]system.net.sockets.tcplistener chat.chatserver::listener
il_000d: callvirt instance class [system]system.net.sockets.tcpclient [system]system.net.sockets.tcplistener::accepttcpclient()
il_0012: stloc.2
il_0013: ldloc.2
il_0014: callvirt instance class [system]system.net.sockets.networkstream [system]system.net.sockets.tcpclient::getstream()
il_0019: stloc.1
il_001a: ldc.i4.s 26
il_001c: newarr [mscorlib]system.byte
il_0021: stloc.0
il_0022: ldloc.1
il_0023: ldloc.0
il_0024: ldc.i4.0
il_0025: ldc.i4.s 24
il_0027: callvirt instance int32 [system]system.net.sockets.networkstream::read(unsigned int8[],
int32,
int32)
il_002c: pop
il_002d: call class [mscorlib]system.text.encoding [mscorlib]system.text.encoding::get_ascii()
il_0032: ldloc.0
il_0033: callvirt instance string [mscorlib]system.text.encoding::getstring(unsigned int8[])
il_0038: stloc.3
il_0039: ldarg.0
il_003a: ldfld class chat.chatserver/incomingchatrequest chat.chatserver::requestconnect
il_003f: ldloca.s strhandle
il_0041: callvirt instance bool chat.chatserver/incomingchatrequest::invoke(string&)
il_0046: brtrue.s il_0051
il_0048: ldloc.2
il_0049: callvirt instance void [system]system.net.sockets.tcpclient::close()
il_004e: nop
il_004f: br.s il_008f
il_0051: nop
il_0052: ldloca.s strhandle
il_0054: ldarg.0
il_0055: ldflda class chat.chatserver/incomingtext chat.chatserver::relaytext
il_005a: ldloca.s chatstream
il_005c: newobj instance void chat.chatserver/textrelayer::.ctor(string&,
class chat.chatserver/incomingtext&,
class [system]system.net.sockets.networkstream&)
il_0061: stloc.s 'handler'
il_0063: ldloc.s 'handler'
il_0065: dup
il_0066: ldvirtftn instance void chat.chatserver/textrelayer::handlechat()
il_006c: newobj instance void [mscorlib]system.threading.threadstart::.ctor(object,
native int)
il_0071: newobj instance void [mscorlib]system.threading.thread::.ctor(class [mscorlib]system.threading.threadstart)
il_0076: stloc.s t
il_0078: ldarg.0
il_0079: ldfld class [system]system.collections.specialized.listdictionary chat.chatserver::chatthreads
il_007e: ldloc.3
il_007f: ldloc.s t
il_0081: callvirt instance void [system]system.collections.specialized.listdictionary::add(object,
object)
il_0086: nop
il_0087: ldloc.s t
il_0089: callvirt instance void [mscorlib]system.threading.thread::start()
il_008e: nop
il_008f: nop
il_0090: nop
il_0091: ldc.i4.1
il_0092: brtrue il_0007
il_0097: leave.s il_00a6
} // end .try
finally
{
il_0099: nop
il_009a: ldstr "listener thread aborted."
il_009f: call void [mscorlib]system.console::writeline(string)
il_00a4: nop
il_00a5: endfinally
} // end handler
il_00a6: nop
il_00a7: nop
il_00a8: ret
} // end of method chatserver::listen
你首先看見的是對當(dāng)前方法的il聲明,其中包括方法的名字,返回類型、參數(shù)列表以及附著于該方法的其他修飾關(guān)鍵詞(static/shared、public、virtual等等)。對象構(gòu)造器則被賦給一個特殊的名字:.ctor。
在il中,方法參數(shù)按照它們在參數(shù)列表中的位置依次被引用。如果方法是靜態(tài)或共享方法,那么參數(shù)0則是參數(shù)列表中的第1個參數(shù)。而對實(shí)例方法來說,參數(shù)0則是指向該方法所在類的實(shí)例的指針(me或者this)。方法中的所有局部變量都在.locals標(biāo)記的段落中以同樣的方式聲明。
在聲明所有的局部變量以后,程序的實(shí)際正文才開始。每條il指令,或opcode都可以根據(jù)你的喜好以一個il_ 標(biāo)記作為代碼行開頭。我們接下來再了解些更重要的il指令。
變量用法
以ld開頭的指令把變量從內(nèi)存裝載到堆棧供其操作。裝載指令有若干條,每一條裝載指令都操作特定類型的變量。以下就是其中的一些裝載指令:
ldc把一個數(shù)字常數(shù)裝入堆棧。這條指令有兩個修飾詞。第一個是類型標(biāo)識符,第二個是實(shí)際的數(shù)值。
ldloc把一個局部變量裝入堆棧。另外還有一條ldloca指令把一個局部變量的地址(而非變量的內(nèi)容)裝入堆棧。變量由它們在.locals節(jié)的位置標(biāo)識。這些指令裝載位置4及以后位置使用不同的語法,但是索引號會出現(xiàn)在指令中。
ldarg裝載成員的一個參數(shù),而ldarga指令則裝載參數(shù)的地址。變量由它們在.locals節(jié)中的位置標(biāo)識。這些指令裝載位置4及以后位置使用不同的語法,但是索引號仍然出現(xiàn)在指令中。
ldelem把數(shù)組元素裝入堆棧而且通常先于表示這個索引的其他裝載語句之前使用。
ldlen把一個數(shù)組的長度裝入堆棧。
ldfld和ldsfld把類域(成員變量)和靜態(tài)類域裝入堆棧。域由一個全名識別。
每一條裝載指令都有對應(yīng)的一條存儲指令,后者以st開頭,負(fù)責(zé)把一個條目存入內(nèi)存。例如,stloc就負(fù)責(zé)把堆棧最頂端的條目存入一個局部變量。存儲指令指定變量的句法規(guī)則通常和它們對應(yīng)的裝載指令類似。
比較操作
如果你不能比較兩個值而且根據(jù)其比較結(jié)果做出決定,那么許多問題都無法用任何程序語言來解決。il有一套比較操作符,它們都以c字母開頭,比較堆棧中的值。通常,如果比較結(jié)果為真則會把1推入堆棧否則就推入0。
大多數(shù)這類指令都很容易由它們的名字區(qū)分出來。例如,ceq比較兩個值是否相等,而cgt則確定堆棧最頂端的值是否比第二個最頂端值更大。 clt類同于cgt,不過執(zhí)行的是小于比較操作。
goto
通常,在對兩個值進(jìn)行比較之后會根據(jù)比較的結(jié)果結(jié)果實(shí)施一些操作。il分支指令(以br開頭)根據(jù)堆棧最頂端的條目中的內(nèi)容跳到其他指令。brtrue 和brfalse彈出堆棧最頂端的條目,然后根據(jù)該項(xiàng)為真(1)還是為假(0)而分別跳到指定的代碼行。如果沒有執(zhí)行指令跳躍則繼續(xù)執(zhí)行下一條指令。另外還有一個無條件分支操作符br,它總是跳到指定的代碼行。
你會發(fā)現(xiàn)分支操作就好像源代碼中的if語句以及顯式執(zhí)行的goto操作。il中的分支命令同樣具有高級流程控制結(jié)構(gòu)的對等體,比如:if, case, while, for等等。
創(chuàng)造新對象和調(diào)用其他代碼
call和callvirt指令調(diào)用其他方法和函數(shù)。call通常表示被調(diào)用的方法是靜態(tài)的或共享的,而callvirt則用于實(shí)例方法。就兩種指令來說,方法的名字都會在指令中包括。被送到方法的任何參數(shù)都會被彈出堆棧而且要在方法被調(diào)用之前裝載。
因?yàn)閯?chuàng)建一個新對象需要調(diào)用構(gòu)造器,所以il的對象創(chuàng)建也類似于其他的方法調(diào)用。參數(shù)首先被裝載到堆棧,然后執(zhí)行newobj指令,它調(diào)用對象的構(gòu)造器同時把對象的索引放回堆棧。指令中得有對象的名字。
以上就是大致的il語法操作。除了滿足你內(nèi)心的求知欲望以外,我希望你能從我的闡述中得到足夠的信息來理解il代碼的真實(shí)含義。