Christopher Oliver wrote:<snip>
Sylvain Wallez wrote:
Mmmh... if we push and pop stack frame indications, isn't it enough to build a debugger?
I've also written several other debuggers including a Java debugger and a BPEL4WS debugger which are part of my company's IDE.
Without getting into all the details, I think a proper API for a global debugger would require quite a bit more infrastructure than what you're suggesting.
Since AFAIK, you've written the Rhino debugger, can you enlighten us on the required infrastructure?
Besides knowledge of the call stack, a debugger requires support for accessing variables and their properties (and sub-properties - for display in a tree or tree-table), evaluating expressions, accessing the source code, and notification of line changes (to manage breakpoints). You also have to consider handling multiple threads and allowing for an efficient wire-protocol for remote debugging.
Anyway, attached is a first try at an API.
1) First, a component that implements DebuggableComponent will be called with setDebugger() if it is being debugged.
2) When that component begins execution and enters a "call", it calls the enterFrame() method of the debugger passing in a DebuggableScope object that it implements, and saves the return value (a DebugFrame).
3) During execution of that call, it notifies the debugger of line changes by calling the onLineChange() method on the saved DebugFrame object.
4) If the call completes successully, it notifies the debugger by calling the onExit() method on the DebugFrame.
5) If an exception occurs during the call, it notifies the debugger by calling the onException() method on the DebugFrame
When a frame is entered, the debugger will save a reference to the current DebuggableScope. Depending on its needs and on user input the debugger will call methods on the saved DebuggableScope object to evaluate expressions, get the values of variables, and to retrieve source code.
Breakpoints are maintained internally by the debugger. When a breakpoint is hit, it will block the component's call to onLineChange() until the user steps or continues.
Note that all of the methods of DebuggableScope return String or String[] in order to make remoting easy.
Also the mechanism provided to walk the object graph associated with a variable is designed with JXPath in mind. For example, it can be implemented easily lilke this:
public String[] getProperties(String var, String pointer) {
Object value = <get value of var>
JXPathContext varContext = JXPathContext.newContext(value);
Pointer ptr = varContext.getPointer(pointer);
JXPathContext ptrContext = varContext.getRelativeContext(ptr);
Iterator iter = ptrContext.iteratePointers("*");
List list = new LinkedList();
while (iter.hasNext()) {
Pointer p = (Pointer)iter.next();
list.add(p.asPath());
}
String[] ret = new String[list.size()];
list.toArray(ret);
return ret; }
public String getValue(String var, String pointer) { Object value = <get value of var> JXPathContext varContext = JXPathContext.newContext(value); return varContext.getValue(pointer); }
What do you think?
-- Chris
// Hypothetical Debugging API:
// This interface is implemented by a processing component that can be debugged. // It represents the current stack frame from the component's point of view public interface DebuggableScope { // Get the name of the component that implements this scope public String getComponentName(); // Get the current "method" being executed public String getMethod(); // Get a list of the names of the variables visible in this scope public String[] getVariables() throws Exception; // Get a list of "pointers" to the immediate properties of a variable public String[] getProperties(String var) throws Exception; // Get a list of "pointers" to the subproperties of a property public String[] getProperties(String var, String pointer) throws Exception; // Get the value of a variable public String getValue(String var) throws Exception; // Get the value of the sub[-sub,...] property of {var} identified by {pointer} public String getValue(String var, String pointer) throws Exception; // Evaluate {expression} in the current scope public String evaluate(String expression) throws Exception; // Return the source code identified by uri public String resolveSource(String uri) throws Exception; } // This interface is implemented by the debugger itself. It represents the // current stack frame from the debugger's point of view. public interface DebugFrame { // Get the caller public DebugFrame getCaller(); // Get the scope associated with this frame public DebuggableScope getScope(); // Notify the debugger of a line change public void onLineChange(String uri, int line); // Notify the debugger of the completion of this scope public void onExit(String uri, int line); // Notify the debugger of an exception in this scope public void onException(String uri, int line, String exception, String message); } // Interface to the debugger public interface Debugger { DebugFrame enterFrame(DebuggableScope scope); } // Give a component access to the current debugger public interface DebuggableComponent { public void setDebugger(Debugger debugger); }