1 /*
2  * Copyright (c) 1999, 2008, 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.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 import com.sun.jdi.*;
25 import com.sun.jdi.connect.*;
26 import com.sun.jdi.request.EventRequestManager;
27 
28 import java.util.*;
29 import java.io.*;
30 
31 
32 /**
33  * Manages a VM conection for the JDI test framework.
34  */
35 class VMConnection {
36     private VirtualMachine vm;
37     private Process process = null;
38     private int outputCompleteCount = 0;
39 
40     private final Connector connector;
41     private final Map connectorArgs;
42     private final int traceFlags;
43 
44     /**
45      * Return a String containing VM Options to pass to the debugee
46      * or an empty string if there are none.
47      * These are read from TESTVMOPTS and/or TESTJAVAOPTS.
48      */
getDebuggeeVMOptions()49     static public String getDebuggeeVMOptions() {
50         String retVal = "";
51 
52         // When we run under jtreg, test.classes contains the pathname of
53         // the dir in which the .class files will be placed.
54         String testClasses = System.getProperty("test.classes");
55         if (testClasses == null) {
56             return retVal;
57         }
58         retVal += "-classpath " + testClasses;
59 
60         String vmOpts = System.getProperty("test.vm.opts");
61         System.out.println("vmOpts: '" + vmOpts + "'");
62         if (vmOpts != null && !vmOpts.trim().isEmpty()) {
63             retVal += " " + vmOpts;
64         }
65         String javaOpts = System.getProperty("test.java.opts");
66         System.out.println("javaOpts: '" + javaOpts + "'");
67         if (javaOpts != null && !javaOpts.trim().isEmpty()) {
68             retVal += " " + javaOpts;
69         }
70 
71         return retVal;
72     }
73 
insertDebuggeeVMOptions(String[] cmdLine)74     static public String[] insertDebuggeeVMOptions(String[] cmdLine) {
75         String opts = getDebuggeeVMOptions();
76         if (opts.equals("")) {
77             return cmdLine;
78         }
79         // Insert the options at position 1.  Blanks in args are not allowed!
80         String[] v1 = opts.split(" +");
81         String[] retVal = new String[cmdLine.length + v1.length];
82         retVal[0] = cmdLine[0];
83         System.arraycopy(v1, 0, retVal, 1, v1.length);
84         System.arraycopy(cmdLine, 1, retVal, v1.length + 1, cmdLine.length - 1);
85         return retVal;
86     }
87 
88 
findConnector(String name)89     private Connector findConnector(String name) {
90         List connectors = Bootstrap.virtualMachineManager().allConnectors();
91         Iterator iter = connectors.iterator();
92         while (iter.hasNext()) {
93             Connector connector = (Connector)iter.next();
94             if (connector.name().equals(name)) {
95                 return connector;
96             }
97         }
98         return null;
99     }
100 
parseConnectorArgs(Connector connector, String argString)101     private Map parseConnectorArgs(Connector connector, String argString) {
102         StringTokenizer tokenizer = new StringTokenizer(argString, ",");
103         Map arguments = connector.defaultArguments();
104 
105         while (tokenizer.hasMoreTokens()) {
106             String token = tokenizer.nextToken();
107             int index = token.indexOf('=');
108             if (index == -1) {
109                 throw new IllegalArgumentException("Illegal connector argument: " +
110                                                    token);
111             }
112             String name = token.substring(0, index);
113             String value = token.substring(index + 1);
114             Connector.Argument argument = (Connector.Argument)arguments.get(name);
115             if (argument == null) {
116                 throw new IllegalArgumentException("Argument " + name +
117                                                "is not defined for connector: " +
118                                                connector.name());
119             }
120             argument.setValue(value);
121         }
122         return arguments;
123     }
124 
VMConnection(String connectSpec, int traceFlags)125     VMConnection(String connectSpec, int traceFlags) {
126         String nameString;
127         String argString;
128         int index = connectSpec.indexOf(':');
129         if (index == -1) {
130             nameString = connectSpec;
131             argString = "";
132         } else {
133             nameString = connectSpec.substring(0, index);
134             argString = connectSpec.substring(index + 1);
135         }
136 
137         connector = findConnector(nameString);
138         if (connector == null) {
139             throw new IllegalArgumentException("No connector named: " +
140                                                nameString);
141         }
142 
143         connectorArgs = parseConnectorArgs(connector, argString);
144         this.traceFlags = traceFlags;
145     }
146 
open()147     synchronized VirtualMachine open() {
148         if (connector instanceof LaunchingConnector) {
149             vm = launchTarget();
150         } else if (connector instanceof AttachingConnector) {
151             vm = attachTarget();
152         } else if (connector instanceof ListeningConnector) {
153             vm = listenTarget();
154         } else {
155             throw new InternalError("Invalid connect type");
156         }
157         vm.setDebugTraceMode(traceFlags);
158         System.out.println("JVM version:" + vm.version());
159         System.out.println("JDI version: " + Bootstrap.virtualMachineManager().majorInterfaceVersion() +
160                            "." + Bootstrap.virtualMachineManager().minorInterfaceVersion());
161         System.out.println("JVM description: " + vm.description());
162 
163         return vm;
164     }
165 
setConnectorArg(String name, String value)166     boolean setConnectorArg(String name, String value) {
167         /*
168          * Too late if the connection already made
169          */
170         if (vm != null) {
171             return false;
172         }
173 
174         Connector.Argument argument = (Connector.Argument)connectorArgs.get(name);
175         if (argument == null) {
176             return false;
177         }
178         argument.setValue(value);
179         return true;
180     }
181 
connectorArg(String name)182     String connectorArg(String name) {
183         Connector.Argument argument = (Connector.Argument)connectorArgs.get(name);
184         if (argument == null) {
185             return "";
186         }
187         return argument.value();
188     }
189 
vm()190     public synchronized VirtualMachine vm() {
191         if (vm == null) {
192             throw new InternalError("VM not connected");
193         } else {
194             return vm;
195         }
196     }
197 
isOpen()198     boolean isOpen() {
199         return (vm != null);
200     }
201 
isLaunch()202     boolean isLaunch() {
203         return (connector instanceof LaunchingConnector);
204     }
205 
connector()206     Connector connector() {
207         return connector;
208     }
209 
isListen()210     boolean isListen() {
211         return (connector instanceof ListeningConnector);
212     }
213 
isAttach()214     boolean isAttach() {
215         return (connector instanceof AttachingConnector);
216     }
217 
notifyOutputComplete()218     private synchronized void notifyOutputComplete() {
219         outputCompleteCount++;
220         notifyAll();
221     }
222 
waitOutputComplete()223     private synchronized void waitOutputComplete() {
224         // Wait for stderr and stdout
225         if (process != null) {
226             while (outputCompleteCount < 2) {
227                 try {wait();} catch (InterruptedException e) {}
228             }
229         }
230     }
231 
disposeVM()232     public void disposeVM() {
233         try {
234             if (vm != null) {
235                 vm.dispose();
236                 vm = null;
237             }
238         } finally {
239             if (process != null) {
240                 process.destroy();
241                 process = null;
242             }
243             waitOutputComplete();
244         }
245     }
246 
dumpStream(InputStream stream)247     private void dumpStream(InputStream stream) throws IOException {
248                 PrintStream outStream = System.out;
249                 BufferedReader in =
250                         new BufferedReader(new InputStreamReader(stream));
251                 String line;
252                 while(true){
253                       try{
254                           line = in.readLine();
255                           if( line == null ){
256                               break;
257                           }
258                           outStream.println(line);
259                       }
260                       catch(IOException ieo){
261                            /**
262                             * IOException with "Bad file number..." can happen
263                             * when the debuggee process is destroyed. Ignore such exception.
264                             *
265                            */
266                            String s = ieo.getMessage();
267                            if( s.startsWith("Bad file number") ){
268                                break;
269                            }
270                            throw ieo;
271                       }
272                       catch(NullPointerException npe){
273                           throw new IOException("Bug 4728096 in Java io may cause in.readLine() to throw a NULL pointer exception");
274                       }
275                 }
276     }
277 
278     /**
279      *  Create a Thread that will retrieve and display any output.
280      *  Needs to be high priority, else debugger may exit before
281      *  it can be displayed.
282      */
displayRemoteOutput(final InputStream stream)283     private void displayRemoteOutput(final InputStream stream) {
284         Thread thr = new Thread("output reader") {
285             public void run() {
286                 try {
287                     dumpStream(stream);
288                 } catch (IOException ex) {
289                     System.err.println("IOException reading output of child java interpreter:"
290                                        + ex.getMessage());
291                 } finally {
292                     notifyOutputComplete();
293                 }
294             }
295         };
296         thr.setPriority(Thread.MAX_PRIORITY-1);
297         thr.start();
298     }
299 
dumpFailedLaunchInfo(Process process)300     private void dumpFailedLaunchInfo(Process process) {
301         try {
302             dumpStream(process.getErrorStream());
303             dumpStream(process.getInputStream());
304         } catch (IOException e) {
305             System.err.println("Unable to display process output: " +
306                                e.getMessage());
307         }
308     }
309 
310     /* launch child target vm */
launchTarget()311     private VirtualMachine launchTarget() {
312         LaunchingConnector launcher = (LaunchingConnector)connector;
313         try {
314             VirtualMachine vm = launcher.launch(connectorArgs);
315             process = vm.process();
316             displayRemoteOutput(process.getErrorStream());
317             displayRemoteOutput(process.getInputStream());
318             return vm;
319         } catch (IOException ioe) {
320             ioe.printStackTrace();
321             System.err.println("\n Unable to launch target VM.");
322         } catch (IllegalConnectorArgumentsException icae) {
323             icae.printStackTrace();
324             System.err.println("\n Internal debugger error.");
325         } catch (VMStartException vmse) {
326             System.err.println(vmse.getMessage() + "\n");
327             dumpFailedLaunchInfo(vmse.process());
328             System.err.println("\n Target VM failed to initialize.");
329         }
330         return null; // Shuts up the compiler
331     }
332 
333     /* attach to running target vm */
attachTarget()334     private VirtualMachine attachTarget() {
335         AttachingConnector attacher = (AttachingConnector)connector;
336         try {
337             return attacher.attach(connectorArgs);
338         } catch (IOException ioe) {
339             ioe.printStackTrace();
340             System.err.println("\n Unable to attach to target VM.");
341         } catch (IllegalConnectorArgumentsException icae) {
342             icae.printStackTrace();
343             System.err.println("\n Internal debugger error.");
344         }
345         return null; // Shuts up the compiler
346     }
347 
348     /* listen for connection from target vm */
listenTarget()349     private VirtualMachine listenTarget() {
350         ListeningConnector listener = (ListeningConnector)connector;
351         try {
352             String retAddress = listener.startListening(connectorArgs);
353             System.out.println("Listening at address: " + retAddress);
354             vm = listener.accept(connectorArgs);
355             listener.stopListening(connectorArgs);
356             return vm;
357         } catch (IOException ioe) {
358             ioe.printStackTrace();
359             System.err.println("\n Unable to attach to target VM.");
360         } catch (IllegalConnectorArgumentsException icae) {
361             icae.printStackTrace();
362             System.err.println("\n Internal debugger error.");
363         }
364         return null; // Shuts up the compiler
365     }
366 }
367