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