java.lang.instrument 中的堆栈溢出错误与 ASM 字节码转换



我是Java代理检测和ASM字节码检测的新手。我从加州大学洛杉矶分校的教程中获取了代码,并使用java.lang.instrument将其用于javagent检测。

第一个问题,ASM字节码库中是否有任何与javaagent仪器不兼容的内容?

这是经过一定编辑形式的程序:

public class Instrumenter {
public static void premain(String args, Instrumentation inst) throws Exception {
Transformer tr = new Transformer();
inst.addTransformer(tr);
}
}
class Transformer implements ClassFileTransformer {
public Transformer() {
}
@Override
public byte[] transform( ClassLoader loader, String className, Class<?> klass, ProtectionDomain domain, byte[] klassFileBuffer ) throws IllegalClassFormatException {
byte[] barray;
ClassWriter cwriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
ClassReader creader;
try {
creader = new ClassReader(new ByteArrayInputStream(klassFileBuffer));
} catch (Exception exc) {
throw new IllegalClassFormatException(exc.getMessage());
}
ClassVisitor cvisitor = new ClassAdapter(cwriter);
creader.accept(cvisitor, 0);
barray = cwriter.toByteArray();
return barray;
}
}
class ClassAdapter extends ClassVisitor implements Opcodes {
public ClassAdapter(ClassVisitor cv) {
super(ASM7, cv);
}

@Override
public MethodVisitor visitMethod( final int access, final String name, final String desc, final String signature, final String[] exceptions ) {
this.pwriter.println(ClassAdapter.nextMethodId + "," + this.className + "#" + name);
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if (mv == null) {
return null;
} else {
return new MethodAdapter(mv);
}
}
}
class MethodAdapter extends MethodVisitor implements Opcodes {
public MethodAdapter(final MethodVisitor mv) {
super(ASM7, mv);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
mv.visitLdcInsn("CALL " + name);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
// do call
mv.visitMethodInsn(opcode, owner, name, desc, itf);
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
mv.visitLdcInsn("RETURN " + name);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
}

因此,javaagent instrument适用于小程序。我尝试在DaCapo基准测试套件上运行它,它抛出了一个StackOverflowError,如下所示:

Exception: java.lang.StackOverflowError thrown from the UncaughtExceptionHandler in thread "main"

当我删除在 visitMethodInsn 中添加的说明时,代理成功运行。我对此进行了更多的研究,并在ASM文档中发现了一些关于必须调用MethodVisitor.visitMaxs的内容。这似乎是StackOverflowError的最可能原因。

所以进一步的问题:

  • 是这样吗?我必须在某个时候打电话给visitMaxs吗?如果是,在哪里?
  • 如果不是,那我做错了什么?或者我应该怎么做才能确保没有堆栈溢出?

当你注册一个ClassFileTransformer时,它将为每个后续加载的类调用。这可能包括您注入的打印操作本身使用的类(如果以前未使用这些类)。您正在为每个方法调用(包括构造函数调用)注入 print 语句,并且System.err.println(…)背后的操作将涉及方法调用和对象构造,因此如果这些操作被检测,它们将进入另一个打印操作,此递归将导致StackOverflowError

显然,安装了一个UncaughtExceptionHandler,它尝试打印StackOverflowError,它本身以相同的方式进行检测,将再次导致StackOverflowError,因此错误消息读作"StackOverflowErrorUncaughtExceptionHandler中抛出"。

例如,当类的loadernull时,您不能变换该类,以排除引导类加载器加载的所有类。或者,您检查name参数以排除以java.开头的类。或者更详细的解决方案是增强您正在注入的代码,检测它何时在注入的打印操作中,而不是进入递归。

顺便说一句,使用new ClassReader(klassFileBuffer),你不需要try … catch块。此外,当您插入像您这样简单的代码时,您可以使用ClassWriter.COMPUTE_MAXS而不是ClassWriter.COMPUTE_FRAMES,以避免对堆栈映射框进行昂贵的重新计算。由于您没有向读取器指定SKIP_FRAMES,因此它会向写入器报告原始帧,并且 ASM 能够调整位置,因此当您插入一些简单的指令时没有问题。仅当您插入或删除分支或引入必须跨分支保留的变量时,您才需要调整或重新计算帧。

相关内容

  • 没有找到相关文章

最新更新