package com.lambda.Debugger; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.jar.Attributes; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.zip.CRC32; import java.util.zip.ZipEntry; import org.apache.bcel.Constants; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.ArrayType; import org.apache.bcel.generic.ClassGen; import org.apache.bcel.generic.ConstantPoolGen; import org.apache.bcel.generic.INVOKESPECIAL; import org.apache.bcel.generic.INVOKESTATIC; import org.apache.bcel.generic.INVOKEVIRTUAL; import org.apache.bcel.generic.Instruction; import org.apache.bcel.generic.InstructionConstants; import org.apache.bcel.generic.InstructionFactory; import org.apache.bcel.generic.InstructionHandle; import org.apache.bcel.generic.InstructionList; import org.apache.bcel.generic.InvokeInstruction; import org.apache.bcel.generic.MethodGen; import org.apache.bcel.generic.PUSH; import org.apache.bcel.generic.Type; /** */ public class InstrumentorForCL { private static Class debugClassLoader; private static java.lang.reflect.Method method; // Instrument ClassLoader.class to call InstrumentorForCL.debugify() before defineClass0(). Then write // both files out to ClassLoader.jar. public void instrument(String destJar) throws Exception { File dest = new File(destJar); if (dest.exists() && !dest.canWrite()) { throw new Exception(destJar + " exists and is not writable"); } // patch the java.lang.ClassLoader InputStream is = ClassLoader.getSystemClassLoader().getParent().getResourceAsStream("java/lang/ClassLoader.class"); byte[] bytes = inputStreamToByteArray(is); is.close(); byte[] patched = patchCL(bytes); // Include this file in the ajr is = ClassLoader.getSystemClassLoader().getResourceAsStream("com/lambda/Debugger/InstrumentorForCL.class"); bytes = inputStreamToByteArray(is); is.close(); // pack the jar file Manifest mf = new Manifest(); Attributes at = mf.getMainAttributes(); at.putValue(Attributes.Name.MANIFEST_VERSION.toString(), "1.0"); at.putValue("Created-By", "ODB [java " + System.getProperty("java.version") + "]"); CRC32 crc = new CRC32(); JarOutputStream jar = new JarOutputStream(new FileOutputStream(dest), mf); { ZipEntry entry = new ZipEntry("java/lang/ClassLoader.class"); entry.setSize(patched.length); entry.setCrc(crc.getValue()); crc.update(patched); jar.putNextEntry(entry); jar.write(patched); jar.closeEntry(); } { ZipEntry entry = new ZipEntry("com/lambda/Debugger/InstrumentorForCL.class"); entry.setSize(bytes.length); entry.setCrc(crc.getValue()); crc.update(bytes); jar.putNextEntry(entry); jar.write(bytes); jar.closeEntry(); } jar.close(); } public static byte[] inputStreamToByteArray(InputStream is) throws IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(); for (int b = is.read(); b != -1; b = is.read()) { os.write(b); } return os.toByteArray(); } public byte[] patchCL(byte[] b) { try { final ClassGen cg = new ClassGen(new ClassParser(new ByteArrayInputStream(b), "<generated>").parse()); final String className = cg.getClassName(); final Method[] methods = cg.getMethods(); final ConstantPoolGen cpg = cg.getConstantPool(); final InstructionFactory factory = new InstructionFactory(cg); // for all methods, look for caller side "this.define0" calls for (int i = 0; i < methods.length; i++) { final MethodGen mg = new MethodGen(methods[i], className, cpg); final InstructionList il = mg.getInstructionList(); if (il == null) continue; InstructionHandle ih = il.getStart(); String methodName = methods[i].getName(); while (ih != null) { final Instruction ins = ih.getInstruction(); if (ins instanceof INVOKESPECIAL || ins instanceof INVOKESTATIC || ins instanceof INVOKEVIRTUAL) { final InvokeInstruction invokeInst = (InvokeInstruction)ins; final String callerSideMethodClassName = invokeInst.getClassName(cpg); final String callerSideMethodName = invokeInst.getMethodName(cpg); if ("java.lang.ClassLoader".equals(callerSideMethodClassName) && "defineClass0".equals(callerSideMethodName)) { //assert compliant JRE Type args[] = invokeInst.getArgumentTypes(cpg); assertSupported(args); // store former method args in local vars InstructionHandle ihc = null; if (args.length > 5) { // IBM like JRE with extra args ihc = il.append(ih.getPrev(), factory.createStore(args[args.length - 1], 2100 + args.length - 1)); for (int index = args.length - 2; index >= 5; index--) { ihc = il.append(ihc, InstructionFactory.createStore(args[index], 2100 + index)); } ihc = il.append(ihc, factory.createStore(Type.OBJECT, 2016));//protection domain } else { // SUN regular JRE ihc = il.append(ih.getPrev(), factory.createStore(Type.OBJECT, 2016));//protection domain } ihc = il.append(ihc, factory.createStore(Type.INT, 2015));//length ihc = il.append(ihc, factory.createStore(Type.INT, 2014));//index ihc = il.append(ihc, factory.createStore(Type.OBJECT, 2013));//bytes ihc = il.append(ihc, factory.createStore(Type.OBJECT, 2012));//name // prepare method call stack ihc = il.append(ihc, factory.createLoad(Type.OBJECT, 2012)); ihc = il.append(ihc, factory.createLoad(Type.OBJECT, 2013)); ihc = il.append(ihc, factory.createInvoke( "com.lambda.Debugger.InstrumentorForCL", "debugify", new ArrayType(Type.BYTE, 1), new Type[]{ Type.STRING, new ArrayType(Type.BYTE, 1), }, Constants.INVOKESTATIC)); ihc = il.append(ihc, factory.createStore(Type.OBJECT, 3018));//result bytes // rebuild former method call stack ihc = il.append(ihc, factory.createLoad(Type.OBJECT, 2012));//name ihc = il.append(ihc, factory.createLoad(Type.OBJECT, 3018));//bytes ihc = il.append(ihc, new PUSH(cpg, 0)); ihc = il.append(ihc, factory.createLoad(Type.OBJECT, 3018));//bytes ihc = il.append(ihc, InstructionConstants.ARRAYLENGTH);//.length ihc = il.append(ihc, factory.createLoad(Type.OBJECT, 2016));//protection domain // extra args for IBM like JRE if (args.length > 5) { for (int index = 5; index < args.length; index++) { ihc = il.append(ihc, factory.createLoad(args[index], 2100 + index)); } } } } ih = ih.getNext(); } mg.setInstructionList(il); mg.setMaxLocals(); mg.setMaxStack(); methods[i] = mg.getMethod(); } cg.setMethods(methods); return cg.getJavaClass().getBytes(); } catch (Exception e) { System.err.println("failed to patch ClassLoader:"); e.printStackTrace(); return b; } } /** * Check the signature of defineClass0 * @param args */ private static void assertSupported(Type[] args) { if (args.length >= 5 && ( args[0].getSignature().equals("Ljava/lang/String;") && args[1].getSignature().equals("[B") && args[2].getSignature().equals("I") && args[3].getSignature().equals("I") && args[4].getSignature().equals("Ljava/security/ProtectionDomain;") )) ; else { StringBuffer sign = new StringBuffer("("); for (int i = 0; i < args.length; i++) { sign.append(args[i].toString()); if (i < args.length - 1) sign.append(", "); } sign.append(")"); throw new Error("non standard JDK, native call not supported " + sign.toString()); } } // Use reflection to avoid putting the Debugger on bootclasspath. NO! ON PATH! public static synchronized byte[] debugify(String name, byte[] b) { if (name.startsWith("java")) return b; if (name.startsWith("JAVAX")) return b; if (name.startsWith("edu.insa.LSD")) return b; if (name.startsWith("lambda.Debugger")) return b; if (name.startsWith("com.lambda.Debugger")) return b; if (name.startsWith("org.apache.bcel")) return b; // System.out.println("Defining: " + name); try { if (debugClassLoader == null) { // NB: Not using this as a classloader, just calling debugify(). debugClassLoader = ClassLoader.getSystemClassLoader().loadClass("com.lambda.Debugger.DebugifyingClassLoader"); method = debugClassLoader.getDeclaredMethod("debugify", new Class[] { String.class, byte[].class }); } Object[] argList = new Object[] {name, b}; byte[] patched = (byte[])method.invoke(null, argList); // DebugifyingClassLoader.debugify("com.l.MyClass", byte[..]) return patched; } catch (Exception e) { System.out.println("ODB: IMPOSSIBLE. Missing debugify"); e.printStackTrace(); System.exit(1); } return b; } public static void main(String args[]) throws Exception { String destination = "/Users/bil/Debugger/com"; if (args.length > 0) destination = args[0]; destination += "ClassLoader.jar"; InstrumentorForCL ins = new InstrumentorForCL(); ins.instrument(destination); System.out.println("Patched ClassLoader written to: " + destination); } }