1 /* 2 * Copyright (c) 1998, 2013, 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.gui; 36 37 import java.io.*; 38 import java.util.*; 39 40 import com.sun.jdi.*; 41 import com.sun.tools.example.debug.bdi.*; 42 43 public class CommandInterpreter { 44 45 boolean echo; 46 47 Environment env; 48 49 private ContextManager context; 50 private ExecutionManager runtime; 51 private ClassManager classManager; 52 private SourceManager sourceManager; 53 54 private OutputSink out; //### Hack! Should be local in each method used. 55 private String lastCommand = "help"; 56 57 public CommandInterpreter(Environment env) { 58 this(env, true); 59 } 60 61 public CommandInterpreter(Environment env, boolean echo) { 62 this.env = env; 63 this.echo = echo; 64 this.runtime = env.getExecutionManager(); 65 this.context = env.getContextManager(); 66 this.classManager = env.getClassManager(); 67 this.sourceManager = env.getSourceManager(); 68 } 69 70 private ThreadReference[] threads = null; 71 72 /* 73 * The numbering of threads is relative to the current set of threads, 74 * and may be affected by the creation and termination of new threads. 75 * Commands issued using such thread ids will only give reliable behavior 76 * relative to what was shown earlier in 'list' commands if the VM is interrupted. 77 * We need a better scheme. 78 */ 79 80 private ThreadReference[] threads() throws NoSessionException { 81 if (threads == null) { 82 ThreadIterator ti = new ThreadIterator(getDefaultThreadGroup()); 83 List<ThreadReference> tlist = new ArrayList<ThreadReference>(); 84 while (ti.hasNext()) { 85 tlist.add(ti.nextThread()); 86 } 87 threads = tlist.toArray(new ThreadReference[tlist.size()]); 88 } 89 return threads; 90 } 91 92 private ThreadReference findThread(String idToken) throws NoSessionException { 93 String id; 94 ThreadReference thread = null; 95 if (idToken.startsWith("t@")) { 96 id = idToken.substring(2); 97 } else { 98 id = idToken; 99 } 100 try { 101 ThreadReference[] threads = threads(); 102 long threadID = Long.parseLong(id, 16); 103 for (ThreadReference thread2 : threads) { 104 if (thread2.uniqueID() == threadID) { 105 thread = thread2; 106 break; 107 } 108 } 109 if (thread == null) { 110 //env.failure("No thread for id \"" + idToken + "\""); 111 env.failure("\"" + idToken + "\" is not a valid thread id."); 112 } 113 } catch (NumberFormatException e) { 114 env.error("Thread id \"" + idToken + "\" is ill-formed."); 115 thread = null; 116 } 117 return thread; 118 } 119 120 private ThreadIterator allThreads() throws NoSessionException { 121 threads = null; 122 //### Why not use runtime.allThreads().iterator() ? 123 return new ThreadIterator(runtime.topLevelThreadGroups()); 124 } 125 126 private ThreadIterator currentThreadGroupThreads() throws NoSessionException { 127 threads = null; 128 return new ThreadIterator(getDefaultThreadGroup()); 129 } 130 131 private ThreadGroupIterator allThreadGroups() throws NoSessionException { 132 threads = null; 133 return new ThreadGroupIterator(runtime.topLevelThreadGroups()); 134 } 135 136 private ThreadGroupReference defaultThreadGroup; 137 138 private ThreadGroupReference getDefaultThreadGroup() throws NoSessionException { 139 if (defaultThreadGroup == null) { 140 defaultThreadGroup = runtime.systemThreadGroup(); 141 } 142 return defaultThreadGroup; 143 } 144 145 private void setDefaultThreadGroup(ThreadGroupReference tg) { 146 defaultThreadGroup = tg; 147 } 148 149 /* 150 * Command handlers. 151 */ 152 153 // Command: classes 154 155 private void commandClasses() throws NoSessionException { 156 OutputSink out = env.getOutputSink(); 157 //out.println("** classes list **"); 158 for (ReferenceType refType : runtime.allClasses()) { 159 out.println(refType.name()); 160 } 161 out.show(); 162 } 163 164 165 // Command: methods 166 167 private void commandMethods(StringTokenizer t) throws NoSessionException { 168 if (!t.hasMoreTokens()) { 169 env.error("No class specified."); 170 return; 171 } 172 String idClass = t.nextToken(); 173 ReferenceType cls = findClass(idClass); 174 if (cls != null) { 175 List<Method> methods = cls.allMethods(); 176 OutputSink out = env.getOutputSink(); 177 for (int i = 0; i < methods.size(); i++) { 178 Method method = methods.get(i); 179 out.print(method.declaringType().name() + " " + 180 method.name() + "("); 181 Iterator<String> it = method.argumentTypeNames().iterator(); 182 if (it.hasNext()) { 183 while (true) { 184 out.print(it.next()); 185 if (!it.hasNext()) { 186 break; 187 } 188 out.print(", "); 189 } 190 } 191 out.println(")"); 192 } 193 out.show(); 194 } else { 195 //### Should validate class name syntax. 196 env.failure("\"" + idClass + "\" is not a valid id or class name."); 197 } 198 } 199 200 private ReferenceType findClass(String pattern) throws NoSessionException { 201 List<ReferenceType> results = runtime.findClassesMatchingPattern(pattern); 202 if (results.size() > 0) { 203 //### Should handle multiple results sensibly. 204 return results.get(0); 205 } 206 return null; 207 } 208 209 // Command: threads 210 211 private void commandThreads(StringTokenizer t) throws NoSessionException { 212 if (!t.hasMoreTokens()) { 213 OutputSink out = env.getOutputSink(); 214 printThreadGroup(out, getDefaultThreadGroup(), 0); 215 out.show(); 216 return; 217 } 218 String name = t.nextToken(); 219 ThreadGroupReference tg = findThreadGroup(name); 220 if (tg == null) { 221 env.failure(name + " is not a valid threadgroup name."); 222 } else { 223 OutputSink out = env.getOutputSink(); 224 printThreadGroup(out, tg, 0); 225 out.show(); 226 } 227 } 228 229 private ThreadGroupReference findThreadGroup(String name) throws NoSessionException { 230 //### Issue: Uniqueness of thread group names is not enforced. 231 ThreadGroupIterator tgi = allThreadGroups(); 232 while (tgi.hasNext()) { 233 ThreadGroupReference tg = tgi.nextThreadGroup(); 234 if (tg.name().equals(name)) { 235 return tg; 236 } 237 } 238 return null; 239 } 240 241 private int printThreadGroup(OutputSink out, ThreadGroupReference tg, int iThread) { 242 out.println("Group " + tg.name() + ":"); 243 List<ThreadReference> tlist = tg.threads(); 244 int maxId = 0; 245 int maxName = 0; 246 for (int i = 0 ; i < tlist.size() ; i++) { 247 ThreadReference thr = tlist.get(i); 248 int len = Utils.description(thr).length(); 249 if (len > maxId) { 250 maxId = len; 251 } 252 String name = thr.name(); 253 int iDot = name.lastIndexOf('.'); 254 if (iDot >= 0 && name.length() > iDot) { 255 name = name.substring(iDot + 1); 256 } 257 if (name.length() > maxName) { 258 maxName = name.length(); 259 } 260 } 261 String maxNumString = String.valueOf(iThread + tlist.size()); 262 int maxNumDigits = maxNumString.length(); 263 for (int i = 0 ; i < tlist.size() ; i++) { 264 ThreadReference thr = tlist.get(i); 265 char buf[] = new char[80]; 266 for (int j = 0; j < 79; j++) { 267 buf[j] = ' '; 268 } 269 buf[79] = '\0'; 270 StringBuilder sbOut = new StringBuilder(); 271 sbOut.append(buf); 272 273 // Right-justify the thread number at start of output string 274 String numString = String.valueOf(iThread + i + 1); 275 sbOut.insert(maxNumDigits - numString.length(), 276 numString); 277 sbOut.insert(maxNumDigits, "."); 278 279 int iBuf = maxNumDigits + 2; 280 sbOut.insert(iBuf, Utils.description(thr)); 281 iBuf += maxId + 1; 282 String name = thr.name(); 283 int iDot = name.lastIndexOf('.'); 284 if (iDot >= 0 && name.length() > iDot) { 285 name = name.substring(iDot + 1); 286 } 287 sbOut.insert(iBuf, name); 288 iBuf += maxName + 1; 289 sbOut.insert(iBuf, Utils.getStatus(thr)); 290 sbOut.setLength(79); 291 out.println(sbOut.toString()); 292 } 293 for (ThreadGroupReference tg0 : tg.threadGroups()) { 294 if (!tg.equals(tg0)) { // TODO ref mgt 295 iThread += printThreadGroup(out, tg0, iThread + tlist.size()); 296 } 297 } 298 return tlist.size(); 299 } 300 301 // Command: threadgroups 302 303 private void commandThreadGroups() throws NoSessionException { 304 ThreadGroupIterator it = allThreadGroups(); 305 int cnt = 0; 306 OutputSink out = env.getOutputSink(); 307 while (it.hasNext()) { 308 ThreadGroupReference tg = it.nextThreadGroup(); 309 ++cnt; 310 out.println("" + cnt + ". " + Utils.description(tg) + " " + tg.name()); 311 } 312 out.show(); 313 } 314 315 // Command: thread 316 317 private void commandThread(StringTokenizer t) throws NoSessionException { 318 if (!t.hasMoreTokens()) { 319 env.error("Thread number not specified."); 320 return; 321 } 322 ThreadReference thread = findThread(t.nextToken()); 323 if (thread != null) { 324 //### Should notify user. 325 context.setCurrentThread(thread); 326 } 327 } 328 329 // Command: threadgroup 330 331 private void commandThreadGroup(StringTokenizer t) throws NoSessionException { 332 if (!t.hasMoreTokens()) { 333 env.error("Threadgroup name not specified."); 334 return; 335 } 336 String name = t.nextToken(); 337 ThreadGroupReference tg = findThreadGroup(name); 338 if (tg == null) { 339 env.failure(name + " is not a valid threadgroup name."); 340 } else { 341 //### Should notify user. 342 setDefaultThreadGroup(tg); 343 } 344 } 345 346 // Command: run 347 348 private void commandRun(StringTokenizer t) throws NoSessionException { 349 if (doLoad(false, t)) { 350 env.notice("Running ..."); 351 } 352 } 353 354 // Command: load 355 356 private void commandLoad(StringTokenizer t) throws NoSessionException { 357 if (doLoad(true, t)) {} 358 } 359 360 private boolean doLoad(boolean suspended, 361 StringTokenizer t) throws NoSessionException { 362 363 String clname; 364 365 if (!t.hasMoreTokens()) { 366 clname = context.getMainClassName(); 367 if (!clname.equals("")) { 368 // Run from prevously-set class name. 369 try { 370 String vmArgs = context.getVmArguments(); 371 runtime.run(suspended, 372 vmArgs, 373 clname, 374 context.getProgramArguments()); 375 return true; 376 } catch (VMLaunchFailureException e) { 377 env.failure("Attempt to launch main class \"" + clname + "\" failed."); 378 } 379 } else { 380 env.failure("No main class specified and no current default defined."); 381 } 382 } else { 383 clname = t.nextToken(); 384 StringBuilder str = new StringBuilder(); 385 // Allow VM arguments to be specified here? 386 while (t.hasMoreTokens()) { 387 String tok = t.nextToken(); 388 str.append(tok); 389 if (t.hasMoreTokens()) { 390 str.append(' '); 391 } 392 } 393 String args = str.toString(); 394 try { 395 String vmArgs = context.getVmArguments(); 396 runtime.run(suspended, vmArgs, clname, args); 397 context.setMainClassName(clname); 398 //context.setVmArguments(vmArgs); 399 context.setProgramArguments(args); 400 return true; 401 } catch (VMLaunchFailureException e) { 402 env.failure("Attempt to launch main class \"" + clname + "\" failed."); 403 } 404 } 405 return false; 406 } 407 408 // Command: connect 409 410 private void commandConnect(StringTokenizer t) { 411 try { 412 LaunchTool.queryAndLaunchVM(runtime); 413 } catch (VMLaunchFailureException e) { 414 env.failure("Attempt to connect failed."); 415 } 416 } 417 418 // Command: attach 419 420 private void commandAttach(StringTokenizer t) { 421 String portName; 422 if (!t.hasMoreTokens()) { 423 portName = context.getRemotePort(); 424 if (!portName.equals("")) { 425 try { 426 runtime.attach(portName); 427 } catch (VMLaunchFailureException e) { 428 env.failure("Attempt to attach to port \"" + portName + "\" failed."); 429 } 430 } else { 431 env.failure("No port specified and no current default defined."); 432 } 433 } else { 434 portName = t.nextToken(); 435 try { 436 runtime.attach(portName); 437 } catch (VMLaunchFailureException e) { 438 env.failure("Attempt to attach to port \"" + portName + "\" failed."); 439 } 440 context.setRemotePort(portName); 441 } 442 } 443 444 // Command: detach 445 446 private void commandDetach(StringTokenizer t) throws NoSessionException { 447 runtime.detach(); 448 } 449 450 // Command: interrupt 451 452 private void commandInterrupt(StringTokenizer t) throws NoSessionException { 453 runtime.interrupt(); 454 } 455 456 // Command: suspend 457 458 private void commandSuspend(StringTokenizer t) throws NoSessionException { 459 if (!t.hasMoreTokens()) { 460 // Suspend all threads in the current thread group. 461 //### Issue: help message says default is all threads. 462 //### Behavior here agrees with 'jdb', however. 463 ThreadIterator ti = currentThreadGroupThreads(); 464 while (ti.hasNext()) { 465 // TODO - don't suspend debugger threads 466 ti.nextThread().suspend(); 467 } 468 env.notice("All (non-system) threads suspended."); 469 } else { 470 while (t.hasMoreTokens()) { 471 ThreadReference thread = findThread(t.nextToken()); 472 if (thread != null) { 473 //thread.suspend(); 474 runtime.suspendThread(thread); 475 } 476 } 477 } 478 } 479 480 // Command: resume 481 482 private void commandResume(StringTokenizer t) throws NoSessionException { 483 if (!t.hasMoreTokens()) { 484 // Suspend all threads in the current thread group. 485 //### Issue: help message says default is all threads. 486 //### Behavior here agrees with 'jdb', however. 487 ThreadIterator ti = currentThreadGroupThreads(); 488 while (ti.hasNext()) { 489 // TODO - don't suspend debugger threads 490 ti.nextThread().resume(); 491 } 492 env.notice("All threads resumed."); 493 } else { 494 while (t.hasMoreTokens()) { 495 ThreadReference thread = findThread(t.nextToken()); 496 if (thread != null) { 497 //thread.resume(); 498 runtime.resumeThread(thread); 499 } 500 } 501 } 502 } 503 504 // Command: cont 505 506 private void commandCont() throws NoSessionException { 507 try { 508 runtime.go(); 509 } catch (VMNotInterruptedException e) { 510 //### failure? 511 env.notice("Target VM is already running."); 512 } 513 } 514 515 // Command: step 516 517 private void commandStep(StringTokenizer t) throws NoSessionException{ 518 ThreadReference current = context.getCurrentThread(); 519 if (current == null) { 520 env.failure("No current thread."); 521 return; 522 } 523 try { 524 if (t.hasMoreTokens() && 525 t.nextToken().toLowerCase().equals("up")) { 526 runtime.stepOut(current); 527 } else { 528 runtime.stepIntoLine(current); 529 } 530 } catch (AbsentInformationException e) { 531 env.failure("No linenumber information available -- " + 532 "Try \"stepi\" to step by instructions."); 533 } 534 } 535 536 // Command: stepi 537 538 private void commandStepi() throws NoSessionException { 539 ThreadReference current = context.getCurrentThread(); 540 if (current == null) { 541 env.failure("No current thread."); 542 return; 543 } 544 runtime.stepIntoInstruction(current); 545 } 546 547 // Command: next 548 549 private void commandNext() throws NoSessionException { 550 ThreadReference current = context.getCurrentThread(); 551 if (current == null) { 552 env.failure("No current thread."); 553 return; 554 } 555 try { 556 runtime.stepOverLine(current); 557 } catch (AbsentInformationException e) { 558 env.failure("No linenumber information available -- " + 559 "Try \"nexti\" to step by instructions."); 560 } 561 } 562 563 // Command: nexti (NEW) 564 565 private void commandNexti() throws NoSessionException { 566 ThreadReference current = context.getCurrentThread(); 567 if (current == null) { 568 env.failure("No current thread."); 569 return; 570 } 571 runtime.stepOverInstruction(current); 572 } 573 574 // Command: kill 575 576 private void commandKill(StringTokenizer t) throws NoSessionException { 577 //### Should change the way in which thread ids and threadgroup names 578 //### are distinguished. 579 if (!t.hasMoreTokens()) { 580 env.error("Usage: kill <threadgroup name> or <thread id>"); 581 return; 582 } 583 while (t.hasMoreTokens()) { 584 String idToken = t.nextToken(); 585 ThreadReference thread = findThread(idToken); 586 if (thread != null) { 587 runtime.stopThread(thread); 588 env.notice("Thread " + thread.name() + " killed."); 589 return; 590 } else { 591 /* Check for threadgroup name, NOT skipping "system". */ 592 //### Should skip "system"? Classic 'jdb' does this. 593 //### Should deal with possible non-uniqueness of threadgroup names. 594 ThreadGroupIterator itg = allThreadGroups(); 595 while (itg.hasNext()) { 596 ThreadGroupReference tg = itg.nextThreadGroup(); 597 if (tg.name().equals(idToken)) { 598 ThreadIterator it = new ThreadIterator(tg); 599 while (it.hasNext()) { 600 runtime.stopThread(it.nextThread()); 601 } 602 env.notice("Threadgroup " + tg.name() + "killed."); 603 return; 604 } 605 } 606 env.failure("\"" + idToken + 607 "\" is not a valid threadgroup or id."); 608 } 609 } 610 } 611 612 613 /************* 614 // TODO 615 private void commandCatchException(StringTokenizer t) throws NoSessionException {} 616 // TODO 617 private void commandIgnoreException(StringTokenizer t) throws NoSessionException {} 618 *************/ 619 620 // Command: up 621 622 //### Print current frame after command? 623 624 int readCount(StringTokenizer t) { 625 int cnt = 1; 626 if (t.hasMoreTokens()) { 627 String idToken = t.nextToken(); 628 try { 629 cnt = Integer.valueOf(idToken).intValue(); 630 } catch (NumberFormatException e) { 631 cnt = -1; 632 } 633 } 634 return cnt; 635 } 636 637 void commandUp(StringTokenizer t) throws NoSessionException { 638 ThreadReference current = context.getCurrentThread(); 639 if (current == null) { 640 env.failure("No current thread."); 641 return; 642 } 643 int nLevels = readCount(t); 644 if (nLevels <= 0) { 645 env.error("usage: up [n frames]"); 646 return; 647 } 648 try { 649 int delta = context.moveCurrentFrameIndex(current, -nLevels); 650 if (delta == 0) { 651 env.notice("Already at top of stack."); 652 } else if (-delta < nLevels) { 653 env.notice("Moved up " + delta + " frames to top of stack."); 654 } 655 } catch (VMNotInterruptedException e) { 656 env.failure("Target VM must be in interrupted state."); 657 } 658 } 659 660 private void commandDown(StringTokenizer t) throws NoSessionException { 661 ThreadReference current = context.getCurrentThread(); 662 if (current == null) { 663 env.failure("No current thread."); 664 return; 665 } 666 int nLevels = readCount(t); 667 if (nLevels <= 0) { 668 env.error("usage: down [n frames]"); 669 return; 670 } 671 try { 672 int delta = context.moveCurrentFrameIndex(current, nLevels); 673 if (delta == 0) { 674 env.notice("Already at bottom of stack."); 675 } else if (delta < nLevels) { 676 env.notice("Moved down " + delta + " frames to bottom of stack."); 677 } 678 } catch (VMNotInterruptedException e) { 679 env.failure("Target VM must be in interrupted state."); 680 } 681 } 682 683 // Command: frame 684 685 private void commandFrame(StringTokenizer t) throws NoSessionException { 686 ThreadReference current = context.getCurrentThread(); 687 if (current == null) { 688 env.failure("No current thread."); 689 return; 690 } 691 if (!t.hasMoreTokens()) { 692 env.error("usage: frame <frame-index>"); 693 return; 694 } 695 String idToken = t.nextToken(); 696 int n; 697 try { 698 n = Integer.valueOf(idToken).intValue(); 699 } catch (NumberFormatException e) { 700 n = 0; 701 } 702 if (n <= 0) { 703 env.error("use positive frame index"); 704 return; 705 } 706 try { 707 int delta = context.setCurrentFrameIndex(current, n); 708 if (delta == 0) { 709 env.notice("Frame unchanged."); 710 } else if (delta < 0) { 711 env.notice("Moved up " + -delta + " frames."); 712 } else { 713 env.notice("Moved down " + delta + " frames."); 714 } 715 } catch (VMNotInterruptedException e) { 716 env.failure("Target VM must be in interrupted state."); 717 } 718 } 719 720 // Command: where 721 722 //### Should we insist that VM be interrupted here? 723 //### There is an inconsistency between the 'where' command 724 //### and 'up' and 'down' in this respect. 725 726 private void commandWhere(StringTokenizer t, boolean showPC) 727 throws NoSessionException { 728 ThreadReference current = context.getCurrentThread(); 729 if (!t.hasMoreTokens()) { 730 if (current == null) { 731 env.error("No thread specified."); 732 return; 733 } 734 dumpStack(current, showPC); 735 } else { 736 String token = t.nextToken(); 737 if (token.toLowerCase().equals("all")) { 738 ThreadIterator it = allThreads(); 739 while (it.hasNext()) { 740 ThreadReference thread = it.next(); 741 out.println(thread.name() + ": "); 742 dumpStack(thread, showPC); 743 } 744 } else { 745 ThreadReference thread = findThread(t.nextToken()); 746 //### Do we want to set current thread here? 747 //### Should notify user of change. 748 if (thread != null) { 749 context.setCurrentThread(thread); 750 } 751 dumpStack(thread, showPC); 752 } 753 } 754 } 755 756 private void dumpStack(ThreadReference thread, boolean showPC) { 757 //### Check for these. 758 //env.failure("Thread no longer exists."); 759 //env.failure("Target VM must be in interrupted state."); 760 //env.failure("Current thread isn't suspended."); 761 //### Should handle extremely long stack traces sensibly for user. 762 List<StackFrame> stack = null; 763 try { 764 stack = thread.frames(); 765 } catch (IncompatibleThreadStateException e) { 766 env.failure("Thread is not suspended."); 767 } 768 //### Fix this! 769 //### Previously mishandled cases where thread was not current. 770 //### Now, prints all of the stack regardless of current frame. 771 int frameIndex = 0; 772 //int frameIndex = context.getCurrentFrameIndex(); 773 if (stack == null) { 774 env.failure("Thread is not running (no stack)."); 775 } else { 776 OutputSink out = env.getOutputSink(); 777 int nFrames = stack.size(); 778 for (int i = frameIndex; i < nFrames; i++) { 779 StackFrame frame = stack.get(i); 780 Location loc = frame.location(); 781 Method meth = loc.method(); 782 out.print(" [" + (i + 1) + "] "); 783 out.print(meth.declaringType().name()); 784 out.print('.'); 785 out.print(meth.name()); 786 out.print(" ("); 787 if (meth.isNative()) { 788 out.print("native method"); 789 } else if (loc.lineNumber() != -1) { 790 try { 791 out.print(loc.sourceName()); 792 } catch (AbsentInformationException e) { 793 out.print("<unknown>"); 794 } 795 out.print(':'); 796 out.print(loc.lineNumber()); 797 } 798 out.print(')'); 799 if (showPC) { 800 long pc = loc.codeIndex(); 801 if (pc != -1) { 802 out.print(", pc = " + pc); 803 } 804 } 805 out.println(); 806 } 807 out.show(); 808 } 809 } 810 811 private void listEventRequests() throws NoSessionException { 812 // Print set breakpoints 813 List<EventRequestSpec> specs = runtime.eventRequestSpecs(); 814 if (specs.isEmpty()) { 815 env.notice("No breakpoints/watchpoints/exceptions set."); 816 } else { 817 OutputSink out = env.getOutputSink(); 818 out.println("Current breakpoints/watchpoints/exceptions set:"); 819 for (EventRequestSpec bp : specs) { 820 out.println("\t" + bp); 821 } 822 out.show(); 823 } 824 } 825 826 private BreakpointSpec parseBreakpointSpec(String bptSpec) { 827 StringTokenizer t = new StringTokenizer(bptSpec); 828 BreakpointSpec bpSpec = null; 829 // try { 830 String token = t.nextToken("@:( \t\n\r"); 831 // We can't use hasMoreTokens here because it will cause any leading 832 // paren to be lost. 833 String rest; 834 try { 835 rest = t.nextToken("").trim(); 836 } catch (NoSuchElementException e) { 837 rest = null; 838 } 839 if ((rest != null) && rest.startsWith("@")) { 840 t = new StringTokenizer(rest.substring(1)); 841 String sourceName = token; 842 String lineToken = t.nextToken(); 843 int lineNumber = Integer.valueOf(lineToken).intValue(); 844 if (t.hasMoreTokens()) { 845 return null; 846 } 847 bpSpec = runtime.createSourceLineBreakpoint(sourceName, 848 lineNumber); 849 } else if ((rest != null) && rest.startsWith(":")) { 850 t = new StringTokenizer(rest.substring(1)); 851 String classId = token; 852 String lineToken = t.nextToken(); 853 int lineNumber = Integer.valueOf(lineToken).intValue(); 854 if (t.hasMoreTokens()) { 855 return null; 856 } 857 bpSpec = runtime.createClassLineBreakpoint(classId, lineNumber); 858 } else { 859 // Try stripping method from class.method token. 860 int idot = token.lastIndexOf('.'); 861 if ( (idot <= 0) || /* No dot or dot in first char */ 862 (idot >= token.length() - 1) ) { /* dot in last char */ 863 return null; 864 } 865 String methodName = token.substring(idot + 1); 866 String classId = token.substring(0, idot); 867 List<String> argumentList = null; 868 if (rest != null) { 869 if (!rest.startsWith("(") || !rest.endsWith(")")) { 870 //### Should throw exception with error message 871 //out.println("Invalid method specification: " 872 // + methodName + rest); 873 return null; 874 } 875 // Trim the parens 876 //### What about spaces in arglist? 877 rest = rest.substring(1, rest.length() - 1); 878 argumentList = new ArrayList<String>(); 879 t = new StringTokenizer(rest, ","); 880 while (t.hasMoreTokens()) { 881 argumentList.add(t.nextToken()); 882 } 883 } 884 bpSpec = runtime.createMethodBreakpoint(classId, 885 methodName, 886 argumentList); 887 } 888 // } catch (Exception e) { 889 // env.error("Exception attempting to create breakpoint: " + e); 890 // return null; 891 // } 892 return bpSpec; 893 } 894 895 private void commandStop(StringTokenizer t) throws NoSessionException { 896 String token; 897 898 if (!t.hasMoreTokens()) { 899 listEventRequests(); 900 } else { 901 token = t.nextToken(); 902 // Ignore optional "at" or "in" token. 903 // Allowed for backward compatibility. 904 if (token.equals("at") || token.equals("in")) { 905 if (t.hasMoreTokens()) { 906 token = t.nextToken(); 907 } else { 908 env.error("Missing breakpoint specification."); 909 return; 910 } 911 } 912 BreakpointSpec bpSpec = parseBreakpointSpec(token); 913 if (bpSpec != null) { 914 //### Add sanity-checks for deferred breakpoint. 915 runtime.install(bpSpec); 916 } else { 917 env.error("Ill-formed breakpoint specification."); 918 } 919 } 920 } 921 922 private void commandClear(StringTokenizer t) throws NoSessionException { 923 if (!t.hasMoreTokens()) { 924 // Print set breakpoints 925 listEventRequests(); 926 return; 927 } 928 //### need 'clear all' 929 BreakpointSpec bpSpec = parseBreakpointSpec(t.nextToken()); 930 if (bpSpec != null) { 931 List<EventRequestSpec> specs = runtime.eventRequestSpecs(); 932 933 if (specs.isEmpty()) { 934 env.notice("No breakpoints set."); 935 } else { 936 List<EventRequestSpec> toDelete = new ArrayList<EventRequestSpec>(); 937 for (EventRequestSpec spec : specs) { 938 if (spec.equals(bpSpec)) { 939 toDelete.add(spec); 940 } 941 } 942 // The request used for matching should be found 943 if (toDelete.size() <= 1) { 944 env.notice("No matching breakpoint set."); 945 } 946 for (EventRequestSpec spec : toDelete) { 947 runtime.delete(spec); 948 } 949 } 950 } else { 951 env.error("Ill-formed breakpoint specification."); 952 } 953 } 954 955 // Command: list 956 957 private void commandList(StringTokenizer t) throws NoSessionException { 958 ThreadReference current = context.getCurrentThread(); 959 if (current == null) { 960 env.error("No thread specified."); 961 return; 962 } 963 Location loc; 964 try { 965 StackFrame frame = context.getCurrentFrame(current); 966 if (frame == null) { 967 env.failure("Thread has not yet begun execution."); 968 return; 969 } 970 loc = frame.location(); 971 } catch (VMNotInterruptedException e) { 972 env.failure("Target VM must be in interrupted state."); 973 return; 974 } 975 SourceModel source = sourceManager.sourceForLocation(loc); 976 if (source == null) { 977 if (loc.method().isNative()) { 978 env.failure("Current method is native."); 979 return; 980 } 981 env.failure("No source available for " + Utils.locationString(loc) + "."); 982 return; 983 } 984 ReferenceType refType = loc.declaringType(); 985 int lineno = loc.lineNumber(); 986 if (t.hasMoreTokens()) { 987 String id = t.nextToken(); 988 // See if token is a line number. 989 try { 990 lineno = Integer.valueOf(id).intValue(); 991 } catch (NumberFormatException nfe) { 992 // It isn't -- see if it's a method name. 993 List<Method> meths = refType.methodsByName(id); 994 if (meths == null || meths.size() == 0) { 995 env.failure(id + 996 " is not a valid line number or " + 997 "method name for class " + 998 refType.name()); 999 return; 1000 } else if (meths.size() > 1) { 1001 env.failure(id + 1002 " is an ambiguous method name in" + 1003 refType.name()); 1004 return; 1005 } 1006 loc = meths.get(0).location(); 1007 lineno = loc.lineNumber(); 1008 } 1009 } 1010 int startLine = (lineno > 4) ? lineno - 4 : 1; 1011 int endLine = startLine + 9; 1012 String sourceLine = source.sourceLine(lineno); 1013 if (sourceLine == null) { 1014 env.failure("" + 1015 lineno + 1016 " is an invalid line number for " + 1017 refType.name()); 1018 } else { 1019 OutputSink out = env.getOutputSink(); 1020 for (int i = startLine; i <= endLine; i++) { 1021 sourceLine = source.sourceLine(i); 1022 if (sourceLine == null) { 1023 break; 1024 } 1025 out.print(i); 1026 out.print("\t"); 1027 if (i == lineno) { 1028 out.print("=> "); 1029 } else { 1030 out.print(" "); 1031 } 1032 out.println(sourceLine); 1033 } 1034 out.show(); 1035 } 1036 } 1037 1038 // Command: use 1039 // Get or set the source file path list. 1040 1041 private void commandUse(StringTokenizer t) { 1042 if (!t.hasMoreTokens()) { 1043 out.println(sourceManager.getSourcePath().asString()); 1044 } else { 1045 //### Should throw exception for invalid path. 1046 //### E.g., vetoable property change. 1047 sourceManager.setSourcePath(new SearchPath(t.nextToken())); 1048 } 1049 } 1050 1051 // Command: sourcepath 1052 // Get or set the source file path list. (Alternate to 'use'.) 1053 1054 private void commandSourcepath(StringTokenizer t) { 1055 if (!t.hasMoreTokens()) { 1056 out.println(sourceManager.getSourcePath().asString()); 1057 } else { 1058 //### Should throw exception for invalid path. 1059 //### E.g., vetoable property change. 1060 sourceManager.setSourcePath(new SearchPath(t.nextToken())); 1061 } 1062 } 1063 1064 // Command: classpath 1065 // Get or set the class file path list. 1066 1067 private void commandClasspath(StringTokenizer t) { 1068 if (!t.hasMoreTokens()) { 1069 out.println(classManager.getClassPath().asString()); 1070 } else { 1071 //### Should throw exception for invalid path. 1072 //### E.g., vetoable property change. 1073 classManager.setClassPath(new SearchPath(t.nextToken())); 1074 } 1075 } 1076 1077 // Command: view 1078 // Display source for source file or class. 1079 1080 private void commandView(StringTokenizer t) throws NoSessionException { 1081 if (!t.hasMoreTokens()) { 1082 env.error("Argument required"); 1083 } else { 1084 String name = t.nextToken(); 1085 if (name.endsWith(".java") || 1086 name.indexOf(File.separatorChar) >= 0) { 1087 env.viewSource(name); 1088 } else { 1089 //### JDI crashes taking line number for class. 1090 /***** 1091 ReferenceType cls = findClass(name); 1092 if (cls != null) { 1093 env.viewLocation(cls.location()); 1094 } else { 1095 env.failure("No such class"); 1096 } 1097 *****/ 1098 String fileName = name.replace('.', File.separatorChar) + ".java"; 1099 env.viewSource(fileName); 1100 } 1101 } 1102 } 1103 1104 // Command: locals 1105 // Print all local variables in current stack frame. 1106 1107 private void commandLocals() throws NoSessionException { 1108 ThreadReference current = context.getCurrentThread(); 1109 if (current == null) { 1110 env.failure("No default thread specified: " + 1111 "use the \"thread\" command first."); 1112 return; 1113 } 1114 StackFrame frame; 1115 try { 1116 frame = context.getCurrentFrame(current); 1117 if (frame == null) { 1118 env.failure("Thread has not yet created any stack frames."); 1119 return; 1120 } 1121 } catch (VMNotInterruptedException e) { 1122 env.failure("Target VM must be in interrupted state."); 1123 return; 1124 } 1125 1126 List<LocalVariable> vars; 1127 try { 1128 vars = frame.visibleVariables(); 1129 if (vars == null || vars.size() == 0) { 1130 env.failure("No local variables"); 1131 return; 1132 } 1133 } catch (AbsentInformationException e) { 1134 env.failure("Local variable information not available." + 1135 " Compile with -g to generate variable information"); 1136 return; 1137 } 1138 1139 OutputSink out = env.getOutputSink(); 1140 out.println("Method arguments:"); 1141 for (LocalVariable var : vars) { 1142 if (var.isArgument()) { 1143 printVar(out, var, frame); 1144 } 1145 } 1146 out.println("Local variables:"); 1147 for (LocalVariable var : vars) { 1148 if (!var.isArgument()) { 1149 printVar(out, var, frame); 1150 } 1151 } 1152 out.show(); 1153 return; 1154 } 1155 1156 /** 1157 * Command: monitor 1158 * Monitor an expression 1159 */ 1160 private void commandMonitor(StringTokenizer t) throws NoSessionException { 1161 if (!t.hasMoreTokens()) { 1162 env.error("Argument required"); 1163 } else { 1164 env.getMonitorListModel().add(t.nextToken("")); 1165 } 1166 } 1167 1168 /** 1169 * Command: unmonitor 1170 * Unmonitor an expression 1171 */ 1172 private void commandUnmonitor(StringTokenizer t) throws NoSessionException { 1173 if (!t.hasMoreTokens()) { 1174 env.error("Argument required"); 1175 } else { 1176 env.getMonitorListModel().remove(t.nextToken("")); 1177 } 1178 } 1179 1180 // Print a stack variable. 1181 1182 private void printVar(OutputSink out, LocalVariable var, StackFrame frame) { 1183 out.print(" " + var.name()); 1184 if (var.isVisible(frame)) { 1185 Value val = frame.getValue(var); 1186 out.println(" = " + val.toString()); 1187 } else { 1188 out.println(" is not in scope"); 1189 } 1190 } 1191 1192 // Command: print 1193 // Evaluate an expression. 1194 1195 private void commandPrint(StringTokenizer t, boolean dumpObject) throws NoSessionException { 1196 if (!t.hasMoreTokens()) { 1197 //### Probably confused if expresion contains whitespace. 1198 env.error("No expression specified."); 1199 return; 1200 } 1201 ThreadReference current = context.getCurrentThread(); 1202 if (current == null) { 1203 env.failure("No default thread specified: " + 1204 "use the \"thread\" command first."); 1205 return; 1206 } 1207 StackFrame frame; 1208 try { 1209 frame = context.getCurrentFrame(current); 1210 if (frame == null) { 1211 env.failure("Thread has not yet created any stack frames."); 1212 return; 1213 } 1214 } catch (VMNotInterruptedException e) { 1215 env.failure("Target VM must be in interrupted state."); 1216 return; 1217 } 1218 while (t.hasMoreTokens()) { 1219 String expr = t.nextToken(""); 1220 Value val = null; 1221 try { 1222 val = runtime.evaluate(frame, expr); 1223 } catch(Exception e) { 1224 env.error("Exception: " + e); 1225 //### Fix this! 1226 } 1227 if (val == null) { 1228 return; // Error message already printed 1229 } 1230 OutputSink out = env.getOutputSink(); 1231 if (dumpObject && (val instanceof ObjectReference) && 1232 !(val instanceof StringReference)) { 1233 ObjectReference obj = (ObjectReference)val; 1234 ReferenceType refType = obj.referenceType(); 1235 out.println(expr + " = " + val.toString() + " {"); 1236 dump(out, obj, refType, refType); 1237 out.println("}"); 1238 } else { 1239 out.println(expr + " = " + val.toString()); 1240 } 1241 out.show(); 1242 } 1243 } 1244 1245 private void dump(OutputSink out, 1246 ObjectReference obj, ReferenceType refType, 1247 ReferenceType refTypeBase) { 1248 for (Field field : refType.fields()) { 1249 out.print(" "); 1250 if (!refType.equals(refTypeBase)) { 1251 out.print(refType.name() + "."); 1252 } 1253 out.print(field.name() + ": "); 1254 Object o = obj.getValue(field); 1255 out.println((o == null) ? "null" : o.toString()); // Bug ID 4374471 1256 } 1257 if (refType instanceof ClassType) { 1258 ClassType sup = ((ClassType)refType).superclass(); 1259 if (sup != null) { 1260 dump(out, obj, sup, refTypeBase); 1261 } 1262 } else if (refType instanceof InterfaceType) { 1263 for (InterfaceType sup : ((InterfaceType)refType).superinterfaces()) { 1264 dump(out, obj, sup, refTypeBase); 1265 } 1266 } 1267 } 1268 1269 /* 1270 * Display help message. 1271 */ 1272 1273 private void help() { 1274 out.println("** command list **"); 1275 out.println("threads [threadgroup] -- list threads"); 1276 out.println("thread <thread id> -- set default thread"); 1277 out.println("suspend [thread id(s)] -- suspend threads (default: all)"); 1278 out.println("resume [thread id(s)] -- resume threads (default: all)"); 1279 out.println("where [thread id] | all -- dump a thread's stack"); 1280 out.println("wherei [thread id] | all -- dump a thread's stack, with pc info"); 1281 out.println("threadgroups -- list threadgroups"); 1282 out.println("threadgroup <name> -- set current threadgroup\n"); 1283 // out.println("print <expression> -- print value of expression"); 1284 out.println("dump <expression> -- print all object information\n"); 1285 // out.println("eval <expression> -- evaluate expression (same as print)"); 1286 out.println("locals -- print all local variables in current stack frame\n"); 1287 out.println("classes -- list currently known classes"); 1288 out.println("methods <class id> -- list a class's methods\n"); 1289 out.println("stop [in] <class id>.<method>[(argument_type,...)] -- set a breakpoint in a method"); 1290 out.println("stop [at] <class id>:<line> -- set a breakpoint at a line"); 1291 out.println("up [n frames] -- move up a thread's stack"); 1292 out.println("down [n frames] -- move down a thread's stack"); 1293 out.println("frame <frame-id> -- to a frame"); 1294 out.println("clear <class id>.<method>[(argument_type,...)] -- clear a breakpoint in a method"); 1295 out.println("clear <class id>:<line> -- clear a breakpoint at a line"); 1296 out.println("clear -- list breakpoints"); 1297 out.println("step -- execute current line"); 1298 out.println("step up -- execute until the current method returns to its caller"); 1299 out.println("stepi -- execute current instruction"); 1300 out.println("next -- step one line (step OVER calls)"); 1301 out.println("nexti -- step one instruction (step OVER calls)"); 1302 out.println("cont -- continue execution from breakpoint\n"); 1303 // out.println("catch <class id> -- break for the specified exception"); 1304 // out.println("ignore <class id> -- ignore when the specified exception\n"); 1305 out.println("view classname|filename -- display source file"); 1306 out.println("list [line number|method] -- print source code context at line or method"); 1307 out.println("use <source file path> -- display or change the source path\n"); 1308 //### new 1309 out.println("sourcepath <source file path> -- display or change the source path\n"); 1310 //### new 1311 out.println("classpath <class file path> -- display or change the class path\n"); 1312 out.println("monitor <expression> -- evaluate an expression each time the program stops\n"); 1313 out.println("unmonitor <monitor#> -- delete a monitor\n"); 1314 out.println("read <filename> -- read and execute a command file\n"); 1315 // out.println("memory -- report memory usage"); 1316 // out.println("gc -- free unused objects\n"); 1317 out.println("run <class> [args] -- start execution of a Java class"); 1318 out.println("run -- re-execute last class run"); 1319 out.println("load <class> [args] -- start execution of a Java class, initially suspended"); 1320 out.println("load -- re-execute last class run, initially suspended"); 1321 out.println("attach <portname> -- debug existing process\n"); 1322 out.println("detach -- detach from debuggee process\n"); 1323 out.println("kill <thread(group)> -- kill a thread or threadgroup\n"); 1324 out.println("!! -- repeat last command"); 1325 out.println("help (or ?) -- list commands"); 1326 out.println("exit (or quit) -- exit debugger"); 1327 } 1328 1329 /* 1330 * Execute a command. 1331 */ 1332 1333 public void executeCommand(String command) { 1334 //### Treatment of 'out' here is dirty... 1335 out = env.getOutputSink(); 1336 if (echo) { 1337 out.println(">>> " + command); 1338 } 1339 StringTokenizer t = new StringTokenizer(command); 1340 try { 1341 String cmd; 1342 if (t.hasMoreTokens()) { 1343 cmd = t.nextToken().toLowerCase(); 1344 lastCommand = cmd; 1345 } else { 1346 cmd = lastCommand; 1347 } 1348 if (cmd.equals("print")) { 1349 commandPrint(t, false); 1350 } else if (cmd.equals("eval")) { 1351 commandPrint(t, false); 1352 } else if (cmd.equals("dump")) { 1353 commandPrint(t, true); 1354 } else if (cmd.equals("locals")) { 1355 commandLocals(); 1356 } else if (cmd.equals("classes")) { 1357 commandClasses(); 1358 } else if (cmd.equals("methods")) { 1359 commandMethods(t); 1360 } else if (cmd.equals("threads")) { 1361 commandThreads(t); 1362 } else if (cmd.equals("thread")) { 1363 commandThread(t); 1364 } else if (cmd.equals("suspend")) { 1365 commandSuspend(t); 1366 } else if (cmd.equals("resume")) { 1367 commandResume(t); 1368 } else if (cmd.equals("cont")) { 1369 commandCont(); 1370 } else if (cmd.equals("threadgroups")) { 1371 commandThreadGroups(); 1372 } else if (cmd.equals("threadgroup")) { 1373 commandThreadGroup(t); 1374 } else if (cmd.equals("run")) { 1375 commandRun(t); 1376 } else if (cmd.equals("load")) { 1377 commandLoad(t); 1378 } else if (cmd.equals("connect")) { 1379 commandConnect(t); 1380 } else if (cmd.equals("attach")) { 1381 commandAttach(t); 1382 } else if (cmd.equals("detach")) { 1383 commandDetach(t); 1384 } else if (cmd.equals("interrupt")) { 1385 commandInterrupt(t); 1386 //### Not implemented. 1387 // } else if (cmd.equals("catch")) { 1388 // commandCatchException(t); 1389 //### Not implemented. 1390 // } else if (cmd.equals("ignore")) { 1391 // commandIgnoreException(t); 1392 } else if (cmd.equals("step")) { 1393 commandStep(t); 1394 } else if (cmd.equals("stepi")) { 1395 commandStepi(); 1396 } else if (cmd.equals("next")) { 1397 commandNext(); 1398 } else if (cmd.equals("nexti")) { 1399 commandNexti(); 1400 } else if (cmd.equals("kill")) { 1401 commandKill(t); 1402 } else if (cmd.equals("where")) { 1403 commandWhere(t, false); 1404 } else if (cmd.equals("wherei")) { 1405 commandWhere(t, true); 1406 } else if (cmd.equals("up")) { 1407 commandUp(t); 1408 } else if (cmd.equals("down")) { 1409 commandDown(t); 1410 } else if (cmd.equals("frame")) { 1411 commandFrame(t); 1412 } else if (cmd.equals("stop")) { 1413 commandStop(t); 1414 } else if (cmd.equals("clear")) { 1415 commandClear(t); 1416 } else if (cmd.equals("list")) { 1417 commandList(t); 1418 } else if (cmd.equals("use")) { 1419 commandUse(t); 1420 } else if (cmd.equals("sourcepath")) { 1421 commandSourcepath(t); 1422 } else if (cmd.equals("classpath")) { 1423 commandClasspath(t); 1424 } else if (cmd.equals("monitor")) { 1425 commandMonitor(t); 1426 } else if (cmd.equals("unmonitor")) { 1427 commandUnmonitor(t); 1428 } else if (cmd.equals("view")) { 1429 commandView(t); 1430 // } else if (cmd.equals("read")) { 1431 // readCommand(t); 1432 } else if (cmd.equals("help") || cmd.equals("?")) { 1433 help(); 1434 } else if (cmd.equals("quit") || cmd.equals("exit")) { 1435 try { 1436 runtime.detach(); 1437 } catch (NoSessionException e) { 1438 // ignore 1439 } 1440 env.terminate(); 1441 } else { 1442 //### Dubious repeat-count feature inherited from 'jdb' 1443 if (t.hasMoreTokens()) { 1444 try { 1445 int repeat = Integer.parseInt(cmd); 1446 String subcom = t.nextToken(""); 1447 while (repeat-- > 0) { 1448 executeCommand(subcom); 1449 } 1450 return; 1451 } catch (NumberFormatException exc) { 1452 } 1453 } 1454 out.println("huh? Try help..."); 1455 out.flush(); 1456 } 1457 } catch (NoSessionException e) { 1458 out.println("There is no currently attached VM session."); 1459 out.flush(); 1460 } catch (Exception e) { 1461 out.println("Internal exception: " + e.toString()); 1462 out.flush(); 1463 System.out.println("JDB internal exception: " + e.toString()); 1464 e.printStackTrace(); 1465 } 1466 out.show(); 1467 } 1468 }