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

标签 java java-bytecode-asm javaagents jvm-bytecode

我是 Java 代理检测和 ASM 字节码检测的新手。我从this UCLA tutorial获取代码并将其用于 javagent 检测,使用 java.lang.instrument .

第一个问题,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 benchmark suite 上运行它它会抛出一个 StackOverflowError ,如下所示:

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

当我删除在visitMethodInsn中添加的指令时,代理成功运行。我对此进行了更多研究,并在 ASM docs about having to call 中找到了一些内容MethodVisitor.visitMaxs 。这似乎是 StackOverflowError 最可能的原因。

进一步的问题:

  • 是这样吗?我是否必须在某个时候调用visitMaxs?如果是的话,在哪里?
  • 如果不是,那我做错了什么?或者我应该做什么来确保没有堆栈溢出?

最佳答案

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

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

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

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

关于java - 使用 ASM 字节码转换时 java.lang.instrument 中出现堆栈溢出错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55216428/

相关文章:

java - Scala:需要 "explicit return"吗?

java - 在appdynamics中监控java应用程序

java - 使用 Objectweb ASM 字节码增加 Java Fied 值

java - 在 catalinaopts 中附加多个 javaagents

java - 如何在Gradle中启用Java代理调试?

java - 注解处理中如何获取父类(super class)名

java - 无法解释的 Java 诡计——可能与线程有关?

java - 如何在 Java Mad Libs 程序中使用随机发生器?

java - 使用 ASM Java 库拆箱

java - 在我的项目包之外的类上使用asm(java字节码)类读取器?