通过检测字节码调用类的静态方法时出现 NoClassDefFoundError

Posted

技术标签:

【中文标题】通过检测字节码调用类的静态方法时出现 NoClassDefFoundError【英文标题】:NoClassDefFoundError when invoking static method of a class by instrumenting byte code 【发布时间】:2017-03-07 23:42:51 【问题描述】:

每当分配对象但遇到问题时,我都会尝试调用静态方法。我已将我的代码缩减为较小的工作示例。

MemorySnifferAgent.java

public class MemorySnifferAgent 
    public static void premain(String agentArguments, Instrumentation instrumentation) 
        Callback.main(); //Make sure the class is loaded ?
        instrumentation.addTransformer(new MemoryAllocationTransformer());
    

MemoryAllocationTransformer.java

public class MemoryAllocationTransformer implements ClassFileTransformer 
    public byte[] instrumentByteCode(byte[] bytecode) 
        ClassReader reader = new ClassReader(bytecode);
        ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        CustomClassReader customVisitor = new CustomClassReader(Opcodes.ASM4, writer);
        reader.accept(customVisitor, 0);
        return writer.toByteArray();
    

    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException 
        byte[] bytes = instrumentByteCode(classfileBuffer);
        return  bytes;
    

CustomClassReader.java

public class CustomClassReader extends ClassVisitor 
    public CustomClassReader(int api, ClassWriter classWriter) 
        super(api, classWriter);
    

    public MethodVisitor visitMethod(final int access, final String name,
                                     final String desc, final String signature, final String[] exceptions) 
        MethodVisitor methodWritter = super.visitMethod(access, name, desc, signature, exceptions);
        return new CustomMethodWritter(api, name, methodWritter);
    

CustomMethodWritter.java

class CustomMethodWritter extends MethodVisitor 
    String name;

    public CustomMethodWritter(int i, String name, MethodVisitor methodWriter) 
        super(i, methodWriter);
        this.name = name;
    

    @Override
    public void visitTypeInsn(int opcode, String type) 
        super.visitTypeInsn(opcode, type);
        if (opcode == Opcodes.NEW) 
            super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/chasingnanos/Callback", "onAllocation", "()V", false);
        
    

注意:

我意识到利用所有分配需要利用所有 NEW* 操作码和反射 API super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/chasingnanos/Callback", "onAllocation", "()V", false);似乎是问题所在。虽然我无法找到问题所在。

抱歉,如果这是一个非常基本的问题。我是字节码操作的新手。

我得到的错误:

java.lang.NoClassDefFoundError - 类:'java/lang/NoClassDefFoundError'

【问题讨论】:

【参考方案1】:

我认为这个问题与不同的ClassLoaders 提供的隔离有关。特别是代理的代码和“主”代码由不同的Classloaders 加载,因此您不能从“主”代码调用您的Callback。解决此问题的方法是将您的 Callback 类移动到不同的 jar 并使用 -Xbootclasspath/a 选项将其添加到引导类路径中。

另见类似问题 Java NoClassDefFoundError when calling own class from instrumented method

更新(过滤)

如果您想过滤掉无法访问Callback 的类而不是使用引导程序,您可能应该执行类似的操作,而不是只检查null

public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException 
    // check if the class inside the loader can access Callback
    // by analyzing its hierarchy of parent class loaders
    for (ClassLoader curLoader = loader; ; curLoader = curLoader.getParent()) 
        if (curLoader == null) 
            System.out.println("Skip '" + className + "' for " + loader);
            return null;
         else if (curLoader == Callback.class.getClassLoader())
            break;
    
    byte[] bytes = instrumentByteCode(classfileBuffer);
    return bytes;

【讨论】:

你是对的!!!看起来 premain 是由 sun.misc.Launcher$AppClassLoader@18b4aac2 调用的,它看起来正确,但 transform 是由引导加载程序调用的。我添加了下面的 sn-p 来转换并且一切正常。 if(loader == null) System.out.println("由引导加载程序转换");返回空值; else System.out.println("由" + loader.toString() + " loader");关于为什么在 AppClassLoader 之后调用引导加载程序的任何想法? 我不确定我是否明白你的问题。回答“因为所有类都是延迟加载(按需)”让你满意吗?我还想警告您,在执行自己的基于 ClassLoader 的魔术的复杂应用程序中(例如 web/app 容器),仅检查 loader == null 可能还不够 我知道这些类是延迟加载的,但我不确定是否调用了引导程序。我认为它在第一次调用 AppClassLoader 之前只调用了一次。 您使用单词“bootstrap”作为名词,在这种情况下对我来说没有意义。有一个“引导类路径”和一个“引导类加载器”,其中“引导”是一个形容词。实际上,“引导类加载器”会加载一大堆类。看看String.class.getClassLoader()。哪个加载器将加载类不是由加载类的时刻定义的,而是由它在类路径中的位置定义的。实际上整个rt.jar 默认情况下在引导类路径中(请参阅docs.oracle.com/javase/8/docs/technotes/tools/…),如果您更改它,您将破坏许可证 必须强调的是,如果没有过滤,这个转换器将被所有随后加载的类调用,包括它自己使用的类,例如的 ASM 库。该错误消息表明,即使报告失败,即加载 NoClassDefFoundError 本身,也失败了。通常,对每个分配执行操作而不进行过滤是自找麻烦。如果 com.chasingnanos.Callback 类或其依赖项之一尚未加载,它们也会被检测,因此如果 onAllocation() 执行任何分配,您将获得无限递归。

以上是关于通过检测字节码调用类的静态方法时出现 NoClassDefFoundError的主要内容,如果未能解决你的问题,请参考以下文章

获得类的字节码对象的三种方式

为啥在 Eloquent 模型中调用方法时出现“不应静态调用非静态方法”?

模拟静态方法时出现内容类型错误

Java 字节码检测:对 defineClass 的反射调用中的 NullPointerException

jvm原理(32)方法重载与invokevirtual字节码指令的关系

jvm原理(32)方法重载与invokevirtual字节码指令的关系