學習-java高級asm4使用指南_第1頁
學習-java高級asm4使用指南_第2頁
學習-java高級asm4使用指南_第3頁
學習-java高級asm4使用指南_第4頁
學習-java高級asm4使用指南_第5頁
已閱讀5頁,還剩99頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

第1章引言1.1parsing ysis設計的stub編譯器或skeleton編譯器,以及JIT(即時)編譯器,等等。ASM庫的小型化也非常重要,一方面是為了在內存有限的環境中使用,另一方面,也為了避免使那些使用ASM的小型應用程序或庫增大過多。從。其主要優點如下:①ASM的名字沒有任何含義:它只是C語言中的asm關鍵字,這個關鍵字允許執行一些用匯編有一個簡單的模塊APIJava虛擬機中的加載都采用這種字節數組形式。為此,ASM提供了一些工具,使用高于類型、Java類結構元素,等等。注意,ASM庫的范圍嚴格限制于類的讀、寫、轉換和分析。具ASM庫提供了兩個用于生成和轉換已編譯類的API,一個是API,以基于事件的形式來表示類,另一個是樹API,以基于對象的形式來表示類。它的一個標頭、一個字段、一個方法、一條指令,等等。基于事件的API定義了一組可能于對象的API提供了法,可以將表示一個類的事件序列轉換為表示同一個類的對象樹,也可以反過來,將對象樹表示為等價的事件序列。換言之,基于對象的API構建在基于事件的API之上。API(Simple檔的“文檔對象模型(ObjectModel,DOM)API”相比較:基于事件的API類似于SAXAPIDOMAPIAPI之上,類似于DOM可在SAX的上層提供。ASM之所以要提供兩個API,是因為沒有哪種API是最佳的。實際上,每個API都有自己基于事件的API要快于基于對象的API,所需要的內存也較少,因為它不需要在內存中創建和用于表示類的對象樹(SAX與DOM之間也有同樣的差異。但在使用基于事件的API時,類轉換的實現可能要更難一些,因為在任意給定時刻,類中只有一個元素可供使用(也就是與當前事件對應的元素API注意,這兩個API都是僅能同時一個類,而且獨立于其他類,也就是說,它們不會維(aspect器進行的,在這一結構中可以添加用戶定義的、使用器和篩選器。因此,這一API的使基于對象的API也有一系結構方面:實際上,用于操作類樹的類或轉換器組件ASM應用程序中的大多數組件體系結構都非常簡單,但還是可以想象一下類似于 是基于API的。這個含在 mons.jar存檔文件中。 .objectweb.asm.treeasm-tree.jar存檔文件中,定義了基于對 ysis包提供了一個類分析框架和幾個預定義的 本文檔分為兩部分。第一部分介紹API,即asm、asm-util和mons存檔文APIasm-treeasm-ysisAPI每章都會介紹編程接口及相關的工具與預定義組件。所有示例的源代碼都可以從ASM上獲幾節中。因此,建議依次閱讀本文檔。如需有關ASMAPI的參考手冊,請使用Javadoc。印刷約定第一部分第2章類本章說明如何使用ASMAPI來生成和轉換經過編譯的Java類。首先介紹已編譯類,然Java字節代碼指令的形式,包含了該方法的已編譯了一個類,這個類又有一個類,那這個源文件會被編譯為兩個類文件:主類和內部類各一個文件。但是,主類文件中包含對其類的,定義了方法的內層類會包含,引向其封裝的方法。(comment以利用這些屬性為相應元素關聯信息。Java5中引入可用于同一目的的注釋另一個非常重要的結構性差異是已編譯類中包含常量池(constantpool)部分。這個池是一中定義一次,然后可以利用其索引,在類文件中的所有其他各部分進行。幸好,ASM隱藏結構。其確切結構在《Java虛擬機規范》第4節中描述。注釋屬性類字段注釋屬性方法注釋屬性2‐1已編譯類的整體結構(*表示零個或多個Java類型在已編譯類和源文件類中的表示不同。后面幾節將解釋它們名例如,String的名為java/lang/String。Java類型,比如字段類型,在已編譯類中都是用類型描述符表示的(見圖22。JavaZCBSIFJD2‐2一些Java類型的類型描述boleancharbyte名,前面加上字符L,后面跟有一個分號。例如,String的類型描述符為voidm(inti,floatintm(ObjectObjectm(int[]2.3方法描述符舉它接受一個int類型的參數,返回一個int。圖2.3給出了幾個方法描述符示例。ASMAPIClassVisitor抽象類的(24。這個任意復雜度,這樣的部分可以用一個初始方法調用來,返回一個輔助的者類。visitAnnotation、visitField和visitMethod方法就是這種情況,它們分別返回AnnotationVisitor、FieldVisitor和MethodVisitor. class{publicClassVisitor(intpublicClassVisitor(intapi,ClassVisitorpublicvoidvisit(intversion,intaccess,Stringname,Stringsignature,StringsuperName,String[]interfaces);publicvoidvisitSource(Stringsource,StringpublicvoidvisitOuterClass(Stringowner,Stringname,Stringdesc);AnnotationVisitorvisitAnnotation(Stringdesc,booleanvisible);publicvoidvisitAttribute(Attributeattr);publicvoidvisitInnerClass(Stringname,StringouterName,StringinnerName,intaccess);Stringsignature,Objectvalue);publicMethodVisitorvisitMethod(intaccess,Stringname,StringStringsignature,String[]exceptions);voidvisitEnd();}2.4ClassVisitor針對這些輔助類遞歸適用同樣的原則。例如,FieldVisitor抽象類中的每個方法(見圖章僅限于只需ClassVisitor類本身就能解決的簡單問題。 class{publicFieldVisitor(intpublicFieldVisitor(intapi,FieldVisitorpublicAnnotationVisitorvisitAnnotation(Stringdesc,booleanpublicvoidvisitAttribute(Attributeattr);publicvoidvisitEnd();}2.5FieldVisitorClassVisitor類的方法必須按以下順序調用(在這個類的Javadoc中規定visitvisitSource?visitOuterClass?(visitAnnotation|visitAttribute)*(visitInnerClass|visitField|visitMethod)*這意味著必須首先調用visit,然后是對visitSource的最多一個調用,接下來是對visitOuterClass的最多一個調用,然后是可按任意順序對visitAnnotation和visitAttribute的任意多個,接下來是可按任意順序對visitInnerClass、visitFieldvisitMethod的任意多個調用,最后以一個visitEnd調用結束。ASM提供了三個基于ClassVisitorAPI的組件,用于生成和變化類ClassReader類分析以字節數組形式給出的已編譯類,并針對在其accept方法參數中傳送的ClassVisitor實例,調用相應的visit方法。這個類可以看作一個事ClassWriter類是ClassVisitor抽象類的一個子類,它直接以二進制形式生成編譯后的類。它會生成一個字節數組形式的輸出,其中包含了已編譯類,可以用toByteArray方法來提取。這個類可以看作一個事件使用器。ClassVisitor類將它收到的所有方法調用都委托給另一個ClassVisitor類。這個在分析一個已經存在的類時,惟一必需的組件是ClassReader組件。讓用一個例子publicclassClassPrinterextendsClassVisitorpublic{}publicvoidvisit(intversion,intaccess,Stringname,Stringsignature,StringsuperName,String[]interfaces){System.out.println(name+"extends"+superName+"}publicvoidvisitSource(Stringsource,Stringdebug)}{}publicAnnotationVisitorvisitAnnotation(Stringdesc,booleanvisible){return}publicvoidvisitAttribute(Attributeattr)}publicvoidvisitInnerClass(Stringname,StringouterName,StringinnerName,intaccess){}publicFieldVisitorvisitField(intaccess,Stringname,StringStringsignature,Object{System.out.println(""+desc+""+name);returnnull;}publicMethodVisitorvisitMethod(intaccess,Stringname,Stringdesc,Stringsignature,String[]exceptions){System.out.println(""+name+return}publicvoidvisitEnd()}}產生的事件由的ClassPrinter使用:ClassPrintercp=newClassReadercr=newClassReader("java.lang.Runnable");cr.accept(cp,0);java/lang/Runnableextends{}可以像字母數組或InputStream一樣用值來指定。利用ClassLoader的 方法,可以獲得一個類內容的輸入流,如下為生成一個類,惟一必需的組件是ClassWriter組件。讓用一個例子來進行說明。packagepublicinterfaceComparableextends{intLESS=-1;intEQUAL=0;intGREATER=1;intcompareTo(Object}ClassWritercw=newcw.visit(V1_5,ACC_PUBLIC+ + parable",null,"java/lang/Object",newString[]{"pkg/Mesurable"});cw.visitField(ACC_PUBLIC+ACC_FINAL+ACC_STATIC,"LESS",null,newInteger(-cw.visitField(ACC_PUBLIC+ACC_FINAL+ACC_STATIC,"EQUAL",null,newcw.visitField(ACC_PUBLIC+ACC_FINAL+ACC_STATIC,"GREATER",null,newInteger(1)).visitEnd();cw.visitMethod(ACC_PUBLIC+ACC_ ,"compareTo","(Ljava/lang/Object;)I",null,null).visitEnd();byte[]b=對visit方法的調用定義了類的標頭。V1_5參數是一個常數,與所有其他ASM常量一樣,在ASMOpcodes接口中定義。它指明了類的版本——Java1.5。ACC_常量是與Java修飾符對應的標志。這里規定這個類是一個接口,而且它是public和 被實例化。下一個參數以形式規定了類的名字(見2.1.2節。回憶一下,已編譯類不包含Object這里,這些字段是int字段,它們的描述符是I。第四個參數對應于泛型。在的例子中,用于真正的常量字段,也就是finalstatic字段。對于其他字段,它必須為null。由于此處沒有注釋,所以立即調用所返回的FieldVisitorvisitEnd方法,即對其visitAnnotation或visitAttribute方法沒有任何調用。四個參數對應于泛型。在的例子中,它是null,因為這個方法沒有使用泛型。最后一個參是抽象的,所以立即調用所返回的MethodVisitor的visitEnd方法。使用生成的類前面的字節數組可以在一個Comparable.class文件中,供以后使用。或者,也可以用ClassLoader動態加載它。法是定義一個ClassLoader子類,它的defineClassclassMyClassLoaderextendsClassLoaderpublicClassdefineClass(Stringname,byte[]{returndefineClass(name,b,0,}}Classc= classStubClassLoaderextends{protectedClassfindClass(Stringname)throwsClassNotFoundException{if{ClassWritercw=newbyte[]b=returndefineClass(name,b,0,}return}}ASMAPI的范圍。如果你正在方式使用一個ClassLoader。到目前為止,ClassReader和ClassWriter組件都是單獨使用的。這些事件是“人工”ClassWriterClassReader產生,然后byte[]b1=ClassWritercw=newClassWriter(0);ClassReadercr=newClassReader(b1);cr.accept(cw,0);byteb2cw.toByteArrayb2b1表示同一個一等。下一步是在類器和類寫入器之間引入一個ClassVisitor:byte[]b1=ClassWritercw=newClassVisitorcv=newClassVisitor(ASM4,cw){};ClassReadercr=newClassReader(b1);cr.accept(cv,0);byteb2cw.toByteArrayb2b1表示同一個器器 2.6轉換但結果并沒有改變,因為ClassVisitor事件篩選器沒有篩選任何東西。但現在,為了能夠轉換一個類,只需重寫一些方法,篩選一些事件就足夠了。例如,考慮下面的ClassVisitor子類:publicclassChangeVersionAdapterextends{publicChangeVersionAdapter(ClassVisitorcv){super(ASM4,cv);}publicvoidvisit(intversion,intaccess,Stringname,Stringsignature,StringsuperName,String[]interfaces){cv.visit(V1_5,access,name,signature,superName,}}2.7ChangeVersionAdapter的程序可以向實現接口的列表中添加一個接口。還可以改變類的名字,但進行這種改變所需要做的工作要多得多,不只是改變visit方法的name參數了。實際上,類的名字可以出現在一個已編譯類的許多不同地方,要真正實現類的重命名,必須修改類中出現的所有這些類名字。優前面的轉換只修改了原類的節。但是,在使用上面的代碼時,整個b1均被分析,并利b2b1中不被轉換的部分直接到b2中,不對其分析,也不生成相應的事件,其效率就會高得多。ASM自動為方法執ClassReader組件的accept方法參數中傳送了ClassVisitor,如果ClassReader檢測到這個ClassVisitor返回的MethodVisitor來自一個是ClassWriter中表示這個方法的字節數組。如果ClassReader和ClassWriter組件擁有對對方的,則由它們進行這種優化,byte[]b1=ClassReadercr=newClassReader(b1);ClassWritercw=newClassWriter(cr,0);ChangeVersionAdapterca=newChangeVersionAdapter(cw);cr.accept(ca,0);byte[]b2=ChangeVersionAdapter沒有轉換任何方法,所以以上代碼的10%20%的量級。遺憾的是,這一優化需要將原類中使用轉換后的類如上節所述,轉換后的類b2可以在磁盤上,或者用ClassLoader加載。但在:publicstaticvoidpremain(StringagentArgs,Instrumentation{inst.addTransformer(newClassFileTransformer()publicbyte[]transform(ClassLoaderl,Stringname,Classc,Protectiond,byte[]b)throws{ClassReadercr=newClassReader(b);ClassWritercw=newClassWriter(cr,0);ClassVisitorcv=newChangeVersionAdapter(cw);cr.accept(cv,0);return}}上一節用于轉換類版本的方法當然也可用于ClassVisitor類的其他方法。例如,通過改變visitField和visitMethod方法的access或name參數,可以改變一個字段或一個方法publicclassRemoveDebugAdapterextends{publicRemoveDebugAdapter(ClassVisitorcv){super(ASM4,cv);}publicvoidvisitSource(Stringsource,Stringdebug)}publicvoidvisitOuterClass(Stringowner,Stringname,Stringdesc)}publicvoidvisitInnerClass(Stringname,StringouterName,StringinnerName,intaccess){}}:publicclass pterextends{privateStringmName;privateStringmDesc;ClassVisitorcv,StringmName,String{super(ASM4,cv);this.mName=mName;this.mDesc=}publicMethodVisitorvisitMethod(intaccess,Stringname,Stringdesc,Stringsignature,String[]exceptions)if(name.equals(mName)&&desc.equals(mDesc))return}}}只要遵守各個visit必須遵循的調用順序即可(2.2.1節)。中這樣做,因為這樣可能會導致對visitField的調用之后跟有visitSource、因,不能將這個新調用放在visitSource、visitOuterClass、visitAnnotation或visitAttribute方法中.僅有的可能位置是visitInnerClassvisitField、visitMethodvisitEnd方法。,在d,在d方中所這成后能成一作這做是當限的在或_i到d樹I有制以IpublicclassAddFielpterextends{privateintfAcc;privateStringfName;privateStringprivatebooleanpublicAddFielpter(ClassVisitorcv,intfAcc,StringfName,StringfDesc){super(ASM4,cv);this.fAcc=fAcc;this.fName=fName;this.fDesc=}publicFieldVisitorvisitField(intaccess,Stringname,Stringdesc,Stringsignature,Objectvalue){if{isFieldPresent=}returncv.visitField(access,name,desc,signature,}publicvoidvisitEnd()if(!isFieldPresent)FieldVisitorfv=cv.visitField(fAcc,fName,fDesc,null,null);if(fv!=null){}}}}這個字段被添加在visitEnd方法中。visitField方法未被重寫為修改已有字段或刪除一個字段,只是檢測一下希望添加的字段是否已經存在。注意visitEnd方法中在調用fv.visitEnd()之前的fv!=null檢測:這是因為一個類器可以在visitField中返null,在上一節已經看到這一點。到目前為止,已經看到一些由ClassReader、類適配器和ClassWriter組成的簡單publicclassMultiClassAdapterextends{protectedClassVisitor[]{super(ASM4);this.cvs=cvs;}@Overridepublicvoidvisit(intversion,intaccess,Stringname,Stringsignature,StringsuperName,String[]interfaces){for(ClassVisitorcv:cvs)}}}ClassVisitor(這需要采取一些預防措施,確保visitvisitEndClassVisitor恰好僅被調用一次。因2.8所2.8一個復雜轉.objectweb.asm.util包中提供了幾個工具,這些工具在開發類或適配器時可能非常有用,但在運行時不需要它們。ASM還提供了一個實用類,用于在運行時處理名、類幾節已經看到,ASMAPI公開Java類型的形式就是它們在已編譯類中的形式,也便于閱讀。但這樣就需要在ClassReader和ClassWriter中的兩種表示形式之間進行系統getInternalName方法返回一個Type的名。例如,Type.getType(String.class).getInternalName()給出String類的名,即getDescriptor方法返回一個Type的描述符。比如,在代碼中可以不使用"Ljava/lang/String;",而是使用Type.getType(String.class).象構建。getDescriptorgetArgumentTypesgetReturnType方法可用于獲取與一個方法的參數類型和返回類型相對應的Type對象。例如,Type.getArgumentTypes("(I)V")返回一個僅有一個元素Type.INT_TYPE的數組。與此類似,調用Type.getReturnType("(I)V將返回Type.VOID_TYPE對象。rrrr類,并生成所類的文本表示。因此,不是用r來生成類,而是使用o,以獲得關于實際所生成內容的一個可讀軌跡。甚至可以同時使用這兩r實際上還可以將對其方法如se:ClassWritercw=newTraceClassVisitorcv=newTraceClassVisitor(cw,printWriter);byteb[]=后將這些調用的一份文本表示打印到printWriter。例如,如果在2.2.3節的例子中使用//標志 //標志publicfinalstaticILESS=-//標志publicfinalstaticIEQUAL=//標志publicfinalstaticIGREATER=//標志 }TraceClassVisitor,以查看在鏈中這一ClassWriter之前使用。還要注意,有了這個適配器生成的類的文本表示形式,可能很輕松地用String.equals()來對比兩個類。ClassWriter類并不會核實對其方法的調用順序是否恰當,以及參數是否有效。因此,有可能會生成一些被Java虛擬機驗證器的無效類。為了盡可能提前檢測出部分此類錯誤,可以使用CheckClassAdapter類。和TraceClassVisitor類似,這個類也擴展了ClassVisitor類,并將對其方法的所有調用都委托到另一個ClassVisitor,比如一個錯誤時,會拋出IllegalStateException或IllegalArgumentException。ClassWritercw=newTraceClassVisitortcv=newTraceClassVisitor(cw,printWriter);CheckClassAdaptercv=newCheckClassAdapter(tcv);byteb[]=ClassWritercw=newCheckClassAdaptercca=newTraceClassVisitorcv=newTraceClassVisitor(cca,和使用TraceClassVisitor時一樣,也可以在一個生成鏈或轉換鏈的任意位置使用這個類為TraceClassVisitor工具提供了一種替代后端(印用ASM生成這個類的源代碼。如果用這個器來一個已經存在的類,那這一點是很有編譯它,并用ASMifier來這個編譯后的類。將會得到生成這個已編譯類的ASM代碼!java-classpathasm.jar:asm-util.jar.objectweb.asm.util.ASMifier\packageasm.java.lang;publicclassRunnableDumpimplements{publicstaticbyte[]dump()throws{ClassWritercw=newClassWriter(0);FieldVisitorfv;MethodVisitormv;cw.visit(V1_5,ACC_PUBLIC+ +{mv=cw.visitMethod(ACC_PUBLIC+ACC_ ,"run","()V",null,null);}return}}第3章方法本章解釋如何用ASMAPI生成和轉換已編譯方法。首先介紹編譯后的方法,然后介紹用于生成和轉換它們的相應ASM接口、組件和工具,并給出大量說明性示例。始編寫簡單的類與轉換器代碼。如需完整定義,應當閱讀Java虛擬機規范。在介紹字節代碼指令之前,有必要先來介紹Java虛擬機執行模型。知道,Java代碼是3.13幀的執行圖3.1給出了一個具有3幀的示例執行棧。第一幀包含3個局部變量,其操作數棧的最大值為4,其中包含兩個值。第二幀包含2個局部變量,操作數棧中有兩個值。最后是第三幀,位于執行棧的頂端,包含4個局部變量和兩個操作數。棧,前兩個局部變量被初始化為ab(其他局部變量未被初始化)。局部變量部分和操作數棧部分中的每個槽(slot)可以保存除long和double變量之外的Java值。longdouble變量需要兩個槽。這使局部變量的管理變得復雜:例如,第i個方法參數不一定在局部變量i中。例如,調用Math.max(1L,2L)創建一個幀,1L值位于前兩個局部變量槽中,值2L在第三和第四個槽中。記符號NOP表示,對應于不做任何操作的指令。參數GOTO標記指ILOAD,LLOAD,FLOAD,DLOAD和ALOAD指令一個局部變量,并將它的值壓到操charshort或int局部變量。LLOADFLOAD和DLOAD分別用于加載longfloat或double(LLOAD和DLOAD實際加載兩個槽i和i1。最后,ALOAD用于加載任意非基元值,即對可以看到,xLOAD和xSTORE指令被賦入了類型(事實上,下面將要看出,幾乎所有指令任意內存位置在局部變量1中,并將這個地址轉換為對象!但是,如果向一個局部變量中一個值,而這個值的類型不同于該局部變量中的當前值,卻是完全合法的。這意味附件A1):棧這些指令用于處理棧上的值:POP彈出棧頂部的值,DUP這些指令在操作數棧壓入一個常量值:ACONST_NULLnull,ICONST_0壓入int0,FCONST_0壓入0f,DCONST_0壓入0d,BIPUSHb壓入字節值b,SIPUSHs壓入short值s,LDCcst壓入任意intfloatlongdoubleString或class①常量cst,等等。算術與邏輯這些指令從操作數棧彈出數值,合并它們,并將結果壓入棧中。它們沒有任何參數。xADD、xSUB、xMUL、xDIVxREM對應于+、-、*、/和%運算xI、處理int和long值。類型變換這些指令從棧出一個值,將其轉換為另一類型,并將結果壓入棧中。它們對Java中的類型轉換表達I2F,F2D,L2D等將數值由一種數值類型轉換為另一種類型。CHECKCASTt將一個值轉換為類型t。一個type類型的新對象壓入棧中(其中type是一個名。字段這些指令讀或寫一個字段的值。GETFIELDownernamedesc彈出一個對象,并將這個值在它的name字段中。在這兩種情況下,該對象都必須是owner類型,它方法INVOKESPECIAL用于私有方法和構造器,INVOKEINTERFACE用于接口中定義的方法。最后,對于Java7中的類,INVOKEDYNAMIC用于新動態方法調用機制。這些指令用于讀寫數組中的值。xALOAD指令彈出一個索引和一個數組,并壓入此索xI、L、F、DAB、CS。跳轉這些指令無條件地或者在某一條件為真時跳轉到一條任意指令。它們用于編譯if、for、do、while、break和continue指令。例如,IFEQlabel從棧出一個int值,如果這個值為0,則跳轉到由這個label指定的指令處(否常執行下一條指令)。還有許多其他跳轉指令,比如IFNEIFGE。最后,TABLESWITCH和①返回最后,xRETURNRETURN指令用于終止一個方法的執行,并將其結果返回給調用者。RETURN用于返回void的方法,xRETURN用于其他方法。packagepkg;publicclass{privateintf;publicintgetF(){return}publicvoidsetF(int{this.f=}}ALOADGETFIELDpkg/BeanfIthis即this.f。最后一條指令從棧出這個值,并將其返回給調用者。圖3.2中給出了這個方法3.2getF方法的持續幀狀態:a)初始狀態,b)ALOAD0之后,c)GETFIELD之ALOADILOADPUTFIELDpkg/BeanfI和之前一樣,第一條指令將this壓入操作數棧。第二條指令壓入局部變量1,在為這個方法調用創建幀期間,以f參數初始化該變量。第三條指令彈出這兩個值,并將int值在被對象的f字段中,即在this.f中。最后一條指令在源代碼中是隱式的,但在編譯后3.3setF方法的持續狀態:a)初始狀態,b)ALOAD0之后,c)ILOAD1之后,d)Bean(super()。這個構造器的字節代碼ALOADINVOKESPECIALjava/lang/Object<init>()V第一條指令將this壓入操作數棧中。第二條指令從棧出這個值,并調用在ObjectpublicvoidcheckAndSetF(int{if(f>=0){this.f=f;}elsethrownew}}ILOADIFLTALOADILOADPUTFIELDpkg/BeanfIGOTOendINVOKESPECIALjava/lang/IllegalArgumentException<init>()V它與0進行比較。如果它小于(LT)0,則跳轉到由label標記指定的指令,否則不做任何事情,繼續執行下一條指令。接下來的三條指令與setF方法中相同。GOTO指令無條件跳轉到由end標記指定的指令,也就是RETURN指令。label和end標記之間的指令創建和拋出一個異常:NEW指令創建一個異常對象,并將它壓入操作數棧中。DUP指令在棧中重復這個值。INVOKESPECIAL指令彈出這兩個副本之一,并對其調用異常構造器。最后,ATHROW指令彈出異常處理器一起,這個列表規定了在某方法中一給定部分拋出異常時必須執行的代碼。異常處理器類似于yhyhpublicstaticvoidsleep(long{try{}catch(InterruptedException{}}TRYCATCHBLOCKtrycatchcatchLLOADINVOKESTATICjava/lang/Threadsleep(J)VINVOKEVIRTUALjava/lang/InterruptedExceptionprintStackTrace()VTry和catch標記之間的代碼對應于try塊,而catch標記之后的代碼對應于catch。TRYCATCHBLOCK行指定了一個異常處理器,覆蓋了try和catch標記之間的范圍,有一個開catchInterruptedException的子類。這意味著,如果在try和catch之間拋出了這樣一個異常,棧將被清空,異常被壓入這個空棧中,執行過程在catch處繼續。幀除了字節代碼指令之外,用Java6或更高版本編譯的類中還包含一組棧幀,用于加快Java虛擬機中類驗證過程的速度。棧幀給出一個方法的執行幀在執行過程中某一時刻的狀例如,如果考慮上一節的getF方法,可以定義三個棧幀,給出執行幀在即將執行ALOAD、即將執行GETFIELD和即將執行IRETURN之前的狀態。這三個棧幀對應于圖3.2如下代碼之前的執行幀狀 指 ALOAD 可以對checkAndSetF方法進行相同操作: [pkg/BeanI] ILOAD[pkg/BeanI] IFLT[pkg/BeanI] ALOAD[pkg/BeanI] ILOAD[pkg/BeanI][pkg/Bean [pkg/BeanI] GOTO[pkg/BeanI] label[pkg/BeanI] [pkg/BeanI][Uninitialized(label)[pkg/BeanI]

[pkg/BeanI] end[pkg/BeanI] 除了Uninitialized(label)類型之外,它與前面的方法均類似。這是一種僅在棧幀棧幀可使用三種其他特殊類型:UNINITIALIZED_THIS是構造器中局部變量0的初始類型,TOP對應于一個未定義的值,而NULL對應于null。在checkAndSetF方法的情景中,這意味著僅兩個幀:一個用于NEW指令,因為它是IFLT指令的目標,還因為它跟在無條件跳轉GOTO指令之后,另一個用于RETURN指令,因為它是GOTO指令的目標,還因為它跟在“無條件跳轉”ATHROW指令之后。于初始幀,所以它們被為單字節值,由F_SAME助記符表示。可以在與這些幀相關聯的字節代碼指令之前給出這些幀。這就給出了F_SAME方法的最終字節代碼:ILOADIFLTALOADILOADPUTFIELDpkg/BeanfIGOTOendINVOKESPECIALjava/lang/IllegalArgumentException<init>()V用于生成和轉換已編譯方法的ASMAPI是基于MethodVisitor抽象類的(見圖34),它由ClassVisitor的visitMethod方法返回。除了一些與注釋和調試信息有關的方法之(這的參數個數和(這些類別并非對應于3.1.2節給出的類別這些方法必須按以下順(在MethodVisitor接口的Javadoc中還規定了其他一些約束條件:(visitAnnotation|visitParameterAnnotation|visitAttribute)*(visitCode(visitTryCatchBlock|visitLabel|visitFrame| InsnvisitMaxs法的字節代碼。對于這些方法,其代碼必須按順序,位于對visitCode的調用(有且僅有一個調用)與對visitMaxs的調用(有且僅有一個調用)之間。classMethodVisitor{//publicaccessorsommitedMethodVisitor(intapi);MethodVisitor(intapi,MethodVisitormv);AnnotationVisitorvisitAnnotation(Stringdesc,booleanvisible);AnnotationVisitorvisitParameterAnnotation(intparameter,Stringdesc,booleanvisible);voidvisitAttribute(Attributeattr);voidvisitCode();voidvisitFrame(inttype,intnLocal,Object[]local,intnStack,Object[]stack);voidvisitInsn(intvoidvisitIntInsn(intopcode,intoperand);voidvisitVarInsn(intopcode,intvar);voidvisitTypeInsn(intopcode,Stringdesc);voidvisitFieldInsn(intopc,Stringowner,Stringname,StringvoidvisitMethodInsn(intopc,Stringowner,Stringname,Stringdesc);voidvisitInvokeDynamicInsn(Stringname,Stringdesc,Handlebsm,Object...bsmArgs);voidvisitJumpInsn(intopcode,Labellabel);voidvisitLabel(Labellabel);voidvisitLdcInsn(ObjectvoidvisitIincInsn(intvar,intvoidvisitTableSwitchInsn(intmin,intmax,Labeldflt,Label[]labels);voidvisitLookupSwitchInsn(Labeldflt,int[]keys,Label[]labels);voidvisitMultiANewArrayInsn(Stringdesc,intdims);voidvisitTryCatchBlock(Labelstart,Labelend,Labelhandler,Stringtype);voidvisitLocalVariable(Stringname,Stringdesc,Stringsignature,Labelstart,Labelend,intindex);voidvisitLineNumber(intline,Labelstart);voidvisitMaxs(intmaxStack,intmaxLocals);voidvisitEnd();}3.4MethodVisitor于是,visitCode和visitMaxs方法可用于檢測該方法的字節代碼在一個事件序列中的ClassVisitorcv=...;MethodVisitormv1=cv.visitMethod(...,"m1",...);MethodVisitormv2=cv.visitMethod(...,"m2",...);例是完全獨立的,可按任意順序使用(只要還沒有調用cv.visitEnd()):ClassVisitorcv=...;MethodVisitormv1=cv.visitMethod(...,"m1",...);MethodVisitormv2=cv.visitMethod(...,"m2",...);ASM提供了三個基于MethodVisitorAPI的組件,用于生成和轉換方法ClassReader類分析已編譯方法的內容,在其accept方法的參數中傳送了ClassVisitor,ClassReader類將針對這一ClassVisitor返回的MethodVisitor對象調用相應方法。ClassWritervisitMethod方法返MethodVisitor接口的一個實現,它直MethodVisitor類將它接收到的所有方法調用委托給另一個MethodVisitor方法。在3.1.5節已經看到,為一個方法計算棧幀并不是非常容易:必須計算所有幀,找出與幸好ASM能為完成這一計算。在創建ClassWriter時,可以指定必須自動計算哪些在使用newClassWriter(0)時,不會自動計算任何東西。必須自行計算幀、局部變在使用newClassWriter( 在newClassWriter( 不再需要調用visitFrame,但仍然必須調用visitMaxs(參數將被忽略并重新計這些選項的使用很方便,但有一個代價:COMPUTE_MAXS選項使ClassWriter的速度降低10%,而使用COMPUT_FRAMES選項則使其降低一半。這必須與自行計算時所耗費的時間進行比較:在特定情況下,經常會存在一些比ASM所用算法更容易、更快速的計算方法,但ASMClassWriter為你執行壓縮步驟。為此,只需要用visitFrame(F_NEW,nLocals,locals,nStack,stack)未壓縮幀,其中的nLocals和nStack是局部變量的個數和操作數棧的大小,locals和stack是包含相應類型的數組(細節請參閱Javadoc。還要注意,為了自動計算幀,有時需要計算兩個給定類的公共超類。默認情況下,ClassWriter類會在 monSuperClass方法中進行這一計算,它會將兩個類加載到JVM中,并使用反射API。如果正在生成幾個相互的類,那可能會導致問題,因為被 monSuperClass方法來解決這一問題。如果mv是一個MethodVisitor,則3.1.3節定義的getF方法的字節代碼可以用以下方mv.visitFieldInsn(GETFIELD,"pkg/Bean","f","I");出,字節代碼與ASMAPI之間的非常簡單。對visitMaxs的調用必須在已經了所有setF方法和構造器的字節代碼可以用一種類似方法生成。一個更有意義的示例是mv.visitVarInsn(ILOAD,1);Labellabel=newLabel();mv.visitVarInsn(ALOAD,0);mv.visitVarInsn(ILOAD,1);mv.visitFieldInsn(PUTFIELD,"pkg/Bean","f","I");Labelend=newLabel();mv.visitJumpInsn(GOTO,end);mv.visitFrame(F_SAME,0,null,0,null);"java/lang/IllegalArgumentException","<init>","()V");mv.visitLabel(end);mv.visitFrame(F_SAME,0,null,0,null);mv.visitInsn(RETURN);在visitCode和visitEnd調用之間,可以看到恰好到3.1.5節末尾所示字節代碼的方法調用:每條指令、標記或幀分別有個調用(僅有的例外是label和endLabel對象的個標記則必須用visitLabel恰好 將刪除一條指令;在接收到的調用之間調用,將增加新的指令。MethodVisitor類提供publicclassRemoveNopAdapterextends{publicRemoveNopAdapter(MethodVisitormv){super(ASM4,mv);}publicvoidvisitInsn(intopcode)if(opcode!={}}}publicclassRemoveNopClassAdapterextends{publicRemoveNopClassAdapter(ClassVisitorcv){super(ASM4,cv);}publicMethodVisitorvisitMethod(intaccess,Stringname,Stringdesc,Stringsignature,String[]exceptions)mv=cv.visitMethod(access,name,desc,signature,exceptions);if(mv!=null){mv=new}return}}并返回這個適配器。其效果就是構造了一個類似于類適配器鏈的方法適配器鏈(見圖35。3.5RemoveNopAdapter的程序mv=cv.visitMethod(access,name,desc,signature,exceptions);if(mv!=null&&!name.equals("<init>")){mv=new}visitMethod創建幾個在一起的適配器。方法適配器鏈的拓撲結構甚至都可以不同publicMethodVisitorvisitMethod(intaccess,Stringname,Stringdesc,Stringsignature,String[]exceptions)MethodVisitormv1,mv1=cv.visitMethod(access,name,desc,signature,mv2=cv.visitMethod(access,"_"+name,desc,signature,exceptions);returnnewMultiMethopter(mv1,mv2);}實現一個比RemoveNopAdapter更有意義的適配器。一個類C:publicclassCpublicvoidm()throws{}}publicclassCpublicstaticlongpublicvoidm()throws{timer-=timer+=}}為了了解可以如何在ASM中實現它,可以編譯這兩個類,并針對這兩個版本比較TraceClassVisitor的輸出(或者是使用默認的Textifier后端,或者是使用ASMifier:GETSTATICC.timer:PUTSTATICC.timer:LDCGETSTATICC.timer:PUTSTATICC.timer:MAXSTACK=4MAXLOCALS=要更新操作數棧的最大尺寸。此方法代碼的開頭部分用visitCode方法。因此,可以通過重publicvoid{mv.visitFieldInsn(GETSTATIC,owner,"timer","J");mv.visitFieldInsn(PUTSTATIC,owner,"timer",}其中的owner必須被設定為所轉換類的名字。現在必須在任意RETURN之前添加其他四條指令,還要在任何xRETURN或ATHROW之前添加,它們都是終止該方法執行過程的指令。這些publicvoidvisitInsn(intopcode)if((opcode>=IRETURN&&opcode<=RETURN)||opcode=={mv.visitFieldInsn(GETSTATIC,owner,"timer","J");mv.visitFieldInsn(PUTSTATIC,owner,"timer",}}前時的大小。只知道它小于或等于s。因此,只能說,在返回指令之前添加的代碼可能要求操作數棧的大小達到s+4。這種最糟情景在實際中很少發生:使用常見編譯器時,RETURN之前需要考慮最糟情景。①必須重寫visitMaxs方法如下:publicvoidvisitMaxs(intmaxStack,int{mv.visitMaxs(maxStack+4,}最優值,而不是情景中的值。但對于這種簡單的轉換,以人工更新maxStack并不需要花現在可以將所有元素一起放入相關聯的ClassVisitor和MethodVisitor子類publicclassAddTimerAdapterextends{privateStringowner;publicAddTimerAdapter(ClassVisitor{super(ASM4,}@Overridepublicvoidvisit(intversion,intaccess,Stringname,Stringsignature,StringsuperName,String[]interfaces){owner=isInterface=(access&ACC_INTERFACE)!=}@OverridepublicMethodVisitorvisitMethod(intaccess,Stringname,Stringdesc,Stringsignature,String[]exceptions){MethodVisitormv=cv.visitMethod(access,name,desc,signature,if(!isInterface&&mv!=null&&{mv=newAddTimerMetho}return}@OverridepublicvoidvisitEnd()if(!isInterface)FieldVisitorfv=cv.visitField(ACC_PUBLIC+ACC_STATIC,"timer","J",null,null);if(fv!=null)}}}class pterextends{publicAddTimerMethopter(MethodVisitormv){super(ASM4,mv);}@OverridepublicvoidvisitCode()mv.visitFieldInsn(GETSTATIC,owner,"timer","J");mv.visitFieldInsn(PUTSTATIC,owner,"timer",}@OverridepublicvoidvisitInsn(intopcode)if((opcode>=IRETURN&&opcode<=RETURN)||opcode=={mv.visitFieldInsn(GETSTATIC,owner,"timer","J");mv.visitFieldInsn(PUTSTATIC,owner,"timer",}}@OverridepublicvoidvisitMaxs(intmaxStack,int{mv.visitMaxs(maxStack+4,}}}RETURN指令之前添加的代碼也是如此。這種轉換稱刪除所有出現的ICONST_0IADD序列,這個序列的操作就是加入0,沒有什么實際效果。顯然,在一條IADD指令時,只有當上一條被的指令是ICONST_0時,才必須刪除該指令。讓更仔細地研究一下這個例子。在ICONST_0時,只有當下一條指令是IADD時條指令:如果下一指令是IADD,則刪除兩條指令,否則,發出ICONST_0和當前指令。 classPatternMethopterextends{protectedfinalstaticintSEEN_NOTHING=0;protectedintstate;publicPatternMethopter(intapi,MethodVisitor{super(api,}@OverridpublicvoidvisitInsn(intopcode)}@OverridepublicvoidvisitIntInsn(intopcode,intoperand)} void}publicclassRemoveAddZeroAdapterextendsPatternMetho{privatestaticintSEEN_ICONST_0=1;publicRemoveAddZeroAdapter(MethodVisitormv){super(ASM4,}@OverridepublicvoidvisitInsn(int{if(state==SEEN_ICONST_0){if(opcode==IADD){state=SEEN_NOTHING;}}if(opcode=={state=SEEN_ICONST_0;}}@Overrideprotectedvoid{if(state==SEEN_ICONST_0){}state=}}state,并立即返回,其效果就是刪除該序列。在其他情況下,它會調用公用的visitInsn方法,如果ICONST_0是最后一條被序列,它就會發出該指令。于是,如果當前指令是標記和幀情況呢?如果某一指令可能跳轉到ICONST_0,這意味著有一個指定這一指令的標記。在刪除了這兩條指令后,這個標記將指向跟在被刪除IADD之后的指令,這正是希望的。但如果某一指令可能跳轉到IADD,就不能刪除這個指令序列(不能確保在這一跳轉之前,已經在棧中壓入了一個0)。幸好,在這種情況下,ICONST_0和IADD之間必然有一個標記,PatternMethopter中完成(注意,visitMaxs也會調用公用的visitInsn方法;它: @OverridepublicvoidvisitFrame(inttype,intnLocal,Object[]local,intnStack,Object[]stack){}@OverridepublicvoidvisitLabel(Labellabel)}@OverridepublicvoidvisitMaxs(intmaxStack,intmaxLocals)}}信息用visitLineNumber方法,它也與指令同時被調用。但是,在一個指令序列的中間一個更復雜的例子0ALOAD0GETFIELDfPUTFIELDf。在實現這一轉換之前,最好是將狀態機設計為能夠識別這一序列(見圖36。3.6ALOAD0ALOAD0GETFIELDfPUTFIELDf的狀態:class pterextends {privatefinalstaticintSEEN_ALOAD_0=1;privatefinalstaticintSEEN_ALOAD_0ALOAD_0=2;privatefinalstaticintSEEN_ALOAD_0ALOAD_0GETFIELD=3;privateStringfieldOwner;privateStringfieldName;privateStringpublic pter(MethodVisitor{}publicvoidvisitVarInsn(intopcode,int{switch(state)caseSEEN_NOTHING://S0->if(opcode==ALOAD&&var=={state=SEEN_ALOAD_0;}caseSEEN_ALOAD_0://S1->if(opcode==ALOAD&&var=={state=}caseSEEN_ALOAD_0ALOAD_0://S2->if(opcode==ALOAD&&var=={mv.visitVarInsn(ALOAD,0);}}}publicvoidvisitFieldInsn(intopcode,Stringowner,Stringname,Stringdesc){switch(state)caseSEEN_ALOAD_0ALOAD_0://S2->if(opcode==GETFIELD)state=fieldOwner=owner;fieldName=name;fieldDesc=desc;}caseSEEN_ALOAD_0ALOAD_0GETFIELD://S3->if(opcode==PUTFIELD&&{state=SEEN_NOTHING;}}}@Overrideprotectedvoid{switch(state)caseSEEN_ALOAD_0://S1->S0mv.visitVarInsn(ALOAD,0);caseSEEN_ALOAD_0ALOAD_0://S2->caseSEEN_ALOAD_0ALOAD_0GETFIELD://S3->mv.visitFieldInsn(GETFIELD,fieldOwner,fieldName,fieldDesc);}state=}} mons包中包含了一些預定義的方法適配器,可用于定義自己的適配器。這一節將介紹其中的三個,并用3.2.4節的AddTimerAdapter示例說明如許多字節代碼指令,比如xLOAD、xADD或xRETURN依賴于將它們應用于哪種類型。Type類提供了一個getOpcode方法,可用于為這些指令獲取與一給定類型相對應的操作碼。這一方法int類型的操作碼,針對哪種類型調用該方法,則返回該哪種類型的操作碼。例如t.getOpcode(IMUL),若t等于Type.FLOAT_TYPE,則返回FMUL。 java-classpathasm.jar:asm-util.jar//classversion49.0//accessflagspublicfinalclassjava/lang/Void//accessflags//signature//declaration:java.lang.Class<java.lang.Void>publicfinalstaticLjava/lang/Class;TYPE//accessflags2privateALOADINVOKESPECIALjava/lang/Object.<init>()VMAXSTACK=MAXLOCALS=//accessflags8LDCINVOKESTATICjava/lang/Class.getPrimitiveClass(...)...PUTSTATICjava/lang/Void.TYPE:Ljava/lang/Class;MAXSTACK=MAXLOCALS=}它說明如何生成一個靜態塊static{...},也就是用<clinit>方法(用于CLassINITializer有內容,可以用TraceMethodVisitor代替TraceClassVisitor(在這種情況下,必須顯式指定后端;這里使用了一個Textifier:publicMethodVisitorvisitMethod(intaccess,Stringname,Stringdesc,Stringsignature,String[]exceptions){MethodVisitormv=cv.visitMethod(access,name,desc,signature,if(debug&&mv!=null&&...){//如果必須此方Printerp=new{@Overridepublicvoid{}mv=newTraceMethodVisitor(mv,}returnnewMyMetho}這一代碼輸出該方法經MyMethopter轉換過后的結果在一個轉換鏈中任意點的使用是否正常。和TraceMethodVisitor類似,可以用publicMethodVisitorvisitMethod(intaccess,Stringname,Stringdesc,Stringsignature,String[]exceptions)MethodVisitormv=cv.visitMethod(access,name,desc,signature,ifdebug&&mvnull&&如果必須檢查這個方mv=newCheckMetho}returnnewMyMetho}這一代碼驗證MyMethopter正確地使用了MethodVisitorAPI。但要注意,這一適配器并沒有ISTORE1ALOAD1是無效的。Javadoc中提供有效的maxStack和maxLocals參數,那這種錯誤是可以被檢測出來的。問這個類。你會得到ASM代碼,以生成與源代碼相對應的字節代碼。這個方法適配器根據visitFrame中的幀,計算每條指令之前的棧幀。實際上,用一個使用COMPUTE_FRAMES選項的ASM適配器升級到Java6。在的AddTimerAdapter示例中,這個適配器可用于獲得操作數棧恰在RETURN指令之前的大小,從而允許為visitMaxs中的maxStack計算一個最優的已轉換值(事實上,在實踐中并不建議使用這一方法,因為它的效率要遠低于使用COMPUTE_MAXS:privateintpublicAddTimerMethopter2(Stringowner,intaccess,Stringname,Stringdesc,MethodVisitormv){super(ASM4,owner,access,name,desc,}@OverridepublicvoidvisitCode()mv.visitFieldInsn(GETSTATIC,owner,"timer","

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論