1/*
2 * Copyright (c) 1998, 2016, 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
35package com.sun.tools.example.debug.tty;
36
37import com.sun.jdi.*;
38import com.sun.jdi.event.*;
39import com.sun.jdi.request.*;
40import com.sun.jdi.connect.*;
41
42import java.util.*;
43import java.io.*;
44
45public class TTY implements EventNotifier {
46    EventHandler handler = null;
47
48    /**
49     * List of Strings to execute at each stop.
50     */
51    private List<String> monitorCommands = new ArrayList<String>();
52    private int monitorCount = 0;
53
54    /**
55     * The name of this tool.
56     */
57    private static final String progname = "jdb";
58
59    private volatile boolean shuttingDown = false;
60
61    public void setShuttingDown(boolean s) {
62       shuttingDown = s;
63    }
64
65    public boolean isShuttingDown() {
66        return shuttingDown;
67    }
68
69    @Override
70    public void vmStartEvent(VMStartEvent se)  {
71        Thread.yield();  // fetch output
72        MessageOutput.lnprint("VM Started:");
73    }
74
75    @Override
76    public void vmDeathEvent(VMDeathEvent e)  {
77    }
78
79    @Override
80    public void vmDisconnectEvent(VMDisconnectEvent e)  {
81    }
82
83    @Override
84    public void threadStartEvent(ThreadStartEvent e)  {
85    }
86
87    @Override
88    public void threadDeathEvent(ThreadDeathEvent e)  {
89    }
90
91    @Override
92    public void classPrepareEvent(ClassPrepareEvent e)  {
93    }
94
95    @Override
96    public void classUnloadEvent(ClassUnloadEvent e)  {
97    }
98
99    @Override
100    public void breakpointEvent(BreakpointEvent be)  {
101        Thread.yield();  // fetch output
102        MessageOutput.lnprint("Breakpoint hit:");
103    }
104
105    @Override
106    public void fieldWatchEvent(WatchpointEvent fwe)  {
107        Field field = fwe.field();
108        ObjectReference obj = fwe.object();
109        Thread.yield();  // fetch output
110
111        if (fwe instanceof ModificationWatchpointEvent) {
112            MessageOutput.lnprint("Field access encountered before after",
113                                  new Object [] {field,
114                                                 fwe.valueCurrent(),
115                                                 ((ModificationWatchpointEvent)fwe).valueToBe()});
116        } else {
117            MessageOutput.lnprint("Field access encountered", field.toString());
118        }
119    }
120
121    @Override
122    public void stepEvent(StepEvent se)  {
123        Thread.yield();  // fetch output
124        MessageOutput.lnprint("Step completed:");
125    }
126
127    @Override
128    public void exceptionEvent(ExceptionEvent ee) {
129        Thread.yield();  // fetch output
130        Location catchLocation = ee.catchLocation();
131        if (catchLocation == null) {
132            MessageOutput.lnprint("Exception occurred uncaught",
133                                  ee.exception().referenceType().name());
134        } else {
135            MessageOutput.lnprint("Exception occurred caught",
136                                  new Object [] {ee.exception().referenceType().name(),
137                                                 Commands.locationString(catchLocation)});
138        }
139    }
140
141    @Override
142    public void methodEntryEvent(MethodEntryEvent me) {
143        Thread.yield();  // fetch output
144        /*
145         * These can be very numerous, so be as efficient as possible.
146         * If we are stopping here, then we will see the normal location
147         * info printed.
148         */
149        if (me.request().suspendPolicy() != EventRequest.SUSPEND_NONE) {
150            // We are stopping; the name will be shown by the normal mechanism
151            MessageOutput.lnprint("Method entered:");
152        } else {
153            // We aren't stopping, show the name
154            MessageOutput.print("Method entered:");
155            printLocationOfEvent(me);
156        }
157    }
158
159    @Override
160    public boolean methodExitEvent(MethodExitEvent me) {
161        Thread.yield();  // fetch output
162        /*
163         * These can be very numerous, so be as efficient as possible.
164         */
165        Method mmm = Env.atExitMethod();
166        Method meMethod = me.method();
167
168        if (mmm == null || mmm.equals(meMethod)) {
169            // Either we are not tracing a specific method, or we are
170            // and we are exitting that method.
171
172            if (me.request().suspendPolicy() != EventRequest.SUSPEND_NONE) {
173                // We will be stopping here, so do a newline
174                MessageOutput.println();
175            }
176            if (Env.vm().canGetMethodReturnValues()) {
177                MessageOutput.print("Method exitedValue:", me.returnValue() + "");
178            } else {
179                MessageOutput.print("Method exited:");
180            }
181
182            if (me.request().suspendPolicy() == EventRequest.SUSPEND_NONE) {
183                // We won't be stopping here, so show the method name
184                printLocationOfEvent(me);
185
186            }
187
188            // In case we want to have a one shot trace exit some day, this
189            // code disables the request so we don't hit it again.
190            if (false) {
191                // This is a one shot deal; we don't want to stop
192                // here the next time.
193                Env.setAtExitMethod(null);
194                EventRequestManager erm = Env.vm().eventRequestManager();
195                for (EventRequest eReq : erm.methodExitRequests()) {
196                    if (eReq.equals(me.request())) {
197                        eReq.disable();
198                    }
199                }
200            }
201            return true;
202        }
203
204        // We are tracing a specific method, and this isn't it.  Keep going.
205        return false;
206    }
207
208    @Override
209    public void vmInterrupted() {
210        Thread.yield();  // fetch output
211        printCurrentLocation();
212        for (String cmd : monitorCommands) {
213            StringTokenizer t = new StringTokenizer(cmd);
214            t.nextToken();  // get rid of monitor number
215            executeCommand(t);
216        }
217        MessageOutput.printPrompt();
218    }
219
220    @Override
221    public void receivedEvent(Event event) {
222    }
223
224    private void printBaseLocation(String threadName, Location loc) {
225        MessageOutput.println("location",
226                              new Object [] {threadName,
227                                             Commands.locationString(loc)});
228    }
229
230    private void printCurrentLocation() {
231        ThreadInfo threadInfo = ThreadInfo.getCurrentThreadInfo();
232        StackFrame frame;
233        try {
234            frame = threadInfo.getCurrentFrame();
235        } catch (IncompatibleThreadStateException exc) {
236            MessageOutput.println("<location unavailable>");
237            return;
238        }
239        if (frame == null) {
240            MessageOutput.println("No frames on the current call stack");
241        } else {
242            Location loc = frame.location();
243            printBaseLocation(threadInfo.getThread().name(), loc);
244            // Output the current source line, if possible
245            if (loc.lineNumber() != -1) {
246                String line;
247                try {
248                    line = Env.sourceLine(loc, loc.lineNumber());
249                } catch (java.io.IOException e) {
250                    line = null;
251                }
252                if (line != null) {
253                    MessageOutput.println("source line number and line",
254                                          new Object [] {loc.lineNumber(),
255                                                         line});
256                }
257            }
258        }
259        MessageOutput.println();
260    }
261
262    private void printLocationOfEvent(LocatableEvent theEvent) {
263        printBaseLocation(theEvent.thread().name(), theEvent.location());
264    }
265
266    void help() {
267        MessageOutput.println("zz help text");
268    }
269
270    private static final String[][] commandList = {
271        /*
272         * NOTE: this list must be kept sorted in ascending ASCII
273         *       order by element [0].  Ref: isCommand() below.
274         *
275         *Command      OK when        OK when
276         * name      disconnected?   readonly?
277         *------------------------------------
278         */
279        {"!!",           "n",         "y"},
280        {"?",            "y",         "y"},
281        {"bytecodes",    "n",         "y"},
282        {"catch",        "y",         "n"},
283        {"class",        "n",         "y"},
284        {"classes",      "n",         "y"},
285        {"classpath",    "n",         "y"},
286        {"clear",        "y",         "n"},
287        {"connectors",   "y",         "y"},
288        {"cont",         "n",         "n"},
289        {"disablegc",    "n",         "n"},
290        {"down",         "n",         "y"},
291        {"dump",         "n",         "y"},
292        {"enablegc",     "n",         "n"},
293        {"eval",         "n",         "y"},
294        {"exclude",      "y",         "n"},
295        {"exit",         "y",         "y"},
296        {"extension",    "n",         "y"},
297        {"fields",       "n",         "y"},
298        {"gc",           "n",         "n"},
299        {"help",         "y",         "y"},
300        {"ignore",       "y",         "n"},
301        {"interrupt",    "n",         "n"},
302        {"kill",         "n",         "n"},
303        {"lines",        "n",         "y"},
304        {"list",         "n",         "y"},
305        {"load",         "n",         "y"},
306        {"locals",       "n",         "y"},
307        {"lock",         "n",         "n"},
308        {"memory",       "n",         "y"},
309        {"methods",      "n",         "y"},
310        {"monitor",      "n",         "n"},
311        {"next",         "n",         "n"},
312        {"pop",          "n",         "n"},
313        {"print",        "n",         "y"},
314        {"quit",         "y",         "y"},
315        {"read",         "y",         "y"},
316        {"redefine",     "n",         "n"},
317        {"reenter",      "n",         "n"},
318        {"resume",       "n",         "n"},
319        {"run",          "y",         "n"},
320        {"save",         "n",         "n"},
321        {"set",          "n",         "n"},
322        {"sourcepath",   "y",         "y"},
323        {"step",         "n",         "n"},
324        {"stepi",        "n",         "n"},
325        {"stop",         "y",         "n"},
326        {"suspend",      "n",         "n"},
327        {"thread",       "n",         "y"},
328        {"threadgroup",  "n",         "y"},
329        {"threadgroups", "n",         "y"},
330        {"threadlocks",  "n",         "y"},
331        {"threads",      "n",         "y"},
332        {"trace",        "n",         "n"},
333        {"unmonitor",    "n",         "n"},
334        {"untrace",      "n",         "n"},
335        {"unwatch",      "y",         "n"},
336        {"up",           "n",         "y"},
337        {"use",          "y",         "y"},
338        {"version",      "y",         "y"},
339        {"watch",        "y",         "n"},
340        {"where",        "n",         "y"},
341        {"wherei",       "n",         "y"},
342    };
343
344    /*
345     * Look up the command string in commandList.
346     * If found, return the index.
347     * If not found, return index < 0
348     */
349    private int isCommand(String key) {
350        //Reference: binarySearch() in java/util/Arrays.java
351        //           Adapted for use with String[][0].
352        int low = 0;
353        int high = commandList.length - 1;
354        while (low <= high) {
355            int mid = (low + high) >>> 1;
356            String midVal = commandList[mid][0];
357            int compare = midVal.compareTo(key);
358            if (compare < 0) {
359                low = mid + 1;
360            } else if (compare > 0) {
361                high = mid - 1;
362            }
363            else {
364                return mid; // key found
365        }
366        }
367        return -(low + 1);  // key not found.
368    };
369
370    /*
371     * Return true if the command is OK when disconnected.
372     */
373    private boolean isDisconnectCmd(int ii) {
374        if (ii < 0 || ii >= commandList.length) {
375            return false;
376        }
377        return (commandList[ii][1].equals("y"));
378    }
379
380    /*
381     * Return true if the command is OK when readonly.
382     */
383    private boolean isReadOnlyCmd(int ii) {
384        if (ii < 0 || ii >= commandList.length) {
385            return false;
386        }
387        return (commandList[ii][2].equals("y"));
388    };
389
390
391    void executeCommand(StringTokenizer t) {
392        String cmd = t.nextToken().toLowerCase();
393        // Normally, prompt for the next command after this one is done
394        boolean showPrompt = true;
395
396
397        /*
398         * Anything starting with # is discarded as a no-op or 'comment'.
399         */
400        if (!cmd.startsWith("#")) {
401            /*
402             * Next check for an integer repetition prefix.  If found,
403             * recursively execute cmd that number of times.
404             */
405            if (Character.isDigit(cmd.charAt(0)) && t.hasMoreTokens()) {
406                try {
407                    int repeat = Integer.parseInt(cmd);
408                    String subcom = t.nextToken("");
409                    while (repeat-- > 0) {
410                        executeCommand(new StringTokenizer(subcom));
411                        showPrompt = false; // Bypass the printPrompt() below.
412                    }
413                } catch (NumberFormatException exc) {
414                    MessageOutput.println("Unrecognized command.  Try help...", cmd);
415                }
416            } else {
417                int commandNumber = isCommand(cmd);
418                /*
419                 * Check for an unknown command
420                 */
421                if (commandNumber < 0) {
422                    MessageOutput.println("Unrecognized command.  Try help...", cmd);
423                } else if (!Env.connection().isOpen() && !isDisconnectCmd(commandNumber)) {
424                    MessageOutput.println("Command not valid until the VM is started with the run command",
425                                          cmd);
426                } else if (Env.connection().isOpen() && !Env.vm().canBeModified() &&
427                           !isReadOnlyCmd(commandNumber)) {
428                    MessageOutput.println("Command is not supported on a read-only VM connection",
429                                          cmd);
430                } else {
431
432                    Commands evaluator = new Commands();
433                    try {
434                        if (cmd.equals("print")) {
435                            evaluator.commandPrint(t, false);
436                            showPrompt = false;        // asynchronous command
437                        } else if (cmd.equals("eval")) {
438                            evaluator.commandPrint(t, false);
439                            showPrompt = false;        // asynchronous command
440                        } else if (cmd.equals("set")) {
441                            evaluator.commandSet(t);
442                            showPrompt = false;        // asynchronous command
443                        } else if (cmd.equals("dump")) {
444                            evaluator.commandPrint(t, true);
445                            showPrompt = false;        // asynchronous command
446                        } else if (cmd.equals("locals")) {
447                            evaluator.commandLocals();
448                        } else if (cmd.equals("classes")) {
449                            evaluator.commandClasses();
450                        } else if (cmd.equals("class")) {
451                            evaluator.commandClass(t);
452                        } else if (cmd.equals("connectors")) {
453                            evaluator.commandConnectors(Bootstrap.virtualMachineManager());
454                        } else if (cmd.equals("methods")) {
455                            evaluator.commandMethods(t);
456                        } else if (cmd.equals("fields")) {
457                            evaluator.commandFields(t);
458                        } else if (cmd.equals("threads")) {
459                            evaluator.commandThreads(t);
460                        } else if (cmd.equals("thread")) {
461                            evaluator.commandThread(t);
462                        } else if (cmd.equals("suspend")) {
463                            evaluator.commandSuspend(t);
464                        } else if (cmd.equals("resume")) {
465                            evaluator.commandResume(t);
466                        } else if (cmd.equals("cont")) {
467                            evaluator.commandCont();
468                        } else if (cmd.equals("threadgroups")) {
469                            evaluator.commandThreadGroups();
470                        } else if (cmd.equals("threadgroup")) {
471                            evaluator.commandThreadGroup(t);
472                        } else if (cmd.equals("catch")) {
473                            evaluator.commandCatchException(t);
474                        } else if (cmd.equals("ignore")) {
475                            evaluator.commandIgnoreException(t);
476                        } else if (cmd.equals("step")) {
477                            evaluator.commandStep(t);
478                        } else if (cmd.equals("stepi")) {
479                            evaluator.commandStepi();
480                        } else if (cmd.equals("next")) {
481                            evaluator.commandNext();
482                        } else if (cmd.equals("kill")) {
483                            evaluator.commandKill(t);
484                        } else if (cmd.equals("interrupt")) {
485                            evaluator.commandInterrupt(t);
486                        } else if (cmd.equals("trace")) {
487                            evaluator.commandTrace(t);
488                        } else if (cmd.equals("untrace")) {
489                            evaluator.commandUntrace(t);
490                        } else if (cmd.equals("where")) {
491                            evaluator.commandWhere(t, false);
492                        } else if (cmd.equals("wherei")) {
493                            evaluator.commandWhere(t, true);
494                        } else if (cmd.equals("up")) {
495                            evaluator.commandUp(t);
496                        } else if (cmd.equals("down")) {
497                            evaluator.commandDown(t);
498                        } else if (cmd.equals("load")) {
499                            evaluator.commandLoad(t);
500                        } else if (cmd.equals("run")) {
501                            evaluator.commandRun(t);
502                            /*
503                             * Fire up an event handler, if the connection was just
504                             * opened. Since this was done from the run command
505                             * we don't stop the VM on its VM start event (so
506                             * arg 2 is false).
507                             */
508                            if ((handler == null) && Env.connection().isOpen()) {
509                                handler = new EventHandler(this, false);
510                            }
511                        } else if (cmd.equals("memory")) {
512                            evaluator.commandMemory();
513                        } else if (cmd.equals("gc")) {
514                            evaluator.commandGC();
515                        } else if (cmd.equals("stop")) {
516                            evaluator.commandStop(t);
517                        } else if (cmd.equals("clear")) {
518                            evaluator.commandClear(t);
519                        } else if (cmd.equals("watch")) {
520                            evaluator.commandWatch(t);
521                        } else if (cmd.equals("unwatch")) {
522                            evaluator.commandUnwatch(t);
523                        } else if (cmd.equals("list")) {
524                            evaluator.commandList(t);
525                        } else if (cmd.equals("lines")) { // Undocumented command: useful for testing.
526                            evaluator.commandLines(t);
527                        } else if (cmd.equals("classpath")) {
528                            evaluator.commandClasspath(t);
529                        } else if (cmd.equals("use") || cmd.equals("sourcepath")) {
530                            evaluator.commandUse(t);
531                        } else if (cmd.equals("monitor")) {
532                            monitorCommand(t);
533                        } else if (cmd.equals("unmonitor")) {
534                            unmonitorCommand(t);
535                        } else if (cmd.equals("lock")) {
536                            evaluator.commandLock(t);
537                            showPrompt = false;        // asynchronous command
538                        } else if (cmd.equals("threadlocks")) {
539                            evaluator.commandThreadlocks(t);
540                        } else if (cmd.equals("disablegc")) {
541                            evaluator.commandDisableGC(t);
542                            showPrompt = false;        // asynchronous command
543                        } else if (cmd.equals("enablegc")) {
544                            evaluator.commandEnableGC(t);
545                            showPrompt = false;        // asynchronous command
546                        } else if (cmd.equals("save")) { // Undocumented command: useful for testing.
547                            evaluator.commandSave(t);
548                            showPrompt = false;        // asynchronous command
549                        } else if (cmd.equals("bytecodes")) { // Undocumented command: useful for testing.
550                            evaluator.commandBytecodes(t);
551                        } else if (cmd.equals("redefine")) {
552                            evaluator.commandRedefine(t);
553                        } else if (cmd.equals("pop")) {
554                            evaluator.commandPopFrames(t, false);
555                        } else if (cmd.equals("reenter")) {
556                            evaluator.commandPopFrames(t, true);
557                        } else if (cmd.equals("extension")) {
558                            evaluator.commandExtension(t);
559                        } else if (cmd.equals("exclude")) {
560                            evaluator.commandExclude(t);
561                        } else if (cmd.equals("read")) {
562                            readCommand(t);
563                        } else if (cmd.equals("help") || cmd.equals("?")) {
564                            help();
565                        } else if (cmd.equals("version")) {
566                            evaluator.commandVersion(progname,
567                                                     Bootstrap.virtualMachineManager());
568                        } else if (cmd.equals("quit") || cmd.equals("exit")) {
569                            if (handler != null) {
570                                handler.shutdown();
571                            }
572                            Env.shutdown();
573                        } else {
574                            MessageOutput.println("Unrecognized command.  Try help...", cmd);
575                        }
576                    } catch (VMCannotBeModifiedException rovm) {
577                        MessageOutput.println("Command is not supported on a read-only VM connection", cmd);
578                    } catch (UnsupportedOperationException uoe) {
579                        MessageOutput.println("Command is not supported on the target VM", cmd);
580                    } catch (VMNotConnectedException vmnse) {
581                        MessageOutput.println("Command not valid until the VM is started with the run command",
582                                              cmd);
583                    } catch (Exception e) {
584                        MessageOutput.printException("Internal exception:", e);
585                    }
586                }
587            }
588        }
589        if (showPrompt) {
590            MessageOutput.printPrompt();
591        }
592    }
593
594    /*
595     * Maintain a list of commands to execute each time the VM is suspended.
596     */
597    void monitorCommand(StringTokenizer t) {
598        if (t.hasMoreTokens()) {
599            ++monitorCount;
600            monitorCommands.add(monitorCount + ": " + t.nextToken(""));
601        } else {
602            for (String cmd : monitorCommands) {
603                MessageOutput.printDirectln(cmd);// Special case: use printDirectln()
604            }
605        }
606    }
607
608    void unmonitorCommand(StringTokenizer t) {
609        if (t.hasMoreTokens()) {
610            String monTok = t.nextToken();
611            int monNum;
612            try {
613                monNum = Integer.parseInt(monTok);
614            } catch (NumberFormatException exc) {
615                MessageOutput.println("Not a monitor number:", monTok);
616                return;
617            }
618            String monStr = monTok + ":";
619            for (String cmd : monitorCommands) {
620                StringTokenizer ct = new StringTokenizer(cmd);
621                if (ct.nextToken().equals(monStr)) {
622                    monitorCommands.remove(cmd);
623                    MessageOutput.println("Unmonitoring", cmd);
624                    return;
625                }
626            }
627            MessageOutput.println("No monitor numbered:", monTok);
628        } else {
629            MessageOutput.println("Usage: unmonitor <monitor#>");
630        }
631    }
632
633
634    void readCommand(StringTokenizer t) {
635        if (t.hasMoreTokens()) {
636            String cmdfname = t.nextToken();
637            if (!readCommandFile(new File(cmdfname))) {
638                MessageOutput.println("Could not open:", cmdfname);
639            }
640        } else {
641            MessageOutput.println("Usage: read <command-filename>");
642        }
643    }
644
645    /**
646     * Read and execute a command file.  Return true if the file was read
647     * else false;
648     */
649    boolean readCommandFile(File f) {
650        BufferedReader inFile = null;
651        try {
652            if (f.canRead()) {
653                // Process initial commands.
654                MessageOutput.println("*** Reading commands from", f.getPath());
655                inFile = new BufferedReader(new FileReader(f));
656                String ln;
657                while ((ln = inFile.readLine()) != null) {
658                    StringTokenizer t = new StringTokenizer(ln);
659                    if (t.hasMoreTokens()) {
660                        executeCommand(t);
661                    }
662                }
663            }
664        } catch (IOException e) {
665        } finally {
666            if (inFile != null) {
667                try {
668                    inFile.close();
669                } catch (Exception exc) {
670                }
671            }
672        }
673        return inFile != null;
674    }
675
676    /**
677     * Try to read commands from dir/fname, unless
678     * the canonical path passed in is the same as that
679     * for dir/fname.
680     * Return null if that file doesn't exist,
681     * else return the canonical path of that file.
682     */
683    String readStartupCommandFile(String dir, String fname, String canonPath) {
684        File dotInitFile = new File(dir, fname);
685        if (!dotInitFile.exists()) {
686            return null;
687        }
688
689        String myCanonFile;
690        try {
691            myCanonFile = dotInitFile.getCanonicalPath();
692        } catch (IOException ee) {
693            MessageOutput.println("Could not open:", dotInitFile.getPath());
694            return null;
695        }
696        if (canonPath == null || !canonPath.equals(myCanonFile)) {
697            if (!readCommandFile(dotInitFile)) {
698                MessageOutput.println("Could not open:", dotInitFile.getPath());
699            }
700        }
701        return myCanonFile;
702    }
703
704
705    public TTY() throws Exception {
706
707        MessageOutput.println("Initializing progname", progname);
708
709        if (Env.connection().isOpen() && Env.vm().canBeModified()) {
710            /*
711             * Connection opened on startup. Start event handler
712             * immediately, telling it (through arg 2) to stop on the
713             * VM start event.
714             */
715            this.handler = new EventHandler(this, true);
716        }
717        try {
718            BufferedReader in =
719                    new BufferedReader(new InputStreamReader(System.in));
720
721            String lastLine = null;
722
723            Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
724
725            /*
726             * Read start up files.  This mimics the behavior
727             * of gdb which will read both ~/.gdbinit and then
728             * ./.gdbinit if they exist.  We have the twist that
729             * we allow two different names, so we do this:
730             *  if ~/jdb.ini exists,
731             *      read it
732             *  else if ~/.jdbrc exists,
733             *      read it
734             *
735             *  if ./jdb.ini exists,
736             *      if it hasn't been read, read it
737             *      It could have been read above because ~ == .
738             *      or because of symlinks, ...
739             *  else if ./jdbrx exists
740             *      if it hasn't been read, read it
741             */
742            {
743                String userHome = System.getProperty("user.home");
744                String canonPath;
745
746                if ((canonPath = readStartupCommandFile(userHome, "jdb.ini", null)) == null) {
747                    // Doesn't exist, try alternate spelling
748                    canonPath = readStartupCommandFile(userHome, ".jdbrc", null);
749                }
750
751                String userDir = System.getProperty("user.dir");
752                if (readStartupCommandFile(userDir, "jdb.ini", canonPath) == null) {
753                    // Doesn't exist, try alternate spelling
754                    readStartupCommandFile(userDir, ".jdbrc", canonPath);
755                }
756            }
757
758            // Process interactive commands.
759            MessageOutput.printPrompt();
760            while (true) {
761                String ln = in.readLine();
762                if (ln == null) {
763                    /*
764                     *  Jdb is being shutdown because debuggee exited, ignore any 'null'
765                     *  returned by readLine() during shutdown. JDK-8154144.
766                     */
767                    if (!isShuttingDown()) {
768                        MessageOutput.println("Input stream closed.");
769                    }
770                    ln = "quit";
771                }
772
773                if (ln.startsWith("!!") && lastLine != null) {
774                    ln = lastLine + ln.substring(2);
775                    MessageOutput.printDirectln(ln);// Special case: use printDirectln()
776                }
777
778                StringTokenizer t = new StringTokenizer(ln);
779                if (t.hasMoreTokens()) {
780                    lastLine = ln;
781                    executeCommand(t);
782                } else {
783                    MessageOutput.printPrompt();
784                }
785            }
786        } catch (VMDisconnectedException e) {
787            handler.handleDisconnectedException();
788        }
789    }
790
791    private static void usage() {
792        MessageOutput.println("zz usage text", new Object [] {progname,
793                                                     File.pathSeparator});
794        System.exit(1);
795    }
796
797    static void usageError(String messageKey) {
798        MessageOutput.println(messageKey);
799        MessageOutput.println();
800        usage();
801    }
802
803    static void usageError(String messageKey, String argument) {
804        MessageOutput.println(messageKey, argument);
805        MessageOutput.println();
806        usage();
807    }
808
809    private static boolean supportsSharedMemory() {
810        for (Connector connector :
811                 Bootstrap.virtualMachineManager().allConnectors()) {
812            if (connector.transport() == null) {
813                continue;
814            }
815            if ("dt_shmem".equals(connector.transport().name())) {
816                return true;
817            }
818        }
819        return false;
820    }
821
822    private static String addressToSocketArgs(String address) {
823        int index = address.indexOf(':');
824        if (index != -1) {
825            String hostString = address.substring(0, index);
826            String portString = address.substring(index + 1);
827            return "hostname=" + hostString + ",port=" + portString;
828        } else {
829            return "port=" + address;
830        }
831    }
832
833    private static boolean hasWhitespace(String string) {
834        int length = string.length();
835        for (int i = 0; i < length; i++) {
836            if (Character.isWhitespace(string.charAt(i))) {
837                return true;
838            }
839        }
840        return false;
841    }
842
843    private static String addArgument(String string, String argument) {
844        if (hasWhitespace(argument) || argument.indexOf(',') != -1) {
845            // Quotes were stripped out for this argument, add 'em back.
846            StringBuilder sb = new StringBuilder(string);
847            sb.append('"');
848            for (int i = 0; i < argument.length(); i++) {
849                char c = argument.charAt(i);
850                if (c == '"') {
851                    sb.append('\\');
852                }
853                sb.append(c);
854            }
855            sb.append("\" ");
856            return sb.toString();
857        } else {
858            return string + argument + ' ';
859        }
860    }
861
862    public static void main(String argv[]) throws MissingResourceException {
863        String cmdLine = "";
864        String javaArgs = "";
865        int traceFlags = VirtualMachine.TRACE_NONE;
866        boolean launchImmediately = false;
867        String connectSpec = null;
868
869        MessageOutput.textResources = ResourceBundle.getBundle
870            ("com.sun.tools.example.debug.tty.TTYResources",
871             Locale.getDefault());
872
873        for (int i = 0; i < argv.length; i++) {
874            String token = argv[i];
875            if (token.equals("-dbgtrace")) {
876                if ((i == argv.length - 1) ||
877                    ! Character.isDigit(argv[i+1].charAt(0))) {
878                    traceFlags = VirtualMachine.TRACE_ALL;
879                } else {
880                    String flagStr = "";
881                    try {
882                        flagStr = argv[++i];
883                        traceFlags = Integer.decode(flagStr).intValue();
884                    } catch (NumberFormatException nfe) {
885                        usageError("dbgtrace flag value must be an integer:",
886                                   flagStr);
887                        return;
888                    }
889                }
890            } else if (token.equals("-X")) {
891                usageError("Use java minus X to see");
892                return;
893            } else if (
894                   // Standard VM options passed on
895                   token.equals("-v") || token.startsWith("-v:") ||  // -v[:...]
896                   token.startsWith("-verbose") ||                  // -verbose[:...]
897                   token.startsWith("-D") ||
898                   // -classpath handled below
899                   // NonStandard options passed on
900                   token.startsWith("-X") ||
901                   // Old-style options (These should remain in place as long as
902                   //  the standard VM accepts them)
903                   token.equals("-noasyncgc") || token.equals("-prof") ||
904                   token.equals("-verify") || token.equals("-noverify") ||
905                   token.equals("-verifyremote") ||
906                   token.equals("-verbosegc") ||
907                   token.startsWith("-ms") || token.startsWith("-mx") ||
908                   token.startsWith("-ss") || token.startsWith("-oss") ) {
909
910                javaArgs = addArgument(javaArgs, token);
911            } else if (token.equals("-tclassic")) {
912                usageError("Classic VM no longer supported.");
913                return;
914            } else if (token.equals("-tclient")) {
915                // -client must be the first one
916                javaArgs = "-client " + javaArgs;
917            } else if (token.equals("-tserver")) {
918                // -server must be the first one
919                javaArgs = "-server " + javaArgs;
920            } else if (token.equals("-sourcepath")) {
921                if (i == (argv.length - 1)) {
922                    usageError("No sourcepath specified.");
923                    return;
924                }
925                Env.setSourcePath(argv[++i]);
926            } else if (token.equals("-classpath")) {
927                if (i == (argv.length - 1)) {
928                    usageError("No classpath specified.");
929                    return;
930                }
931                javaArgs = addArgument(javaArgs, token);
932                javaArgs = addArgument(javaArgs, argv[++i]);
933            } else if (token.equals("-attach")) {
934                if (connectSpec != null) {
935                    usageError("cannot redefine existing connection", token);
936                    return;
937                }
938                if (i == (argv.length - 1)) {
939                    usageError("No attach address specified.");
940                    return;
941                }
942                String address = argv[++i];
943
944                /*
945                 * -attach is shorthand for one of the reference implementation's
946                 * attaching connectors. Use the shared memory attach if it's
947                 * available; otherwise, use sockets. Build a connect
948                 * specification string based on this decision.
949                 */
950                if (supportsSharedMemory()) {
951                    connectSpec = "com.sun.jdi.SharedMemoryAttach:name=" +
952                                   address;
953                } else {
954                    String suboptions = addressToSocketArgs(address);
955                    connectSpec = "com.sun.jdi.SocketAttach:" + suboptions;
956                }
957            } else if (token.equals("-listen") || token.equals("-listenany")) {
958                if (connectSpec != null) {
959                    usageError("cannot redefine existing connection", token);
960                    return;
961                }
962                String address = null;
963                if (token.equals("-listen")) {
964                    if (i == (argv.length - 1)) {
965                        usageError("No attach address specified.");
966                        return;
967                    }
968                    address = argv[++i];
969                }
970
971                /*
972                 * -listen[any] is shorthand for one of the reference implementation's
973                 * listening connectors. Use the shared memory listen if it's
974                 * available; otherwise, use sockets. Build a connect
975                 * specification string based on this decision.
976                 */
977                if (supportsSharedMemory()) {
978                    connectSpec = "com.sun.jdi.SharedMemoryListen:";
979                    if (address != null) {
980                        connectSpec += ("name=" + address);
981                    }
982                } else {
983                    connectSpec = "com.sun.jdi.SocketListen:";
984                    if (address != null) {
985                        connectSpec += addressToSocketArgs(address);
986                    }
987                }
988            } else if (token.equals("-launch")) {
989                launchImmediately = true;
990            } else if (token.equals("-listconnectors")) {
991                Commands evaluator = new Commands();
992                evaluator.commandConnectors(Bootstrap.virtualMachineManager());
993                return;
994            } else if (token.equals("-connect")) {
995                /*
996                 * -connect allows the user to pick the connector
997                 * used in bringing up the target VM. This allows
998                 * use of connectors other than those in the reference
999                 * implementation.
1000                 */
1001                if (connectSpec != null) {
1002                    usageError("cannot redefine existing connection", token);
1003                    return;
1004                }
1005                if (i == (argv.length - 1)) {
1006                    usageError("No connect specification.");
1007                    return;
1008                }
1009                connectSpec = argv[++i];
1010            } else if (token.equals("-help")) {
1011                usage();
1012            } else if (token.equals("-version")) {
1013                Commands evaluator = new Commands();
1014                evaluator.commandVersion(progname,
1015                                         Bootstrap.virtualMachineManager());
1016                System.exit(0);
1017            } else if (token.startsWith("-")) {
1018                usageError("invalid option", token);
1019                return;
1020            } else {
1021                // Everything from here is part of the command line
1022                cmdLine = addArgument("", token);
1023                for (i++; i < argv.length; i++) {
1024                    cmdLine = addArgument(cmdLine, argv[i]);
1025                }
1026                break;
1027            }
1028        }
1029
1030        /*
1031         * Unless otherwise specified, set the default connect spec.
1032         */
1033
1034        /*
1035         * Here are examples of jdb command lines and how the options
1036         * are interpreted as arguments to the program being debugged.
1037         * arg1       arg2
1038         * ----       ----
1039         * jdb hello a b       a          b
1040         * jdb hello "a b"     a b
1041         * jdb hello a,b       a,b
1042         * jdb hello a, b      a,         b
1043         * jdb hello "a, b"    a, b
1044         * jdb -connect "com.sun.jdi.CommandLineLaunch:main=hello  a,b"   illegal
1045         * jdb -connect  com.sun.jdi.CommandLineLaunch:main=hello "a,b"   illegal
1046         * jdb -connect 'com.sun.jdi.CommandLineLaunch:main=hello "a,b"'  arg1 = a,b
1047         * jdb -connect 'com.sun.jdi.CommandLineLaunch:main=hello "a b"'  arg1 = a b
1048         * jdb -connect 'com.sun.jdi.CommandLineLaunch:main=hello  a b'   arg1 = a  arg2 = b
1049         * jdb -connect 'com.sun.jdi.CommandLineLaunch:main=hello "a," b' arg1 = a, arg2 = b
1050         */
1051        if (connectSpec == null) {
1052            connectSpec = "com.sun.jdi.CommandLineLaunch:";
1053        } else if (!connectSpec.endsWith(",") && !connectSpec.endsWith(":")) {
1054            connectSpec += ","; // (Bug ID 4285874)
1055        }
1056
1057        cmdLine = cmdLine.trim();
1058        javaArgs = javaArgs.trim();
1059
1060        if (cmdLine.length() > 0) {
1061            if (!connectSpec.startsWith("com.sun.jdi.CommandLineLaunch:")) {
1062                usageError("Cannot specify command line with connector:",
1063                           connectSpec);
1064                return;
1065            }
1066            connectSpec += "main=" + cmdLine + ",";
1067        }
1068
1069        if (javaArgs.length() > 0) {
1070            if (!connectSpec.startsWith("com.sun.jdi.CommandLineLaunch:")) {
1071                usageError("Cannot specify target vm arguments with connector:",
1072                           connectSpec);
1073                return;
1074            }
1075            connectSpec += "options=" + javaArgs + ",";
1076        }
1077
1078        try {
1079            if (! connectSpec.endsWith(",")) {
1080                connectSpec += ","; // (Bug ID 4285874)
1081            }
1082            Env.init(connectSpec, launchImmediately, traceFlags);
1083            new TTY();
1084        } catch(Exception e) {
1085            MessageOutput.printException("Internal exception:", e);
1086        }
1087    }
1088}
1089