繁体   English   中英

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

[英]Stack overflow error in java.lang.instrument with ASM bytecode transformation

我是Java代理检测和ASM字节码检测的新手。 我从此UCLA教程中获取了代码,并使用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工具适用于小型程序。 我尝试在DaCapo基准测试套件上运行它,并且抛出了StackOverflowError,如下所示:

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

当我删除visitMethodInsn中添加的说明时,该代理成功运行。 我对此进行了进一步研究,并在ASM文档中找到了一些有关必须调用 MethodVisitor.visitMaxs的信息 这似乎是StackOverflowError的最可能原因。

因此,进一步的问题:

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

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

显然,已经安装了UncaughtExceptionHandler ,它试图打印StackOverflowError ,它本身以相同的方式进行检测,将再次导致StackOverflowError ,因此错误消息的内容类似于“ UncaughtExceptionHandler抛出的StackOverflowError ”。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM