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