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