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