View Javadoc

    1   /***************************************************************************************
    2    * Copyright (c) Jonas BonŽr, Alexandre Vasseur. All rights reserved.                 *
    3    * http://aspectwerkz.codehaus.org                                                    *
    4    * ---------------------------------------------------------------------------------- *
    5    * The software in this package is published under the terms of the LGPL license      *
    6    * a copy of which has been included with this distribution in the license.txt file.  *
    7    **************************************************************************************/
    8   package org.codehaus.aspectwerkz.hook;
    9
    10  import com.sun.jdi.Bootstrap;
    11  import com.sun.jdi.ReferenceType;
    12  import com.sun.jdi.VirtualMachine;
    13  import com.sun.jdi.connect.AttachingConnector;
    14  import com.sun.jdi.connect.Connector;
    15  import com.sun.jdi.connect.IllegalConnectorArgumentsException;
    16
    17  import java.io.BufferedOutputStream;
    18  import java.io.ByteArrayOutputStream;
    19  import java.io.DataOutputStream;
    20  import java.io.File;
    21  import java.io.FileOutputStream;
    22  import java.io.IOException;
    23  import java.io.InputStream;
    24  import java.lang.reflect.InvocationTargetException;
    25  import java.lang.reflect.Method;
    26  import java.net.ConnectException;
    27  import java.util.HashMap;
    28  import java.util.Iterator;
    29  import java.util.List;
    30  import java.util.Map;
    31
    32  /***
    33   * Utility methods to manipulate class redefinition of java.lang.ClassLoader in xxxStarter
    34   *
    35   * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
    36   */
    37  public class ClassLoaderPatcher {
    38      /***
    39       * Converts an input stream to a byte[]
    40       */
    41      public static byte[] inputStreamToByteArray(InputStream is) throws IOException {
    42          ByteArrayOutputStream os = new ByteArrayOutputStream();
    43          for (int b = is.read(); b != -1; b = is.read()) {
    44              os.write(b);
    45          }
    46          return os.toByteArray();
    47      }
    48
    49      /***
    50       * Gets the bytecode of the modified java.lang.ClassLoader using given ClassLoaderPreProcessor class name
    51       */
    52      static byte[] getPatchedClassLoader(String preProcessorName) {
    53          byte[] abyte = null;
    54          InputStream is = null;
    55          try {
    56              is = ClassLoader.getSystemClassLoader().getParent().getResourceAsStream("java/lang/ClassLoader.class");
    57              abyte = inputStreamToByteArray(is);
    58          } catch (IOException e) {
    59              throw new Error("failed to read java.lang.ClassLoader: " + e.toString());
    60          } finally {
    61              try {
    62                  is.close();
    63              } catch (Exception e) {
    64                  ;
    65              }
    66          }
    67          if (preProcessorName != null) {
    68              try {
    69                  ClassLoaderPreProcessor clpi = (ClassLoaderPreProcessor) Class.forName(preProcessorName).newInstance();
    70                  abyte = clpi.preProcess(abyte);
    71              } catch (Exception e) {
    72                  System.err.println("failed to instrument java.lang.ClassLoader: preprocessor not found");
    73                  e.printStackTrace();
    74              }
    75          }
    76          return abyte;
    77      }
    78
    79      /***
    80       * Dump bytecode bytes in dir/className.class directory, created if needed
    81       */
    82      private static void writeClass(String className, byte[] bytes, String dir) {
    83          String filename = dir + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
    84          int pos = filename.lastIndexOf(File.separatorChar);
    85          if (pos > 0) {
    86              String finalDir = filename.substring(0, pos);
    87              (new File(finalDir)).mkdirs();
    88          }
    89          try {
    90              DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(filename)));
    91              out.write(bytes);
    92              out.close();
    93          } catch (IOException e) {
    94              System.err.println("failed to write " + className + " in " + dir);
    95              e.printStackTrace();
    96          }
    97      }
    98
    99      /***
    100      * HotSwap className in target VM
    101      */
    102     private static void redefineClass(VirtualMachine vm, String className, byte[] bytes) {
    103         // determine if VM support class HotSwap with introspection
    104         try {
    105             Method canM = VirtualMachine.class.getMethod("canRedefineClasses", new Class[]{});
    106             if (((Boolean) canM.invoke(vm, new Object[]{})).equals(Boolean.FALSE)) {
    107                 throw new Error("target JVM cannot redefine classes, please force the use of -Xbootclasspath");
    108             }
    109             List classList = vm.classesByName(className);
    110             if (classList.size() == 0) {
    111                 throw new Error("Fatal error: Can't find class " + className);
    112             }
    113             ReferenceType rt = (ReferenceType) classList.get(0);
    114             Map map = new HashMap();
    115             map.put(rt, bytes);
    116             Method doM = VirtualMachine.class.getMethod(
    117                     "redefineClasses", new Class[]{
    118                         Map.class
    119                     }
    120             );
    121             doM.invoke(
    122                     vm, new Object[]{
    123                         map
    124                     }
    125             );
    126         } catch (NoSuchMethodException e) {
    127             // java 1.3 or not HotSwap compatible JVM
    128             throw new Error("target JVM cannot redefine classes, please force the use of -Xbootclasspath");
    129         } catch (InvocationTargetException e) {
    130             // java 1.4+ failure
    131             System.err.println("failed to HotSwap " + className + ':');
    132             e.getTargetException().printStackTrace();
    133             throw new Error("try to force force the use of -Xbootclasspath");
    134         } catch (IllegalAccessException e) {
    135             // java 1.4+ failure
    136             System.err.println("failed to HotSwap " + className + ':');
    137             e.printStackTrace();
    138             throw new Error("try to force force the use of -Xbootclasspath");
    139         }
    140     }
    141
    142     /***
    143      * Patch java.lang.ClassLoader with preProcessorName instance and dump class bytecode in dir
    144      */
    145     public static void patchClassLoader(String preProcessorName, String dir) {
    146         byte[] cl = getPatchedClassLoader(preProcessorName);
    147         writeClass("java.lang.ClassLoader", cl, dir);
    148     }
    149
    150     /***
    151      * Patch java.lang.ClassLoader with preProcessorName instance and hotswap in target VM using a JDWP attaching
    152      * connector Don't wait before connecting
    153      */
    154     public static VirtualMachine hotswapClassLoader(String preProcessorName, String transport, String address) {
    155         return hotswapClassLoader(preProcessorName, transport, address, 0);
    156     }
    157
    158     /***
    159      * Patch java.lang.ClassLoader with preProcessorName instance and hotswap in target VM using a JDWP attaching
    160      * connector
    161      */
    162     public static VirtualMachine hotswapClassLoader(String preProcessorName,
    163                                                     String transport,
    164                                                     String address,
    165                                                     int secondsToWait) {
    166         String name = null;
    167         if ("dt_socket".equals(transport)) {
    168             name = "com.sun.jdi.SocketAttach";
    169         } else if ("dt_shmem".equals(transport)) {
    170             name = "com.sun.jdi.SharedMemoryAttach";
    171         }
    172         AttachingConnector connector = null;
    173         for (Iterator i = Bootstrap.virtualMachineManager().attachingConnectors().iterator(); i.hasNext();) {
    174             AttachingConnector aConnector = (AttachingConnector) i.next();
    175             if (aConnector.name().equals(name)) {
    176                 connector = aConnector;
    177                 break;
    178             }
    179         }
    180         if (connector == null) {
    181             throw new Error("no AttachingConnector for transport: " + transport);
    182         }
    183         Map args = connector.defaultArguments();
    184         if ("dt_socket".equals(transport)) {
    185             ((Connector.Argument) args.get("port")).setValue(address);
    186         } else if ("dt_shmem".equals(transport)) {
    187             ((Connector.Argument) args.get("name")).setValue(address);
    188         }
    189         try {
    190             if (secondsToWait > 0) {
    191                 try {
    192                     Thread.sleep(1000 * secondsToWait);
    193                 } catch (Exception e) {
    194                     ;
    195                 }
    196             }
    197
    198             // loop 10 times, during 5 sec max. It appears some VM under Linux take time to accept
    199             // connections
    200             // this avoid to specifically set -Daspectwerkz.classloader.wait
    201             VirtualMachine vm = null;
    202             ConnectException vmConnectionRefused = new ConnectException("should not appear as is");
    203             for (int retry = 0; retry < 10; retry++) {
    204                 try {
    205                     vm = connector.attach(args);
    206                     break;
    207                 } catch (ConnectException ce) {
    208                     vmConnectionRefused = ce;
    209                     try {
    210                         Thread.sleep(500);
    211                     } catch (Throwable t) {
    212                         ;
    213                     }
    214                 }
    215             }
    216             if (vm == null) {
    217                 throw vmConnectionRefused;
    218             }
    219             redefineClass(vm, "java.lang.ClassLoader", getPatchedClassLoader(preProcessorName));
    220             return vm;
    221         } catch (IllegalConnectorArgumentsException e) {
    222             System.err.println("failed to attach to VM (" + transport + ", " + address + "):");
    223             e.printStackTrace();
    224             for (Iterator i = e.argumentNames().iterator(); i.hasNext();) {
    225                 System.err.println("wrong or missing argument - " + i.next());
    226             }
    227             return null;
    228         } catch (IOException e) {
    229             System.err.println("failed to attach to VM (" + transport + ", " + address + "):");
    230             e.printStackTrace();
    231             return null;
    232         }
    233     }
    234 }