1 /*
   2  * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27  * This source code is provided to illustrate the usage of a given feature
  28  * or technique and has been deliberately simplified. Additional steps
  29  * required for a production-quality application, such as security checks,
  30  * input validation and proper error handling, might not be present in
  31  * this sample code.
  32  */
  33 
  34 
  35 package com.sun.tools.example.trace;
  36 
  37 import com.sun.jdi.VirtualMachine;
  38 import com.sun.jdi.Bootstrap;
  39 import com.sun.jdi.connect.*;
  40 
  41 import java.util.Map;
  42 import java.util.List;
  43 
  44 import java.io.PrintWriter;
  45 import java.io.FileWriter;
  46 import java.io.IOException;
  47 
  48 /**
  49  * This program traces the execution of another program.
  50  * See "java Trace -help".
  51  * It is a simple example of the use of the Java Debug Interface.
  52  *
  53  * @author Robert Field
  54  */
  55 public class Trace {
  56 
  57     // Running remote VM
  58     private final VirtualMachine vm;
  59 
  60     // Thread transferring remote error stream to our error stream
  61     private Thread errThread = null;
  62 
  63     // Thread transferring remote output stream to our output stream
  64     private Thread outThread = null;
  65 
  66     // Mode for tracing the Trace program (default= 0 off)
  67     private int debugTraceMode = 0;
  68 
  69     //  Do we want to watch assignments to fields
  70     private boolean watchFields = false;
  71 
  72     // Class patterns for which we don't want events
  73     private String[] excludes = {"java.*", "javax.*", "sun.*",
  74                                  "com.sun.*"};
  75 
  76     /**
  77      * main
  78      */
  79     public static void main(String[] args) {
  80         new Trace(args);
  81     }
  82 
  83     /**
  84      * Parse the command line arguments.
  85      * Launch target VM.
  86      * Generate the trace.
  87      */
  88     Trace(String[] args) {
  89         PrintWriter writer = new PrintWriter(System.out);
  90         int inx;
  91         for (inx = 0; inx < args.length; ++inx) {
  92             String arg = args[inx];
  93             if (arg.charAt(0) != '-') {
  94                 break;
  95             }
  96             if (arg.equals("-output")) {
  97                 try {
  98                     writer = new PrintWriter(new FileWriter(args[++inx]));
  99                 } catch (IOException exc) {
 100                     System.err.println("Cannot open output file: " + args[inx]
 101                                        + " - " +  exc);
 102                     System.exit(1);
 103                 }
 104             } else if (arg.equals("-all")) {
 105                 excludes = new String[0];
 106             } else if (arg.equals("-fields")) {
 107                 watchFields = true;
 108             } else if (arg.equals("-dbgtrace")) {
 109                 debugTraceMode = Integer.parseInt(args[++inx]);
 110             } else if (arg.equals("-help")) {
 111                 usage();
 112                 System.exit(0);
 113             } else {
 114                 System.err.println("No option: " + arg);
 115                 usage();
 116                 System.exit(1);
 117             }
 118         }
 119         if (inx >= args.length) {
 120             System.err.println("<class> missing");
 121             usage();
 122             System.exit(1);
 123         }
 124         StringBuilder sb = new StringBuilder();
 125         sb.append(args[inx]);
 126         for (++inx; inx < args.length; ++inx) {
 127             sb.append(' ');
 128             sb.append(args[inx]);
 129         }
 130         vm = launchTarget(sb.toString());
 131         generateTrace(writer);
 132     }
 133 
 134 
 135     /**
 136      * Generate the trace.
 137      * Enable events, start thread to display events,
 138      * start threads to forward remote error and output streams,
 139      * resume the remote VM, wait for the final event, and shutdown.
 140      */
 141     void generateTrace(PrintWriter writer) {
 142         vm.setDebugTraceMode(debugTraceMode);
 143         EventThread eventThread = new EventThread(vm, excludes, writer);
 144         eventThread.setEventRequests(watchFields);
 145         eventThread.start();
 146         redirectOutput();
 147         vm.resume();
 148 
 149         // Shutdown begins when event thread terminates
 150         try {
 151             eventThread.join();
 152             errThread.join(); // Make sure output is forwarded
 153             outThread.join(); // before we exit
 154         } catch (InterruptedException exc) {
 155             // we don't interrupt
 156         }
 157         writer.close();
 158     }
 159 
 160     /**
 161      * Launch target VM.
 162      * Forward target's output and error.
 163      */
 164     VirtualMachine launchTarget(String mainArgs) {
 165         LaunchingConnector connector = findLaunchingConnector();
 166         Map<String, Connector.Argument> arguments =
 167            connectorArguments(connector, mainArgs);
 168         try {
 169             return connector.launch(arguments);
 170         } catch (IOException exc) {
 171             throw new Error("Unable to launch target VM: " + exc);
 172         } catch (IllegalConnectorArgumentsException exc) {
 173             throw new Error("Internal error: " + exc);
 174         } catch (VMStartException exc) {
 175             throw new Error("Target VM failed to initialize: " +
 176                             exc.getMessage());
 177         }
 178     }
 179 
 180     void redirectOutput() {
 181         Process process = vm.process();
 182 
 183         // Copy target's output and error to our output and error.
 184         errThread = new StreamRedirectThread("error reader",
 185                                              process.getErrorStream(),
 186                                              System.err);
 187         outThread = new StreamRedirectThread("output reader",
 188                                              process.getInputStream(),
 189                                              System.out);
 190         errThread.start();
 191         outThread.start();
 192     }
 193 
 194     /**
 195      * Find a com.sun.jdi.CommandLineLaunch connector
 196      */
 197     LaunchingConnector findLaunchingConnector() {
 198         List<Connector> connectors = Bootstrap.virtualMachineManager().allConnectors();
 199         for (Connector connector : connectors) {
 200             if (connector.name().equals("com.sun.jdi.CommandLineLaunch")) {
 201                 return (LaunchingConnector)connector;
 202             }
 203         }
 204         throw new Error("No launching connector");
 205     }
 206 
 207     /**
 208      * Return the launching connector's arguments.
 209      */
 210     Map<String, Connector.Argument> connectorArguments(LaunchingConnector connector, String mainArgs) {
 211         Map<String, Connector.Argument> arguments = connector.defaultArguments();
 212         Connector.Argument mainArg =
 213                            (Connector.Argument)arguments.get("main");
 214         if (mainArg == null) {
 215             throw new Error("Bad launching connector");
 216         }
 217         mainArg.setValue(mainArgs);
 218 
 219         if (watchFields) {
 220             // We need a VM that supports watchpoints
 221             Connector.Argument optionArg =
 222                 (Connector.Argument)arguments.get("options");
 223             if (optionArg == null) {
 224                 throw new Error("Bad launching connector");
 225             }
 226             optionArg.setValue("-classic");
 227         }
 228         return arguments;
 229     }
 230 
 231     /**
 232      * Print command line usage help
 233      */
 234     void usage() {
 235         System.err.println("Usage: java Trace <options> <class> <args>");
 236         System.err.println("<options> are:");
 237         System.err.println(
 238 "  -output <filename>   Output trace to <filename>");
 239         System.err.println(
 240 "  -all                 Include system classes in output");
 241         System.err.println(
 242 "  -help                Print this help message");
 243         System.err.println("<class> is the program to trace");
 244         System.err.println("<args> are the arguments to <class>");
 245     }
 246 }