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