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