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