6 java instrumentation bytecode agent
我有一个问题,我无法解决.假设我们有以下两个类和一个继承关系:
public class A {
}
public class B extends A {
public void foo() {}
}
Run Code Online (Sandbox Code Playgroud)
我想要检测其他代码,使其看起来如下:
public class A {
public void print() { }
}
public class B extends A {
public void foo() { print(); }
}
Run Code Online (Sandbox Code Playgroud)
为了实现这个目标,我将我的实现基于java.lang.instrument包,使用具有我自己的类文件转换器的代理.该机制也称为动态字节码检测.
到目前为止一块蛋糕.现在,我的测试方法执行以下操作:
码:
B b = new B();
b.foo();
Run Code Online (Sandbox Code Playgroud)
由于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没有仪器的情况下加载了类.(代理不会拦截加载过程,尽管它应该)
任何想法,如何解决这个问题?您是否看到为什么操作某些字节码的代理无法手动加载另一个类,然后希望通过该/任何代理发送的类?
请注意,以下代码正常工作,因为在操作B之前加载了A并进行了检测.
A a = new A();
B b = new B();
b.foo();
Run Code Online (Sandbox Code Playgroud)
非常感谢!
当我在Sun 1.6.0_15和1.5.0_17 JRE(我使用ASM)上的A之前转换B时,我没有看到任何问题.我会通过在外部运行并检查结果类(例如使用javap)来仔细检查转换代码.我还会检查你的类路径配置,以确保在你的代理之前没有加载A由于某种原因(也许用getAllLoadedClasses检查你的premain).
编辑:
如果您A在代理中加载类,如下所示:
Class.forName("A");
Run Code Online (Sandbox Code Playgroud)
...然后抛出异常:
Exception in thread "main" java.lang.NoSuchMethodError: B.print()V
Run Code Online (Sandbox Code Playgroud)
这是有道理的 - 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());
}
}
Run Code Online (Sandbox Code Playgroud)
方法注入器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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
方法替换器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);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
通用基本代码:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
对于一些明显的效果,我添加了一个System.out.println('X');到A.print().
在此代码上运行时:
public class MainInstrumented {
public static void main(String[] args) {
new B().foo();
}
}
Run Code Online (Sandbox Code Playgroud)
...它产生这个输出:
transform: MainInstrumented
transform: B
transform: A
X
Run Code Online (Sandbox Code Playgroud)