| /* |
| * Copyright (c) 1998, 2008, 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(); |
| } |
| } |