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
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
128 throw new Error("target JVM cannot redefine classes, please force the use of -Xbootclasspath");
129 } catch (InvocationTargetException e) {
130
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
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
199
200
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 }