When our debugger is running, Eclipse provides different toolbar actions, depending on what selection is active in the Debug view. There are four different types of actions:
- Terminate
- Suspend/Resume
- Disconnect
- Stepping
In this tutorial we will implement all these features step by step.
Debug Framework Tutorials
For a list of all debug related tutorials see Debug Framework Tutorials Overview.
Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online.
Step 1: Suspend/Resume
Our debugger is very simple. We do not have multiple threads, so suspend/resume functionality is the same for the DebugTarget, a Thread or a StackFrame (we will look into this in another tutorial).
As suspend/resume is already implemented, we simply refactor stuff and move the ISuspendResume functionality to our base class TextDebugElement.
public abstract class TextDebugElement extends DebugElement implements ISuspendResume { public enum State { NOT_STARTED, SUSPENDED, RESUMED, TERMINATED, DISCONNECTED }; private State fState = State.NOT_STARTED; @Override public TextDebugTarget getDebugTarget() { return (TextDebugTarget) super.getDebugTarget(); } protected void setState(State state) { // only the DebugTarget saves the correct state. ((TextDebugElement) getDebugTarget()).fState = state; } protected State getState() { return ((TextDebugElement) getDebugTarget()).fState; } // ************************************************************ // ISuspendResume // ************************************************************ @Override public boolean canResume() { return isSuspended(); } @Override public boolean canSuspend() { // we cannot interrupt our debugger once it is running return false; } @Override public boolean isSuspended() { return (getState() == State.SUSPENDED); } @Override public void resume() { // resume request from eclipse // send resume request to debugger getDebugTarget().fireModelEvent(new ResumeRequest()); } @Override public void suspend() throws DebugException { throw new DebugException(new Status(IStatus.ERROR, "com.codeandme.textinterpreter.debugger", "suspend() not supported")); } }
Some minor changes are needed to the derived classes which are not shown here.
Any action that might be triggered from a toolbar item has a canSomething() method that will be checked for button enablement (lines 26-35). Resuming is just a matter of sending a ResumeRequest to the debugger (line 47).
Moving to the base class allows any of our model implementations to use resuming. Besides we may remove some obsolete code from TextDebugTarget and TextThread.
When you run the debugger now try to select the Text Thread element in the Debug view. Previously we could not resume the thread, but only the Text DebugTarget. Now we can resume both.
Step 2: Implement terminate and disconnect support
Again we add the functionality to the base class:
public abstract class TextDebugElement extends DebugElement implements ISuspendResume, IDisconnect, ITerminate { // ************************************************************ // IDisconnect // ************************************************************ @Override public boolean canDisconnect() { return canTerminate(); } @Override public void disconnect() { // disconnect request from eclipse // send disconnect request to debugger getDebugTarget().fireModelEvent(new DisconnectRequest()); // debugger is detached, simulate terminate event getDebugTarget().handleEvent(new TerminatedEvent()); } @Override public boolean isDisconnected() { return isTerminated(); } // ************************************************************ // ITerminate // ************************************************************ @Override public boolean canTerminate() { return !isTerminated(); } @Override public boolean isTerminated() { return (getState() == State.TERMINATED); } @Override public void terminate() { // terminate request from eclipse // send terminate request to debugger getDebugTarget().fireModelEvent(new TerminateRequest()); } }While terminate() will immediately stop the debug session (along with the interpreter), disconnect() will only detach the eclipse debugger, but resume the interpreter. Therefore we need to extend TextDebugger.handleEvent():
public void handleEvent(final IDebugEvent event) { if (event instanceof ResumeRequest) fInterpreter.resume(); else if (event instanceof TerminateRequest) fInterpreter.terminate(); else if (event instanceof DisconnectRequest) { fInterpreter.setDebugger(null); fInterpreter.resume(); } }On a disconnect we remove the debugger instance and resume. This allows our interpreter to continue execution without triggering further debug events.
Whether disconnect support shall be implemented or not depends on the debugger type. The default Java debugger in Eclipse does not provide disconnect support.
Step 3: Stepping support
Now for the interesting stuff: implement stepping.
Our fictional language is so simple, that it cannot support step into or step return. So what remains is step over. Functionality for all three step types would be very similar anyhow. The only difference is made in your debugger implementation that needs to know when to suspend next. As before we implement the functionality in the base class.
public abstract class TextDebugElement extends DebugElement implements ISuspendResume, IDisconnect, ITerminate, IStep { public enum State { NOT_STARTED, SUSPENDED, RESUMED, TERMINATED, DISCONNECTED, STEPPING }; // ************************************************************ // IStep // ************************************************************ @Override public boolean canStepInto() { return false; } @Override public boolean canStepOver() { return isSuspended(); } @Override public boolean canStepReturn() { return false; } @Override public boolean isStepping() { return (getState() == State.STEPPING); } @Override public void stepInto() throws DebugException { throw new DebugException(new Status(IStatus.ERROR, "com.codeandme.textinterpreter.debugger", "stepInto() not supported")); } @Override public void stepOver() { // stepOver request from eclipse // send stepOver request to debugger getDebugTarget().fireModelEvent(new ResumeRequest(ResumeRequest.STEP_OVER)); } @Override public void stepReturn() throws DebugException { throw new DebugException(new Status(IStatus.ERROR, "com.codeandme.textinterpreter.debugger", "stepReturn() not supported")); } }This will enable the Step over toolbar button when the debugger is suspended.
The debug framework in Eclipse distinguishes between resuming and stepping. Therefore we added a new debugger state: STEPPING.
Next step is to update the TextDebugger class to keep track of the resume type (continue, step over, step into, step return):
public class TextDebugger implements IDebugger, IEventProcessor { private boolean fIsStepping = false; @Override public void resumed() { fireEvent(new ResumedEvent(fIsStepping ? ResumedEvent.STEPPING : ResumedEvent.CONTINUE)); } @Override public boolean isBreakpoint(final int lineNumber) { return fIsStepping; } @Override public void handleEvent(final IDebugEvent event) { if (Activator.getDefault().isDebugging()) System.out.println("Debugger : process " + event); if (event instanceof ResumeRequest) { fIsStepping = (((ResumeRequest) event).getType() == ResumeRequest.STEP_OVER); fInterpreter.resume(); } else if (event instanceof TerminateRequest) fInterpreter.terminate(); else if (event instanceof DisconnectRequest) { fInterpreter.setDebugger(null); fInterpreter.resume(); } } }Currently isBreakpoint() is called for each executed line. We use the fIsStepping marker to break after a step over event.
Finally the TextDebugTarget needs to fire an adequate event for the debug framework:
public class TextDebugTarget extends TextDebugElement implements IDebugTarget, IEventProcessor { @Override public void handleEvent(final IDebugEvent event) { if (!isDisconnected()) { [...] } else if (event instanceof ResumedEvent) { if (((ResumedEvent) event).getType() == ResumedEvent.STEPPING) { setState(State.STEPPING); fireResumeEvent(DebugEvent.STEP_OVER); } else { setState(State.RESUMED); fireResumeEvent(DebugEvent.UNSPECIFIED); } } [...] } } }By now you should be able to run the debugger in single step mode. To verify results watch the sysout output in the console.
Hello Christian,
ReplyDeletefirst of all great tutorial but im facing some issues.
1. I cant toggle breakpoints
2. I cant resume, step over or terminate any of this and i know that the events are waiting.
If you can help me it would be wonderful, is there any other way to contact with you?
Did you try to run the example code from github? You should be able to see how it works from there. For problems with the example code please stick to the comments so all readers can benefit from it.
DeleteYes i did try the example code it worked i can press resume and etc.
DeleteBut now the breakpoints dont work im trying to press right click in the left side where i can toggle breakpoints usually but it dosent work. what can i do? maybe we can talk elsewhere? mail? discord? facebook?
As you are posting your question under this tutorial: did you also do the next 2 tutorials which deal with breakpoint integration?
DeleteYes and it still didn't work..
Delete