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