如何解决注入 try/catch 块以通过 ASM 对字节码进行序列化检查
我是 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());
}
因为我试图对每个对象都这样做,所以我在 visitvarInsn()
中覆盖了 MethodVisitor
,如下所示:
@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","(Ljava/io/OutputStream;)V",var + 2);
// ==> oos.writeObject(someObject);
mv.visitvarInsn(Opcodes.ALOAD,var + 2);
mv.visitvarInsn(Opcodes.ALOAD,var);
mv.visitMethodInsn(INVOKEVIRTUAL,"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,"toByteArray","()[B",false);
mv.visitMethodInsn(INVOKEVIRTUAL,"java/util/Base64$Encoder","encodetoString","([B)Ljava/lang/String;",var + 3);
// ==> oos.close();
mv.visitvarInsn(Opcodes.ALOAD,var + 2);
mv.visitMethodInsn(INVOKEVIRTUAL,"close",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/Object","toString","()Ljava/lang/String;",false);
mv.visitLabel(catchEnd);
// not sure whether I should add this.
mv.visitLocalVariable("e","Ljava/io/IOException;",null,catchEnd,var + 1);
break;
default: // do nothing
}
}
但是当我测试时,我一直收到 NotSerializableException
-- 我以为我使用 try-catch 来捕获此异常。
我不确定是否应该为 try-catch 块添加 visitFrame
(我也不知道该怎么做)。
PS -- 任何有关为每个局部变量进行日志记录的其他更好方法的指针也将受到高度赞赏!
解决方法
您构建 try-catch 块的逻辑是正确的,只是您使用的变量 var + 1
到 var + 3
可能与原始代码的使用冲突。当我尝试使用您的代码来检测一个专门选择的示例以使其没有此类变量冲突时,它会起作用。
您可以使用 LocalVariablesSorter
解决此类问题,但它需要调用 newLocal
来为您注入的代码声明一个变量,并且由于您的代码中没有这样的调用,我假设您'不使用 LocalVariablesSorter
。
通常,注入如此复杂的代码,甚至可能多次注入,不仅容易出错,而且可能会显着增加代码大小,甚至超过方法的最大代码大小。
最好的方法是将复杂的代码单独移动到一个方法中,甚至可以以预编译的形式交付,即使用普通 Java 源代码创建,并且只注入对该方法的调用。
所以,假设有一个像
这样的助手类package mypackage;
import java.io.*;
import java.util.Base64;
public class MyUtil {
public static void printSerializedWithToStringFallback(Object someObject) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(someObject);
oos.close();
System.out.println(Base64.getEncoder().encodeToString(bos.toByteArray()));
} catch(IOException ex) {
System.out.println(someObject.toString());
}
}
}
你可以像这样注入调用
@Override
public void visitVarInsn(int opcode,int var) {
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);
}
}
注入这个调用不会引入任何分支,所以不需要重新计算堆栈映射表。甚至对堆栈的要求也不会改变。注入的代码不会引入新的局部变量,其最高堆栈大小在 aload
之后,与 astore
之前的堆栈大小相同。所以这个简单的检测不需要 COMPUTE_FRAMES
选项,甚至不需要 COMPUTE_MAXS
。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。