我是ASM的新手,我想要一些与字节码转换相关的帮助。
问题:我想通过ASM在字节码中为整个方法添加try / catch块,并希望使用java -noverify选项运行该方法。我可以为整个方法添加try / catch块,但是当我尝试执行该方法时,我得到的是java.lang.VerifyError'。如果我使用java -noverify选项,那么它将运行。请帮帮我。
以下是详细信息。
public class Example {
public static void hello() {
System.out.println("Hello world");
}
}
我想转换上面的代码,如下所示,使用ASM字节码检测引入try / catch块。
public class Example {
public static void hello() {
try
{
System.out.println("Hello world");
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
下面的代码添加了try / catch块,但无法使用java -noverify选项执行代码。
public class InstrumentExample {
/**
* Our custom method modifier method visitor class. It delegate all calls to
* the super class. Do our logic of adding try/catch block
*
*/
public static class ModifierMethodWriter extends MethodVisitor {
// methodName to make sure adding try catch block for the specific
// method.
private String methodName;
// below label variables are for adding try/catch blocks in instrumented
// code.
private Label lTryBlockStart;
private Label lTryBlockEnd;
private Label lCatchBlockStart;
private Label lCatchBlockEnd;
/**
* constructor for accepting methodVisitor object and methodName
*
* @param api: the ASM API version implemented by this visitor
* @param mv: MethodVisitor obj
* @param methodName : methodName to make sure adding try catch block for the specific method.
*/
public ModifierMethodWriter(int api, MethodVisitor mv, String methodName) {
super(api, mv);
this.methodName = methodName;
}
// We want to add try/catch block for the entire code in the method
// so adding the try/catch when the method is started visiting the code.
@Override
public void visitCode() {
super.visitCode();
// adding try/catch block only if the method is hello()
if (methodName.equals("hello")) {
lTryBlockStart = new Label();
lTryBlockEnd = new Label();
lCatchBlockStart = new Label();
lCatchBlockEnd = new Label();
// set up try-catch block for RuntimeException
visitTryCatchBlock(lTryBlockStart, lTryBlockEnd,
lCatchBlockStart, "java/lang/Exception");
// started the try block
visitLabel(lTryBlockStart);
}
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
// closing the try block and opening the catch block if the method
// is hello()
if (methodName.equals("hello")) {
// closing the try block
visitLabel(lTryBlockEnd);
// when here, no exception was thrown, so skip exception handler
visitJumpInsn(GOTO, lCatchBlockEnd);
// exception handler starts here, with RuntimeException stored
// on stack
visitLabel(lCatchBlockStart);
// store the RuntimeException in local variable
visitVarInsn(ASTORE, 2);
// here we could for example do e.printStackTrace()
visitVarInsn(ALOAD, 2); // load it
visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception",
"printStackTrace", "()V", false);
// exception handler ends here:
visitLabel(lCatchBlockEnd);
}
super.visitMaxs(maxStack, maxLocals);
}
}
/**
* Our class modifier class visitor. It delegate all calls to the super
* class Only makes sure that it returns our MethodVisitor for every method
*
*/
public static class ModifierClassWriter extends ClassVisitor {
private int api;
public ModifierClassWriter(int api, ClassWriter cv) {
super(api, cv);
this.api = api;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature,
exceptions);
// Our custom MethodWriter
ModifierMethodWriter mvw = new ModifierMethodWriter(api, mv, name);
return mvw;
}
}
public static void main(String[] args) throws IOException {
DataOutputStream dout = null;
try {
// loading the class
InputStream in = InstrumentExample.class
.getResourceAsStream("Example.class");
ClassReader classReader = new ClassReader(in);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
// Wrap the ClassWriter with our custom ClassVisitor
ModifierClassWriter mcw = new ModifierClassWriter(ASM4, cw);
ClassVisitor cv = new CheckClassAdapter(mcw);
classReader.accept(cv, 0);
byte[] byteArray = cw.toByteArray();
dout = new DataOutputStream(new FileOutputStream(new File("Example.class")));
dout.write(byteArray);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (dout != null)
dout.close();
}
}
}
为了调试我使用了CheckClassAdapter,我遇到了以下验证问题。
Message:org.objectweb.asm.tree.analysis.AnalyzerException: Execution can fall off end of the code
at org.objectweb.asm.tree.analysis.Analyzer.findSubroutine(Unknown Source)
at org.objectweb.asm.tree.analysis.Analyzer.findSubroutine(Unknown Source)
at org.objectweb.asm.tree.analysis.Analyzer.analyze(Unknown Source)
at org.objectweb.asm.util.CheckClassAdapter.verify(Unknown Source)
at org.objectweb.asm.util.CheckClassAdapter.verify(Unknown Source)
at com.mfr.instrumentation.selenium.work.InstrumentExample.main(InstrumentExample.java:166)
hello()V
00000 ? : L0
00001 ? : GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
00002 ? : LDC "Hello world"
00003 ? : INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
00004 ? : RETURN
00005 ? : L1
00006 ? : GOTO L2
00007 ? : L3
00008 ? : ASTORE 2
00009 ? : ALOAD 2
00010 ? : INVOKEVIRTUAL java/lang/Exception.printStackTrace ()V
00011 ? : L2
TRYCATCHBLOCK L0 L1 L3 java/lang/Exception
我无法理解上述验证消息。
答案 0 :(得分:5)
您需要遍历您的类并在此过程中使用修改后的MethodVisitor
。如果将整个方法包装在try
- catch
构造中。您可以通过拦截调用块的开始和结束的回调来插入构造。这些方法是visitCode
和visitEnd
,你可以这样截取:
class MyMethodVisitor extends MethodVisitor {
// constructor omitted
private final Label start = new Label(),
end = new Label(),
handler = new Label();
@Override
public void visitCode() {
super.visitCode();
visitTryCatchBlock(start,
end,
handler,
"java/lang/Exception");
visitLabel(start);
}
@Override
public void visitEnd() {
visitJumpInsn(GOTO, end);
visitLabel(handler);
visitMethodInsn(INVOKEVIRTUAL,
"java/lang/RuntimeException",
"printStackTrace",
"()V");
visitInsn(RETURN);
visitLabel(lCatchBlockEnd);
super.visitEnd();
}
}
但是,请注意,此示例不包括在为Java 7 +生成字节代码时需要添加的堆栈映射帧。
请注意,此解决方案将在方法的异常表的开头注册一个显性处理程序,该异常表覆盖方法中已存在的所有其他try-catch-finally块!
注意:在较新版本的ASM中,处理程序的代码需要在方法visitMaxs(int,int)中编写:
@Override
public void visitMaxs(int maxStack, int maxLocals) {
// visit the corresponding instructions
super.visitMaxs(maxStack, maxLocals);
}
这是因为标签和说明只能在visitMaxs
之前访问,而visitMaxs
只能在visitEnd
之前访问,因此在visitEnd
中生成代码会导致错误。
答案 1 :(得分:4)
上述异常与计算stackmap帧有关。 ASM提供了自己提供stackmap帧的机制。我们需要在ClassWriter构造函数中将参数标志用作COMPUTE_FRAMES。
Ex:ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
public static final int COMPUTE_FRAMES 标记以从头开始自动计算方法的堆栈映射帧。如果设置了此标志,则忽略对MethodVisitor.visitFrame(int,int,java.lang.Object [],int,java.lang.Object [])方法的调用,并从中重新计算堆栈映射帧。方法字节码。 visitMaxs方法的参数也被忽略并从字节码重新计算。换句话说,computeFrames意味着computeMaxs。
来自ASM ClassWriter API。