1 /* 2 * Copyright (c) 1998, 2016, 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 evaluator.commandCont(); 468 } else if (cmd.equals("threadgroups")) { 469 evaluator.commandThreadGroups(); 470 } else if (cmd.equals("threadgroup")) { 471 evaluator.commandThreadGroup(t); 472 } else if (cmd.equals("catch")) { 473 evaluator.commandCatchException(t); 474 } else if (cmd.equals("ignore")) { 475 evaluator.commandIgnoreException(t); 476 } else if (cmd.equals("step")) { 477 evaluator.commandStep(t); 478 } else if (cmd.equals("stepi")) { 479 evaluator.commandStepi(); 480 } else if (cmd.equals("next")) { 481 evaluator.commandNext(); 482 } else if (cmd.equals("kill")) { 483 evaluator.commandKill(t); 484 } else if (cmd.equals("interrupt")) { 485 evaluator.commandInterrupt(t); 486 } else if (cmd.equals("trace")) { 487 evaluator.commandTrace(t); 488 } else if (cmd.equals("untrace")) { 489 evaluator.commandUntrace(t); 490 } else if (cmd.equals("where")) { 491 evaluator.commandWhere(t, false); 492 } else if (cmd.equals("wherei")) { 493 evaluator.commandWhere(t, true); 494 } else if (cmd.equals("up")) { 495 evaluator.commandUp(t); 496 } else if (cmd.equals("down")) { 497 evaluator.commandDown(t); 498 } else if (cmd.equals("load")) { 499 evaluator.commandLoad(t); 500 } else if (cmd.equals("run")) { 501 evaluator.commandRun(t); 502 /* 503 * Fire up an event handler, if the connection was just 504 * opened. Since this was done from the run command 505 * we don't stop the VM on its VM start event (so 506 * arg 2 is false). 507 */ 508 if ((handler == null) && Env.connection().isOpen()) { 509 handler = new EventHandler(this, false); 510 } 511 } else if (cmd.equals("memory")) { 512 evaluator.commandMemory(); 513 } else if (cmd.equals("gc")) { 514 evaluator.commandGC(); 515 } else if (cmd.equals("stop")) { 516 evaluator.commandStop(t); 517 } else if (cmd.equals("clear")) { 518 evaluator.commandClear(t); 519 } else if (cmd.equals("watch")) { 520 evaluator.commandWatch(t); 521 } else if (cmd.equals("unwatch")) { 522 evaluator.commandUnwatch(t); 523 } else if (cmd.equals("list")) { 524 evaluator.commandList(t); 525 } else if (cmd.equals("lines")) { // Undocumented command: useful for testing. 526 evaluator.commandLines(t); 527 } else if (cmd.equals("classpath")) { 528 evaluator.commandClasspath(t); 529 } else if (cmd.equals("use") || cmd.equals("sourcepath")) { 530 evaluator.commandUse(t); 531 } else if (cmd.equals("monitor")) { 532 monitorCommand(t); 533 } else if (cmd.equals("unmonitor")) { 534 unmonitorCommand(t); 535 } else if (cmd.equals("lock")) { 536 evaluator.commandLock(t); 537 showPrompt = false; // asynchronous command 538 } else if (cmd.equals("threadlocks")) { 539 evaluator.commandThreadlocks(t); 540 } else if (cmd.equals("disablegc")) { 541 evaluator.commandDisableGC(t); 542 showPrompt = false; // asynchronous command 543 } else if (cmd.equals("enablegc")) { 544 evaluator.commandEnableGC(t); 545 showPrompt = false; // asynchronous command 546 } else if (cmd.equals("save")) { // Undocumented command: useful for testing. 547 evaluator.commandSave(t); 548 showPrompt = false; // asynchronous command 549 } else if (cmd.equals("bytecodes")) { // Undocumented command: useful for testing. 550 evaluator.commandBytecodes(t); 551 } else if (cmd.equals("redefine")) { 552 evaluator.commandRedefine(t); 553 } else if (cmd.equals("pop")) { 554 evaluator.commandPopFrames(t, false); 555 } else if (cmd.equals("reenter")) { 556 evaluator.commandPopFrames(t, true); 557 } else if (cmd.equals("extension")) { 558 evaluator.commandExtension(t); 559 } else if (cmd.equals("exclude")) { 560 evaluator.commandExclude(t); 561 } else if (cmd.equals("read")) { 562 readCommand(t); 563 } else if (cmd.equals("help") || cmd.equals("?")) { 564 help(); 565 } else if (cmd.equals("version")) { 566 evaluator.commandVersion(progname, 567 Bootstrap.virtualMachineManager()); 568 } else if (cmd.equals("quit") || cmd.equals("exit")) { 569 if (handler != null) { 570 handler.shutdown(); 571 } 572 Env.shutdown(); 573 } else { 574 MessageOutput.println("Unrecognized command. Try help...", cmd); 575 } 576 } catch (VMCannotBeModifiedException rovm) { 577 MessageOutput.println("Command is not supported on a read-only VM connection", cmd); 578 } catch (UnsupportedOperationException uoe) { 579 MessageOutput.println("Command is not supported on the target VM", cmd); 580 } catch (VMNotConnectedException vmnse) { 581 MessageOutput.println("Command not valid until the VM is started with the run command", 582 cmd); 583 } catch (Exception e) { 584 MessageOutput.printException("Internal exception:", e); 585 } 586 } 587 } 588 } 589 if (showPrompt) { 590 MessageOutput.printPrompt(); 591 } 592 } 593 594 /* 595 * Maintain a list of commands to execute each time the VM is suspended. 596 */ 597 void monitorCommand(StringTokenizer t) { 598 if (t.hasMoreTokens()) { 599 ++monitorCount; 600 monitorCommands.add(monitorCount + ": " + t.nextToken("")); 601 } else { 602 for (String cmd : monitorCommands) { 603 MessageOutput.printDirectln(cmd);// Special case: use printDirectln() 604 } 605 } 606 } 607 608 void unmonitorCommand(StringTokenizer t) { 609 if (t.hasMoreTokens()) { 610 String monTok = t.nextToken(); 611 int monNum; 612 try { 613 monNum = Integer.parseInt(monTok); 614 } catch (NumberFormatException exc) { 615 MessageOutput.println("Not a monitor number:", monTok); 616 return; 617 } 618 String monStr = monTok + ":"; 619 for (String cmd : monitorCommands) { 620 StringTokenizer ct = new StringTokenizer(cmd); 621 if (ct.nextToken().equals(monStr)) { 622 monitorCommands.remove(cmd); 623 MessageOutput.println("Unmonitoring", cmd); 624 return; 625 } 626 } 627 MessageOutput.println("No monitor numbered:", monTok); 628 } else { 629 MessageOutput.println("Usage: unmonitor <monitor#>"); 630 } 631 } 632 633 634 void readCommand(StringTokenizer t) { 635 if (t.hasMoreTokens()) { 636 String cmdfname = t.nextToken(); 637 if (!readCommandFile(new File(cmdfname))) { 638 MessageOutput.println("Could not open:", cmdfname); 639 } 640 } else { 641 MessageOutput.println("Usage: read <command-filename>"); 642 } 643 } 644 645 /** 646 * Read and execute a command file. Return true if the file was read 647 * else false; 648 */ 649 boolean readCommandFile(File f) { 650 BufferedReader inFile = null; 651 try { 652 if (f.canRead()) { 653 // Process initial commands. 654 MessageOutput.println("*** Reading commands from", f.getPath()); 655 inFile = new BufferedReader(new FileReader(f)); 656 String ln; 657 while ((ln = inFile.readLine()) != null) { 658 StringTokenizer t = new StringTokenizer(ln); 659 if (t.hasMoreTokens()) { 660 executeCommand(t); 661 } 662 } 663 } 664 } catch (IOException e) { 665 } finally { 666 if (inFile != null) { 667 try { 668 inFile.close(); 669 } catch (Exception exc) { 670 } 671 } 672 } 673 return inFile != null; 674 } 675 676 /** 677 * Try to read commands from dir/fname, unless 678 * the canonical path passed in is the same as that 679 * for dir/fname. 680 * Return null if that file doesn't exist, 681 * else return the canonical path of that file. 682 */ 683 String readStartupCommandFile(String dir, String fname, String canonPath) { 684 File dotInitFile = new File(dir, fname); 685 if (!dotInitFile.exists()) { 686 return null; 687 } 688 689 String myCanonFile; 690 try { 691 myCanonFile = dotInitFile.getCanonicalPath(); 692 } catch (IOException ee) { 693 MessageOutput.println("Could not open:", dotInitFile.getPath()); 694 return null; 695 } 696 if (canonPath == null || !canonPath.equals(myCanonFile)) { 697 if (!readCommandFile(dotInitFile)) { 698 MessageOutput.println("Could not open:", dotInitFile.getPath()); 699 } 700 } 701 return myCanonFile; 702 } 703 704 705 public TTY() throws Exception { 706 707 MessageOutput.println("Initializing progname", progname); 708 709 if (Env.connection().isOpen() && Env.vm().canBeModified()) { 710 /* 711 * Connection opened on startup. Start event handler 712 * immediately, telling it (through arg 2) to stop on the 713 * VM start event. 714 */ 715 this.handler = new EventHandler(this, true); 716 } 717 try { 718 BufferedReader in = 719 new BufferedReader(new InputStreamReader(System.in)); 720 721 String lastLine = null; 722 723 Thread.currentThread().setPriority(Thread.NORM_PRIORITY); 724 725 /* 726 * Read start up files. This mimics the behavior 727 * of gdb which will read both ~/.gdbinit and then 728 * ./.gdbinit if they exist. We have the twist that 729 * we allow two different names, so we do this: 730 * if ~/jdb.ini exists, 731 * read it 732 * else if ~/.jdbrc exists, 733 * read it 734 * 735 * if ./jdb.ini exists, 736 * if it hasn't been read, read it 737 * It could have been read above because ~ == . 738 * or because of symlinks, ... 739 * else if ./jdbrx exists 740 * if it hasn't been read, read it 741 */ 742 { 743 String userHome = System.getProperty("user.home"); 744 String canonPath; 745 746 if ((canonPath = readStartupCommandFile(userHome, "jdb.ini", null)) == null) { 747 // Doesn't exist, try alternate spelling 748 canonPath = readStartupCommandFile(userHome, ".jdbrc", null); 749 } 750 751 String userDir = System.getProperty("user.dir"); 752 if (readStartupCommandFile(userDir, "jdb.ini", canonPath) == null) { 753 // Doesn't exist, try alternate spelling 754 readStartupCommandFile(userDir, ".jdbrc", canonPath); 755 } 756 } 757 758 // Process interactive commands. 759 MessageOutput.printPrompt(); 760 while (true) { 761 String ln = in.readLine(); 762 if (ln == null) { 763 /* 764 * Jdb is being shutdown because debuggee exited, ignore any 'null' 765 * returned by readLine() during shutdown. JDK-8154144. 766 */ 767 if (!isShuttingDown()) { 768 MessageOutput.println("Input stream closed."); 769 } 770 ln = "quit"; 771 } 772 773 if (ln.startsWith("!!") && lastLine != null) { 774 ln = lastLine + ln.substring(2); 775 MessageOutput.printDirectln(ln);// Special case: use printDirectln() 776 } 777 778 StringTokenizer t = new StringTokenizer(ln); 779 if (t.hasMoreTokens()) { 780 lastLine = ln; 781 executeCommand(t); 782 } else { 783 MessageOutput.printPrompt(); 784 } 785 } 786 } catch (VMDisconnectedException e) { 787 handler.handleDisconnectedException(); 788 } 789 } 790 791 private static void usage() { 792 MessageOutput.println("zz usage text", new Object [] {progname, 793 File.pathSeparator}); 794 System.exit(1); 795 } 796 797 static void usageError(String messageKey) { 798 MessageOutput.println(messageKey); 799 MessageOutput.println(); 800 usage(); 801 } 802 803 static void usageError(String messageKey, String argument) { 804 MessageOutput.println(messageKey, argument); 805 MessageOutput.println(); 806 usage(); 807 } 808 809 private static boolean supportsSharedMemory() { 810 for (Connector connector : 811 Bootstrap.virtualMachineManager().allConnectors()) { 812 if (connector.transport() == null) { 813 continue; 814 } 815 if ("dt_shmem".equals(connector.transport().name())) { 816 return true; 817 } 818 } 819 return false; 820 } 821 822 private static String addressToSocketArgs(String address) { 823 int index = address.indexOf(':'); 824 if (index != -1) { 825 String hostString = address.substring(0, index); 826 String portString = address.substring(index + 1); 827 return "hostname=" + hostString + ",port=" + portString; 828 } else { 829 return "port=" + address; 830 } 831 } 832 833 private static boolean hasWhitespace(String string) { 834 int length = string.length(); 835 for (int i = 0; i < length; i++) { 836 if (Character.isWhitespace(string.charAt(i))) { 837 return true; 838 } 839 } 840 return false; 841 } 842 843 private static String addArgument(String string, String argument) { 844 if (hasWhitespace(argument) || argument.indexOf(',') != -1) { 845 // Quotes were stripped out for this argument, add 'em back. 846 StringBuilder sb = new StringBuilder(string); 847 sb.append('"'); 848 for (int i = 0; i < argument.length(); i++) { 849 char c = argument.charAt(i); 850 if (c == '"') { 851 sb.append('\\'); 852 } 853 sb.append(c); 854 } 855 sb.append("\" "); 856 return sb.toString(); 857 } else { 858 return string + argument + ' '; 859 } 860 } 861 862 public static void main(String argv[]) throws MissingResourceException { 863 String cmdLine = ""; 864 String javaArgs = ""; 865 int traceFlags = VirtualMachine.TRACE_NONE; 866 boolean launchImmediately = false; 867 String connectSpec = null; 868 869 MessageOutput.textResources = ResourceBundle.getBundle 870 ("com.sun.tools.example.debug.tty.TTYResources", 871 Locale.getDefault()); 872 873 for (int i = 0; i < argv.length; i++) { 874 String token = argv[i]; 875 if (token.equals("-dbgtrace")) { 876 if ((i == argv.length - 1) || 877 ! Character.isDigit(argv[i+1].charAt(0))) { 878 traceFlags = VirtualMachine.TRACE_ALL; 879 } else { 880 String flagStr = ""; 881 try { 882 flagStr = argv[++i]; 883 traceFlags = Integer.decode(flagStr).intValue(); 884 } catch (NumberFormatException nfe) { 885 usageError("dbgtrace flag value must be an integer:", 886 flagStr); 887 return; 888 } 889 } 890 } else if (token.equals("-X")) { 891 usageError("Use java minus X to see"); 892 return; 893 } else if ( 894 // Standard VM options passed on 895 token.equals("-v") || token.startsWith("-v:") || // -v[:...] 896 token.startsWith("-verbose") || // -verbose[:...] 897 token.startsWith("-D") || 898 // -classpath handled below 899 // NonStandard options passed on 900 token.startsWith("-X") || 901 // Old-style options (These should remain in place as long as 902 // the standard VM accepts them) 903 token.equals("-noasyncgc") || token.equals("-prof") || 904 token.equals("-verify") || token.equals("-noverify") || 905 token.equals("-verifyremote") || 906 token.equals("-verbosegc") || 907 token.startsWith("-ms") || token.startsWith("-mx") || 908 token.startsWith("-ss") || token.startsWith("-oss") ) { 909 910 javaArgs = addArgument(javaArgs, token); 911 } else if (token.equals("-tclassic")) { 912 usageError("Classic VM no longer supported."); 913 return; 914 } else if (token.equals("-tclient")) { 915 // -client must be the first one 916 javaArgs = "-client " + javaArgs; 917 } else if (token.equals("-tserver")) { 918 // -server must be the first one 919 javaArgs = "-server " + javaArgs; 920 } else if (token.equals("-sourcepath")) { 921 if (i == (argv.length - 1)) { 922 usageError("No sourcepath specified."); 923 return; 924 } 925 Env.setSourcePath(argv[++i]); 926 } else if (token.equals("-classpath")) { 927 if (i == (argv.length - 1)) { 928 usageError("No classpath specified."); 929 return; 930 } 931 javaArgs = addArgument(javaArgs, token); 932 javaArgs = addArgument(javaArgs, argv[++i]); 933 } else if (token.equals("-attach")) { 934 if (connectSpec != null) { 935 usageError("cannot redefine existing connection", token); 936 return; 937 } 938 if (i == (argv.length - 1)) { 939 usageError("No attach address specified."); 940 return; 941 } 942 String address = argv[++i]; 943 944 /* 945 * -attach is shorthand for one of the reference implementation's 946 * attaching connectors. Use the shared memory attach if it's 947 * available; otherwise, use sockets. Build a connect 948 * specification string based on this decision. 949 */ 950 if (supportsSharedMemory()) { 951 connectSpec = "com.sun.jdi.SharedMemoryAttach:name=" + 952 address; 953 } else { 954 String suboptions = addressToSocketArgs(address); 955 connectSpec = "com.sun.jdi.SocketAttach:" + suboptions; 956 } 957 } else if (token.equals("-listen") || token.equals("-listenany")) { 958 if (connectSpec != null) { 959 usageError("cannot redefine existing connection", token); 960 return; 961 } 962 String address = null; 963 if (token.equals("-listen")) { 964 if (i == (argv.length - 1)) { 965 usageError("No attach address specified."); 966 return; 967 } 968 address = argv[++i]; 969 } 970 971 /* 972 * -listen[any] is shorthand for one of the reference implementation's 973 * listening connectors. Use the shared memory listen if it's 974 * available; otherwise, use sockets. Build a connect 975 * specification string based on this decision. 976 */ 977 if (supportsSharedMemory()) { 978 connectSpec = "com.sun.jdi.SharedMemoryListen:"; 979 if (address != null) { 980 connectSpec += ("name=" + address); 981 } 982 } else { 983 connectSpec = "com.sun.jdi.SocketListen:"; 984 if (address != null) { 985 connectSpec += addressToSocketArgs(address); 986 } 987 } 988 } else if (token.equals("-launch")) { 989 launchImmediately = true; 990 } else if (token.equals("-listconnectors")) { 991 Commands evaluator = new Commands(); 992 evaluator.commandConnectors(Bootstrap.virtualMachineManager()); 993 return; 994 } else if (token.equals("-connect")) { 995 /* 996 * -connect allows the user to pick the connector 997 * used in bringing up the target VM. This allows 998 * use of connectors other than those in the reference 999 * implementation. 1000 */ 1001 if (connectSpec != null) { 1002 usageError("cannot redefine existing connection", token); 1003 return; 1004 } 1005 if (i == (argv.length - 1)) { 1006 usageError("No connect specification."); 1007 return; 1008 } 1009 connectSpec = argv[++i]; 1010 } else if (token.equals("-help")) { 1011 usage(); 1012 } else if (token.equals("-version")) { 1013 Commands evaluator = new Commands(); 1014 evaluator.commandVersion(progname, 1015 Bootstrap.virtualMachineManager()); 1016 System.exit(0); 1017 } else if (token.startsWith("-")) { 1018 usageError("invalid option", token); 1019 return; 1020 } else { 1021 // Everything from here is part of the command line 1022 cmdLine = addArgument("", token); 1023 for (i++; i < argv.length; i++) { 1024 cmdLine = addArgument(cmdLine, argv[i]); 1025 } 1026 break; 1027 } 1028 } 1029 1030 /* 1031 * Unless otherwise specified, set the default connect spec. 1032 */ 1033 1034 /* 1035 * Here are examples of jdb command lines and how the options 1036 * are interpreted as arguments to the program being debugged. 1037 * arg1 arg2 1038 * ---- ---- 1039 * jdb hello a b a b 1040 * jdb hello "a b" a b 1041 * jdb hello a,b a,b 1042 * jdb hello a, b a, b 1043 * jdb hello "a, b" a, b 1044 * jdb -connect "com.sun.jdi.CommandLineLaunch:main=hello a,b" illegal 1045 * jdb -connect com.sun.jdi.CommandLineLaunch:main=hello "a,b" illegal 1046 * jdb -connect 'com.sun.jdi.CommandLineLaunch:main=hello "a,b"' arg1 = a,b 1047 * jdb -connect 'com.sun.jdi.CommandLineLaunch:main=hello "a b"' arg1 = a b 1048 * jdb -connect 'com.sun.jdi.CommandLineLaunch:main=hello a b' arg1 = a arg2 = b 1049 * jdb -connect 'com.sun.jdi.CommandLineLaunch:main=hello "a," b' arg1 = a, arg2 = b 1050 */ 1051 if (connectSpec == null) { 1052 connectSpec = "com.sun.jdi.CommandLineLaunch:"; 1053 } else if (!connectSpec.endsWith(",") && !connectSpec.endsWith(":")) { 1054 connectSpec += ","; // (Bug ID 4285874) 1055 } 1056 1057 cmdLine = cmdLine.trim(); 1058 javaArgs = javaArgs.trim(); 1059 1060 if (cmdLine.length() > 0) { 1061 if (!connectSpec.startsWith("com.sun.jdi.CommandLineLaunch:")) { 1062 usageError("Cannot specify command line with connector:", 1063 connectSpec); 1064 return; 1065 } 1066 connectSpec += "main=" + cmdLine + ","; 1067 } 1068 1069 if (javaArgs.length() > 0) { 1070 if (!connectSpec.startsWith("com.sun.jdi.CommandLineLaunch:")) { 1071 usageError("Cannot specify target vm arguments with connector:", 1072 connectSpec); 1073 return; 1074 } 1075 connectSpec += "options=" + javaArgs + ","; 1076 } 1077 1078 try { 1079 if (! connectSpec.endsWith(",")) { 1080 connectSpec += ","; // (Bug ID 4285874) 1081 } 1082 Env.init(connectSpec, launchImmediately, traceFlags); 1083 new TTY(); 1084 } catch(Exception e) { 1085 MessageOutput.printException("Internal exception:", e); 1086 } 1087 } 1088 }