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