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.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 &lt;targetJar.jar&gt;
40   *     java [options..] org.codehaus.aspectwerkz.hook.Plug -hotswap &lt;jdwp options&gt;
41   *     java [options..] org.codehaus.aspectwerkz.hook.Plug -resume &lt;jdwp options&gt;
42   *     java [options..] org.codehaus.aspectwerkz.hook.Plug -info &lt;jdwp options&gt;
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         // patch the java.lang.ClassLoader
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         //@todo refactor Patcher to handle errors instead of stderr warnings / Error
107         // pack the jar file
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         // @todo check it works at runtime not suspended
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 }