/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.apmrouter.byteman.sockets.impl; import javassist.*; import org.helios.apmrouter.jmx.ConfigurationHelper; import org.helios.apmrouter.util.SimpleLogger; import java.io.File; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.net.*; import java.security.ProtectionDomain; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; /** * <p>Title: SocketImplTransformer</p> * <p>Description: Transforms {@link SocketImpl} classes</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.byteman.sockets.impl.SocketImplTransformer</code></p> * TODO: Sysprops and impls for: * logging * class writes */ public class SocketImplTransformer implements ClassFileTransformer { /** The class name of {@link SocketImpl} */ public static final String SOCK_NAME = "java.net.SocketImpl"; /** The class name of {@link Socket} */ public static final String SOCKET_NAME = "java.net.Socket"; /** The binary class name of {@link Socket} */ public static final String SOCKET_BIN_NAME = "java/net/Socket"; /** The class name of {@link ServerSocket} */ public static final String SERVER_SOCKET_NAME = "java.net.ServerSocket"; /** The binary class name of {@link ServerSocket} */ public static final String SERVER_SOCKET_BIN_NAME = "java/net/ServerSocket"; /** The class name of SocketOutputStream */ public static final String SOCKET_OS_NAME = "java.net.SocketOutputStream"; /** The binary class name of SocketOutputStream */ public static final String SOCKET_OS_BIN_NAME = "java/net/SocketOutputStream"; /** The class name of SocketInputStream */ public static final String SOCKET_IS_NAME = "java.net.SocketInputStream"; /** The binary class name of SocketInputStream */ public static final String SOCKET_IS_BIN_NAME = "java/net/SocketInputStream"; /** The binary class name of {@link SocketImpl} */ public static final String SOCK_BIN_NAME = "java/net/SocketImpl"; /** The class name of {@link SocketImplFactory} */ public static final String SOCK_FACTORY_NAME = "java.net.SocketImplFactory"; /** The binary class name of {@link SocketImplFactory} */ public static final String SOCK_FACTORY_BIN_NAME = "java/net/SocketImplFactory"; /** The system property or env variable to enable verbose logging */ public static final String VERBOSE_PROP = "org.helios.sockets.verbose"; /** The system property or env variable to enable instrumented class files writes (the name of the directory) */ public static final String CLASSDIR_PROP = "org.helios.sockets.classdir"; /** The verbose logging default value */ public static final boolean DEFAULT_VERBOSE = true; /** The name of the directory to write class files to */ public static final String DEFAULT_CLASSDIR = System.getProperty("java.io.tmpdir") + "/org/helios"; // /** The wrapping socket impl factory */ // public static final Class<TrackingSocketImplFactory> TrackingSocketImplFactoryClass = TrackingSocketImplFactory.class; /** The wrapping socket impl */ public static final Class<TrackingSocketImpl> TrackingSocketImplClass = TrackingSocketImpl.class; /** The publicizing interface to be applied to socketimpls */ public static final Class<ISocketImpl> ISocketImplClass = ISocketImpl.class; /** The class name of {@link SocketOptions} */ public static final String SOCK_OPT_NAME = "java.net.SocketOptions"; static { // try { // Socket.setSocketImplFactory(new TrackingSocketImplFactory()); // TrackingSocketImpl.setInstalled(); // } catch (Exception e) { // /* Noop */ // } } /** The recursion level for the current thread */ protected static final ThreadLocal<AtomicInteger> recursionLevel = new ThreadLocal<AtomicInteger>() { @Override protected AtomicInteger initialValue() { return new AtomicInteger(); } }; /** A thread local containing the transformed method signatures of the current class */ protected static final ThreadLocal<Set<String>> currentSignatures = new ThreadLocal<Set<String>>(); /** The javassist classpool used by this transformer */ protected final ClassPool classPool; /** The javassist ctclass representation of {@link SocketImpl} */ protected final CtClass socketImplCtClass; /** The javassist ctclass representation of {@link Socket} */ protected final CtClass socketCtClass; /** The javassist ctclass representation of {@link ServerSocket} */ protected final CtClass serverSocketCtClass; /** The javassist ctclass representation of {@link java.io.FileDescriptor} */ protected final CtClass serverFileDescriptorCtClass; /** The javassist ctclass representation of {@link SocketImplFactory} */ protected final CtClass socketImplFactoryCtClass; /** The javassist method representation of {@link SocketImplFactory#createSocketImpl} */ protected final CtMethod createSocketImplMethod; // /** The javassist ctclass representation of {@link TrackingSocketImpl} */ // protected final CtClass trackingSocketImplCtClass; // /** The javassist ctclass representation of {@link TrackingSocketImplFactory} */ // protected final CtClass trackingSocketImplFactoryCtClass; /** The javassist ctclass representation of {@link ISocketImpl} */ protected final CtClass iSocketImplCtClass; /** The javassist ctclass representation of {@link SocketOptions} */ protected final CtClass socketOptionsCtClass; /** The javassist ctclass representation of {@link Object} */ protected final CtClass objectCtClass; /** The configured verbosity */ protected final boolean verbose; /** The configured class write directory */ protected final File classDir; /** A map of post-istrumentation bytecode instrumented classes keyed by the binary class name. */ protected final Map<String, byte[]> instrumented = new ConcurrentHashMap<String, byte[]>(); /** A map of pre-istrumentation bytecode instrumented classes keyed by the binary class name. */ protected final Map<String, byte[]> preInstrumented = new ConcurrentHashMap<String, byte[]>(); /** A map of socket impl class names keyed by the signature of the instrumented method */ protected final Map<String, String> socketImplSigs = new ConcurrentHashMap<String, String>(); public void flushInstr() { instrumented.clear(); socketImplSigs.clear(); } /** * Creates a new SocketImplTransformer */ public SocketImplTransformer() { try { verbose = ConfigurationHelper.getBooleanSystemThenEnvProperty(VERBOSE_PROP, DEFAULT_VERBOSE); String dir = ConfigurationHelper.getSystemThenEnvProperty(CLASSDIR_PROP, DEFAULT_CLASSDIR); if(dir==null || dir.trim().isEmpty()) { classDir = null; } else { File tmp = new File(dir.trim()); if(!tmp.exists()) { if(tmp.mkdirs()) { classDir = tmp; } else { classDir = null; } } else { if(tmp.isDirectory()) { classDir = tmp; } else { classDir = null; } } } if(classDir!=null) { SimpleLogger.info("Generated class files will be written to [" , classDir , "]"); } classPool = new ClassPool(true); // classPool.appendClassPath(new ClassClassPath(TrackingSocketImplFactoryClass)); // classPool.importPackage(TrackingSocketImplFactoryClass.getPackage().getName()); // trackingSocketImplCtClass = classPool.get(TrackingSocketImpl.class.getName()); // trackingSocketImplFactoryCtClass = classPool.get(TrackingSocketImplFactoryClass.getName()); serverSocketCtClass = classPool.get(SERVER_SOCKET_BIN_NAME); socketCtClass = classPool.get(SOCKET_BIN_NAME); iSocketImplCtClass = classPool.get(ISocketImplClass.getName()); socketImplCtClass = classPool.get(SOCK_NAME); socketOptionsCtClass = classPool.get(SOCK_OPT_NAME); socketImplFactoryCtClass = classPool.get(SOCK_FACTORY_NAME); createSocketImplMethod = socketImplFactoryCtClass.getDeclaredMethod("createSocketImpl"); objectCtClass = classPool.get("java.lang.Object"); serverFileDescriptorCtClass = classPool.get("java.io.FileDescriptor"); SimpleLogger.info("Created SocketImplTransformer"); } catch (Exception ex) { SimpleLogger.error("Failed to initialize SocketImpleTransformer", ex); throw new RuntimeException("Failed to initialize SocketImpleTransformer", ex); } } /** * {@inheritDoc} * @see java.lang.instrument.ClassFileTransformer#transform(java.lang.ClassLoader, java.lang.String, java.lang.Class, java.security.ProtectionDomain, byte[]) */ @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, final byte[] classfileBuffer) throws IllegalClassFormatException { LoaderClassPath lcp = null; final ByteArrayClassPath bcp = new ByteArrayClassPath(convertFromBinaryName(className), classfileBuffer); final int recLevel = recursionLevel.get().incrementAndGet(); final boolean createdSigs = currentSignatures.get()==null; if(createdSigs) currentSignatures.set(new HashSet<String>()); if(instrumented.containsKey(className)) return instrumented.get(className); try { if(loader!=null) { lcp = new LoaderClassPath(loader); classPool.appendClassPath(lcp); } CtClass clazz = null; try { clazz = classPool.get(convertFromBinaryName(className)); } catch (Exception ex) { return classfileBuffer; } // if(SOCKET_BIN_NAME.equals(className)) { // byte[] byteCode = transformSocket(clazz); // preInstrumented.put(className, classfileBuffer); // instrumented.put(className, byteCode); // if(loader!=null) { // loader.loadClass(convertFromBinaryName(className)); // } else { // Class.forName(convertFromBinaryName(className)); // } // return byteCode; // } if(SOCKET_OS_BIN_NAME.equals(className)) { return transformSocketOutputStream(clazz); } if(SOCKET_IS_BIN_NAME.equals(className)) { return transformSocketInputStream(clazz); } if(!clazz.isInterface()) { if(isSocketImpl(clazz)) { SimpleLogger.info("[", Thread.currentThread(), "] SocketImplTransformer Recursion Level:", recLevel); byte[] byteCode = transformSocketImpl(clazz); instrumented.put(className, byteCode); return byteCode; // if(loader!=null) { // loader.loadClass(convertFromBinaryName(className)); // SimpleLogger.info("LOADED SOCKIMPL:[" + className + "] from [", loader, "]"); // } else { // Class.forName(convertFromBinaryName(className)); // SimpleLogger.info("LOADED SOCKIMPL:[" + className + "] from Bootloader"); // } } } // if(clazz.isInterface()) { // if(implementsInterface(clazz, socketImplFactoryCtClass)) { // if(verbose) SimpleLogger.info("[", Thread.currentThread(), "] SocketImplTransformer Recursion Level:", recLevel); // byte[] byteCode = transformSocketImplFactory(clazz); // preInstrumented.put(className, classfileBuffer); // instrumented.put(className, byteCode); // return byteCode; // } // } else { // if(isSocketImpl(clazz)) { // if(verbose) SimpleLogger.info("[", Thread.currentThread(), "] SocketImplTransformer Recursion Level:", recLevel); // return transformSocketImpl(clazz); // } // } return classfileBuffer; } catch (Throwable t) { t.printStackTrace(System.err); return classfileBuffer; } finally{ if(recursionLevel.get().decrementAndGet()==0) { recursionLevel.remove(); } if(createdSigs) { currentSignatures.remove(); } classPool.removeClassPath(bcp); if(lcp!=null) classPool.removeClassPath(lcp); } } /** * Determines if the passed class has {@link SocketImpl} as a direct or indirect superclass and does not already implement {@link ISocketImpl} * @param clazz The class to test * @return true if the passed class has {@link SocketImpl} as a direct or indirect superclass and does not already implement {@link ISocketImpl} * @throws NotFoundException thrown when the superclass cannot be loaded */ protected boolean isSocketImpl(CtClass clazz) throws NotFoundException { if(clazz.isInterface()) return false; CtClass parent = clazz.getSuperclass(); while(parent!=null && !parent.getName().equals(objectCtClass.getName())) { if(parent.getName().equals(SOCK_NAME)) return true; parent = parent.getSuperclass(); } return false; } /** * Determines if the passed class <b><code>clazz</code></b> is a descendent of <b><code>superClazz</code></b>. * @param clazz The class to test * @param superClazz The super class * @return true if <b><code>clazz</code></b> is a descendent of <b><code>superClazz</code></b> * @throws NotFoundException thrown on failure to get a super class */ protected boolean isDescendentOf(CtClass clazz, CtClass superClazz) throws NotFoundException { if(clazz.isInterface()) return false; String superName = superClazz.getName(); CtClass parent = clazz.getSuperclass(); while(parent!=null && !parent.getName().equals(objectCtClass.getName())) { if(parent.getName().equals(superName)) return true; parent = parent.getSuperclass(); } return false; } /** * Determines if the passed class implements the passed interface and is not abstract * @param clazz The class to test * @param iface The interface to test for * @return true if the passed class implements the passed interface, false otherwise * @throws NotFoundException thrown on error getting the class interfaces */ protected boolean implementsInterface(CtClass clazz, CtClass iface) throws NotFoundException { if(Modifier.isAbstract(clazz.getModifiers())) return false; boolean isIface = false; for(CtClass i: clazz.getInterfaces()) { if(i.getName().equals(iface.getName())) { isIface = true; break; } } return isIface; } // /** // * Instruments the passed {@link SocketImplFactory} // * @param socketImplFactoryImpl the javassist representation of a {@link SocketImplFactory} // * @return the class byte code // */ // protected byte[] transformSocketImplFactory(CtClass socketImplFactoryImpl) { // try { // SimpleLogger.info("Transforming SocketImplFactory [", socketImplFactoryImpl.getName(), "]"); // CtMethod originalMethod = socketImplFactoryImpl.getMethod(createSocketImplMethod.getName(), createSocketImplMethod.getSignature()); // originalMethod.setName("_" + createSocketImplMethod.getName()); // CtMethod newMethod = new CtMethod(socketImplCtClass, createSocketImplMethod.getName(), new CtClass[0], socketImplFactoryImpl); // newMethod.setBody("return new TrackingSocketImplFactory(" + originalMethod.getName() + ");"); // if(classDir!=null) { // SimpleLogger.info("Writing SocketImplFactory [", socketImplFactoryImpl.getName(), "]"); // socketImplFactoryImpl.writeFile(classDir.getAbsolutePath()); // } // return socketImplFactoryImpl.toBytecode(); // } catch (Exception ex) { // ex.printStackTrace(System.err); // throw new RuntimeException("Failed to instrument [" + socketImplFactoryImpl.getName() + "]", ex); // } // } // protected byte[] transformSocket(CtClass socket) { // SimpleLogger.info("Transforming Socket"); // try { // socket.defrost(); // CtMethod setMethod = socket.getDeclaredMethod("setSocketImplFactory"); // setMethod.setName("_setSocketImplFactory"); // CtMethod newMethod = new CtMethod(setMethod, socket, null); // newMethod.setName("setSocketImplFactory"); // //new CtMethod(CtClass.voidType, "setSocketImplFactory", new CtClass[]{socketImplFactoryCtClass}, socket); // newMethod.setBody(new StringBuilder("{") // .append("if (factory != null && (factory instanceof TrackingSocketImplFactory)) {") // .append("System.out.println(\"Current Factory:\" + factory.getClass().getName());") // .append("factory = new TrackingSocketImplFactory($1);") // .append("} else { _setSocketImplFactory($1); if($1 instanceof TrackingSocketImplFactory) {}") // .append("System.out.println(\"Installed Factory:\" + factory.getClass().getName());") // .append("}") // .append("}") // .toString() // ); // newMethod.setModifiers(newMethod.getModifiers() | Modifier.STATIC); // newMethod.setModifiers(newMethod.getModifiers() | Modifier.PUBLIC); // socket.addMethod(newMethod); // if(classDir!=null) { // SimpleLogger.info("Writing Socket [", socket.getName(), "]"); // socket.writeFile(classDir.getAbsolutePath()); // } // return socket.toBytecode(); // } catch (Exception ex) { // ex.printStackTrace(System.err); // throw new RuntimeException("Failed to instrument socket", ex); // } // } /** * Transforms a {@link SocketImpl} class * @param socketImplImpl the javassist representation of a {@link SocketImpl} * @return the class byte code * TODO: publify fields * TODO: add socket tracker adapter calls (CtMethod.insertAfter or CodeConverter.insertAfterMethod) * @throws NotFoundException thrown on failure to render class hierarchy */ protected byte[] transformSocketImpl(final CtClass socketImplImpl) throws NotFoundException { SimpleLogger.info("Transforming SocketImpl [", socketImplImpl.getName(), "]"); final Set<String> sigs = currentSignatures.get(); try { socketImplImpl.defrost(); socketImplImpl.setModifiers(socketImplImpl.getModifiers() | Modifier.PUBLIC); CtConstructor ctor = socketImplImpl.getDeclaredConstructor(new CtClass[0]); ctor.setModifiers(ctor.getModifiers() | Modifier.PUBLIC); socketImplImpl.addInterface(iSocketImplCtClass); CtMethod getSockMethod = new CtMethod(socketCtClass, "getSocket", new CtClass[0], socketImplImpl); getSockMethod.setModifiers(getSockMethod.getModifiers() | Modifier.PUBLIC); getSockMethod.setBody("return socket;"); socketImplImpl.addMethod(getSockMethod); CtMethod getServerSockMethod = new CtMethod(serverSocketCtClass, "getServerSocket", new CtClass[0], socketImplImpl); getServerSockMethod.setModifiers(getServerSockMethod.getModifiers() | Modifier.PUBLIC); getServerSockMethod.setBody("return serverSocket;"); socketImplImpl.addMethod(getServerSockMethod); //getFd CtMethod getFdSockMethod = new CtMethod(serverFileDescriptorCtClass, "getFileDescriptor", new CtClass[0], socketImplImpl); getFdSockMethod.setModifiers(getFdSockMethod.getModifiers() | Modifier.PUBLIC); //getFdSockMethod.setModifiers(getFdSockMethod.getModifiers() & ~Modifier.ABSTRACT); getFdSockMethod.setBody("return super.getFileDescriptor();"); socketImplImpl.addMethod(getFdSockMethod); for(CtMethod method: socketImplCtClass.getDeclaredMethods()) { CtMethod toPub = socketImplImpl.getMethod(method.getName(), method.getSignature()); String key = toPub.getName() + "." + toPub.getSignature(); int modifiers = toPub.getModifiers(); if(Modifier.isNative(modifiers) || sigs.contains(key) || socketImplSigs.containsKey(toPub.getName() + toPub.getSignature())) continue; sigs.add(key); socketImplSigs.put(toPub.getName() + toPub.getSignature(), socketImplImpl.getName()); // SimpleLogger.info("ADD KEY [", key, "]"); //socketImplImpl.removeMethod(toPub); toPub.setModifiers(toPub.getModifiers() & ~Modifier.PROTECTED); toPub.setModifiers(toPub.getModifiers() | Modifier.PUBLIC); String transform = SocketTrackingAdapter.SOCKET_IMPL_ADAPTERS.get(key); if(transform!=null) { // CtMethod afterMethod = new CtMethod(toPub.getReturnType(), "_after" + toPub.getName(), toPub.getParameterTypes(), socketImplImpl); // SimpleLogger.info("Adding AfterMethod:[", toPub.getLongName(), " / ", afterMethod.getLongName(), "]\n\t[", transform, "]" ); // afterMethod.setBody(transform); // socketImplImpl.addMethod(afterMethod); // CodeConverter cc = new CodeConverter(); // cc.insertAfterMethod(toPub, afterMethod); // toPub.instrument(cc); transform = transform.replace("##className##", toPub.getDeclaringClass().getSimpleName()); // SimpleLogger.info("Adding insertAfter on:[", toPub.getDeclaringClass().getSimpleName(), " | ", toPub.getSignature(), "]\n\t[", transform, "]" ); toPub.insertAfter(transform); } //socketImplImpl.addMethod(toPub); } if(classDir!=null) { // SimpleLogger.info("Writing SocketImpl [", socketImplImpl.getName(), "]"); socketImplImpl.writeFile(classDir.getAbsolutePath()); } return socketImplImpl.toBytecode(); } catch (Exception ex) { ex.printStackTrace(System.err); throw new RuntimeException("Failed to instrument [" + socketImplImpl.getName() + "]", ex); } } /** * Instruments the SocketOutputStream class * @param socketOutput the javassist representation of the SocketOutputStream class * @return the bytecode of the instrumented class * @throws Exception thrown on any error instrumenting the class */ protected byte[] transformSocketOutputStream(CtClass socketOutput) throws Exception { for(CtMethod method: socketOutput.getDeclaredMethods()) { String key = method.getName() + "." + method.getSignature(); String transform = SocketTrackingAdapter.SOCKET_OS_ADAPTERS.get(key); if(transform!=null) { method.insertAfter(transform); } } return socketOutput.toBytecode(); } /** * Instruments the SocketInputStream class * @param socketInput the javassist representation of the SocketInputStream class * @return the bytecode of the instrumented class * @throws Exception thrown on any error instrumenting the class */ protected byte[] transformSocketInputStream(CtClass socketInput) throws Exception { for(CtMethod method: socketInput.getDeclaredMethods()) { String key = method.getName() + "." + method.getSignature(); String transform = SocketTrackingAdapter.SOCKET_IS_ADAPTERS.get(key); if(transform!=null) { method.insertAfter(transform); } } return socketInput.toBytecode(); } /** * Returns a formatted string of the socket impl instrumented methods * @return a formatted string of the socket impl instrumented methods */ public String getSocketImplSigs() { StringBuilder b = new StringBuilder(); for(Map.Entry<String, String> entry: socketImplSigs.entrySet()) { b.append("\n\t").append(entry.getValue()).append(" : ").append(entry.getKey()); } return b.toString(); } /** * Builds an inherritance hierarchy of the passed class <b><code>impl</code></b> * where all classes extend <b><code>parent</code></b> up to the top class that is <b><code>parent</code></b> * @param impl The class to start from * @param parent the top class to go to * @return a list of classes starting with <b><code>impl</code></b> and ending with <b><code>parent</code></b>. * @throws NotFoundException thrown on any failure to get super classes */ protected List<CtClass> getClassHierarchy(CtClass impl, CtClass parent) throws NotFoundException { List<CtClass> hier = new ArrayList<CtClass>(); if(isDescendentOf(impl, parent)) { hier.add(impl); CtClass sc = impl.getSuperclass(); while(sc!=null && !sc.getName().equals(objectCtClass.getName()) && !sc.getName().equals(parent.getName())) { hier.add(sc); sc = sc.getSuperclass(); } hier.add(parent); } return hier; } /** * Converts a class name to the binary name used by the class file transformer, or returns the passed name if it is already a binary name * @param name The class name to convert * @return the binary name */ protected String convertToBinaryName(String name) { int index = name.indexOf('.'); if(index!=-1) { return name.replace('.', '/'); } return name; } /** * Converts a class name from the binary name used by the class file transformer to the standard dot notated form, or returns the passed name if it is already a binary name * @param name The class name to convert * @return the standard dot notated class name */ protected String convertFromBinaryName(String name) { int index = name.indexOf('/'); if(index!=-1) { return name.replace('/', '.'); } return name; } }