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