Index: modules/jdktools/src/main/java/org/apache/harmony/tools/jdb/Main.java =================================================================== --- modules/jdktools/src/main/java/org/apache/harmony/tools/jdb/Main.java (revision 985896) +++ modules/jdktools/src/main/java/org/apache/harmony/tools/jdb/Main.java (working copy) @@ -17,7 +17,6 @@ package org.apache.harmony.tools.jdb; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -30,38 +29,45 @@ import org.eclipse.jdi.internal.VirtualMachineManagerImpl; import org.eclipse.jdi.internal.connect.SocketAttachingConnectorImpl; -import com.sun.jdi.LocalVariable; import com.sun.jdi.Location; +import com.sun.jdi.Method; import com.sun.jdi.ReferenceType; -import com.sun.jdi.StackFrame; -import com.sun.jdi.StringReference; import com.sun.jdi.ThreadReference; -import com.sun.jdi.Value; import com.sun.jdi.VirtualMachine; import com.sun.jdi.VirtualMachineManager; import com.sun.jdi.connect.Connector; -import com.sun.jdi.connect.IllegalConnectorArgumentsException; import com.sun.jdi.event.BreakpointEvent; import com.sun.jdi.event.ClassPrepareEvent; import com.sun.jdi.event.Event; import com.sun.jdi.event.EventIterator; import com.sun.jdi.event.EventQueue; import com.sun.jdi.event.EventSet; -import com.sun.jdi.event.ExceptionEvent; -import com.sun.jdi.event.LocatableEvent; +import com.sun.jdi.event.MethodExitEvent; +import com.sun.jdi.event.StepEvent; +import com.sun.jdi.event.ThreadStartEvent; import com.sun.jdi.event.VMDisconnectEvent; import com.sun.jdi.event.VMStartEvent; import com.sun.jdi.request.BreakpointRequest; import com.sun.jdi.request.ClassPrepareRequest; import com.sun.jdi.request.EventRequest; import com.sun.jdi.request.EventRequestManager; -import com.sun.jdi.request.ExceptionRequest; +import com.sun.jdi.request.MethodExitRequest; +import com.sun.jdi.request.StepRequest; +import com.sun.jdi.request.ThreadStartRequest; +/** + * The java implementation of Harmony JDB command line tool. + */ public class Main { + /** + * The JDB command enumeration. + */ static enum Command { + // complete CONNECTORS("connectors", "connectors", "") { + @Override @SuppressWarnings("unchecked") final String run(String[] args) { List connectors = @@ -103,159 +109,318 @@ } }, + // seems complete RUN("run", "run [class [args]]", "") { + @Override final String run(String[] args) { if (jdb.status == Status.CONNECTED) { - jdb.targetVM.resume(); + jdb.debuggee.resume(); + jdb.status = Status.STARTED; } - return ""; + return EMPTY_MSG; } }, THREADS("threads", "threads [threadgroup]", "") { + @Override final String run(String[] args) { - StringBuilder sb = new StringBuilder("** command list **\n"); + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "threads"); + } + StringBuilder sb = new StringBuilder("** thread list **\n"); + // TODO print the thread list return sb.toString(); } }, THREAD("thread", "thread ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "thread"); + } + // TODO command logic + return EMPTY_MSG; } }, SUSPEND("suspend", "suspend [thread id(s)]", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "suspend"); + } + // TODO command logic + return EMPTY_MSG; } }, RESUME("resume", "resume [thread id(s)]", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "resume"); + } + // TODO command logic + return EMPTY_MSG; } }, WHERE("where", "where [ | all]", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "where"); + } + // TODO command logic + return EMPTY_MSG; } }, WHEREI("wherei", "wherei [ | all]", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "wherei"); + } + // TODO command logic + return EMPTY_MSG; } }, UP("up", "up [n frames]", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "up"); + } + // TODO command logic + return EMPTY_MSG; } }, DOWN("down", "down [n frames]", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "down"); + } + // TODO command logic + return EMPTY_MSG; } }, KILL("kill", "kill ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "kill"); + } + // TODO command logic + return EMPTY_MSG; } }, INTERRUPT("interrupt", "interrupt ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "interrupt"); + } + // TODO command logic + return EMPTY_MSG; } }, PRINT("print", "print ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "print"); + } + // TODO command logic + return EMPTY_MSG; } }, DUMP("dump", "dump ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "dump"); + } + // TODO command logic + return EMPTY_MSG; } }, EVAL("eval", "eval ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "eval"); + } + // TODO command logic + return EMPTY_MSG; } }, SET("set", "set = ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "set"); + } + // TODO command logic + return EMPTY_MSG; } }, LOCALS("locals", "locals", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "locals"); + } + // TODO command logic + return EMPTY_MSG; } }, CLASSES("classes", "classes", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "classes"); + } + // TODO command logic + return EMPTY_MSG; } }, CLASS("class", "class ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "class"); + } + return EMPTY_MSG; } }, METHODS("methods", "methods ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "methods"); + } + return EMPTY_MSG; } }, FIELDS("fields", "fields ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "fields"); + } + return EMPTY_MSG; } }, THREADGROUPS("threadgroups", "threadgroups", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "threadgroups"); + } + return EMPTY_MSG; } }, THREADGROUP("threadgroup", "threadgroup ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "threadgroup"); + } + // TODO command logic + return EMPTY_MSG; } }, - STOP_IN("stop", "stop in .[(argument_type,...)]", "") { + // TODO complete, not working well though + STOP_IN("stop", "stop in .[(argument_type,...)]", + "") { + @Override final boolean match(String[] args) { return super.match(args) && args[1].equals("in"); } + @Override + @SuppressWarnings("unchecked") final String run(String[] args) { - return ""; + String[] params; + try { + params = parseClassMethod(args[2]); + } catch (Exception e) { + return usage; + } + List classes = + jdb.debuggee.classesByName(params[0]); + // class not loaded yet + if (classes.isEmpty()) { + if (!jdb.deferringMethodBreakpoint.containsKey(params[0])) { + jdb.deferringMethodBreakpoint.put( + params[0], new ArrayList()); + } + // method string: "method|argument_type,..." + jdb.deferringMethodBreakpoint.get( + params[0]).add(params[1] + "|" + params[2]); + return String.format( + "Deferring breakpoint %s\n" + + "it will be set after the class is loaded.", + args[2]); + } else { + // Generate the target argument type string list + String[] argTypeNames = params[2].split(","); + // Get all methods of the class + ReferenceType clazz = classes.get(0); + List methodList = clazz.methodsByName(params[1]); + Method matchedMethod = null; + /* + * As the jdb command argument doesn't supply the result + * value type, it's impossible to generate a jni signature + * for the specified method. I just have to search... + */ + for (Method m : methodList) { + List types = m.argumentTypeNames(); + if (types.size() != argTypeNames.length) { + continue; + } else { + boolean matched = true; + for (int i = 0; i < argTypeNames.length; i++) { + if (!types.get(i).equals(argTypeNames[i])) { + matched = false; + break; + } + } + if (matched) { + matchedMethod = m; + break; + } + } + } + if (null != matchedMethod) { + Location loc = matchedMethod.location(); + if (null != loc) { + BreakpointRequest breakpointRequest = + jdb.eventRequestManager. + createBreakpointRequest(loc); + breakpointRequest.setSuspendPolicy( + EventRequest.SUSPEND_ALL); + breakpointRequest.enable(); + jdb.breakpointRegisterMap.put( + loc.toString(), breakpointRequest); + return String.format("Breakpoint set: " + loc); + } + } + } + return EMPTY_MSG; } }, + // TODO complete, working well, need more check STOP_AT("stop", "stop at :", "") { + @Override final boolean match(String[] args) { return super.match(args) && args[1].equals("at"); } + @Override + @SuppressWarnings("unchecked") final String run(String[] args) throws Exception { String[] params = args[2].split(":"); List classes = - jdb.targetVM.classesByName(params[0]); + jdb.debuggee.classesByName(params[0]); if (classes.isEmpty()) { if (!jdb.deferringLineBreakpoint.containsKey(params[0])) { jdb.deferringLineBreakpoint.put( @@ -270,68 +435,209 @@ } else { List locations = classes.get(0). locationsOfLine(Integer.parseInt(params[1])); - if (locations.isEmpty()) { - Location location = locations.get(0); - BreakpointRequest breakpointRequest = jdb.eventRequestManager - .createBreakpointRequest(location); - breakpointRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL); + if (!locations.isEmpty()) { + Location loc = locations.get(0); + BreakpointRequest breakpointRequest = + jdb.eventRequestManager. + createBreakpointRequest(loc); + breakpointRequest.setSuspendPolicy( + EventRequest.SUSPEND_ALL); breakpointRequest.enable(); - return String.format("Breakpoint set: " + location); + jdb.breakpointRegisterMap.put( + loc.toString(), breakpointRequest); + return String.format("Breakpoint set: " + loc); } else { - return ""; + return EMPTY_MSG; } } } }, + // TODO complete, not working well though CLEAR_METHOD("clear", "clear .[(argument_type,...)]", "") { final boolean match(String[] args) { - return false; + return args.length >= 2 + && args[0].equals(prefix) + && args[1].indexOf(':') == -1 + && args[1].indexOf('.') != -1; } + @SuppressWarnings("unchecked") final String run(String[] args) { - return ""; + String[] params; + boolean isFound = false; + try { + params = parseClassMethod(args[1]); + } catch (Exception e) { + return usage; + } + String methodStr = params[1] + "|" + params[2]; + List methodStrList = + jdb.deferringMethodBreakpoint.get(params[0]); + for (String str : methodStrList) { + if (str.equals(methodStr)) { + methodStrList.remove(str); + isFound = true; + break; + } + } + if (!isFound) { + List classes = + jdb.debuggee.classesByName(params[0]); + if (!classes.isEmpty()) { + // Generate the target argument type string list + String[] argTypeNames = params[2].split(","); + // Get all methods of the class + ReferenceType clazz = classes.get(0); + List methodList = clazz.methodsByName(params[1]); + Method matchedMethod = null; + /* + * As the jdb command argument doesn't supply the result + * value type, it's impossible to generate a jni signature + * for the specified method. I just have to search... + */ + for (Method m : methodList) { + List types = m.argumentTypeNames(); + if (types.size() != argTypeNames.length) { + continue; + } else { + boolean matched = true; + for (int i = 0; i < argTypeNames.length; i++) { + if (!types.get(i).equals(argTypeNames[i])) { + matched = false; + break; + } + } + if (matched) { + matchedMethod = m; + break; + } + } + } + if (null != matchedMethod) { + Location loc = matchedMethod.location(); + if (null != loc) { + String key = loc.toString(); + jdb.breakpointRegisterMap.get(key).disable(); + jdb.breakpointRegisterMap.remove(key); + isFound = true; + } + } + } + } + if (isFound) { + return String.format("Breakpoint cleared: " + args[1]); + } else { + return String.format( + "Not found: breakpoint %s", args[1]); + } } }, + // TODO complete, not working well though CLEAR_LINE("clear", "clear :", "") { final boolean match(String[] args) { - return false; + return args.length >= 2 + && args[0].equals(prefix) + && args[1].indexOf(':') != -1; } - final String run(String[] args) { - return ""; + @SuppressWarnings("unchecked") + final String run(String[] args) throws Exception { + String[] params = args[1].split(":"); + int line = Integer.parseInt(params[1]); + boolean isFound = false; + List lineList = + jdb.deferringLineBreakpoint.get(params[0]); + for (Integer i : lineList) { + if (i.intValue() == line) { + lineList.remove(i); + isFound = true; + break; + } + } + if (!isFound) { + List classes = + jdb.debuggee.classesByName(params[0]); + if (!classes.isEmpty()) { + List locations = classes.get(0). + locationsOfLine(line); + if (!locations.isEmpty()) { + Location loc = locations.get(0); + String key = loc.toString(); + jdb.breakpointRegisterMap.get(key).disable(); + jdb.breakpointRegisterMap.remove(key); + isFound = true; + } + } + } + if (isFound) { + return String.format("Breakpoint cleared: " + args[1]); + } else { + return String.format( + "Not found: breakpoint %s", args[1]); + } } }, CLEAR("clear", "clear", "") { final boolean match(String[] args) { - return false; + return args.length == 1 && args[0].equals(prefix); } + @SuppressWarnings("unchecked") final String run(String[] args) { - return ""; + /* + * TODO have not include deferred breakpoints yet + */ + List list = + jdb.eventRequestManager.breakpointRequests(); + if (list.size() == 0) { + return "No breakpoints set."; + } else { + StringBuilder sb = new StringBuilder(); + for (BreakpointRequest request : list) { + sb.append(request.location() + "\n"); + } + return sb.toString(); + } } }, CATCH("catch", "catch [uncaught|caught|all]", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "catch"); + } + // TODO command logic + return EMPTY_MSG; } }, IGNORE("ignore", "ignore [uncaught|caught|all]", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "ignore"); + } + // TODO command logic + return EMPTY_MSG; } }, WATCH("watch", "watch [access|all] .", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "watch"); + } + // TODO command logic + return EMPTY_MSG; } }, UNWATCH("unwatch", "unwatch [access|all] .", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "unwatch"); + } + // TODO command logic + return EMPTY_MSG; } }, @@ -345,7 +651,11 @@ } } final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "trace"); + } + // TODO command logic + return EMPTY_MSG; } }, @@ -354,16 +664,25 @@ return false; } final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "trace"); + } + // TODO command logic + return EMPTY_MSG; } }, UNTRACE("untrace", "untrace [methods]", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "untrace"); + } + // TODO command logic + return EMPTY_MSG; } }, + // TODO complete, not working well though STEP("step", "step", "") { final boolean match(String[] args) { if (args[0].equals(prefix) && @@ -374,10 +693,20 @@ } } final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "step"); + } + StepRequest request = + jdb.eventRequestManager.createStepRequest(jdb.thread, + StepRequest.STEP_LINE, + StepRequest.STEP_INTO); + request.addCountFilter(1);// next step only + request.enable(); + return "Step one line."; } }, + // TODO complete, not working well though STEP_UP("step", "step up", "") { final boolean match(String[] args) { if (args[0].equals(prefix) && @@ -388,28 +717,67 @@ } } final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "step up"); + } + MethodExitRequest request = + jdb.eventRequestManager.createMethodExitRequest(); + request.addThreadFilter(jdb.thread); + request.addCountFilter(1); + request.enable(); + return "Step up to the caller."; } }, + // TODO complete, not working well though STEPI("stepi", "stepi", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "stepi"); + } + StepRequest request = + jdb.eventRequestManager.createStepRequest(jdb.thread, + StepRequest.STEP_MIN, + StepRequest.STEP_INTO); + request.addCountFilter(1);// next step only + request.enable(); + return "Step one instruction."; } }, + // TODO complete, not working well though NEXT("next", "next", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "next"); + } + StepRequest request = + jdb.eventRequestManager.createStepRequest(jdb.thread, + StepRequest.STEP_LINE, + StepRequest.STEP_OVER); + request.addCountFilter(1);// next step only + request.enable(); + return "Step one line (over calls)"; } }, + // TODO, complete, working well, need more check CONT("cont", "cont", "") { + final String run(String[] args) { + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "cont"); + } + return EMPTY_MSG; + } }, LIST("list", "list [line number|method]", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "list"); + } + // TODO command logic + return EMPTY_MSG; } }, @@ -422,99 +790,145 @@ } } final String run(String[] args) { - return ""; + // TODO command logic + return EMPTY_MSG; } }, EXCLUDE("exclude", "exclude [, ... | \"none\"]", "") { final String run(String[] args) { - return ""; + // TODO command logic + return EMPTY_MSG; } }, CLASSPATH("classpath", "classpath", "") { final String run(String[] args) { - return ""; + // TODO command logic + return EMPTY_MSG; } }, - MONITOR_CMD("monitoer", "monitor ", "") { + MONITOR_CMD("monitor", "monitor ", "") { final boolean match(String[] args) { return false; } final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "monitor"); + } + // TODO command logic + return EMPTY_MSG; } }, - MONITOR("monitoer", "monitor", "") { + MONITOR("monitor", "monitor", "") { final boolean match(String[] args) { return false; } final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "monitor"); + } + // TODO command logic + return EMPTY_MSG; } }, UNMONITOR("unmonitor", "unmonitor ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "unmonitor"); + } + // TODO command logic + return EMPTY_MSG; } }, READ("read", "read ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "read"); + } + // TODO command logic + return EMPTY_MSG; } }, LOCK("lock", "lock ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "lock"); + } + // TODO command logic + return EMPTY_MSG; } }, THREADLOCKS("threadlocks", "threadlocks [thread id]", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "threadlocks"); + } + // TODO command logic + return EMPTY_MSG; } }, POP("pop", "pop", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "pop"); + } + // TODO command logic + return EMPTY_MSG; } }, REENTER("reenter", "reenter", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "reenter"); + } + // TODO command logic + return EMPTY_MSG; } }, REDEFINE("redefine", "redefine ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "redefine"); + } + // TODO command logic + return EMPTY_MSG; } }, DISABLEGC("disablegc", "disablegc ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "disablegc"); + } + // TODO command logic + return EMPTY_MSG; } }, ENABLEGC("enablegc", "enablegc ", "") { final String run(String[] args) { - return ""; + if (jdb.status != Status.STARTED) { + return String.format(NOT_VALID_UNTIL_STARTED, "enablegc"); + } + // TODO command logic + return EMPTY_MSG; } }, REPEAT("!!", "!!", "") { - Command lastCmd = null; - String[] lastArgs = null; final String run(String[] args) { - return ""; + // TODO command logic + return EMPTY_MSG; } }, @@ -528,13 +942,14 @@ } final String run(String[] args) { StringBuilder sb = new StringBuilder("** command list **\n"); + // TODO build the info string return sb.toString(); } }, VERSION("version", "version", "") { final String run(String[] args) { - return ""; + return VERSION_STR; } }, @@ -546,20 +961,28 @@ return false; } } + final String run(String[] args) { + return EMPTY_MSG; + } }, + // Returned by the search method when no real command is matched NONCMD("", "" ,"") { final String run(String[] args) { return String.format("Unsupported command: '%s'.", args[0]); } }; + // Expected command prefix final String usage; + // Command format and options final String comment; + // Help information of the command final String prefix; + // Iterate the command list and match the args against them static final Command search(final String[] args) { for (Command cmd : Command.values()) { if (cmd != NONCMD && cmd.match(args)) { @@ -569,12 +992,48 @@ return NONCMD; } + // parse the ".[(argument_type,...)]" string + static final String[] parseClassMethod(String str) { + String[] params = new String[3]; + int argumentStart = str.indexOf('('); + if (argumentStart == -1) { + // class id + params[0] = str.substring( + 0, str.lastIndexOf('.')); + // method name + params[1] = str.substring( + str.lastIndexOf('.') + 1); + // empty argument type list + params[2] = ""; + } else { + String classMethod = str.substring(argumentStart); + // class id + params[0] = classMethod.substring( + 0, classMethod.lastIndexOf('.')); + // method name + params[1] = classMethod.substring( + classMethod.lastIndexOf('.') + 1); + // argument type list + params[2] = str.substring( + argumentStart + 1, str.length() - 1); + } + return params; + } + + /* + * Generally, a command matching only needs to compare its prefix, + * override it in enum instance if necessary. + */ boolean match(String[] args) { return args[0].equals(prefix); } + /* + * The default command running output is do nothing and return an empty + * string, override it in enum instance if necessary. + */ String run(String[] args) throws Exception { - return ""; + return EMPTY_MSG; } Command(String prefix, String usage, String comment) { @@ -585,19 +1044,42 @@ } + // TODO not decided how many statuses needed yet static enum Status { - UNDEFINED, NO_VM_CONNECTED, CONNECTED + UNDEFINED, NO_VM_CONNECTED, CONNECTED, STARTED } + // The default prompt private static final String PROMPT = "> "; - private static Main jdb = new Main(); + /* + * The empty message indicates there's no information coming out with + * the command result + */ + private static final String EMPTY_MSG = ""; + // The jdb version information + private static final String VERSION_STR = + "This is jdb version 1.6 "; + + /* + * The string indicates that a command is not valid until the VM is started + * with the 'run' command. + */ + private static final String NOT_VALID_UNTIL_STARTED = + "Command '%s' is not valid until " + + "the VM is started with the 'run' command"; + + // The singleton instance of jdb + private static final Main jdb = new Main(); + private boolean isExit = false; + // The debuggee status private Status status = Status.UNDEFINED; - private VirtualMachineManager virturalMachineManager = Bootstrap.virtualMachineManager(); + private VirtualMachineManager virturalMachineManager = + Bootstrap.virtualMachineManager(); private EventRequestManager eventRequestManager = null; @@ -605,11 +1087,13 @@ private EventSet eventSet = null; - private VirtualMachine targetVM = null; + private VirtualMachine debuggee = null; - private Process VMProcess = null; + //private Process VMProcess = null; + // Current thread + private ThreadReference thread = null; - private String mainClass = null; + //private String mainClass = null; private HashMap> deferringLineBreakpoint = new HashMap>(); @@ -617,6 +1101,16 @@ private HashMap> deferringMethodBreakpoint = new HashMap>(); + /* + * A map that stores references to all breakpoints(not include the deferred + * ones). The key of the map is a string indicate the location of the + * breakpoint. The map is used by those commands that will clear some + * breakpoint in certain location. + */ + private HashMap breakpointRegisterMap = + new HashMap(); + + // TODO currentPrompt is prompt that indicate current context(fixed for now) private String currentPrompt = PROMPT; private Main() {} @@ -626,36 +1120,50 @@ * and corresponding parameter, then launch jdb and get ready to process * commands. */ + @SuppressWarnings("unchecked") private void init(String[] args) throws Exception { + /* + * TODO As currently jdb just supports "jdb -attach [port]" command, + * more work to do to parse the jdb launching command option + */ if (args.length >= 2 && args[0].equals("-attach")) { - SocketAttachingConnectorImpl sac = new SocketAttachingConnectorImpl( - (VirtualMachineManagerImpl) Bootstrap.virtualMachineManager()); - + SocketAttachingConnectorImpl sac = + new SocketAttachingConnectorImpl( + (VirtualMachineManagerImpl) Bootstrap. + virtualMachineManager()); Map argMap = sac.defaultArguments(); Connector.Argument value; value = (Connector.Argument) argMap.get("hostname"); value.setValue("localhost"); value = (Connector.Argument) argMap.get("port"); value.setValue(args[1]); - targetVM = sac.attach(argMap); - VMProcess = targetVM.process(); + debuggee = sac.attach(argMap); + //VMProcess = debuggee.process(); } else { System.out.println("Wrong command option. " + "The jdb currently only support jdb -attach [port]."); System.exit(0); } - eventRequestManager = targetVM.eventRequestManager(); + eventRequestManager = debuggee.eventRequestManager(); + // Enable class prepare request for all classes ClassPrepareRequest classPrepareRequest = eventRequestManager.createClassPrepareRequest(); classPrepareRequest.addClassFilter("*"); classPrepareRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL); classPrepareRequest.enable(); + + // Enable thread start request for all threads + ThreadStartRequest threadStartRequest + = eventRequestManager.createThreadStartRequest(); + threadStartRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + threadStartRequest.enable(); } + // Start the loop private void start() throws Exception { - eventQueue = targetVM.eventQueue(); + eventQueue = debuggee.eventQueue(); while (true) { if (isExit == true) { break; @@ -672,51 +1180,137 @@ } } + @SuppressWarnings("unchecked") private void execute(Event event) throws Exception { + /* + * The variable indicates whether go to command looping after handling + * the current event + */ boolean keepRunning = true; if (event instanceof VMStartEvent) { - //eventRequestManager = targetVM.eventRequestManager(); - //ExceptionRequest excReq = - //eventRequestManager.createExceptionRequest(null, false, true); - //excReq.setSuspendPolicy(EventRequest.SUSPEND_ALL); - //excReq.enable(); - //System.out.println("Set uncaught java.lang.Throwable"); - //System.out.println("Set deferred uncaught java.lang.Throwable"); status = Status.CONNECTED; System.out.println("Initializing jdb ..."); System.out.println( "VM Started: No frames on the current call stack"); - //currentPrompt = ((VMStartEvent)event).thread().name(); + // stop after the vm starts keepRunning = false; } + if (event instanceof ThreadStartEvent) { + thread = ((ThreadStartEvent) event).thread(); + } + if (event instanceof MethodExitEvent) { + // run command step up -> stop + keepRunning = false; + } + if (event instanceof StepEvent) { + // run commands step, stepi, or next -> stop + keepRunning = false; + } + /* + * after a class is loaded, check whether there are deferred + * breakpoints(both line and method) related to the class; if yes, + * set these breakpoints. The jdb won't stop running. + */ if (event instanceof ClassPrepareEvent) { - String className = ((ClassPrepareEvent) event).referenceType().name(); + // get the class name + String className = ((ClassPrepareEvent) + event).referenceType().name(); + // set the possible deferred line breakpoints List lineList = deferringLineBreakpoint.get(className); if (lineList != null) { - ReferenceType classType = ((ClassPrepareEvent) event).referenceType(); + ReferenceType classType = ((ClassPrepareEvent) + event).referenceType(); for (Integer line : lineList) { List locations = classType.locationsOfLine(line); if (!locations.isEmpty()) { - Location location = locations.get(0); - BreakpointRequest breakpointRequest = jdb.eventRequestManager - .createBreakpointRequest(location); - breakpointRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL); + Location loc = locations.get(0); + BreakpointRequest breakpointRequest = + jdb.eventRequestManager. + createBreakpointRequest(loc); + breakpointRequest.setSuspendPolicy( + EventRequest.SUSPEND_ALL); breakpointRequest.enable(); - System.out.println(String.format("Breakpoint set: " + location)); + jdb.breakpointRegisterMap.put( + loc.toString(), breakpointRequest); + System.out.println( + String.format("Breakpoint set: " + loc)); } } } + // set the possible deferred method breakpoints + List methodStrList = deferringMethodBreakpoint.get(className); + if (methodStrList != null) { + ReferenceType classType = ((ClassPrepareEvent) + event).referenceType(); + for (String methodStr : methodStrList) { + String[] params = methodStr.split("|"); + // Generate the target argument type string list + String[] argTypeNames; + if (params.length > 1) { + argTypeNames = params[1].split(","); + } else { + argTypeNames = new String[0]; + } + // Get all methods of the class + List methodList = classType.methodsByName(params[0]); + Method matchedMethod = null; + /* + * As the jdb command argument doesn't supply the result + * value type, it's impossible to generate a jni signature + * for the specified method. I just have to search... + */ + for (Method m : methodList) { + List types = m.argumentTypeNames(); + if (types.size() != argTypeNames.length) { + continue; + } else { + boolean matched = true; + for (int i = 0; i < argTypeNames.length; i++) { + if (!types.get(i).equals(argTypeNames[i])) { + matched = false; + break; + } + } + if (matched) { + matchedMethod = m; + break; + } + } + } + if (null != matchedMethod) { + Location loc = matchedMethod.location(); + if (null != loc) { + BreakpointRequest breakpointRequest = + jdb.eventRequestManager. + createBreakpointRequest(loc); + breakpointRequest.setSuspendPolicy( + EventRequest.SUSPEND_ALL); + breakpointRequest.enable(); + jdb.breakpointRegisterMap.put( + loc.toString(), breakpointRequest); + System.out.println( + String.format("Breakpoint set: " + loc)); + } + } + } + } } if (event instanceof BreakpointEvent) { + // reach breakpoint and stop System.out.println("Reach breakpoint at " + ((BreakpointEvent) event).location()); keepRunning = false; } if (event instanceof VMDisconnectEvent) { + // The jdb will end when disconnected from target vm System.out.println("Application ends."); isExit = true; } + /* + * If the event requires the jdb to stop running, the jdb turn to the user + * interaction mode before resuming. + */ if (!keepRunning) { Scanner cmdScanner = new Scanner(System.in); while (true) { @@ -729,16 +1323,18 @@ isExit = true; break; } - if (cmd == Command.CONT) { - break; + String result = cmd.run(args); + if (!result.equals(EMPTY_MSG)) { + System.out.println(result); } - String result = cmd.run(args); - if (cmd == Command.RUN) { + // If the command is one of those execution control + if (cmd == Command.STEP + || cmd == Command.STEP_UP + || cmd == Command.STEPI + || cmd == Command.RUN + || cmd == Command.CONT) { break; } - if (!result.equals("")) { - System.out.println(result); - } } } } @@ -748,7 +1344,7 @@ jdb.init(args); jdb.start(); if (jdb.status == Status.CONNECTED) { - jdb.targetVM.dispose(); + jdb.debuggee.dispose(); } }