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