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