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




1166         try {
1167             Env.init(connectSpec, launchImmediately, traceFlags, javaArgs);
1168             new TTY();
1169         } catch(Exception e) {
1170             MessageOutput.printException("Internal exception:", e);
1171         }
1172     }
1173 }
--- EOF ---