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