/************************************************************************************** * Copyright (c) Jonas Bon�r, Alexandre Vasseur. All rights reserved. * * http://aspectwerkz.codehaus.org * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the LGPL license * * a copy of which has been included with this distribution in the license.txt file. * **************************************************************************************/ package org.codehaus.aspectwerkz.hook.impl; import org.codehaus.aspectwerkz.hook.ClassLoaderPatcher; import org.codehaus.aspectwerkz.hook.ClassLoaderPreProcessor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassAdapter; import org.objectweb.asm.MethodAdapter; import org.objectweb.asm.Type; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Label; import java.io.InputStream; import java.io.OutputStream; import java.io.FileOutputStream; import java.io.File; import java.util.HashMap; import java.util.Map; /** * Instruments the java.lang.ClassLoader to plug in the Class PreProcessor mechanism using ASM. <p/>We are using a * lazy initialization of the class preprocessor to allow all class pre processor logic to be in system classpath and * not in bootclasspath. <p/>This implementation should support IBM custom JRE * * @author Chris Nokleberg * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a> */ public class ClassLoaderPreProcessorImpl implements ClassLoaderPreProcessor { private final static String CLASSLOADER_CLASS_NAME = "java/lang/ClassLoader"; private final static String DEFINECLASS0_METHOD_NAME = "defineClass0"; private final static String DEFINECLASS1_METHOD_NAME = "defineClass1";//For JDK 5 private final static String DEFINECLASS2_METHOD_NAME = "defineClass2";//For JDK 5 private static final String DESC_CORE = "Ljava/lang/String;[BIILjava/security/ProtectionDomain;"; private static final String DESC_PREFIX = "(" + DESC_CORE; private static final String DESC_HELPER = "(Ljava/lang/ClassLoader;" + DESC_CORE + ")[B"; private static final String DESC_BYTEBUFFER_CORE = "Ljava/lang/String;Ljava/nio/ByteBuffer;IILjava/security/ProtectionDomain;"; private static final String DESC_BYTEBUFFER_PREFIX = "(" + DESC_BYTEBUFFER_CORE; private static final String DESC_BYTEBUFFER_HELPER = "(Ljava/lang/ClassLoader;" + DESC_BYTEBUFFER_CORE + ")[B"; public ClassLoaderPreProcessorImpl() { } /** * Patch caller side of defineClass0 * byte[] weaved = ..hook.impl.ClassPreProcessorHelper.defineClass0Pre(this, args..); * klass = defineClass0(name, weaved, 0, weaved.length, protectionDomain); * * @param classLoaderBytecode * @return */ public byte[] preProcess(byte[] classLoaderBytecode) { try { ClassWriter cw = new ClassWriter(true); ClassLoaderVisitor cv = new ClassLoaderVisitor(cw); ClassReader cr = new ClassReader(classLoaderBytecode); cr.accept(cv, false); return cw.toByteArray(); } catch (Exception e) { System.err.println("failed to patch ClassLoader:"); e.printStackTrace(); return classLoaderBytecode; } } private static class ClassLoaderVisitor extends ClassAdapter { public ClassLoaderVisitor(ClassVisitor cv) { super(cv); } public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor cv = super.visitMethod(access, name, desc, signature, exceptions); Type[] args = Type.getArgumentTypes(desc); return new PreProcessingVisitor(cv, access, args); } } /** * @author Chris Nokleberg */ private static class PreProcessingVisitor extends RemappingMethodVisitor { public PreProcessingVisitor(MethodVisitor mv, int access, Type[] args) { super(mv, access, args); } public void visitMethodInsn(int opcode, String owner, String name, String desc) { if ((DEFINECLASS0_METHOD_NAME.equals(name) || (DEFINECLASS1_METHOD_NAME.equals(name))) && CLASSLOADER_CLASS_NAME.equals(owner)) { Type[] args = Type.getArgumentTypes(desc); if (args.length < 5 || !desc.startsWith(DESC_PREFIX)) { throw new Error("non supported JDK, native call not supported: " + desc); } // store all args in local variables int[] locals = new int[args.length]; for (int i = args.length - 1; i >= 0; i--) { mv.visitVarInsn( args[i].getOpcode(Opcodes.ISTORE), locals[i] = nextLocal(args[i].getSize()) ); } for (int i = 0; i < 5; i++) { mv.visitVarInsn(args[i].getOpcode(Opcodes.ILOAD), locals[i]); } super.visitMethodInsn( Opcodes.INVOKESTATIC, "org/codehaus/aspectwerkz/hook/impl/ClassPreProcessorHelper", "defineClass0Pre", DESC_HELPER ); mv.visitVarInsn(Opcodes.ASTORE, locals[1]); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, locals[0]); // name mv.visitVarInsn(Opcodes.ALOAD, locals[1]); // bytes mv.visitInsn(Opcodes.ICONST_0); // offset mv.visitVarInsn(Opcodes.ALOAD, locals[1]); mv.visitInsn(Opcodes.ARRAYLENGTH); // length mv.visitVarInsn(Opcodes.ALOAD, locals[4]); // protection domain for (int i = 5; i < args.length; i++) { mv.visitVarInsn(args[i].getOpcode(Opcodes.ILOAD), locals[i]); } } else if (DEFINECLASS2_METHOD_NAME.equals(name) && CLASSLOADER_CLASS_NAME.equals(owner)) { Type[] args = Type.getArgumentTypes(desc); if (args.length < 5 || !desc.startsWith(DESC_BYTEBUFFER_PREFIX)) { throw new Error("non supported JDK, bytebuffer native call not supported: " + desc); } // store all args in local variables int[] locals = new int[args.length]; for (int i = args.length - 1; i >= 0; i--) { mv.visitVarInsn( args[i].getOpcode(Opcodes.ISTORE), locals[i] = nextLocal(args[i].getSize()) ); } for (int i = 0; i < 5; i++) { mv.visitVarInsn(args[i].getOpcode(Opcodes.ILOAD), locals[i]); } super.visitMethodInsn( Opcodes.INVOKESTATIC, "org/codehaus/aspectwerkz/hook/impl/ClassPreProcessorHelper", "defineClass0Pre", DESC_BYTEBUFFER_HELPER ); mv.visitVarInsn(Opcodes.ASTORE, locals[1]); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, locals[0]); // name mv.visitVarInsn(Opcodes.ALOAD, locals[1]); // bytes mv.visitInsn(Opcodes.ICONST_0); // offset mv.visitVarInsn(Opcodes.ALOAD, locals[1]); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Ljava/nio/Buffer;", "remaining", "()I"); mv.visitVarInsn(Opcodes.ALOAD, locals[4]); // protection domain for (int i = 5; i < args.length; i++) { mv.visitVarInsn(args[i].getOpcode(Opcodes.ILOAD), locals[i]); } // we should rebuild a new ByteBuffer... } super.visitMethodInsn(opcode, owner, name, desc); } } /** * @author Chris Nokleberg */ private static class State { Map locals = new HashMap(); int firstLocal; int nextLocal; State(int access, Type[] args) { nextLocal = ((Opcodes.ACC_STATIC & access) != 0) ? 0 : 1; for (int i = 0; i < args.length; i++) { nextLocal += args[i].getSize(); } firstLocal = nextLocal; } } /** * @author Chris Nokleberg */ private static class IntRef { int key; public boolean equals(Object o) { return key == ((IntRef) o).key; } public int hashCode() { return key; } } /** * @author Chris Nokleberg */ private static class RemappingMethodVisitor extends MethodAdapter { private State state; private IntRef check = new IntRef(); public RemappingMethodVisitor(MethodVisitor v, int access, Type[] args) { super(v); state = new State(access, args); } public RemappingMethodVisitor(RemappingMethodVisitor wrap) { super(wrap.mv); this.state = wrap.state; } protected int nextLocal(int size) { int var = state.nextLocal; state.nextLocal += size; return var; } private int remap(int var, int size) { if (var < state.firstLocal) { return var; } check.key = (size == 2) ? ~var : var; Integer value = (Integer) state.locals.get(check); if (value == null) { IntRef ref = new IntRef(); ref.key = check.key; state.locals.put(ref, value = new Integer(nextLocal(size))); } return value.intValue(); } public void visitIincInsn(int var, int increment) { mv.visitIincInsn(remap(var, 1), increment); } public void visitLocalVariable(String name, String desc, String sig, Label start, Label end, int index) { mv.visitLocalVariable(name, desc, sig, start, end, remap(index, 0)); } public void visitVarInsn(int opcode, int var) { int size; switch (opcode) { case Opcodes.LLOAD: case Opcodes.LSTORE: case Opcodes.DLOAD: case Opcodes.DSTORE: size = 2; break; default: size = 1; } mv.visitVarInsn(opcode, remap(var, size)); } public void visitMaxs(int maxStack, int maxLocals) { mv.visitMaxs(0, 0); } } public static void main(String args[]) throws Exception { ClassLoaderPreProcessor me = new ClassLoaderPreProcessorImpl(); InputStream is = ClassLoader.getSystemClassLoader().getParent().getResourceAsStream( "java/lang/ClassLoader.class" ); byte[] out = me.preProcess(ClassLoaderPatcher.inputStreamToByteArray(is)); is.close(); File dir = new File("_boot/java/lang/"); dir.mkdirs(); OutputStream os = new FileOutputStream("_boot/java/lang/ClassLoader.class"); os.write(out); os.close(); } }