我正在研究ASM框架一段时间,我成功编辑了.class文件。我处于需要用另一个方法调用替换方法调用的情况。我通过覆盖super.visitMethodInsn()
我在这里发布编码:
未修改的课程:UserSampleClass.java
package checking;
public class UserSampleClass {
public static void main(String[] args) {
UserSampleClass usc= new UserSampleClass();
usc.display();
}
public String display() {
System.out.println("Hello Method: "+hello());
System.out.println("Bye Method: "+bye());
System.out.println("Hello Method: "+hello());
return "i'm display";
}
private String bye() {
return "Bye";
}
private String hello() {
return "Hello";
}
}
上述程序的输出将是:
Hello Method: Hello
Bye Method: Bye
Hello Method: Hello
现在我修改了原始类文件并将三个方法调用(即hello(),bye(),hello())更改为其他3个方法调用,如下所述:
替换第一个hello方法:replaceFirstHelloInvoke.java
。
package chekingDepend;
public class replaceFirstHelloInvoke {
public replaceFirstHelloInvoke()
{}
public static String replaceFirstHello() {
return "one";
}
}
替换再见方法:replaceByeInvoke.java
package chekingDepend;
public class replaceByeInvoke {
public replaceByeInvoke()
{}
public static String replaceBye() {
return "two";
}
}
替换第二个问候:replaceSecondHelloInvoke.java
package chekingDepend;
public class replaceSecondHelloInvoke {
public replaceSecondHelloInvoke()
{}
public static String replaceSecondHello() {
return "three";
}
}
我已将所有这些方法创建为static
,因为它们可以通过类名直接引用。
我能够通过ASM成功生成修改后的类。
现在的问题是修改后的类没有运行。当我试图运行它时出现此错误:
Exception in thread "main" java.lang.VerifyError: (class: checking/UserSampleClass, method: display signature: ()Ljava/lang/String;) Incompatible object argument for function call
Could not find the main class: UserSampleClass. Program will exit.
我也检查了包的依赖性。一切似乎都是正确的。
我还使用了ASM提供的字节码比较工具来检查字节码级别的修改。
所有变化似乎都是正确的。
我将在此处发布字节代码:
未修改的字节代码:UserSampleClass.class
// class version 49.0 (49)
// access flags 0x21
public class checking/UserSampleClass {
// compiled from: UserSampleClass.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lchecking/UserSampleClass; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 6 L0
NEW checking/UserSampleClass
DUP
INVOKESPECIAL checking/UserSampleClass.<init> ()V
ASTORE 1
L1
LINENUMBER 7 L1
ALOAD 1
INVOKEVIRTUAL checking/UserSampleClass.display ()Ljava/lang/String;
POP
L2
LINENUMBER 8 L2
RETURN
L3
LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
LOCALVARIABLE usc Lchecking/UserSampleClass; L1 L3 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1
public display()Ljava/lang/String;
L0
LINENUMBER 12 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
NEW java/lang/StringBuilder
DUP
LDC "hello:"
INVOKESPECIAL java/lang/StringBuilder.<init> (Ljava/lang/String;)V
ALOAD 0
INVOKESPECIAL checking/UserSampleClass.hello ()Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC " "
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "bye:"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
INVOKESPECIAL checking/UserSampleClass.bye ()Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC " "
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "hello:"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
INVOKESPECIAL checking/UserSampleClass.hello ()Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
LINENUMBER 13 L1
LDC "i'm display"
ARETURN
L2
LOCALVARIABLE this Lchecking/UserSampleClass; L0 L2 0
MAXSTACK = 4
MAXLOCALS = 1
// access flags 0x2
private bye()Ljava/lang/String;
L0
LINENUMBER 18 L0
LDC "Bye"
ARETURN
L1
LOCALVARIABLE this Lchecking/UserSampleClass; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x2
private hello()Ljava/lang/String;
L0
LINENUMBER 23 L0
LDC "Hello"
ARETURN
L1
LOCALVARIABLE this Lchecking/UserSampleClass; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
}
修改后的字节码:UserSampleClass.class
// class version 49.0 (49)
// access flags 0x21
public class checking/UserSampleClass {
// compiled from: UserSampleClass.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lchecking/UserSampleClass; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 6 L0
NEW checking/UserSampleClass
DUP
INVOKESPECIAL checking/UserSampleClass.<init> ()V
ASTORE 1
L1
LINENUMBER 7 L1
ALOAD 1
INVOKEVIRTUAL checking/UserSampleClass.display ()Ljava/lang/String;
POP
L2
LINENUMBER 8 L2
RETURN
L3
LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
LOCALVARIABLE usc Lchecking/UserSampleClass; L1 L3 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1
public display()Ljava/lang/String;
L0
LINENUMBER 12 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
NEW java/lang/StringBuilder
DUP
LDC "hello:"
INVOKESPECIAL java/lang/StringBuilder.<init> (Ljava/lang/String;)V
ALOAD 0
INVOKESTATIC chekingDepend/replaceFirstHelloInvoke.replaceFirstHello ()Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC " "
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "bye:"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
INVOKESTATIC chekingDepend/replaceByeInvoke.replaceBye ()Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC " "
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "hello:"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
INVOKESTATIC chekingDepend/replaceSecondHelloInvoke.replaceSecondHello ()Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
LINENUMBER 13 L1
LDC "i'm display"
ARETURN
L2
LOCALVARIABLE this Lchecking/UserSampleClass; L0 L2 0
MAXSTACK = 6
MAXLOCALS = 1
// access flags 0x2
private bye()Ljava/lang/String;
L0
LINENUMBER 18 L0
LDC "Bye"
ARETURN
L1
LOCALVARIABLE this Lchecking/UserSampleClass; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x2
private hello()Ljava/lang/String;
L0
LINENUMBER 23 L0
LDC "Hello"
ARETURN
L1
LOCALVARIABLE this Lchecking/UserSampleClass; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
}
重载的visitMethodInsn()
方法
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
MetaData executionUnitMetaData = new MetaData();
String[] methodInvocationToReplace = {"hello", "bye", "hello"};
String[] replacableClassName = {"chekingDepend.replaceFirstHelloInvoke","chekingDepend.replaceByeInvoke","chekingDepend.replaceSecondHelloInvoke"};
String[] replacableMethodName = {"replaceFirstHello","replaceBye","replaceSecondHello"};
if(i<methodInvocationToReplace.length && name.equals(methodInvocationToReplace[i])) {
super.visitMethodInsn(Opcodes.INVOKESTATIC, replacableClassName[i].replace('.', '/'), replacableMethodName[i], desc); // replacableMethodDesc[i]);
i++;
}else {
super.visitMethodInsn(opcode, owner, name, desc);
}
}
}
这里我将文件从执行前复制到具有相同目录结构的另一个目录,以便不修改原始.class文件。修改后的.class文件是在具有相同包结构的seprate文件夹中创建的。
但我很清楚为什么会得到lava.lang.VerifyError
。
请帮我解决这个问题。
我使用Java 1.6和ASM 4.0
如果程序运行正常,我的输出应为:
Hello Method: one
Bye Method: two
Hello Method: three
答案 0 :(得分:1)
当您的原始代码调用私有实例方法时,它会这样:
ALOAD 0 # push `this`
INVOKESPECIAL checking/UserSampleClass.hello ()Ljava/lang/String; # call the method.
...并且INVOKESPECIAL将弹出第一个操作数作为调用目标。
在修改过的代码中,我们改为:
ALOAD 0 # push `this`
INVOKESTATIC chekingDepend/replaceFirstHelloInvoke.replaceFirstHello ()Ljava/lang/String;
...但INVOKESTATIC不会弹出opstack元素。
验证者(正确地)认为这是错误的。
比较相应字节码指令的操作数堆栈要求: