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
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
119 throw new Error("target JVM cannot redefine classes, please force the use of -Xbootclasspath");
120 } catch (InvocationTargetException e) {
121
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
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
191
192
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 }