asm操作字節碼,刪除類的成員變量


https://blog.csdn.net/fyyyr/article/details/102816064

 

ASM基礎

ASM是一個Java字節碼操作框架,可用於class文件的修改。
其原理是將class文件載入,然后構建成一棵樹。然后根據用戶自定義的修改類對該樹進行加工,加工完成后即可得到修改后的class文件。
故而ASM中使用了visitor模式:class文件的結構是固定的,根據其構造出的樹作為被訪問者,則其節點也是固定的。只需要對每個節點定義一個訪問者即可進行指定的修改。
由於修改class主要涉及字段和方法,故最常用的visitor是FieldVisitorMethodVisitor
FieldVisitor為例,當ASM使用FieldVisitor來處理一個class的樹時,則該class的每個方法都會被傳入定義的FieldVisitor。於是只需要在FieldVisitor對特定的方法進行過濾處理即可。
ASM的官方文檔地址為:

https://asm.ow2.io/javadoc/overview-summary.html

打開可以看到其類庫的內容:
lib

其第一個包org.objectweb.asm為核心core包,包含了主要的功能接口和對象。

環境搭建

ASM使用的是com.sun.xml.internal.ws.org.objectweb.asm,因此不需要額外引入庫文件。
創建一個JBoss工程,載入class並修改,然后將修改后的class文件進行保存:

