java - Java是否可以重写( 只有他们的) 系统类的字节码?

  显示原文与译文双语对照的内容
0 0

于是我有了( MyClassLoader ) 维护的一组" 特殊" 类加载在内存中。 这些特殊的类是动态编译并存储在字节数组中MyClassLoader里面。 MyClassLoader时需要提供类,它首先检查其。 specialClasses字典包含它,然后再委托给classloader System 。 ,看起来就像这样:

class MyClassLoader extends ClassLoader {
    Map<String, byte[]> specialClasses;
    public MyClassLoader(Map<String, byte[]> sb) {
        this.specialClasses = sb;
    }
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (specialClasses.containsKey(name)) return findClass(name);
        else return super.loadClass(name);
    }
    @Override
    public Class findClass(String name) {
        byte[] b = specialClasses.get(name);
        return defineClass(name, b, 0, b.length);
    }    
}

如果我想执行转换( e .g 。 instrumentation ) 在 specialClasses我就能做到仅仅是通过修改 byte[],否则我叫 defineClass()谈。

我也给你转换的System classloader类,其System提供的类加载器,但是似乎不提供任何方式来访问raw byte[]它提供的所有类,并给了我 Class对象直接。

我想喝一杯 -javaagent所有类乐器加载到JVM,要是那样就会增加的类开销我不想乐器,我只真正想要的类加载MyClassLoader要检测。

  • 有没有一种方法检索原始的 byte[]父类加载器提供的各个类的,所以我可以检测它们,然后再定义自己的复制?
  • 或者,有没有办法模拟的功能的System classloader,它在哪方面抓住它 byte[]s 中,以便MyClassLoader可以乐器和定义自己的复制所有的System类( Object,string等)?

编辑:

所以我尝试另一种方法:

  • 使用 -javaagent上,捕获 byte[]每一个类的加载,并将其存储在hashtable中,按照片的类的名称。
  • 他们除了myclassLoader,而不是系统的类委派给其父类加载器做加载字节码使用类名和它定义的列表中移除

从理论上说这会让MyClassLoader定义它自己版本的系统类,检测。 但是,它将失败,并显示

java.lang.SecurityException: Prohibited package name: java.lang

清楚地定义JVM不喜欢我 java.lang我自己,即使它应该( 理论上) 是来自同一个类 byte[]源代码应来自自举加载类。 继续寻找解决方案。

ediT2:

我找到了一个( 真的手绘) 解决此问题,但是如果有人知道的比我说的难点Java classloading/instrumentation丝毫找些手绘,那就会好多了

时间:原作者:9个回答

0 0

所以我找到了解决这个。 这不是非常优雅的解决方案,它将导致很多未读邮件代码预览很愤怒,但它似乎工作。 基本点为:

javaAgent

使用 java.lang.instrumentation和一个 -javaagent存储 Instrumentation对象以便日后使用

class JavaAgent {
    private JavaAgent() {}
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("Agent Premain Start");
        Transformer.instrumentation = inst;
        inst.addTransformer(new Transformer(), inst.isRetransformClassesSupported());
    }    
}

classFileTransformer

添加一个 TransformerInstrumentation只适用于标记的类。 大概是

public class Transformer implements ClassFileTransformer {
    public static Set<Class<?>> transformMe = new Set<>()
    public static Instrumentation instrumentation = null; // set during premain()
    @Override
    public byte[] transform(ClassLoader loader,
                            String className,
                            Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] origBytes) {
        if (transformMe.contains(classBeingRedefined)) {
            return instrument(origBytes, loader);
        } else {
            return null;
        }
    }
    public byte[] instrument(byte[] origBytes) {
        // magic happens here
    }
}

classLoader

在classloader中,显式标记每个装入的类( 即使其加载的类委派给parent ),从而得以在 transformMe之前询问 Instrumentationit部门的

public class MyClassLoader extends ClassLoader{
    public Class<?> instrument(Class<?> in){
        try{
            Transformer.transformMe.add(in);
            Transformer.instrumentation.retransformClasses(in);
            Transformer.transformMe.remove(in);
            return in;
        }catch(Exception e){ return null; }
    }
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return instrument(super.loadClass(name));
    }
}

。。。 和好了 ! 每个类都加载这种程序。 MyClassLoader获取转换的 instrument()方法包括所有的系统类,比如 java.lang.Object和朋友,而所有的类都加载默认ClassLoader是保持原状。

我试过,使用一个内存分析 instrument()方法,该方法中插入回调钩子来跟踪内存分配进行校验字节码,并且可以确认 MyClassLoad类触发回调时它们的方法运行( 甚至是系统类),而不是" 正常" 类。

胜利 !

这是当然,糟糕的代码。 共享可变状态无处不在,非本地副作用,globals,一切你能想像的 可能既不是线程安全的。 但它表明,这样的事是可能的,你甚至可以确实有选择地乐器的bytecode类,系统类,作为自定义ClassLoader的一部分操作,而饶过的" rest " 程序。

打开Problems

如果其他人有什么主意怎么让代码更可怕,那我就放心了 我想不出的方法:

  • 使 Instrumentation只 乐器类根据需要通过 retransformClasses()乐器类加载否则
  • 某些元数据存储在每个 Class<?>对象,而这些概念允许 Transformer告诉它是否应转换或没有,没有全局可变hashtable查找。
  • 转换系统类而不使用 Instrumentation.retransformClass()方法。 如上所述,任何尝试动态 defineClassa byte[]转换为 java.lang.*类第一个进行硬编码签入ClassLoader 。java 。

如果有人能找到应对出现的任何问题,都会使这个更不用说勾画。 不管怎么样,我猜能够乐器( e .g 。 分析) 的一些子系统( 我如果不包含。 一个你感兴趣的),同时保持其余部分JVM不动( 没有检测开销) 能够对你有用给其他人除了我,量身订做的。

原作者:
0 0

类加载器不提供公用API访问已加载类的字节代码。 字节代码可能是缓存的某处VM ( 在Oracle VM,这是本机代码) 中完成的,但不能真的把数据作为字节数组来了。

可以做的不过是重新读取类文件以资源的形式。 除非我忘记了什么明显,classloader#getresource( ) 或Class#getResource( ) 应使用相同的搜索路径加载类文件,它用于加载资源:

public byte[] getClassFile(Class<?> clazz) throws IOException {     
    InputStream is =
        clazz.getResourceAsStream(
            "/" + clazz.getName().replace('.', '/') + ".class");
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    int r = 0;
    byte[] buffer = new byte[8192];
    while((r=is.read(buffer))>=0) {
        baos.write(buffer, 0, r);
    }   
    return baos.toByteArray();
}
原作者:
0 0

要重新定义Object class,但此类已经加载之前运行任何程序,包括你的classloader 。 即使你创建自己的Object class,它可能与已加载系统类和冲突this在制造混乱

我看到的唯一方法是校验系统类脱机,即需要在其中所有类rt 。jar,乐器,写回。

原作者:
...