【发布时间】:2026-02-01 11:45:01
【问题描述】:
我是 ASM 新手,我需要一些有关字节码转换的帮助。
我想通过 ASM 为字节码中的每个局部变量添加带有 try/catch 块的打印功能。我发现之前关于添加 try/catch 块的问题是关于整个方法的。 我对堆栈映射框架知之甚少,因此任何指针都将受到高度赞赏。提前致谢。
我对每个对象的期望,例如someObject:如果这个对象是可序列化的,则打印它的序列化表示,如果不是,使用 toString() 打印:
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(someObject);
String serializedObject = Base64.getEncoder().encodeToString(bos.toByteArray());
oos.close();
System.out.println(serializedObject);
} catch (IOException ex) {
System.out.println(someObject.toString());
}
由于我试图对每个对象都这样做,所以我在MethodVisitor 中覆盖visitVarInsn(),如下所示:
@Override
public void visitVarInsn(int opcode, int var) {
super.visitVarInsn(opcode, var);
switch (opcode) {
case Opcodes.ASTORE:
Label tryStart = new Label ();
Label tryEnd = new Label ();
Label catchStart = new Label ();
Label catchEnd = new Label ();
mv.visitTryCatchBlock(tryStart, tryEnd, catchStart, "java/io/IOException");
mv.visitLabel(tryStart);
// ==> ByteArrayOutputStream bos = new ByteArrayOutputStream();
mv.visitTypeInsn(Opcodes.NEW, "java/io/ByteArrayOutputStream");
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/io/ByteArrayOutputStream", "<init>", "()V", false);
mv.visitVarInsn(Opcodes.ASTORE, var + 1);
// ==> ObjectOutputStream oos = new ObjectOutputStream(bos);
mv.visitTypeInsn(Opcodes.NEW, "java/io/ObjectOutputStream");
mv.visitInsn(Opcodes.DUP);
mv.visitVarInsn(Opcodes.ALOAD, var + 1);
mv.visitMethodInsn(INVOKESPECIAL, "java/io/ObjectOutputStream", "<init>", "(Ljava/io/OutputStream;)V", false);
mv.visitVarInsn(Opcodes.ASTORE, var + 2);
// ==> oos.writeObject(someObject);
mv.visitVarInsn(Opcodes.ALOAD, var + 2);
mv.visitVarInsn(Opcodes.ALOAD, var);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/ObjectOutputStream", "writeObject", "(Ljava/lang/Object;)V", false);
// ==> String serializedObject = Base64.getEncoder().encodeToString(bos.toByteArray());
mv.visitMethodInsn(INVOKESTATIC, "java/util/Base64", "getEncoder", "()Ljava/util/Base64$Encoder;", false);
mv.visitVarInsn(Opcodes.ALOAD, var + 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/ByteArrayOutputStream", "toByteArray", "()[B", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/util/Base64$Encoder", "encodeToString", "([B)Ljava/lang/String;", false);
mv.visitVarInsn(Opcodes.ASTORE, var + 3);
// ==> oos.close();
mv.visitVarInsn(Opcodes.ALOAD, var + 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/ObjectOutputStream", "close", "()V", false);
// ==> System.out.println
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitVarInsn(Opcodes.ALOAD, var + 3);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitLabel(tryEnd);
mv.visitJumpInsn(Opcodes.GOTO, catchEnd);
mv.visitLabel(catchStart);
mv.visitVarInsn(ASTORE, var + 1); // store exception
// ==> System.out.println
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitVarInsn(Opcodes.ALOAD, var);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitLabel(catchEnd);
// not sure whether I should add this.
mv.visitLocalVariable("e", "Ljava/io/IOException;", null, catchStart, catchEnd, var + 1);
break;
default: // do nothing
}
}
但是当我测试时,我不断收到NotSerializableException——我以为我使用了 try-catch 来捕获这个异常。
我不确定是否应该为 try-catch 块添加 visitFrame(我也不知道该怎么做)。
PS -- 任何关于其他更好的方法来记录每个局部变量的指针也将不胜感激!
【问题讨论】:
-
你注射太多了。您可能会覆盖原始代码使用的变量(除非您使用
LocalVariableSorter)并且为每个存储操作注入此代码块会使代码大小爆炸。创建一个专门的方法,封装你的第一个代码sn-p的操作,可以正常编译,只需注入该辅助方法的调用。 -
此外,请注意,要确保所有序列化数据都已刷新到
ByteArrayOutputStream,您必须在closeObjectOutputStream之前 调用toByteArray()。但是为什么你认为base64编码的序列化数据比toString()输出更好呢? -
@Holger 生成序列化数据的原因是我想将这些数据反序列化回对象并进行比较。
-
@Holger Inject an invocation -- 你的意思是在
MethodVisitor之外进行吗? -
不,只是在某处创建一个方法,比如
public static void printSerializedWithToStringFallback(Object someObject)在类mypackage.MyUtil中包含所有代码,然后,而不是所有复杂的代码,你只是在做super.visitVarInsn(opcode, var); if(opcode == Opcodes.ASTORE) { super.visitVarInsn(Opcodes.ALOAD, var); super.visitMethodInsn(Opcodes.INVOKESTATIC, "mypackage/MyUtil", "printSerializedWithToStringFallback", "(Ljava/lang/Object;)V", false); }注入一个调用该方法传递要打印的对象。
标签: java bytecode java-bytecode-asm bytecode-manipulation jvm-bytecode