import com.sun.xml.internal.ws.org.objectweb.asm.ClassAdapter; import com.sun.xml.internal.ws.org.objectweb.asm.ClassReader; import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter; import java.io.*; public class Main { public static void main(String[] args) throws Exception { // 載入class文件 FileInputStream fis = new FileInputStream("D:\\Test\\Hello.class"); // 修改 ClassReader cr = new ClassReader(fis); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdapter classAdapter = new ASMTest(cw); cr.accept(classAdapter, ClassReader.SKIP_DEBUG); // 保存class文件 FileOutputStream fos = new FileOutputStream("D:\\Test\\Change\\Hello.class"); fos.write(cw.toByteArray()); fos.close(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

這就是總體的框架。至於修改,由ASMTest類負責。
新建一個Java類ASMTest

import com.sun.xml.internal.ws.org.objectweb.asm.*; public class ASMTest extends ClassAdapter { public ASMTest(ClassVisitor cv) { super(cv); } // 字段處理 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { return this.cv.visitField(access, name, desc, signature, value); } // 方法處理 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { return this.cv.visitMethod(access, name, desc, signature, exceptions); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

上面定義了負責進行修改的類ASMTest。直接運行main(),會在目標路徑下生成一個class。由於ASMTest沒有進行任何修改,故而生成的class與原始class內容相同。

解析

ClassReader

ClassReader能夠處理class文件的字節碼數據,構建出一棵該類的抽象樹。然后執行傳入的參數對象所包含的操作,從而對抽象樹進行加工。

ClassAdapter

ClassAdapter繼承自ClassVisitor,負責對class樹進行修改,開發者需要對其繼承並重載對應的修改方法。
也就是說,所有的visitor都集成在了ClassAdapter的方法中,只需要順序調用ClassAdapter的所有方法,即可實現所有visitor順序訪問class樹。
ClassAdapter的定義為:

public class ClassAdapter implements ClassVisitor { protected ClassVisitor cv; public ClassAdapter(ClassVisitor cv) { this.cv = cv; } public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.cv.visit(version, access, name, signature, superName, interfaces); } public void visitSource(String source, String debug) { this.cv.visitSource(source, debug); } public void visitOuterClass(String owner, String name, String desc) { this.cv.visitOuterClass(owner, name, desc); } public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return this.cv.visitAnnotation(desc, visible); } public void visitAttribute(Attribute attr) { this.cv.visitAttribute(attr); } public void visitInnerClass(String name, String outerName, String innerName, int access) { this.cv.visitInnerClass(name, outerName, innerName, access); } public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { return this.cv.visitField(access, name, desc, signature, value); } public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { return this.cv.visitMethod(access, name, desc, signature, exceptions); } public void visitEnd() { this.cv.visitEnd(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

其所有方法都可被重載。前面的例子中,ASMTest重載了visitField()visitMethod(),從而對字段和方法進行修改。
ClassAdapter的所有方法是按已安排好的順序來調用的,也就是ClassAdapter所有方法的定義順序。這一點通常不需要開發者關心。
關於每個方法的具體說明,可參考其父類ClassVisitor的文檔:

https://asm.ow2.io/javadoc/org/objectweb/asm/ClassVisitor.html

修改

以前面ASMTest為例。
設Hello.class的實現為:

public class Hello { String words = "Hello world!"; int value = 1; public void say() { System.out.println(words); } public void think() { words = "new thought"; } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

變量

變量有個descriptor屬性,即描述符,是個字符串,每種類型都對應一個字符串:

java類型 descriptor
boolean Z
char C
byte B
short S
int I
float F
long J
double D
Object Ljava/lang/Object;
int[] [I
Object[][] [[Ljava/lang/Object;

刪除成員變量

刪除Hello.class中的變量value

public class ASMTest extends ClassAdapter { public ASMTest(ClassVisitor cv) { super(cv); } public FieldVisitor visitField(final int access, final String name,final String desc, final String signature, final Object value) { if (name.equals("value")) { return null; } return cv.visitField(access, name, desc, signature, value); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Hello.class的所有成員變量會依次傳入visitField()。當傳入的變量名為value時,直接return null;,會將該變量從class樹中刪除。

修改成員變量權限

修改Hello.class中的變量value權限為public

public class ASMTest extends ClassAdapter { public ASMTest(ClassVisitor cv) { super(cv); } public FieldVisitor visitField(final int access, final String name,final String desc, final String signature, final Object value) { if (name.equals("value")) { return cv.visitField(Opcodes.ACC_PUBLIC, name, desc, signature, value); } return cv.visitField(access, name, desc, signature, value); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

添加成員變量

為Hello.class添加int變量tInt,其初始值為3。

public class ASMTest extends ClassAdapter { public ASMTest(ClassVisitor cv) { super(cv); } public void visitEnd() { FieldVisitor fv = this.cv.visitField(Opcodes.ACC_PRIVATE, "temp", "I", null, 3); if (fv!=null){ fv.visitEnd(); } super.visitEnd(); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

要點:

  1. 在所有字段都訪問完成后,會調用ClassVisitor.visitEnd()作為結束,此時即可進行成員變量的添加。
  2. 調用visitField()來訪問成員變量。若要訪問的成員變量不存在,則創建。

visitField()的定義為:

public FieldVisitor visitField(int access, java.lang.String name, java.lang.String descriptor, java.lang.String signature, java.lang.Object value) 
  • 1
  • 2
  • 3
  • 4
  • 5

其中:

  • descriptor: 類型描述符,即該成員變量的類型。
  • signature: 簽名。默認null即可。
  • value: 初始值。

方法

方法的描述符descriptor是個字符串,包含:

java類型 descriptor
void m(int i, float) (IF)V
int m(Object o) (Ljava/lang/Object;)I
int[] m(int i, String s) (ILjava/lang/String;)[I
Object m(int[] i) ([I)Ljava/lang/Object;

包含兩部分:

  • ()內的是參數類型,多個參數直接拼接即可。例如(IF),指有2個參數,第一個是int,第二個是float
  • ()后的是返回值類型。

刪除方法

刪除Hello.class中的方法think

public class ASMTest extends ClassAdapter { public ASMTest(ClassVisitor cv) { super(cv); } public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (name.equals("think")) { return null; } return this.cv.visitMethod(access, name, desc, signature, exceptions); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Hello.class的所有方法會依次傳入visitMethod()。當傳入的方法名為think時,直接return null;,會將該方法從class樹中刪除。
上面這種寫法是使用方法名來進行判斷。然而,有些類有多個同名方法,使用上面的寫法會將所有同名方法都刪除。若要只刪除某個方法,則需要同時對descriptor進行判斷:

if (name.equals("think") && desc.equals("I")) { return null; } 
  • 1
  • 2
  • 3

修改/添加

方法的修改/添加較為復雜。對於修改,常規做法是攔截到目標方法后,返回一個新的MethodVisitor

public class ASMTest extends ClassAdapter { public ASMTest(ClassVisitor cv) { super(cv); } public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (name.equals("think")) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions); return new NewMethodAdapter(mv); } return this.cv.visitMethod(access, name, desc, signature, exceptions); } } class NewMethodAdapter extends MethodAdapter { public NewMethodAdapter(MethodVisitor mv) { super(mv); } public void visitCode() {} }
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

MethodAdapter繼承自MethodVisitor。通過重載MethodAdapter的各個方法來實現對類方法的修改。可參考MethodVisitor官方文檔


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM