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