Prerequisite reading
- If you haven't already read it, you should take a look at the initial write-up: WebIDEDebug
Current Constraints
- Browser will poll IDE service to get debug state info, including whether a current thread is suspended. There will not be a notification mechanism initially.
- Run AB in non-recycle mode/target VMs in recycle mode
- When source is edited such that lines are added or removed, the breakpoints stay on the same line
- Only one instance of application being debugged at one time
Debug connection(s)
The target VMs will be allowed to run in recycle mode, which means that the VMs could be stopped and started at any time. To satisfy this requirement we will need to set the AB service in listening mode and then launch the target VMs so that they connect back to the AB service. The following describes how this scenario is setup (pasted from the VirtualMachineManager javadoc scenario called "Target VM attaches to previously-running debugger"):
- At startup, debugger selects one or more connectors from the list returned by listeningConnectors() for one or more transports.
- Debugger calls the ListeningConnector.startListening(java.util.Map) method for each selected connector. For each call, a transport-specific address string is generated and returned. The debugger makes the transport names and corresponding address strings available to the end user.
- Debugger calls ListeningConnector.accept(java.util.Map) for each selected connector to wait for a target VM to connect.
- Later, target VM is launched by end user with the options -agentlib:jdwp=transport=xxx,address=yyy where "xxx" the transport for one of the connectors selected by the the debugger and "yyy" is the address generated by ListeningConnector.accept(java.util.Map) for that transport.
- Debugger's call to ListeningConnector.accept(java.util.Map) returns a VirtualMachine mirror.
So, when the an app is first started we will start the listener and call accept() in a new thread and then start the app with the jvm args to connect back to the AB. Whenever the target VM disconnects (as seen by a VMDisconnectRequest on the EventQueue) we will call accept() again to wait for the VM to start again.
The connection flow is currently being
discussed here to handle AB restart better.
Debug State
Since the debug state only needs to live for the life of the AB session and since we are running the AB in non-recycle mode, we will store the debug state in the tmp zone. Note: Breakpoints are not considered debug state; they will be stored in the storage zone where they will be persisted across debug sessions.
VirtualMachine instance
The VirtualMachine instance is the mirror of the state in the target VM. It provides full access to threads/stack frames/local variables/etc and there will be one per application. This object will be stored at /tmp/{appId}/debugVM. When the target VM is recycled this object will be replaced.
Threads URIs
| GET | resources/applications/{appId}/threads | Lists all threads for the application with summary info |
| GET | resources/applications/{appId}/threads/{threadId} | Returns more detailed info for thread |
| POST | resources/applications/{appId}/threads/{threadId} | "action" in payload can be set to "resume", "stepInto", "stepOver", "stepOut", "popStack" to control the execution of the thread |
StackFrames URIs
| GET | resources/applications/{appId}/threads/{threadId}/stackFrames | Lists stack frames for the thread with summary info |
| GET | resources/applications/{appId}/threads/{threadId}/stackFrames/{stackFrameId} | Returns more detailed info for stack frame including local variable names |
LocalVariables URIs
| GET | resources/applications/{appId}/threads/{threadId}/stackFrames/{frameId}/localVariables | Lists local variables for the stack frame and includes type and value information) |
| GET | resources/applications/{appId}/threads/{threadId}/stackFrames/{frameId}/localVariables/{varName} | returns type, value (from toString()) and lists the field names. The field names can be added to the URI to drill down |
Breakpoints
Breakpoints can be set before or during a target debug session. Because of this flexibility and since the class may not be loaded at the time the user is trying to set the breakpoint, the breakpoints need to be stored in the GC. Then whenever a target VM is started or when a class of interest is loaded, the breakpoint will need to be set. Since there will be one set of breakpoints per application and we need the breakpoints to be persisted, they will be stored under /storage/{appId}/breakpoints. Each breakpoint should have the source file and line, the fully-qualified classname, and the program counter.
Breakpoints URIs
| GET | resources/applications/{appId}/breakpoints | Lists all breakpoints |
| POST | resources/applications/{appId}/breakpoints | Creates a breakpoint using data from payload: "sourceName" refers to the fully-qualified class name, "line" refers to line number, "enabled" refers to whether the breakpoint is enabled (default=true) |
|PUT| resources/applications/{appId}/breakpoints/{sourceName}/{line}| Update "enabled" status of breakpoint
| GET | resources/applications/{appId}/breakpoints/{sourceName} | Lists all breakpoints for a source (sourceName is language dependent, but for Java and Groovy it is the fully-qualified classname) |
| DELETE | resources/applications/{appId}/breakpoints/{sourceName}/{lineNo} | removes breakpoint at the sourceName/lineNo location |
Scenarios
- Set a breakpoint (before app is started)
- Start an application in debug mode
- Set a breakpoint (before class is loaded)
- Set a breakpoint (after class is loaded)
- Delete a breakpoint
- Hit a breakpoint, step into, over, out, pop stack
- Stop an application in debug mode while suspended at breakpoint
- Stop an application in debug mode while running
- Set a breakpoint in two different source locations, fire separate requests at locations such that two threads are suspended in different locations
- Set a breakpoint in a Groovy closure
Screen shots
Current Work Items
- GC variable view
- Breakpoint adjustment with source line changes
- Handle setting breakpoints in scripts/classes with same relative name, e.g. public/test.groovy and app/scripts/test.groovy
- Step filter preferences (they are in AB config, but need to move to UI so user can easily change them)
- Port range preference
- Optimize state update to improve responsiveness
Later Items
- PHP debug
- Push updates?
- Exception breakpoints
- Pop stack