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