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.VirtualMachine;
12 import com.sun.jdi.connect.AttachingConnector;
13 import com.sun.jdi.connect.Connector;
14 import com.sun.jdi.connect.IllegalConnectorArgumentsException;
15
16 import java.io.File;
17 import java.io.FileOutputStream;
18 import java.io.IOException;
19 import java.util.HashMap;
20 import java.util.Iterator;
21 import java.util.Map;
22 import java.util.StringTokenizer;
23 import java.util.jar.Attributes;
24 import java.util.jar.JarOutputStream;
25 import java.util.jar.Manifest;
26 import java.util.zip.CRC32;
27 import java.util.zip.ZipEntry;
28
29 /***
30 * Main application that allow two steps preparation of the hook <p/>This can be used instead of ProcessStarter to dual
31 * JVM and stream piping <br/><p/>
32 * <h2>Usage</h2>
33 *
34 * <pre>
35 *
36 *
37 *
38 *
39 * java [options..] org.codehaus.aspectwerkz.hook.Plug -target <targetJar.jar>
40 * java [options..] org.codehaus.aspectwerkz.hook.Plug -hotswap <jdwp options>
41 * java [options..] org.codehaus.aspectwerkz.hook.Plug -resume <jdwp options>
42 * java [options..] org.codehaus.aspectwerkz.hook.Plug -info <jdwp options>
43 *
44 *
45 *
46 *
47 * </pre>
48 *
49 * <ul>
50 * <li>-target targetJar.jar to generate a targetJar.jar containing the patched java.lang.ClassLoader suitable for your
51 * current java installation. <br/>Add this jar in -Xbootclasspath/p: options as other AspectWerkz options [see
52 * documentation]</li>
53 * <li>-hotswap will hotswap the java.lang.ClassLoader in a running or suspended jvm, and will resume the jvm</li>
54 * <li>-resume will resume the (running or) suspended jvm</li>
55 * <li>-info will print out JPDA information and resume the (running or) suspended jvm</li>*
56 * </ul>
57 * For the last two invocations, [jdwp options] must be the subpart of the -Xrunjdwp option indicating how to connect to
58 * the remote JVM (see sample below or documentation). <i>For now, only localhost connection is supported. </i>
59 *
60 * <pre>
61 *
62 *
63 *
64 *
65 * If the JVM was started with -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y
66 * Use java [options..] ..Plug -prepare transport=dt_socket,address=8000
67 *
68 *
69 *
70 *
71 * </pre>
72 *
73 * <b>Be sure to set AspectWerkz option prior to starting the JVM with -Xrunjdwp options. </b>
74 *
75 * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
76 */
77 public class Plug {
78 /***
79 * transport jdwp option
80 */
81 private final static String TRANSPORT_JDWP = "transport";
82
83 /***
84 * address jdwp option
85 */
86 private final static String ADDRESS_JDWP = "address";
87
88 /***
89 * Dumps the modified java.lang.ClassLoader in destJar <p/>The aspectcwerkz.classloader.clclasspreprocessor is used
90 * if specified, else defaults to AspectWerkz layer 1
91 *
92 * @param destJar
93 * @throws Exception
94 */
95 public void target(String destJar) throws Exception {
96 File dest = new File(destJar);
97 if (dest.exists() && !dest.canWrite()) {
98 throw new Exception(destJar + " exists and is not writable");
99 }
100
101
102 byte[] patched = ClassLoaderPatcher.getPatchedClassLoader(System.getProperty(
103 ProcessStarter.CL_PRE_PROCESSOR_CLASSNAME_PROPERTY,
104 org.codehaus.aspectwerkz.hook.impl.ClassLoaderPreProcessorImpl.class.getName()));
105
106
107
108 Manifest mf = new Manifest();
109 Attributes at = mf.getMainAttributes();
110 at.putValue(Attributes.Name.MANIFEST_VERSION.toString(), "1.0");
111 at.putValue("Created-By", "AspectWerkz (c) Plug [java " + System.getProperty("java.version") + ']');
112 ZipEntry entry = new ZipEntry("java/lang/ClassLoader.class");
113 entry.setSize(patched.length);
114 CRC32 crc = new CRC32();
115 crc.update(patched);
116 entry.setCrc(crc.getValue());
117 JarOutputStream jar = new JarOutputStream(new FileOutputStream(dest), mf);
118 jar.putNextEntry(entry);
119 jar.write(patched);
120 jar.closeEntry();
121 jar.close();
122 }
123
124 /***
125 * Connects to a remote JVM using the jdwp options specified in jdwp
126 *
127 * @param jdwp
128 * @return VirtualMachine or null if failure
129 * @throws Exception
130 */
131 private VirtualMachine connect(Map jdwp) throws Exception {
132 String transport = (String) jdwp.get(TRANSPORT_JDWP);
133 String address = (String) jdwp.get(ADDRESS_JDWP);
134 String name = null;
135 if ("dt_socket".equals(transport)) {
136 name = "com.sun.jdi.SocketAttach";
137 } else if ("dt_shmem".equals(transport)) {
138 name = "com.sun.jdi.SharedMemoryAttach";
139 }
140 AttachingConnector connector = null;
141 for (Iterator i = Bootstrap.virtualMachineManager().attachingConnectors().iterator(); i.hasNext();) {
142 AttachingConnector aConnector = (AttachingConnector) i.next();
143 if (aConnector.name().equals(name)) {
144 connector = aConnector;
145 break;
146 }
147 }
148 if (connector == null) {
149 throw new Exception("no AttachingConnector for transport: " + transport);
150 }
151 Map args = connector.defaultArguments();
152 if ("dt_socket".equals(transport)) {
153 ((Connector.Argument) args.get("port")).setValue(address);
154 } else if ("dt_shmem".equals(transport)) {
155 ((Connector.Argument) args.get("name")).setValue(address);
156 }
157 try {
158 VirtualMachine vm = connector.attach(args);
159 return vm;
160 } catch (IllegalConnectorArgumentsException e) {
161 System.err.println("failed to attach to VM (" + transport + ", " + address + "):");
162 e.printStackTrace();
163 for (Iterator i = e.argumentNames().iterator(); i.hasNext();) {
164 System.err.println("wrong or missing argument - " + i.next());
165 }
166 return null;
167 } catch (IOException e) {
168 System.err.println("failed to attach to VM (" + transport + ", " + address + "):");
169 e.printStackTrace();
170 return null;
171 }
172 }
173
174 /***
175 * Resume the remote JVM, without hotswapping classes
176 *
177 * @param jdwp
178 * @throws Exception
179 */
180 public void resume(Map jdwp) throws Exception {
181 VirtualMachine vm = connect(jdwp);
182 if (vm != null) {
183 vm.resume();
184 vm.dispose();
185 }
186 }
187
188 /***
189 * Prints information about the remote JVM and resume
190 *
191 * @param jdwp
192 * @throws Exception
193 */
194 public void info(Map jdwp) throws Exception {
195 VirtualMachine vm = connect(jdwp);
196 if (vm != null) {
197 System.out.println("java.vm.name = " + vm.name());
198 System.out.println("java.version = " + vm.version());
199 System.out.println(vm.description());
200 vm.resume();
201 vm.dispose();
202 }
203 }
204
205 /***
206 * Hotswaps the java.lang.ClassLoader of the remote JVM and resume
207 *
208 * @param jdwp
209 * @throws Exception
210 */
211 public void hotswap(Map jdwp) throws Exception {
212
213 VirtualMachine vm = ClassLoaderPatcher.hotswapClassLoader(System.getProperty(
214 ProcessStarter.CL_PRE_PROCESSOR_CLASSNAME_PROPERTY,
215 org.codehaus.aspectwerkz.hook.impl.ClassLoaderPreProcessorImpl.class.getName()), (String) jdwp
216 .get(TRANSPORT_JDWP), (String) jdwp.get(ADDRESS_JDWP));
217 if (vm != null) {
218 vm.resume();
219 vm.dispose();
220 }
221 }
222
223 /***
224 * Print usage information on stdout
225 */
226 public static void usage() {
227 System.out.println("AspectWerkz (c) Plug");
228 System.out.println("Usage: " + "-target <targetJar.jar>");
229 System.out.println(" " + "-hotswap <jdwp options>");
230 System.out.println(" " + "-resume <jdwp options>");
231 System.out.println(" " + "-info <jdwp options>");
232 }
233
234 /***
235 * Parse a jdwp like string in a Map <p/>transport=dt_socket,address=8000 will produce a Map of 2 entries whose keys
236 * are transport and address
237 *
238 * @param args
239 * @return Map jdwp options
240 */
241 public static Map parseArgs(String args) throws Exception {
242 Map map = new HashMap();
243 StringTokenizer st = new StringTokenizer(args, ",");
244 while (st.hasMoreTokens()) {
245 String token = st.nextToken();
246 int index = token.indexOf("=");
247 if (index < 0) {
248 throw new Exception("invalid jdwp string: " + args);
249 }
250 map.put(token.substring(0, index), token.substring(index + 1));
251 }
252 return map;
253 }
254
255 public static void main(String[] args) {
256 Plug plug = new Plug();
257 if (args.length != 2) {
258 usage();
259 System.exit(1);
260 }
261 if ("-target".equals(args[0])) {
262 try {
263 plug.target(args[1]);
264 System.out.println("done: " + args[1]);
265 } catch (Exception e) {
266 System.err.println("-target failed: " + e.getMessage());
267 e.printStackTrace();
268 }
269 } else {
270 try {
271 Map jdwp = parseArgs(args[1]);
272 if ("-hotswap".equals(args[0])) {
273 plug.hotswap(jdwp);
274 } else if ("-resume".equals(args[0])) {
275 plug.resume(jdwp);
276 } else if ("-info".equals(args[0])) {
277 plug.info(jdwp);
278 } else {
279 usage();
280 System.exit(1);
281 }
282 } catch (Exception e) {
283 System.err.println(args[0] + " failed: " + e.getMessage());
284 e.printStackTrace();
285 }
286 }
287 }
288 }