动态字节码检测 - 问题

时间:2009-08-06 09:24:36

标签: java bytecode instrumentation agent

我有一个问题,我无法解决。假设我们有以下两个类和一个继承关系:

public class A {
}

public class B extends A {
    public void foo() {}
}

我想要检测其他代码,使其看起来如下:

public class A {
    public void print() { }
}

public class B extends A {
     public void foo() { print(); }
}

为了实现这一目标,我将我的实现基于java.lang.instrument包,使用具有我自己的类文件转换器的代理。该机制也称为动态字节码检测。

到目前为止一块蛋糕。 现在,我的测试方法执行以下操作:

代码:

B b = new B();
b.foo();

由于instrumentation包中的以下限制,这不起作用:当调用new B()时,检测从B类开始,并在加载被操作的类时最终出现编译错误,因为超类A没有print()方法呢!问题是我是否以及如何在类B之前触发类A的检测。我的classfiletransformer的transform()方法应该显式地用类A调用!所以我开始阅读并碰到了这个:

java.lang.instrument.ClassFileTransformer.transform()的javadoc说:

  

将要求变压器   每个新的类定义和每个   类重新定义。请求一个   新的类定义是用   ClassLoader.defineClass。请求   用于重新定义类   Instrumentation.redefineClasses或其   原生等价物。

转换方法附带了一个类加载器实例,所以我想,为什么不在B的仪器上用类A调用loadClass方法(loadClass调用defineClass)已经开始。 我预计仪器方法将被调用,但遗憾的是情况并非如此。而是在没有检测的情况下加载了类A。 (代理不会拦截加载过程,尽管应该这样做)

任何想法,如何解决这个问题?您是否看到为什么操作某些字节码的代理无法手动加载另一个类,然后希望通过该/任何代理发送的类?

请注意,以下代码正常工作,因为A在加载B之前已加载并进行了检测。

A a =  new A();
B b = new B();
b.foo();

非常感谢!

1 个答案:

答案 0 :(得分:8)

当我在Sun 1.6.0_15和1.5.0_17 JRE(我使用ASM)上的A之前转换B时,我没有看到任何问题。我会通过在外部运行并检查结果类(例如使用javap)来仔细检查转换代码。我还会检查你的类路径配置,以确保在你的代理之前没有加载A由于某种原因(也许用getAllLoadedClasses检查你的premain。)


编辑:

如果您在代理中加载类A,请执行以下操作:

Class.forName("A");

...然后抛出异常:

Exception in thread "main" java.lang.NoSuchMethodError: B.print()V

这是有道理的 - A成为代理的依赖关系,代理程序检测自己的代码是没有意义的。你会得到一个无限循环,导致堆栈溢出。因此,A不会处理ClassFileTransformer


为了完整性,这是我的测试代码,没有问题。如上所述,它取决于ASM库。

代理人:

public class ClassModifierAgent implements ClassFileTransformer {

  public byte[] transform(ClassLoader loader, String className,
      Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
      byte[] classfileBuffer) throws IllegalClassFormatException {
    System.out.println("transform: " + className);
    if ("A".equals(className)) {
      return new AModifier().modify(classfileBuffer);
    }
    if ("B".equals(className)) {
      return new BModifier().modify(classfileBuffer);
    }
    return classfileBuffer;
  }

  /** Agent "main" equivalent */
  public static void premain(String agentArguments,
      Instrumentation instrumentation) {
    instrumentation.addTransformer(new ClassModifierAgent());
  }

}

A的方法注入器:

public class AModifier extends Modifier {

  @Override
  protected ClassVisitor createVisitor(ClassVisitor cv) {
    return new AVisitor(cv);
  }

  private static class AVisitor extends ClassAdapter {

    public AVisitor(ClassVisitor cv) { super(cv); }

    @Override
    public void visitEnd() {
      MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "print", "()V",
          null, null);
      mv.visitCode();
      mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
          "Ljava/io/PrintStream;");
      mv.visitLdcInsn("X");
      mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",
          "println", "(Ljava/lang/String;)V");
      mv.visitInsn(Opcodes.RETURN);
      mv.visitMaxs(2, 1);
      mv.visitEnd();

      super.visitEnd();
    }

  }

}

B的方法替换器:

public class BModifier extends Modifier {

  @Override
  protected ClassVisitor createVisitor(ClassVisitor cv) {
    return new BVisitor(cv);
  }

  class BVisitor extends ClassAdapter {

    public BVisitor(ClassVisitor cv) { super(cv); }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
        String signature, String[] exceptions) {
      if ("foo".equals(name)) {
        MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "foo", "()V",
            null, null);
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "B", "print", "()V");
        mv.visitInsn(Opcodes.RETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
        return new EmptyVisitor();
      } else {
        return super.visitMethod(access, name, desc, signature, exceptions);
      }
    }
  }
}

通用基本代码:

public abstract class Modifier {

  protected abstract ClassVisitor createVisitor(ClassVisitor cv);

  public byte[] modify(byte[] data) {
    ClassReader reader = new ClassReader(data);
    ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
    ClassVisitor visitor = writer;
    visitor = new CheckClassAdapter(visitor);
    visitor = createVisitor(visitor);
    reader.accept(visitor, 0);
    return writer.toByteArray();
  }

}

对于一些可见的结果,我向System.out.println('X');添加了A.print()

在此代码上运行时:

public class MainInstrumented {
  public static void main(String[] args) {
    new B().foo();
  }
}

...它产生了这个输出:

transform: MainInstrumented
transform: B
transform: A
X