/* * Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * This source code is provided to illustrate the usage of a given feature * or technique and has been deliberately simplified. Additional steps * required for a production-quality application, such as security checks, * input validation and proper error handling, might not be present in * this sample code. */ package com.sun.tools.example.debug.gui; import java.io.*; import java.util.*; import com.sun.jdi.*; import com.sun.tools.example.debug.bdi.*; public class CommandInterpreter { boolean echo; Environment env; private ContextManager context; private ExecutionManager runtime; private ClassManager classManager; private SourceManager sourceManager; private OutputSink out; //### Hack! Should be local in each method used. private String lastCommand = "help"; public CommandInterpreter(Environment env) { this(env, true); } public CommandInterpreter(Environment env, boolean echo) { this.env = env; this.echo = echo; this.runtime = env.getExecutionManager(); this.context = env.getContextManager(); this.classManager = env.getClassManager(); this.sourceManager = env.getSourceManager(); } private ThreadReference[] threads = null; /* * The numbering of threads is relative to the current set of threads, * and may be affected by the creation and termination of new threads. * Commands issued using such thread ids will only give reliable behavior * relative to what was shown earlier in 'list' commands if the VM is interrupted. * We need a better scheme. */ private ThreadReference[] threads() throws NoSessionException { if (threads == null) { ThreadIterator ti = new ThreadIterator(getDefaultThreadGroup()); List<ThreadReference> tlist = new ArrayList<ThreadReference>(); while (ti.hasNext()) { tlist.add(ti.nextThread()); } threads = tlist.toArray(new ThreadReference[tlist.size()]); } return threads; } private ThreadReference findThread(String idToken) throws NoSessionException { String id; ThreadReference thread = null; if (idToken.startsWith("t@")) { id = idToken.substring(2); } else { id = idToken; } try { ThreadReference[] threads = threads(); long threadID = Long.parseLong(id, 16); for (ThreadReference thread2 : threads) { if (thread2.uniqueID() == threadID) { thread = thread2; break; } } if (thread == null) { //env.failure("No thread for id \"" + idToken + "\""); env.failure("\"" + idToken + "\" is not a valid thread id."); } } catch (NumberFormatException e) { env.error("Thread id \"" + idToken + "\" is ill-formed."); thread = null; } return thread; } private ThreadIterator allThreads() throws NoSessionException { threads = null; //### Why not use runtime.allThreads().iterator() ? return new ThreadIterator(runtime.topLevelThreadGroups()); } private ThreadIterator currentThreadGroupThreads() throws NoSessionException { threads = null; return new ThreadIterator(getDefaultThreadGroup()); } private ThreadGroupIterator allThreadGroups() throws NoSessionException { threads = null; return new ThreadGroupIterator(runtime.topLevelThreadGroups()); } private ThreadGroupReference defaultThreadGroup; private ThreadGroupReference getDefaultThreadGroup() throws NoSessionException { if (defaultThreadGroup == null) { defaultThreadGroup = runtime.systemThreadGroup(); } return defaultThreadGroup; } private void setDefaultThreadGroup(ThreadGroupReference tg) { defaultThreadGroup = tg; } /* * Command handlers. */ // Command: classes private void commandClasses() throws NoSessionException { OutputSink out = env.getOutputSink(); //out.println("** classes list **"); for (ReferenceType refType : runtime.allClasses()) { out.println(refType.name()); } out.show(); } // Command: methods private void commandMethods(StringTokenizer t) throws NoSessionException { if (!t.hasMoreTokens()) { env.error("No class specified."); return; } String idClass = t.nextToken(); ReferenceType cls = findClass(idClass); if (cls != null) { List<Method> methods = cls.allMethods(); OutputSink out = env.getOutputSink(); for (int i = 0; i < methods.size(); i++) { Method method = methods.get(i); out.print(method.declaringType().name() + " " + method.name() + "("); Iterator<String> it = method.argumentTypeNames().iterator(); if (it.hasNext()) { while (true) { out.print(it.next()); if (!it.hasNext()) { break; } out.print(", "); } } out.println(")"); } out.show(); } else { //### Should validate class name syntax. env.failure("\"" + idClass + "\" is not a valid id or class name."); } } private ReferenceType findClass(String pattern) throws NoSessionException { List<ReferenceType> results = runtime.findClassesMatchingPattern(pattern); if (results.size() > 0) { //### Should handle multiple results sensibly. return results.get(0); } return null; } // Command: threads private void commandThreads(StringTokenizer t) throws NoSessionException { if (!t.hasMoreTokens()) { OutputSink out = env.getOutputSink(); printThreadGroup(out, getDefaultThreadGroup(), 0); out.show(); return; } String name = t.nextToken(); ThreadGroupReference tg = findThreadGroup(name); if (tg == null) { env.failure(name + " is not a valid threadgroup name."); } else { OutputSink out = env.getOutputSink(); printThreadGroup(out, tg, 0); out.show(); } } private ThreadGroupReference findThreadGroup(String name) throws NoSessionException { //### Issue: Uniqueness of thread group names is not enforced. ThreadGroupIterator tgi = allThreadGroups(); while (tgi.hasNext()) { ThreadGroupReference tg = tgi.nextThreadGroup(); if (tg.name().equals(name)) { return tg; } } return null; } private int printThreadGroup(OutputSink out, ThreadGroupReference tg, int iThread) { out.println("Group " + tg.name() + ":"); List<ThreadReference> tlist = tg.threads(); int maxId = 0; int maxName = 0; for (int i = 0 ; i < tlist.size() ; i++) { ThreadReference thr = tlist.get(i); int len = Utils.description(thr).length(); if (len > maxId) { maxId = len; } String name = thr.name(); int iDot = name.lastIndexOf('.'); if (iDot >= 0 && name.length() > iDot) { name = name.substring(iDot + 1); } if (name.length() > maxName) { maxName = name.length(); } } String maxNumString = String.valueOf(iThread + tlist.size()); int maxNumDigits = maxNumString.length(); for (int i = 0 ; i < tlist.size() ; i++) { ThreadReference thr = tlist.get(i); char buf[] = new char[80]; for (int j = 0; j < 79; j++) { buf[j] = ' '; } buf[79] = '\0'; StringBuffer sbOut = new StringBuffer(); sbOut.append(buf); // Right-justify the thread number at start of output string String numString = String.valueOf(iThread + i + 1); sbOut.insert(maxNumDigits - numString.length(), numString); sbOut.insert(maxNumDigits, "."); int iBuf = maxNumDigits + 2; sbOut.insert(iBuf, Utils.description(thr)); iBuf += maxId + 1; String name = thr.name(); int iDot = name.lastIndexOf('.'); if (iDot >= 0 && name.length() > iDot) { name = name.substring(iDot + 1); } sbOut.insert(iBuf, name); iBuf += maxName + 1; sbOut.insert(iBuf, Utils.getStatus(thr)); sbOut.setLength(79); out.println(sbOut.toString()); } for (ThreadGroupReference tg0 : tg.threadGroups()) { if (!tg.equals(tg0)) { // TODO ref mgt iThread += printThreadGroup(out, tg0, iThread + tlist.size()); } } return tlist.size(); } // Command: threadgroups private void commandThreadGroups() throws NoSessionException { ThreadGroupIterator it = allThreadGroups(); int cnt = 0; OutputSink out = env.getOutputSink(); while (it.hasNext()) { ThreadGroupReference tg = it.nextThreadGroup(); ++cnt; out.println("" + cnt + ". " + Utils.description(tg) + " " + tg.name()); } out.show(); } // Command: thread private void commandThread(StringTokenizer t) throws NoSessionException { if (!t.hasMoreTokens()) { env.error("Thread number not specified."); return; } ThreadReference thread = findThread(t.nextToken()); if (thread != null) { //### Should notify user. context.setCurrentThread(thread); } } // Command: threadgroup private void commandThreadGroup(StringTokenizer t) throws NoSessionException { if (!t.hasMoreTokens()) { env.error("Threadgroup name not specified."); return; } String name = t.nextToken(); ThreadGroupReference tg = findThreadGroup(name); if (tg == null) { env.failure(name + " is not a valid threadgroup name."); } else { //### Should notify user. setDefaultThreadGroup(tg); } } // Command: run private void commandRun(StringTokenizer t) throws NoSessionException { if (doLoad(false, t)) { env.notice("Running ..."); } } // Command: load private void commandLoad(StringTokenizer t) throws NoSessionException { if (doLoad(true, t)) {} } private boolean doLoad(boolean suspended, StringTokenizer t) throws NoSessionException { String clname; if (!t.hasMoreTokens()) { clname = context.getMainClassName(); if (!clname.equals("")) { // Run from prevously-set class name. try { String vmArgs = context.getVmArguments(); runtime.run(suspended, vmArgs, clname, context.getProgramArguments()); return true; } catch (VMLaunchFailureException e) { env.failure("Attempt to launch main class \"" + clname + "\" failed."); } } else { env.failure("No main class specifed and no current default defined."); } } else { clname = t.nextToken(); StringBuffer sbuf = new StringBuffer(); // Allow VM arguments to be specified here? while (t.hasMoreTokens()) { String tok = t.nextToken(); sbuf.append(tok); if (t.hasMoreTokens()) { sbuf.append(' '); } } String args = sbuf.toString(); try { String vmArgs = context.getVmArguments(); runtime.run(suspended, vmArgs, clname, args); context.setMainClassName(clname); //context.setVmArguments(vmArgs); context.setProgramArguments(args); return true; } catch (VMLaunchFailureException e) { env.failure("Attempt to launch main class \"" + clname + "\" failed."); } } return false; } // Command: connect private void commandConnect(StringTokenizer t) { try { LaunchTool.queryAndLaunchVM(runtime); } catch (VMLaunchFailureException e) { env.failure("Attempt to connect failed."); } } // Command: attach private void commandAttach(StringTokenizer t) { String portName; if (!t.hasMoreTokens()) { portName = context.getRemotePort(); if (!portName.equals("")) { try { runtime.attach(portName); } catch (VMLaunchFailureException e) { env.failure("Attempt to attach to port \"" + portName + "\" failed."); } } else { env.failure("No port specifed and no current default defined."); } } else { portName = t.nextToken(); try { runtime.attach(portName); } catch (VMLaunchFailureException e) { env.failure("Attempt to attach to port \"" + portName + "\" failed."); } context.setRemotePort(portName); } } // Command: detach private void commandDetach(StringTokenizer t) throws NoSessionException { runtime.detach(); } // Command: interrupt private void commandInterrupt(StringTokenizer t) throws NoSessionException { runtime.interrupt(); } // Command: suspend private void commandSuspend(StringTokenizer t) throws NoSessionException { if (!t.hasMoreTokens()) { // Suspend all threads in the current thread group. //### Issue: help message says default is all threads. //### Behavior here agrees with 'jdb', however. ThreadIterator ti = currentThreadGroupThreads(); while (ti.hasNext()) { // TODO - don't suspend debugger threads ti.nextThread().suspend(); } env.notice("All (non-system) threads suspended."); } else { while (t.hasMoreTokens()) { ThreadReference thread = findThread(t.nextToken()); if (thread != null) { //thread.suspend(); runtime.suspendThread(thread); } } } } // Command: resume private void commandResume(StringTokenizer t) throws NoSessionException { if (!t.hasMoreTokens()) { // Suspend all threads in the current thread group. //### Issue: help message says default is all threads. //### Behavior here agrees with 'jdb', however. ThreadIterator ti = currentThreadGroupThreads(); while (ti.hasNext()) { // TODO - don't suspend debugger threads ti.nextThread().resume(); } env.notice("All threads resumed."); } else { while (t.hasMoreTokens()) { ThreadReference thread = findThread(t.nextToken()); if (thread != null) { //thread.resume(); runtime.resumeThread(thread); } } } } // Command: cont private void commandCont() throws NoSessionException { try { runtime.go(); } catch (VMNotInterruptedException e) { //### failure? env.notice("Target VM is already running."); } } // Command: step private void commandStep(StringTokenizer t) throws NoSessionException{ ThreadReference current = context.getCurrentThread(); if (current == null) { env.failure("No current thread."); return; } try { if (t.hasMoreTokens() && t.nextToken().toLowerCase().equals("up")) { runtime.stepOut(current); } else { runtime.stepIntoLine(current); } } catch (AbsentInformationException e) { env.failure("No linenumber information available -- " + "Try \"stepi\" to step by instructions."); } } // Command: stepi private void commandStepi() throws NoSessionException { ThreadReference current = context.getCurrentThread(); if (current == null) { env.failure("No current thread."); return; } runtime.stepIntoInstruction(current); } // Command: next private void commandNext() throws NoSessionException { ThreadReference current = context.getCurrentThread(); if (current == null) { env.failure("No current thread."); return; } try { runtime.stepOverLine(current); } catch (AbsentInformationException e) { env.failure("No linenumber information available -- " + "Try \"nexti\" to step by instructions."); } } // Command: nexti (NEW) private void commandNexti() throws NoSessionException { ThreadReference current = context.getCurrentThread(); if (current == null) { env.failure("No current thread."); return; } runtime.stepOverInstruction(current); } // Command: kill private void commandKill(StringTokenizer t) throws NoSessionException { //### Should change the way in which thread ids and threadgroup names //### are distinguished. if (!t.hasMoreTokens()) { env.error("Usage: kill <threadgroup name> or <thread id>"); return; } while (t.hasMoreTokens()) { String idToken = t.nextToken(); ThreadReference thread = findThread(idToken); if (thread != null) { runtime.stopThread(thread); env.notice("Thread " + thread.name() + " killed."); return; } else { /* Check for threadgroup name, NOT skipping "system". */ //### Should skip "system"? Classic 'jdb' does this. //### Should deal with possible non-uniqueness of threadgroup names. ThreadGroupIterator itg = allThreadGroups(); while (itg.hasNext()) { ThreadGroupReference tg = itg.nextThreadGroup(); if (tg.name().equals(idToken)) { ThreadIterator it = new ThreadIterator(tg); while (it.hasNext()) { runtime.stopThread(it.nextThread()); } env.notice("Threadgroup " + tg.name() + "killed."); return; } } env.failure("\"" + idToken + "\" is not a valid threadgroup or id."); } } } /************* // TODO private void commandCatchException(StringTokenizer t) throws NoSessionException {} // TODO private void commandIgnoreException(StringTokenizer t) throws NoSessionException {} *************/ // Command: up //### Print current frame after command? int readCount(StringTokenizer t) { int cnt = 1; if (t.hasMoreTokens()) { String idToken = t.nextToken(); try { cnt = Integer.valueOf(idToken).intValue(); } catch (NumberFormatException e) { cnt = -1; } } return cnt; } void commandUp(StringTokenizer t) throws NoSessionException { ThreadReference current = context.getCurrentThread(); if (current == null) { env.failure("No current thread."); return; } int nLevels = readCount(t); if (nLevels <= 0) { env.error("usage: up [n frames]"); return; } try { int delta = context.moveCurrentFrameIndex(current, -nLevels); if (delta == 0) { env.notice("Already at top of stack."); } else if (-delta < nLevels) { env.notice("Moved up " + delta + " frames to top of stack."); } } catch (VMNotInterruptedException e) { env.failure("Target VM must be in interrupted state."); } } private void commandDown(StringTokenizer t) throws NoSessionException { ThreadReference current = context.getCurrentThread(); if (current == null) { env.failure("No current thread."); return; } int nLevels = readCount(t); if (nLevels <= 0) { env.error("usage: down [n frames]"); return; } try { int delta = context.moveCurrentFrameIndex(current, nLevels); if (delta == 0) { env.notice("Already at bottom of stack."); } else if (delta < nLevels) { env.notice("Moved down " + delta + " frames to bottom of stack."); } } catch (VMNotInterruptedException e) { env.failure("Target VM must be in interrupted state."); } } // Command: frame private void commandFrame(StringTokenizer t) throws NoSessionException { ThreadReference current = context.getCurrentThread(); if (current == null) { env.failure("No current thread."); return; } if (!t.hasMoreTokens()) { env.error("usage: frame <frame-index>"); return; } String idToken = t.nextToken(); int n; try { n = Integer.valueOf(idToken).intValue(); } catch (NumberFormatException e) { n = 0; } if (n <= 0) { env.error("use positive frame index"); return; } try { int delta = context.setCurrentFrameIndex(current, n); if (delta == 0) { env.notice("Frame unchanged."); } else if (delta < 0) { env.notice("Moved up " + -delta + " frames."); } else { env.notice("Moved down " + delta + " frames."); } } catch (VMNotInterruptedException e) { env.failure("Target VM must be in interrupted state."); } } // Command: where //### Should we insist that VM be interrupted here? //### There is an inconsistency between the 'where' command //### and 'up' and 'down' in this respect. private void commandWhere(StringTokenizer t, boolean showPC) throws NoSessionException { ThreadReference current = context.getCurrentThread(); if (!t.hasMoreTokens()) { if (current == null) { env.error("No thread specified."); return; } dumpStack(current, showPC); } else { String token = t.nextToken(); if (token.toLowerCase().equals("all")) { ThreadIterator it = allThreads(); while (it.hasNext()) { ThreadReference thread = it.next(); out.println(thread.name() + ": "); dumpStack(thread, showPC); } } else { ThreadReference thread = findThread(t.nextToken()); //### Do we want to set current thread here? //### Should notify user of change. if (thread != null) { context.setCurrentThread(thread); } dumpStack(thread, showPC); } } } private void dumpStack(ThreadReference thread, boolean showPC) { //### Check for these. //env.failure("Thread no longer exists."); //env.failure("Target VM must be in interrupted state."); //env.failure("Current thread isn't suspended."); //### Should handle extremely long stack traces sensibly for user. List<StackFrame> stack = null; try { stack = thread.frames(); } catch (IncompatibleThreadStateException e) { env.failure("Thread is not suspended."); } //### Fix this! //### Previously mishandled cases where thread was not current. //### Now, prints all of the stack regardless of current frame. int frameIndex = 0; //int frameIndex = context.getCurrentFrameIndex(); if (stack == null) { env.failure("Thread is not running (no stack)."); } else { OutputSink out = env.getOutputSink(); int nFrames = stack.size(); for (int i = frameIndex; i < nFrames; i++) { StackFrame frame = stack.get(i); Location loc = frame.location(); Method meth = loc.method(); out.print(" [" + (i + 1) + "] "); out.print(meth.declaringType().name()); out.print('.'); out.print(meth.name()); out.print(" ("); if (meth.isNative()) { out.print("native method"); } else if (loc.lineNumber() != -1) { try { out.print(loc.sourceName()); } catch (AbsentInformationException e) { out.print("<unknown>"); } out.print(':'); out.print(loc.lineNumber()); } out.print(')'); if (showPC) { long pc = loc.codeIndex(); if (pc != -1) { out.print(", pc = " + pc); } } out.println(); } out.show(); } } private void listEventRequests() throws NoSessionException { // Print set breakpoints List<EventRequestSpec> specs = runtime.eventRequestSpecs(); if (specs.isEmpty()) { env.notice("No breakpoints/watchpoints/exceptions set."); } else { OutputSink out = env.getOutputSink(); out.println("Current breakpoints/watchpoints/exceptions set:"); for (EventRequestSpec bp : specs) { out.println("\t" + bp); } out.show(); } } private BreakpointSpec parseBreakpointSpec(String bptSpec) { StringTokenizer t = new StringTokenizer(bptSpec); BreakpointSpec bpSpec = null; // try { String token = t.nextToken("@:( \t\n\r"); // We can't use hasMoreTokens here because it will cause any leading // paren to be lost. String rest; try { rest = t.nextToken("").trim(); } catch (NoSuchElementException e) { rest = null; } if ((rest != null) && rest.startsWith("@")) { t = new StringTokenizer(rest.substring(1)); String sourceName = token; String lineToken = t.nextToken(); int lineNumber = Integer.valueOf(lineToken).intValue(); if (t.hasMoreTokens()) { return null; } bpSpec = runtime.createSourceLineBreakpoint(sourceName, lineNumber); } else if ((rest != null) && rest.startsWith(":")) { t = new StringTokenizer(rest.substring(1)); String classId = token; String lineToken = t.nextToken(); int lineNumber = Integer.valueOf(lineToken).intValue(); if (t.hasMoreTokens()) { return null; } bpSpec = runtime.createClassLineBreakpoint(classId, lineNumber); } else { // Try stripping method from class.method token. int idot = token.lastIndexOf("."); if ( (idot <= 0) || /* No dot or dot in first char */ (idot >= token.length() - 1) ) { /* dot in last char */ return null; } String methodName = token.substring(idot + 1); String classId = token.substring(0, idot); List<String> argumentList = null; if (rest != null) { if (!rest.startsWith("(") || !rest.endsWith(")")) { //### Should throw exception with error message //out.println("Invalid method specification: " // + methodName + rest); return null; } // Trim the parens //### What about spaces in arglist? rest = rest.substring(1, rest.length() - 1); argumentList = new ArrayList<String>(); t = new StringTokenizer(rest, ","); while (t.hasMoreTokens()) { argumentList.add(t.nextToken()); } } bpSpec = runtime.createMethodBreakpoint(classId, methodName, argumentList); } // } catch (Exception e) { // env.error("Exception attempting to create breakpoint: " + e); // return null; // } return bpSpec; } private void commandStop(StringTokenizer t) throws NoSessionException { String token; if (!t.hasMoreTokens()) { listEventRequests(); } else { token = t.nextToken(); // Ignore optional "at" or "in" token. // Allowed for backward compatibility. if (token.equals("at") || token.equals("in")) { if (t.hasMoreTokens()) { token = t.nextToken(); } else { env.error("Missing breakpoint specification."); return; } } BreakpointSpec bpSpec = parseBreakpointSpec(token); if (bpSpec != null) { //### Add sanity-checks for deferred breakpoint. runtime.install(bpSpec); } else { env.error("Ill-formed breakpoint specification."); } } } private void commandClear(StringTokenizer t) throws NoSessionException { if (!t.hasMoreTokens()) { // Print set breakpoints listEventRequests(); return; } //### need 'clear all' BreakpointSpec bpSpec = parseBreakpointSpec(t.nextToken()); if (bpSpec != null) { List<EventRequestSpec> specs = runtime.eventRequestSpecs(); if (specs.isEmpty()) { env.notice("No breakpoints set."); } else { List<EventRequestSpec> toDelete = new ArrayList<EventRequestSpec>(); for (EventRequestSpec spec : specs) { if (spec.equals(bpSpec)) { toDelete.add(spec); } } // The request used for matching should be found if (toDelete.size() <= 1) { env.notice("No matching breakpoint set."); } for (EventRequestSpec spec : toDelete) { runtime.delete(spec); } } } else { env.error("Ill-formed breakpoint specification."); } } // Command: list private void commandList(StringTokenizer t) throws NoSessionException { ThreadReference current = context.getCurrentThread(); if (current == null) { env.error("No thread specified."); return; } Location loc; try { StackFrame frame = context.getCurrentFrame(current); if (frame == null) { env.failure("Thread has not yet begun execution."); return; } loc = frame.location(); } catch (VMNotInterruptedException e) { env.failure("Target VM must be in interrupted state."); return; } SourceModel source = sourceManager.sourceForLocation(loc); if (source == null) { if (loc.method().isNative()) { env.failure("Current method is native."); return; } env.failure("No source available for " + Utils.locationString(loc) + "."); return; } ReferenceType refType = loc.declaringType(); int lineno = loc.lineNumber(); if (t.hasMoreTokens()) { String id = t.nextToken(); // See if token is a line number. try { lineno = Integer.valueOf(id).intValue(); } catch (NumberFormatException nfe) { // It isn't -- see if it's a method name. List<Method> meths = refType.methodsByName(id); if (meths == null || meths.size() == 0) { env.failure(id + " is not a valid line number or " + "method name for class " + refType.name()); return; } else if (meths.size() > 1) { env.failure(id + " is an ambiguous method name in" + refType.name()); return; } loc = meths.get(0).location(); lineno = loc.lineNumber(); } } int startLine = (lineno > 4) ? lineno - 4 : 1; int endLine = startLine + 9; String sourceLine = source.sourceLine(lineno); if (sourceLine == null) { env.failure("" + lineno + " is an invalid line number for " + refType.name()); } else { OutputSink out = env.getOutputSink(); for (int i = startLine; i <= endLine; i++) { sourceLine = source.sourceLine(i); if (sourceLine == null) { break; } out.print(i); out.print("\t"); if (i == lineno) { out.print("=> "); } else { out.print(" "); } out.println(sourceLine); } out.show(); } } // Command: use // Get or set the source file path list. private void commandUse(StringTokenizer t) { if (!t.hasMoreTokens()) { out.println(sourceManager.getSourcePath().asString()); } else { //### Should throw exception for invalid path. //### E.g., vetoable property change. sourceManager.setSourcePath(new SearchPath(t.nextToken())); } } // Command: sourcepath // Get or set the source file path list. (Alternate to 'use'.) private void commandSourcepath(StringTokenizer t) { if (!t.hasMoreTokens()) { out.println(sourceManager.getSourcePath().asString()); } else { //### Should throw exception for invalid path. //### E.g., vetoable property change. sourceManager.setSourcePath(new SearchPath(t.nextToken())); } } // Command: classpath // Get or set the class file path list. private void commandClasspath(StringTokenizer t) { if (!t.hasMoreTokens()) { out.println(classManager.getClassPath().asString()); } else { //### Should throw exception for invalid path. //### E.g., vetoable property change. classManager.setClassPath(new SearchPath(t.nextToken())); } } // Command: view // Display source for source file or class. private void commandView(StringTokenizer t) throws NoSessionException { if (!t.hasMoreTokens()) { env.error("Argument required"); } else { String name = t.nextToken(); if (name.endsWith(".java") || name.indexOf(File.separatorChar) >= 0) { env.viewSource(name); } else { //### JDI crashes taking line number for class. /***** ReferenceType cls = findClass(name); if (cls != null) { env.viewLocation(cls.location()); } else { env.failure("No such class"); } *****/ String fileName = name.replace('.', File.separatorChar) + ".java"; env.viewSource(fileName); } } } // Command: locals // Print all local variables in current stack frame. private void commandLocals() throws NoSessionException { ThreadReference current = context.getCurrentThread(); if (current == null) { env.failure("No default thread specified: " + "use the \"thread\" command first."); return; } StackFrame frame; try { frame = context.getCurrentFrame(current); if (frame == null) { env.failure("Thread has not yet created any stack frames."); return; } } catch (VMNotInterruptedException e) { env.failure("Target VM must be in interrupted state."); return; } List<LocalVariable> vars; try { vars = frame.visibleVariables(); if (vars == null || vars.size() == 0) { env.failure("No local variables"); return; } } catch (AbsentInformationException e) { env.failure("Local variable information not available." + " Compile with -g to generate variable information"); return; } OutputSink out = env.getOutputSink(); out.println("Method arguments:"); for (LocalVariable var : vars) { if (var.isArgument()) { printVar(out, var, frame); } } out.println("Local variables:"); for (LocalVariable var : vars) { if (!var.isArgument()) { printVar(out, var, frame); } } out.show(); return; } /** * Command: monitor * Monitor an expression */ private void commandMonitor(StringTokenizer t) throws NoSessionException { if (!t.hasMoreTokens()) { env.error("Argument required"); } else { env.getMonitorListModel().add(t.nextToken("")); } } /** * Command: unmonitor * Unmonitor an expression */ private void commandUnmonitor(StringTokenizer t) throws NoSessionException { if (!t.hasMoreTokens()) { env.error("Argument required"); } else { env.getMonitorListModel().remove(t.nextToken("")); } } // Print a stack variable. private void printVar(OutputSink out, LocalVariable var, StackFrame frame) { out.print(" " + var.name()); if (var.isVisible(frame)) { Value val = frame.getValue(var); out.println(" = " + val.toString()); } else { out.println(" is not in scope"); } } // Command: print // Evaluate an expression. private void commandPrint(StringTokenizer t, boolean dumpObject) throws NoSessionException { if (!t.hasMoreTokens()) { //### Probably confused if expresion contains whitespace. env.error("No expression specified."); return; } ThreadReference current = context.getCurrentThread(); if (current == null) { env.failure("No default thread specified: " + "use the \"thread\" command first."); return; } StackFrame frame; try { frame = context.getCurrentFrame(current); if (frame == null) { env.failure("Thread has not yet created any stack frames."); return; } } catch (VMNotInterruptedException e) { env.failure("Target VM must be in interrupted state."); return; } while (t.hasMoreTokens()) { String expr = t.nextToken(""); Value val = null; try { val = runtime.evaluate(frame, expr); } catch(Exception e) { env.error("Exception: " + e); //### Fix this! } if (val == null) { return; // Error message already printed } OutputSink out = env.getOutputSink(); if (dumpObject && (val instanceof ObjectReference) && !(val instanceof StringReference)) { ObjectReference obj = (ObjectReference)val; ReferenceType refType = obj.referenceType(); out.println(expr + " = " + val.toString() + " {"); dump(out, obj, refType, refType); out.println("}"); } else { out.println(expr + " = " + val.toString()); } out.show(); } } private void dump(OutputSink out, ObjectReference obj, ReferenceType refType, ReferenceType refTypeBase) { for (Field field : refType.fields()) { out.print(" "); if (!refType.equals(refTypeBase)) { out.print(refType.name() + "."); } out.print(field.name() + ": "); Object o = obj.getValue(field); out.println((o == null) ? "null" : o.toString()); // Bug ID 4374471 } if (refType instanceof ClassType) { ClassType sup = ((ClassType)refType).superclass(); if (sup != null) { dump(out, obj, sup, refTypeBase); } } else if (refType instanceof InterfaceType) { for (InterfaceType sup : ((InterfaceType)refType).superinterfaces()) { dump(out, obj, sup, refTypeBase); } } } /* * Display help message. */ private void help() { out.println("** command list **"); out.println("threads [threadgroup] -- list threads"); out.println("thread <thread id> -- set default thread"); out.println("suspend [thread id(s)] -- suspend threads (default: all)"); out.println("resume [thread id(s)] -- resume threads (default: all)"); out.println("where [thread id] | all -- dump a thread's stack"); out.println("wherei [thread id] | all -- dump a thread's stack, with pc info"); out.println("threadgroups -- list threadgroups"); out.println("threadgroup <name> -- set current threadgroup\n"); // out.println("print <expression> -- print value of expression"); out.println("dump <expression> -- print all object information\n"); // out.println("eval <expression> -- evaluate expression (same as print)"); out.println("locals -- print all local variables in current stack frame\n"); out.println("classes -- list currently known classes"); out.println("methods <class id> -- list a class's methods\n"); out.println("stop [in] <class id>.<method>[(argument_type,...)] -- set a breakpoint in a method"); out.println("stop [at] <class id>:<line> -- set a breakpoint at a line"); out.println("up [n frames] -- move up a thread's stack"); out.println("down [n frames] -- move down a thread's stack"); out.println("frame <frame-id> -- to a frame"); out.println("clear <class id>.<method>[(argument_type,...)] -- clear a breakpoint in a method"); out.println("clear <class id>:<line> -- clear a breakpoint at a line"); out.println("clear -- list breakpoints"); out.println("step -- execute current line"); out.println("step up -- execute until the current method returns to its caller"); out.println("stepi -- execute current instruction"); out.println("next -- step one line (step OVER calls)"); out.println("nexti -- step one instruction (step OVER calls)"); out.println("cont -- continue execution from breakpoint\n"); // out.println("catch <class id> -- break for the specified exception"); // out.println("ignore <class id> -- ignore when the specified exception\n"); out.println("view classname|filename -- display source file"); out.println("list [line number|method] -- print source code context at line or method"); out.println("use <source file path> -- display or change the source path\n"); //### new out.println("sourcepath <source file path> -- display or change the source path\n"); //### new out.println("classpath <class file path> -- display or change the class path\n"); out.println("monitor <expression> -- evaluate an expression each time the program stops\n"); out.println("unmonitor <monitor#> -- delete a monitor\n"); out.println("read <filename> -- read and execute a command file\n"); // out.println("memory -- report memory usage"); // out.println("gc -- free unused objects\n"); out.println("run <class> [args] -- start execution of a Java class"); out.println("run -- re-execute last class run"); out.println("load <class> [args] -- start execution of a Java class, initially suspended"); out.println("load -- re-execute last class run, initially suspended"); out.println("attach <portname> -- debug existing process\n"); out.println("detach -- detach from debuggee process\n"); out.println("kill <thread(group)> -- kill a thread or threadgroup\n"); out.println("!! -- repeat last command"); out.println("help (or ?) -- list commands"); out.println("exit (or quit) -- exit debugger"); } /* * Execute a command. */ public void executeCommand(String command) { //### Treatment of 'out' here is dirty... out = env.getOutputSink(); if (echo) { out.println(">>> " + command); } StringTokenizer t = new StringTokenizer(command); try { String cmd; if (t.hasMoreTokens()) { cmd = t.nextToken().toLowerCase(); lastCommand = cmd; } else { cmd = lastCommand; } if (cmd.equals("print")) { commandPrint(t, false); } else if (cmd.equals("eval")) { commandPrint(t, false); } else if (cmd.equals("dump")) { commandPrint(t, true); } else if (cmd.equals("locals")) { commandLocals(); } else if (cmd.equals("classes")) { commandClasses(); } else if (cmd.equals("methods")) { commandMethods(t); } else if (cmd.equals("threads")) { commandThreads(t); } else if (cmd.equals("thread")) { commandThread(t); } else if (cmd.equals("suspend")) { commandSuspend(t); } else if (cmd.equals("resume")) { commandResume(t); } else if (cmd.equals("cont")) { commandCont(); } else if (cmd.equals("threadgroups")) { commandThreadGroups(); } else if (cmd.equals("threadgroup")) { commandThreadGroup(t); } else if (cmd.equals("run")) { commandRun(t); } else if (cmd.equals("load")) { commandLoad(t); } else if (cmd.equals("connect")) { commandConnect(t); } else if (cmd.equals("attach")) { commandAttach(t); } else if (cmd.equals("detach")) { commandDetach(t); } else if (cmd.equals("interrupt")) { commandInterrupt(t); //### Not implemented. // } else if (cmd.equals("catch")) { // commandCatchException(t); //### Not implemented. // } else if (cmd.equals("ignore")) { // commandIgnoreException(t); } else if (cmd.equals("step")) { commandStep(t); } else if (cmd.equals("stepi")) { commandStepi(); } else if (cmd.equals("next")) { commandNext(); } else if (cmd.equals("nexti")) { commandNexti(); } else if (cmd.equals("kill")) { commandKill(t); } else if (cmd.equals("where")) { commandWhere(t, false); } else if (cmd.equals("wherei")) { commandWhere(t, true); } else if (cmd.equals("up")) { commandUp(t); } else if (cmd.equals("down")) { commandDown(t); } else if (cmd.equals("frame")) { commandFrame(t); } else if (cmd.equals("stop")) { commandStop(t); } else if (cmd.equals("clear")) { commandClear(t); } else if (cmd.equals("list")) { commandList(t); } else if (cmd.equals("use")) { commandUse(t); } else if (cmd.equals("sourcepath")) { commandSourcepath(t); } else if (cmd.equals("classpath")) { commandClasspath(t); } else if (cmd.equals("monitor")) { commandMonitor(t); } else if (cmd.equals("unmonitor")) { commandUnmonitor(t); } else if (cmd.equals("view")) { commandView(t); // } else if (cmd.equals("read")) { // readCommand(t); } else if (cmd.equals("help") || cmd.equals("?")) { help(); } else if (cmd.equals("quit") || cmd.equals("exit")) { try { runtime.detach(); } catch (NoSessionException e) { // ignore } env.terminate(); } else { //### Dubious repeat-count feature inherited from 'jdb' if (t.hasMoreTokens()) { try { int repeat = Integer.parseInt(cmd); String subcom = t.nextToken(""); while (repeat-- > 0) { executeCommand(subcom); } return; } catch (NumberFormatException exc) { } } out.println("huh? Try help..."); out.flush(); } } catch (NoSessionException e) { out.println("There is no currently attached VM session."); out.flush(); } catch (Exception e) { out.println("Internal exception: " + e.toString()); out.flush(); System.out.println("JDB internal exception: " + e.toString()); e.printStackTrace(); } out.show(); } }