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          try {
55              InputStream is = ClassLoader.getSystemClassLoader().getParent().getResourceAsStream(
56                  "java/lang/ClassLoader.class");
57              abyte = inputStreamToByteArray(is);
58              is.close();
59          } catch (IOException e) {
60              throw new Error("failed to read java.lang.ClassLoader: " + e.toString());
61          }
62          if (preProcessorName != null) {
63              try {
64                  ClassLoaderPreProcessor clpi = (ClassLoaderPreProcessor) Class.forName(preProcessorName).newInstance();
65                  abyte = clpi.preProcess(abyte);
66              } catch (Exception e) {
67                  System.err.println("failed to instrument java.lang.ClassLoader: preprocessor not found");
68                  e.printStackTrace();
69              }
70          }
71          return abyte;
72      }
73  
74      /***
75       * Dump bytecode bytes in dir/className.class directory, created if needed
76       */
77      private static void writeClass(String className, byte[] bytes, String dir) {
78          String filename = dir + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
79          int pos = filename.lastIndexOf(File.separatorChar);
80          if (pos > 0) {
81              String finalDir = filename.substring(0, pos);
82              (new File(finalDir)).mkdirs();
83          }
84          try {
85              DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(filename)));
86              out.write(bytes);
87              out.close();
88          } catch (IOException e) {
89              System.err.println("failed to write " + className + " in " + dir);
90              e.printStackTrace();
91          }
92      }
93  
94      /***
95       * HotSwap className in target VM
96       */
97      private static void redefineClass(VirtualMachine vm, String className, byte[] bytes) {
98          // determine if VM support class HotSwap with introspection
99          try {
100             Method canM = VirtualMachine.class.getMethod("canRedefineClasses", new Class[] {});
101             if (((Boolean) canM.invoke(vm, new Object[] {})).equals(Boolean.FALSE)) {
102                 throw new Error("target JVM cannot redefine classes, please force the use of -Xbootclasspath");
103             }
104             List classList = vm.classesByName(className);
105             if (classList.size() == 0) {
106                 throw new Error("Fatal error: Can't find class " + className);
107             }
108             ReferenceType rt = (ReferenceType) classList.get(0);
109             Map map = new HashMap();
110             map.put(rt, bytes);
111             Method doM = VirtualMachine.class.getMethod("redefineClasses", new Class[] {
112                 Map.class
113             });
114             doM.invoke(vm, new Object[] {
115                 map
116             });
117         } catch (NoSuchMethodException e) {
118             // java 1.3 or not HotSwap compatible JVM
119             throw new Error("target JVM cannot redefine classes, please force the use of -Xbootclasspath");
120         } catch (InvocationTargetException e) {
121             // java 1.4+ failure
122             System.err.println("failed to HotSwap " + className + ':');
123             e.getTargetException().printStackTrace();
124             throw new Error("try to force force the use of -Xbootclasspath");
125         } catch (IllegalAccessException e) {
126             // java 1.4+ failure
127             System.err.println("failed to HotSwap " + className + ':');
128             e.printStackTrace();
129             throw new Error("try to force force the use of -Xbootclasspath");
130         }
131     }
132 
133     /***
134      * Patch java.lang.ClassLoader with preProcessorName instance and dump class bytecode in dir
135      */
136     public static void patchClassLoader(String preProcessorName, String dir) {
137         byte[] cl = getPatchedClassLoader(preProcessorName);
138         writeClass("java.lang.ClassLoader", cl, dir);
139     }
140 
141     /***
142      * Patch java.lang.ClassLoader with preProcessorName instance and hotswap in target VM using a JDWP attaching
143      * connector Don't wait before connecting
144      */
145     public static VirtualMachine hotswapClassLoader(String preProcessorName, String transport, String address) {
146         return hotswapClassLoader(preProcessorName, transport, address, 0);
147     }
148 
149     /***
150      * Patch java.lang.ClassLoader with preProcessorName instance and hotswap in target VM using a JDWP attaching
151      * connector
152      */
153     public static VirtualMachine hotswapClassLoader(
154         String preProcessorName,
155         String transport,
156         String address,
157         int secondsToWait) {
158         String name = null;
159         if ("dt_socket".equals(transport)) {
160             name = "com.sun.jdi.SocketAttach";
161         } else if ("dt_shmem".equals(transport)) {
162             name = "com.sun.jdi.SharedMemoryAttach";
163         }
164         AttachingConnector connector = null;
165         for (Iterator i = Bootstrap.virtualMachineManager().attachingConnectors().iterator(); i.hasNext();) {
166             AttachingConnector aConnector = (AttachingConnector) i.next();
167             if (aConnector.name().equals(name)) {
168                 connector = aConnector;
169                 break;
170             }
171         }
172         if (connector == null) {
173             throw new Error("no AttachingConnector for transport: " + transport);
174         }
175         Map args = connector.defaultArguments();
176         if ("dt_socket".equals(transport)) {
177             ((Connector.Argument) args.get("port")).setValue(address);
178         } else if ("dt_shmem".equals(transport)) {
179             ((Connector.Argument) args.get("name")).setValue(address);
180         }
181         try {
182             if (secondsToWait > 0) {
183                 try {
184                     Thread.sleep(1000 * secondsToWait);
185                 } catch (Exception e) {
186                     ;
187                 }
188             }
189 
190             // loop 10 times, during 5 sec max. It appears some VM under Linux take time to accept
191             // connections
192             // this avoid to specifically set -Daspectwerkz.classloader.wait
193             VirtualMachine vm = null;
194             ConnectException vmConnectionRefused = new ConnectException("should not appear as is");
195             for (int retry = 0; retry < 10; retry++) {
196                 try {
197                     vm = connector.attach(args);
198                     break;
199                 } catch (ConnectException ce) {
200                     vmConnectionRefused = ce;
201                     try {
202                         Thread.sleep(500);
203                     } catch (Throwable t) {
204                         ;
205                     }
206                 }
207             }
208             if (vm == null) {
209                 throw vmConnectionRefused;
210             }
211             redefineClass(vm, "java.lang.ClassLoader", getPatchedClassLoader(preProcessorName));
212             return vm;
213         } catch (IllegalConnectorArgumentsException e) {
214             System.err.println("failed to attach to VM (" + transport + ", " + address + "):");
215             e.printStackTrace();
216             for (Iterator i = e.argumentNames().iterator(); i.hasNext();) {
217                 System.err.println("wrong or missing argument - " + i.next());
218             }
219             return null;
220         } catch (IOException e) {
221             System.err.println("failed to attach to VM (" + transport + ", " + address + "):");
222             e.printStackTrace();
223             return null;
224         }
225     }
226 }