Friday, November 22, 2013

Debugger 12: Memory display

To conclude our sessions on debuggers we will add a memory view to our example. These views typically show a dedicated memory range in an arbitrary format. You are not bound to the typical hex view style, still this is what we will use in this tutorial.

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: Implementing Memory representations

As seen in the previous tutorial variables are bound to a StackFrame. Memory dumps instead are bound to the entire DebugTarget. This makes sense as variables may be valid within a dedicated scope only while the memory is a global resource valid for the whole process.

Technically speaking our interpreter does not support a memory as such. Therefore we fake it by copying "executed code" into the simulated memory.

Lets start with the TextMemoryBlock representation:
The main ingredients are a start address, a length, and the content itself. As before with variables we update the content on demand only when the dirty flag is set.

When the debug framework wants to display a dedicated memory area, it queries the TextDebugTarget:
public class TextDebugTarget extends TextDebugElement implements IDebugTarget, IEventProcessor {

 private List<TextMemoryBlock> fMemoryBlocks = new ArrayList<>();

 // ************************************************************
 // IMemoryBlockRetrieval
 // ************************************************************

 @Override
 public boolean supportsStorageRetrieval() {
  return true;
 }

 @Override
 public IMemoryBlock getMemoryBlock(final long startAddress, final long length) throws DebugException {
  TextMemoryBlock memoryBlock = new TextMemoryBlock(this, startAddress, length);
  fMemoryBlocks.add(memoryBlock);
  memoryBlock.fireCreationEvent();

  return memoryBlock;
 }
}
A new TextMemoryBlock is automatically marked dirty. Therefore it triggers an update event once its content is fetched. Once processed by the debugger we get a refresh event for the memory content in question, which gets handled by the TextDebugTarget.

As memory content will typically change during execution we need to invalidate old TextMemoryBlocks by setting them dirty on each suspend event handled by the TextDebugTarget:
public class TextDebugTarget extends TextDebugElement implements IDebugTarget, IEventProcessor {

 @Override
 public void handleEvent(final IDebugEvent event) {

   [...]

   } else if (event instanceof SuspendedEvent) {
    // debugger got started and waits in suspended mode
    setState(State.SUSPENDED);

    getThreads()[0].getTopStackFrame().setLineNumber(((SuspendedEvent) event).getLineNumber());
    getThreads()[0].getTopStackFrame().fireChangeEvent(DebugEvent.CONTENT);

    // mark memory regions as dirty
    for (TextMemoryBlock memory: fMemoryBlocks)
     memory.setDirty();
    
    // inform eclipse of suspended state
    getThreads()[0].fireSuspendEvent(DebugEvent.BREAKPOINT);
    

   } else if (event instanceof MemoryEvent) {
    int startAddress = ((MemoryEvent) event).getStartAddress();
    for (TextMemoryBlock block : fMemoryBlocks) {
     if ((startAddress >= block.getStartAddress()) && (startAddress < block.getEndAddress())) {
      // block affected
      block.update(startAddress, ((MemoryEvent) event).getData());
      block.fireChangeEvent(DebugEvent.CONTENT);
     }
    }
   }
  }
 }
}

Step 2: Registering a rendering

To register a memory block for rendering we need to add a new extension to our plugin.xml.
Add a new renderingBindings item to org.eclipse.debug.ui.memoryRenderings. You need to provide a primaryId for the default rendering to be used. The Browse... button will show a nice overview of available renderings.

If you want to support multiple renderings you may add them to the list of renderingIds. In addition you may add an enablement to the rendering that checks for instanceof(TextMemoryBlock).

Defining your own renderings is beyond the scope of this tutorial but may be implemented using the same extension point.

Step 3: Using the Memory view

First you have to activate the Debug / Memory view. After the debugger suspends for the first time, you may add memory areas to monitor. For a selected area the primary rendering is used by default. New Renderings... allows to open additional ones.

Thursday, November 21, 2013

Debugger 9: Variables support

Lets continue our little debugger example by adding support to display variables.

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: Implementing variable classes

Variables are attached to StackFrames and will only be queried when the debugger is in suspended state. To implement them, we need a variable description stored in TextVariable:
public class TextVariable extends TextDebugElement implements IVariable {

 private final String fName;
 private IValue fValue;

 protected TextVariable(IDebugTarget target, String name, String value) {
  super(target);
  fName = name;
  setValue(value);
 }

 @Override
 public void setValue(String expression) {
  fValue = new TextValue(getDebugTarget(), expression);
 }

 @Override
 public boolean supportsValueModification() {
  return false;
 }

 @Override
 public IValue getValue() {
  return fValue;
 }

 @Override
 public String getName() {
  return fName;
 }

 @Override
 public String getReferenceTypeName() throws DebugException {
  return "text type";
 }
}
The implementation is straight forward and does not allow to modify the variable content.

An actual value is stored in the TextValue class:
public class TextValue extends TextDebugElement implements IValue {

 private final String fValue;

 public TextValue(IDebugTarget target, String value) {
  super(target);

  fValue = value;
 }

 @Override
 public String getReferenceTypeName() throws DebugException {
  return "text type";
 }

 @Override
 public String getValueString() throws DebugException {
  return fValue;
 }

 @Override
 public IVariable[] getVariables() throws DebugException {
  return new IVariable[0];
 }

 @Override
 public boolean hasVariables() throws DebugException {
  return false;
 }
}
To display hierarchical structures a value might hold child variables (eg. fields of a class, elements of a collection, ...). In that case you need to implement hasVariables() and getVariables() too.

You might have noticed that both classes define a method getReferenceTypeName(). While TextVariable.getReferenceTypeName() denotes the declared variable type, TextValue.getReferenceTypeName() denotes the actual type. Looking at a java example
IValue value = new TextValue();
The declared type would be IValue, while the actual type would be TextValue. You may enable these columns in the debug view by opening the Variables view menu and selecting Layout / Select Columns...

Step 2: Updating variables

Now for the big question: "how and when to update variables".

First we notice that variables are queried by the debug framework when a StackFrame element is selected that is in suspended mode. As we should do all our debugger queries in asynchronous mode we have two options:
  1. populate all stack frames automatically with variables on a suspend event in case our user might query them
  2. first deliver outdated/no data and update them as fast as possible
For the demo I chose the second option: we display whatever variables we had stored on the last query and trigger an asynchronous update.
public class TextStackFrame extends TextDebugElement implements IStackFrame {

 private final List<TextVariable> fVariables = new ArrayList<TextVariable>();
 private boolean fDirtyVariables = true;

 @Override
 public synchronized IVariable[] getVariables() {
  if (fDirtyVariables) {
   fDirtyVariables = false;
   getDebugTarget().fireModelEvent(new FetchVariablesRequest());
  }

  return fVariables.toArray(new IVariable[fVariables.size()]);
 }

 public synchronized void setVariables(Map<String, String> variables) {
  for (String name : variables.keySet()) {
   boolean processed = false;
   // try to find existing variable
   for (TextVariable variable : fVariables) {
    if (variable.getName().equals(name)) {
     variable.setValue(variables.get(name));
     variable.fireChangeEvent(DebugEvent.CONTENT);

     processed = true;
     break;
    }
   }

   if (!processed) {
    // not found, create new variable
    TextVariable textVariable = new TextVariable(getDebugTarget(), name, variables.get(name));
    fVariables.add(textVariable);
    textVariable.fireCreationEvent();
   }
  }
 }

 @Override
 public synchronized void fireChangeEvent(int detail) {
  fDirtyVariables = true;

  super.fireChangeEvent(detail);
 }

 @Override
 public boolean hasVariables() {
  return getVariables().length > 0;
 }
}


The TextDebugger sends its list of variables as a new event to the TextDebugTarget, which simply delegates the call to the topmost StackFrame within its handleEvent() method. During the update we send either change events or creation events for the updated variables. Of course a real implementation should be more intelligent than the implemented version above and use intelligent comparisons and caching.

If you want changed contents to be highlighted you should implement IVariable.hasValueChanged(). An example implementation is provided in the source code on github.

 
UI problems

There is one behavior, I could not fix yet: Sometimes the Variables view remains empty when the debugger suspends. Pushing the view to back and bringing it to front again solves this issue. Not sure if this is a problem in my code or the debugger framework. See bug 528120 for details.

Wednesday, November 20, 2013

Debugger 8: Adding "Run to line" support

Dedicated Run to line support is quite easy to implement. Most parts are already provided by the debug framework, we only need to consume it in the right way.

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: Implement editor support

To use the run to line functionality from the debug framework the source code editor needs to implement IRunToLineTarget.
package com.codeandme.textinterpreter.debugger;

public class RunToLineTarget implements IRunToLineTarget {

 @Override
 public void runToLine(IWorkbenchPart part, ISelection selection, ISuspendResume target) throws CoreException {
  if (target instanceof TextStackFrame) {
   IDebugTarget debugTarget = (IDebugTarget) ((IAdaptable) target).getAdapter(IDebugTarget.class);
   if (debugTarget instanceof TextDebugTarget) {
    IBreakpoint breakpoint = new TextRunToLineBreakpoint(((TextDebugTarget) debugTarget).getFile(), getLineNumber(selection));
    RunToLineHandler handler = new RunToLineHandler(debugTarget, target, breakpoint);
    handler.run(new NullProgressMonitor());
   }
  }
 }

 @Override
 public boolean canRunToLine(IWorkbenchPart part, ISelection selection, ISuspendResume target) {
  return (target instanceof TextStackFrame);
 }

 private static int getLineNumber(ISelection selection) {
  if (selection instanceof ITextSelection)
   // text selections are 0 based
   return ((ITextSelection) selection).getStartLine() + 1;

  return 0;
 }
}
On a runToLine() call we create a new TextRunToLineBreakpoint which the handler automatically activates. The only difference to a TextLineBreakpoint is that we set its PERSISTED flag to false.

Of course the default text editor does not implement IRunToLineTarget. Therefore we need an adapter similar to what we did for the UI breakpoint integration.

That is everything we need to code for the Run to line functionality. Easy, isn't it?

The debug framework implementation also honors the preferences setting Run/Debug / Skip breakpoints during a 'Run to Line' operation. To me it seems as the implementation might be slightly buggy though. As the demo interpreter is lightning fast, conventional breakpoints will not be disabled fast enough sometimes and might still trigger.

Step 2: Register UI integration

To add the action into the editor context menu we need to add a new org.eclipse.ui.popupMenus extension point. Add a viewerContribution to it with id set to "textEditor.editorActions" and targetID set to "#TextEditorContext". Now add the Run to line action as provided in the screenshot.

Tuesday, November 19, 2013

Debugger 7: Source lookup

Having stepping and breakpoint support is nice, but the user needs some visual feedback where the debugger currently suspended. The source code should be opened in an editor and the current line should be highlighted.

The debug framework comes with a dedicated solution that looks for source files in dedicated project folders. If you have a project setup where you define source lookup folders this is the way to go for you. Another option is to implement source lookup completely from scratch. In our case the latter option is the easier one.

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: The big picture

When a debug thread starts processing, it typically calls files, methods and functions. Such elements denote the call stack. The debugger framework uses this stack to resolve the current location within the source code. Each stack element may have a dedicated source location (editor content + selection within editor) attached to it.

An ISourceLocator is attached to a launch extension, typically by using the org.eclipse.debug.core.sourceLocators extension point. This locator converts an IStackFrame to a source element by means of getSourceElement(IStackFrame stackFrame). This element is used to retrieve an IEditorInput and an editorID from the debug model. Now an editor can be opened. The line selection is retrieved from the stack frame.


Step 2: Adding StackFrames

The IStackFrame implementation we use is really simple: we only allow to store the current line number and to retrieve the source file. As we have only one source file available, we store it directly within our debug target.

Create a new class TextStackFrame:
public class TextStackFrame extends TextDebugElement implements IStackFrame {

 private final IThread fThread;
 private int fLineNumber = 1;

 public TextStackFrame(IDebugTarget target, IThread thread) {
  super(target);
  fThread = thread;
 }

 @Override
 public IThread getThread() {
  return fThread;
 }

 @Override
 public int getLineNumber() {
  return fLineNumber;
 }

 @Override
 public int getCharStart() {
  return -1;
 }

 @Override
 public int getCharEnd() {
  return -1;
 }

 @Override
 public String getName() {
  return getSourceFile().getName() + ", line " + getLineNumber();
 }

 public void setLineNumber(int lineNumber) {
  fLineNumber = lineNumber;
 }

 public IFile getSourceFile() {
  return (getDebugTarget()).getFile();
 }
}
For a line oriented language getLineNumber() is important as it is used for the line marker when our code is suspended. Make sure both getCharStart() and getCharEnd() return -1 in that case. If you want to mark a section within a line, implement getCharStart() and getCharEnd().

As we do not have multiple source files, function calls or similar things we can stick to one static StackFrame for the whole debugging session. It is registered and updated from the TextDebugTarget:
public class TextDebugTarget extends TextDebugElement implements IDebugTarget, IEventProcessor {

 @Override
 public void handleEvent(final IDebugEvent event) {

  if (!isDisconnected()) {

   if (event instanceof DebuggerStartedEvent) {

    [...]

    // create stack frame
    TextStackFrame stackFrame = new TextStackFrame(this, thread);
    thread.addStackFrame(stackFrame);
    stackFrame.fireCreationEvent();

   } else if (event instanceof SuspendedEvent) {
    // debugger got started and waits in suspended mode
    setState(State.SUSPENDED);

    getThreads()[0].getTopStackFrame().setLineNumber(((SuspendedEvent) event).getLineNumber());
    getThreads()[0].getTopStackFrame().fireChangeEvent(DebugEvent.CONTENT);

    // inform eclipse of suspended state
    fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
   }

   [...]

  }
 }
}
It is important that the StackFrame is registered before the debug UI suspends for the first time. If this is not the case the Debug view will not fully expand all its nodes and therefore not display the suspended StackFrame and the according source file in the first place. A user would have to manually expand and select the StackFrame.

When executing you will now see a StackFrame child node for our thread, indicating the executed file and line number.

Step 3: Resolving source files

Resolving source elements is handled by a SourceLocator. We have to register a new one in com.codeandme.debugger.textinterpreter.debugger/plugin.xml:

The implementation of TextSourceLocator is straight forward, we only need to deal with getSourceElement():
public class TextSourceLocator implements IPersistableSourceLocator {

 @Override
 public Object getSourceElement(IStackFrame stackFrame) {
  if (stackFrame instanceof TextStackFrame)
   return ((TextStackFrame) stackFrame).getSourceFile();

  return null;
 }
}
Having  a source locator we now may register it to the launch configuration. Open com.codeandme.debugger.textinterpreter.ui/plugin.xml, navigate to the Text Interpreter launchConfigurationType and set sourceLocatorId to com.codeandme.debugger.textinterpreter.debugger.sourceLocator.

Step 4: Adding editor support

The final step is to define the editor to be used. This is handled by TextDebugModelPresentation:
public class TextDebugModelPresentation implements IDebugModelPresentation {

 @Override
 public IEditorInput getEditorInput(Object element) {
  if (element instanceof IFile)
   return new FileEditorInput((IFile) element);

  return null;
 }

 @Override
 public String getEditorId(IEditorInput input, Object element) {
  if (element instanceof IFile)
   return PlatformUI.getWorkbench().getEditorRegistry().getDefaultEditor(((IFile) element).getName()).getId();

  return null;
 }
}
Using the default editor for the source file is a good choice as it is up to the user to define a dedicated editor. We might add some fallback code to open the default text editor instead of returning null in an error case.

Code lookup should work by now. Give it a try and step through some sample scripts.

Monday, November 18, 2013

Debugger 6: Debugger breakpoint integration

After we have breakpoints available in the UI we need to teach our debug model and debugger to use them.

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: Adding breakpoint support to the model

Debuggers generally should suspend when a new source file is loaded. Then the model can attach breakpoints to the debugger and resume operation afterwards.

Additionally we need to listen for breakpoint changes to add new breakpoints to the debugger or to remove them when they get disabled or removed by the user.

Lets start with the listener implementation:
public class TextDebugTarget extends TextDebugElement implements IDebugTarget, IEventProcessor {

 @Override
 public boolean supportsBreakpoint(final IBreakpoint breakpoint) {
  if (fFile.equals(breakpoint.getMarker().getResource())) {
   // breakpoint on our source file
   return true;
  }

  return false;
 }

 private boolean isEnabledBreakpoint(IBreakpoint breakpoint) {
  try {
   return breakpoint.isEnabled() && DebugPlugin.getDefault().getBreakpointManager().isEnabled();
  } catch (CoreException e) {
   // ignore invalid breakpoint
  }

  return false;
 }

 // ************************************************************
 // IBreakpointListener
 // ************************************************************

 @Override
 public void breakpointAdded(final IBreakpoint breakpoint) {
  if ((supportsBreakpoint(breakpoint)) && (isEnabledBreakpoint(breakpoint)))
   fireModelEvent(new BreakpointRequest(breakpoint, BreakpointRequest.ADDED));
 }

 @Override
 public void breakpointRemoved(final IBreakpoint breakpoint, final IMarkerDelta delta) {
  if (supportsBreakpoint(breakpoint))
   fireModelEvent(new BreakpointRequest(breakpoint, BreakpointRequest.REMOVED));
 }

 @Override
 public void breakpointChanged(final IBreakpoint breakpoint, final IMarkerDelta delta) {
  breakpointRemoved(breakpoint, delta);
  breakpointAdded(breakpoint);
 }
}
As breakpoints might be targeting non relevant source files or even other target languages we need to verify that a breakpoint is related to our source file. If yes, we send an adequate event to our debugger.

While there are no further checks for a breakpoint removal, we only add active breakpoints. There exist two separate enablements we need to verify: the breakpoint itself can be disabled and/or the BreakpointManager can be disabled globally.

Next we need to take care that we track breakpoint changes accordingly by subscribing to the BreakpointManager.
public class TextDebugTarget extends TextDebugElement implements IDebugTarget, IEventProcessor {

 @Override
 public void handleEvent(final IDebugEvent event) {

  if (!isDisconnected()) {
   if (event instanceof DebuggerStartedEvent) {
    // create debug thread
    TextThread thread = new TextThread(this);
    fThreads.add(thread);
    thread.fireCreationEvent();

    // debugger got started and waits in suspended mode
    setState(State.SUSPENDED);

    // add breakpoint listener
    DebugPlugin.getDefault().getBreakpointManager().addBreakpointListener(this);

    // attach deferred breakpoints to debugger
    IBreakpoint[] breakpoints = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints(getModelIdentifier());
    for (IBreakpoint breakpoint : breakpoints)
     breakpointAdded(breakpoint);

    // inform eclipse of suspended state
    fireSuspendEvent(DebugEvent.CLIENT_REQUEST);

   } else if (event instanceof TerminatedEvent) {
    // debugger is terminated
    setState(State.TERMINATED);

     // unregister breakpoint listener
     DebugPlugin.getDefault().getBreakpointManager().removeBreakpointListener(this);

    // we do not need our dispatcher anymore
    fDispatcher.terminate();

    // inform eclipse of terminated state
    fireTerminateEvent();
   }
  }
 }
}
On a DebuggerStartedEvent we need to set all relevant breakpoints. If we stay suspended or immediately call resume() is a matter of taste.

Step 2: Adding breakpoint support to the debugger

The debugger needs to cache breakpoints and check them on a line change event. In our case we will simply track line numbers where we want to suspend:
public class TextDebugger implements IDebugger, IEventProcessor {

 private final Collection<Integer> fBreakpoints = new HashSet<>();

 @Override
 public boolean isBreakpoint(final int lineNumber) {
  if (fBreakpoints.contains(lineNumber))
   return true;

  return fIsStepping;
 }

 @Override
 public void handleEvent(final IDebugEvent event) {

  [...]

  } else if (event instanceof BreakpointRequest) {
   int line = ((BreakpointRequest) event).getBreakpoint().getMarker().getAttribute(IMarker.LINE_NUMBER, -1);
   if (line != -1) {
    if (((BreakpointRequest) event).getType() == BreakpointRequest.ADDED)
     fBreakpoints.add(line);

    else if (((BreakpointRequest) event).getType() == BreakpointRequest.REMOVED)
     fBreakpoints.remove(line);
   }
  }
 }
}

Breakpoints are stored for a dedicated IDebugModelPresentation. We introduced one in the previous tutorial. Now we need to let TextDebugElement.getModelIdentifier() refer to this ID. If you do not set this identifier, you will not be able to find any breakpoints.

The mechanisms for setting and caching breakpoints might get a bit more complicated for a real world solution as you might support different kinds of breakpoints like function breakpoints or watchpoints.

Friday, November 15, 2013

Debugger 5: UI breakpoint integration

In the previous tutorial we implemented stepping through our source code. While single stepping is quite nice, we need to add breakpoint support for a better debugging experience. By the end of this tutorial we will be able to set them using the UI:

The debugger will not yet be aware of these breakpoints. This functionality will be implemented in the next tutorial. In case your editor already supports setting breakpoints, you may skip this tutorial.

In eclipse there exist different kinds of breakpoints: line breakpoints (stop execution at a certain line), function breakpoints (stop whenever function foo() is called), watchpoints (stop whenever a variable is accessed/changed) and some more. Not all of them make sense for a dedicated language. In this tutorial we will have a closer look at line breakpoints only.

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: Breakpoint definition

Breakpoints are defined by the breakpoints extension point. To make such breakpoints persistent, eclipse uses markers. So each breakpoint is backed by a marker which is bound to a workspace resource.

Create a new org.eclipse.core.resource.markers extension in com.codeandme.debugger.textinterpreter.debugger/plugin.xml. The marker ID should be a single name without any plugin name qualifier before it, so set it to "textLineBreakpointMarker". The full marker name is automatically prepended with the Plug-in ID. Our marker should be a subtype of org.eclipse.debug.core.lineBreakpointMarker and persistable. Set these attributes by adding subnodes to your marker definition (see screenshot below).

Afterwards we create an org.eclipse.debug.core.breakpoints extension which refers to the marker type just created before.


The implementation is straight forward. We only add a constructor to attach a breakpoint to a resource:
public class TextLineBreakpoint extends LineBreakpoint {

 /**
  * Default constructor needed by the Eclipse debug framework. Do not remove!
  */
 public TextLineBreakpoint() {
 }

 public TextLineBreakpoint(final IResource resource, final int lineNumber) throws CoreException {
  this(resource, lineNumber, true);
 }

 protected TextLineBreakpoint(final IResource resource, final int lineNumber, final boolean persistent) throws CoreException {
  IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
   @Override
   public void run(IProgressMonitor monitor) throws CoreException {
    IMarker marker = resource.createMarker(Activator.PLUGIN_ID + ".textLineBreakpointMarker");
    setMarker(marker);
    marker.setAttribute(IBreakpoint.ENABLED, true);
    marker.setAttribute(IBreakpoint.PERSISTED, persistent);
    marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
    marker.setAttribute(IBreakpoint.ID, getModelIdentifier());
    marker.setAttribute(IMarker.MESSAGE, "Line Breakpoint: " + resource.getName() + " [line: " + lineNumber + "]");
   }
  };
  run(getMarkerRule(resource), runnable);
 }

 @Override
 public String getModelIdentifier() {
  return TextDebugModelPresentation.ID;
 }
}
Make sure to keep the default constructor, as it is needed by the debug framework to restore breakpoints.

As you can see, we used a new class here: TextDebugModelPresentation. It is needed to embrace all parts of the debug model for a certain debugger type. For now it is an empty implementation of IDebugModelPresentation, only providing its ID as a static constant. Do not forget to register it as an extension in com.codeandme.debugger.textinterpreter.debugger/plugin.xml.

Step 2: Add breakpoint support to editors

Users want to set their breakpoints in the code editor. For that our editor of choice needs to implement the IToggleBreakpointsTarget interface. Unfortunately the default text editor we use does not support this interface. Therefore we need to create an adapter for the editor. The adapter pattern is out of scope of this tutorial. If you need more information on how to create adapters you may read this article.

The adapter factory creates instances of TextLineBreakpointTarget:
public class TextLineBreakpointTarget implements IToggleBreakpointsTarget {

 @Override
 public boolean canToggleLineBreakpoints(IWorkbenchPart part, ISelection selection) {
  return true;
 }

 @Override
 public void toggleLineBreakpoints(IWorkbenchPart part, ISelection selection) throws CoreException {
  ITextEditor textEditor = getEditor(part);
  if (textEditor != null) {
   IResource resource = (IResource) textEditor.getEditorInput().getAdapter(IResource.class);
   ITextSelection textSelection = (ITextSelection) selection;
   int lineNumber = textSelection.getStartLine();
   IBreakpoint[] breakpoints = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints(TextDebugModelPresentation.ID);
   for (int i = 0; i < breakpoints.length; i++) {
    IBreakpoint breakpoint = breakpoints[i];
    if (resource.equals(breakpoint.getMarker().getResource())) {
     if (((ILineBreakpoint) breakpoint).getLineNumber() == (lineNumber + 1)) {
      // existing breakpoint; delete
      breakpoint.delete();
      return;
     }
    }
   }

   // new breakpoint; create
   TextLineBreakpoint lineBreakpoint = new TextLineBreakpoint(resource, lineNumber + 1);
   DebugPlugin.getDefault().getBreakpointManager().addBreakpoint(lineBreakpoint);
  }
 }
}
Similar to the step and resume commands in the previous tutorials we have to implement some canToggleBreakpoint() methods. In our case we allow for LineBreakpoints only. The toggling itself is simple:
  1. get the active editor (line 10)
  2. get the current line number from the selection (lines 13, 14)
  3. look for an existing breakpoint at that line (lines 15-25)
  4. optionally delete an existing breakpoint or create a new one (lines 21/29)
When launching your application you should be able to set breakpoints using the Run/Toggle Breakpoint entry from the main menu. This entry is available in the Debug perspective, so make sure to activate it first.

Step 3: Toggle breakpoints from the ruler context menu

To add an item to the context menu of the ruler we need to use actions. Open the plugin.xml of the debugger project and add a new org.eclipse.ui.popupMenus extension. Add a new viewerContribution to it. Set id to "textEditor.rulerActions" and targetID to "#TextRulerContext". Now add a new action with id set to "pda.editor.ruler.toggleBreakpointAction". The implementation is rather boring as it only resolves the line number of the mouse click and then uses our IToggleBreakpointsTarget from before to toggle a line breakpoint.

Toggling breakpoints now works from the ruler context menu. Note that a double click still sets bookmarks and does not toggle breakpoints.
 
Conclusion

We are now able to set breakpoints in the UI. But our debugger does not use them right now. In the next tutorial we will implement debugger support for breakpoints.

Thursday, November 14, 2013

Debugger 4: Stepping, suspending and other actions

During the previous tutorial we created the basic structure for our debugger. Today we will work on the UI integration part.

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
For each of these groups exists an interface (ITerminate, ISuspendResume, IDisconnect and IStep). Whenever the selected element implements one of these interfaces the corresponding toolbar actions become available. Enablement will be handled by interface methods like canDisconnect(), canResume() ...

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.

Monday, November 11, 2013

Debugger 2: The launch framework (not only for debuggers)

In the previous tutorial we created a simple interpreter for a fictional programming language. Now we will add launch support for such scripts. We still will not be debugging yet, but launching is mandatory before we can start a debug session. So be patient, these are the last preparations we need to take.

If launching is something you are already familiar with, you might still want to check out the TextLaunchDelegate implementation, as it provides a convenient multi-purpose launcher.

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. 

Launching

Eclipse provides two ways of launching: you may use a LaunchDelegate or a contextual LaunchShortcut. If your launch is about resources in your workspace you will quite probably implement both methods. After this tutorial we should have launch support for our interpreter.


Step 1: Launch Delegates

Create a new Plug-in Project called com.codeandme.debug.textinterpreter.ui. Switch to the Extensions tab and Add... a new launchConfigurationTypes extension. Provide a unique id and a name. The delegate class implements launch instructions, eg. creating and starting an interpreter. We will look at the implementation a bit later. Finally define the modes this LaunchConfiguration applies to. You may provide "run" and/or "debug" as a comma separated list.

Next add a launchConfigurationTypeImages extension to provide a nice icon for our LaunchConfiguration. The configTypeID needs to match the ID we provided for our launchConfigurationType before. Not much we can do wrong here.

Finally create a launchConfigurationTabGroups extension to provide the tabs to be visible when we open the Run Configurations... dialog. The class implementation is really simple as we only need to set an array of available tabs:
package com.codeandme.debugger.textinterpreter.ui.tabs;

import org.eclipse.debug.ui.AbstractLaunchConfigurationTabGroup;
import org.eclipse.debug.ui.CommonTab;
import org.eclipse.debug.ui.ILaunchConfigurationDialog;
import org.eclipse.debug.ui.ILaunchConfigurationTab;

public class LaunchConfigurationTabGroup extends AbstractLaunchConfigurationTabGroup {

    @Override
    public void createTabs(final ILaunchConfigurationDialog dialog, final String mode) {
        setTabs(new ILaunchConfigurationTab[] { new MainTab(new String[] { "txt" }), new CommonTab() });
    }
}
MainTab is a custom class that lets the user select a project and a file from the workspace. As this is pure UI programming I leave it to you to examine the implementation.

More interesting is the launchMode you may add to the launchConfigurationTabGroup: for one you can provide the mode, which means you may have different  tabs for "run" and "debug". The perspective property allows to set a default perspective when the launch is activated. In debug mode you will typically want to switch to the debug perspective. Right now we will just provide the "run" launchMode.

Step 2: Launch Shortcuts

Launch shortcuts are displayed in the context menu of workspace files and editors. They can be used to quickly launch a file without creating a LaunchConfiguration first.

The launchShortcuts extensions is very similar to the LaunchDelegates we had before. We have modes, an icon and an implementing class. With a contextualLaunch we may provide an enablement. In our case we expect that only one file is selected and its file extension matches "txt".

As we already defined a LaunchConfiguationType before, we may add it to the shortcut by providing a configurationType child node.


Step 3: Launcher implementation

We did not investigate the launcher classes for both launch types yet. As LaunchDelegates and LaunchShortcuts are very similar, I decided to implement both of them in a single class TextLaunchDelegate:
package com.codeandme.debugger.textinterpreter.ui;

public class TextLaunchDelegate implements ILaunchShortcut, ILaunchShortcut2, ILaunchConfigurationDelegate {

 @Override
 public void launch(final ILaunchConfiguration configuration, final String mode, final ILaunch launch, final IProgressMonitor monitor) throws CoreException {
  IFile file = getSourceFile(configuration);
  if (file != null) {
   // we have a valid script, lets feed it to the script engine
   launch(file, configuration, mode, launch, monitor);
  }
 }

 private void launch(final IResource file, final String mode) {

  if (file instanceof IFile) {
   // try to save dirty editors
   PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().saveAllEditors(true);

   try {
    ILaunchConfiguration[] configurations = getLaunchConfgurations(file);
    if (configurations.length == 0) {
     // no configuration found, create new one
     ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager();
     ILaunchConfigurationType type = manager.getLaunchConfigurationType(TextLaunchConstants.LAUNCH_CONFIGURATION_TYPE_ID);

     ILaunchConfigurationWorkingCopy configuration = type.newInstance(null, file.getName());
     configuration.setAttribute(TextLaunchConstants.PROJECT, file.getProject().getName());
     configuration.setAttribute(TextLaunchConstants.FILE_LOCATION, file.getProjectRelativePath().toPortableString());

     // save and return new configuration
     configuration.doSave();

     configurations = new ILaunchConfiguration[] { configuration };
    }

    // launch
    configurations[0].launch(mode, new NullProgressMonitor());

   } catch (CoreException e) {
    // could not create launch configuration, run file directly
    launch((IFile) file, null, mode, null, new NullProgressMonitor());
   }
  }
 }

 private void launch(final IFile file, final ILaunchConfiguration configuration, final String mode, final ILaunch launch, final IProgressMonitor monitor) {
  // create new interpreter
  TextInterpreter interpreter = new TextInterpreter();

  try {
   interpreter.setCode(toString(file));
   interpreter.schedule();

  } catch (Exception e) {
   // TODO handle this exception (but for now, at least know it
   // happened)
   throw new RuntimeException(e);
  }
 }
}
The main intention here is: no matter how our launch was triggered, try to find/create a valid launch configuration and launch that configuration. This is fairly easy for ILaunchConfigurationDelegate.launch(...) as we already get a valid configuration in the parameters list.

ILaunchShortcut.launch() provides an editor or a selection. In that case we extract the resource file to execute. Afterwards we parse all launch configurations and compare their source file with our resource (line 21). The first existing configuration with a matching source file gets executed (line 38). If no matching configuration exists, we create (and store) a new one (lines 22 - 35).

This matches quite closely the behavior of Java LaunchShortcuts: after such a launch you will find a ready to use LaunchConfiguration.

Optional: Dependencies for Launch Shortcuts

When you are running your own RCP and do not have JDT bundled with your product you might encounter a problem where Run As.../Debug As... context menu entries vanish in the navigator views after using them once. While we wait for bug 415317 to be fixed, you might add org.eclipse.jdt.ui and org.eclipse.jdt.debug.ui plug-ins to your product as a workaround.


Optional: Add Run as... / Debug as... toolbar entries to perspective

Eclipse provides nice run/debug toolbar buttons every Eclipse user knows. If you are using a custom perspective, these entries might not be visible.

To enable them add a perspectiveExtensions extension to your plugin.xml. Define the targetId (which is the id of a perspective) to alter (use * as a wildcard pattern) and provide an actionSet child node pointing its id to "org.eclipse.debug.ui.launchActionSet".

Debugger 1: A fictional interpreter

Recently I was playing around with the debug framework to implement a custom debugger. While there exists some documentation, it still is somewhat tricky to fit all parts together. So I am going to start a new series on debugging support.

Debug Framework Tutorials

For a list of all debug related tutorials see Debug Framework Tutorials Overview.

In this series we will handle launching, debugging with all its flavors (stepping, suspend/resume, breakpoints), source lookup to trace the current code location and finally variables and memory support.

But before we can even think of debugging, we need a language definition and an interpreter with debugging support.

Displayed source code will only show relevant parts, please refer to the provided links to see the full implementation.

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. 

Language definition

Lets keep things simple and use a fictional interpreter that simply processes lines of code by printing them to the standard output. Therefore any text file can be used as source input. This way, we may use the Eclipse default Text Editor as our source editor.

Our simple language shall support variable definitions in the form "my variable name = some content". To also make use of variables we will allow for placeholders ${variableName} that will get replaced by the actual variable content.

Finally we support memory dumps. For simplicity each processed line content will add to our memory dump.

A simple script example looks as follows:
hello world
first name = Christian
${first name}, you just defined a variable

counter = 23

we are running
our interpreter

Interpreter implementation

Lets have a look at the TextInterpreter implementation (just the relevant parts):
package com.codeandme.debugger.textinterpreter;

public class TextInterpreter extends Job {

 public TextInterpreter() {
  super("Text interpreter");
 }


 /**
  * Set the source code to be executed. As our interpreter is line oriented, we immediately split the text into separate lines.
  * 
  * @param code
  *            source code
  */
 public void setCode(final String code) {
  String[] lines = code.replaceAll("\r\n", "\n").split("\n");
  fLines = new LinkedList<String>(Arrays.asList(lines));
 }

 @Override
 protected synchronized IStatus run(final IProgressMonitor monitor) {
  fTerminated = false;
  fLineNumber = 1;

  fVariables.clear();
  fMemory = new StringWriter();

  debug(DebugAction.LOADED);

  while ((!fTerminated) && (!monitor.isCanceled()) && (!fLines.isEmpty())) {

   // read line to execute
   String line = fLines.remove(0);
   line = evaluate(line);

   // "execute" line of code
   System.out.println(line);

   // alter our simulated memory
   fMemory.append(line);

   // advance by one line
   fLineNumber++;

   debug(DebugAction.SUSPEND);
  }

  debug(DebugAction.TERMINATE);

  return Status.OK_STATUS;
 }

 public String evaluate(String lineOfCode) {
  // do variable replacement
  Matcher matcher = VARIABLE_MATCHER.matcher(lineOfCode);
  while (matcher.matches()) {
   lineOfCode = matcher.replaceFirst(fVariables.get(matcher.group(1)));
   matcher = VARIABLE_MATCHER.matcher(lineOfCode);
  }

  // try to register variable
  String[] tokens = lineOfCode.split("=");
  if (tokens.length == 2) {
   // variable found
   fVariables.put(tokens[0].trim(), tokens[1].trim());
  }

  return lineOfCode;
 }
}

As interpreters typically run in their own process or thread our interpreter is implemented as a Job. The two important methods so far are setCode() and run(). Everything else is for debugging support and will be explained in the following tutorials.
The main loop (line 31) processes line by line by printing, altering the memory and extracting variables. This is done until all lines are processed or the Job got terminated via the monitor or an external call.

All we have to do to start our interpreter is:
TextInterpreter interpreter = new TextInterpreter();
interpreter.setCode("hello world");
interpreter.schedule();
Launching is a topic of its own and will be briefly handled in the next tutorial.

Debugger 3: A tale of debuggers, processes and threads

Finally you have made it: all preparations are done and we are ready to start with our debugger. By the end of this tutorial we will be able to run a simple debug session:

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.

Debug Framework briefly explained

As each debugger is different, the Debug Framework uses a generic model to interact with the debugger. The main entry point into this model is the DebugTarget. It consists of a single Process which may contain one to many threads. Both can be real (eg. for an external debugger) or virtual implementations if our debugger runs in the eclipse context. 

When threads are suspended they may provide StackFrames. These represent executable units typically with an individual scope. In Java debugging each instance on the call stack is represented as a frame. StackFrames also keep track of variables.

As debugging is triggered by UI events (resume, step into, ...) all interactions between the debugger and the model should be handled in a separate event processing thread. Eclipse uses a "fire-and-forget" policy here. Eg when someone pushes the resume button, IThread.resume() is called, which should return immediately. In the background the event processor should then take means to inform the debugger of the resume request. In case the debugger changes its state to resumed, it will inform our model which will update its internal representation and then send an event to the framework.

The decoupling using events is a bit harder to implement, but necessary to keep the UI responsive.

For a detailed description of the debug framework have a look at

You should definitely read some of the above to fully understand what we are doing in the upcoming tutorials.

As we need to provide lots of classes now I advise to download the project from github. Here we will only look at the relevant parts of the implementation.

Step 1: The event dispatcher

For all debugging stuff we will use a new Plug-In called com.codeandme.debugger.textinterpreter.debugger.

Communication between the debugger and the model (see the diagram above) is handled by our EventDispatchJob. It accepts incoming events with addEvent(), and delivers them in the handleEvent() method. Events itself can either be IModelRequests (for interactions in eclipse that should trigger an action in the debugger) or IDebuggerEvents (when the debugger changes its state, hits breakpoints, ...). Both are just marker interfaces.

The model and the debugger need to implement the IEventProcessor interface to accept events.

Step 2: The debugger

The TextDebugger has to implement the IDebugger interface from our interpreter and the IEventProcessor interface to handle events received from the EventDispatcher. The implementation refers to the left hand side of the block diagram we have seen above. Let's have a look at some relevant code parts:
public class TextDebugger implements IDebugger, IEventProcessor {

 @Override
 public void loaded() {
  fireEvent(new DebuggerStartedEvent());
 }

 @Override
 public void resumed() {
  fireEvent(new ResumedEvent());
 }

 @Override
 public void terminated() {
  fireEvent(new TerminatedEvent());
 }

 @Override
 public void handleEvent(final IDebugEvent event) {
  if (event instanceof ResumeRequest)
   fInterpreter.resume();
 }

 private void fireEvent(final IDebugEvent event) {
  fDispatcher.addEvent(event);
 }
}
We can see that events are fired whenever our interpreter changes its state (loaded(), resumed(), terminated()). The debugger simply passes them on to the dispatcher. The loaded event is a special kind of a resumed event: it tells our model that a new interpreter has been set up and that source has been loaded for processing. In future the model will set breakpoints before the interpreter starts executing the code.

Whenever the model sends events to the debugger, handleEvent() is called by the dispatcher. In our case we simply allow to resume (which is needed after a loaded event, that automatically suspends the interpreter).

Step 3: The model

Lets start with the TextDebugTarget, as it is our main entry point into the model. Again we will look at some relevant parts only.
public class TextDebugTarget extends TextDebugElement implements IDebugTarget, IEventProcessor {

 public enum State {
  NOT_STARTED, SUSPENDED, RESUMED, TERMINATED, DISCONNECTED
 };

 public TextDebugTarget(final ILaunch launch, IFile file) {
  super(null);

  fLaunch = launch;
  fFile = file;

  fireCreationEvent();

  // create a process
  fProcess = new TextProcess(this);
  fProcess.fireCreationEvent();
 }

 void fireModelEvent(final IDebugEvent event) {
  fDispatcher.addEvent(event);
 }

 @Override
 public void handleEvent(final IDebugEvent event) {

  if (!isDisconnected()) {
   if (event instanceof DebuggerStartedEvent) {
    // create debug thread
    TextThread thread = new TextThread(this);
    fThreads.add(thread);
    thread.fireCreationEvent();

    // debugger got started and waits in suspended mode
    fState = State.SUSPENDED;

    // inform eclipse of suspended state
    fireSuspendEvent(DebugEvent.CLIENT_REQUEST);

   } else if (event instanceof ResumedEvent) {
    fState = State.RESUMED;

    // inform eclipse of resumed state
    fireResumeEvent(DebugEvent.UNSPECIFIED);

   } else if (event instanceof TerminatedEvent) {
    // debugger is terminated
    fState = State.TERMINATED;

    // we do not need our dispatcher anymore
    fDispatcher.terminate();

    // inform eclipse of terminated state
    fireTerminateEvent();
   }
  }
 }

 // ************************************************************
 // ISuspendResume
 // ************************************************************

 @Override
 public boolean canResume() {
  return isSuspended();
 }

 @Override
 public boolean isSuspended() {
  return (fState == State.SUSPENDED);
 }

 @Override
 public void resume() {
  // resume request from eclipse

  // send resume request to debugger
  fireModelEvent(new ResumeRequest());
 }

Eclipse provides a nice base class for all model parts: DebugElement. We add our own base class TextDebugElement on top of that. Right now it does nothing, but we will need it for the next tutorial.

The first thing we see is, that the model needs to track the state of the debugger (line 4, 35, 41, ...). This is necessary to enable/disable debugging options on the toolbar.

In the constructor we fire a creation event (line 13). This event is sent to the Eclipse Debug Framework and might trigger UI interactions (like the switch to the debug perspective). After that we create a TextProcess and again fire a creation event for that. We will see this pattern all the time when interacting with the framework. Whenever we think that something relevant has changed, we need to inform the framework of that change by sending an event. There is no other means of interaction available for our model.

handleEvent() is the counterpart to TextDebugger.handleEvent(). On a DebuggerStartedEvent we create a new Thread, adjust our state model and tell Eclipse that our debugger is in suspended mode. Eclipse will then enable the resume toolbar button for us. When the TerminateEvent arrives we need to shut down our scheduler, otherwise it would run forever.

Before Eclipse really enables the resume button (as described above) it queries our model calling canResume(). We only allow Eclipse to resume when the debugger is currently suspended. resume() then triggers a new event that is passed to the Dispatcher and finally handled by the debugger.

TextDebugTarget needs to implement lots of interfaces for breakpoint handling, terminate/resume/suspend support and many things more. Right now these implementations will simply throw exceptions as they are not implemented yet. Within the next tutorials we will improve them step by step.

TextProcess and TextThread are almost empty, so we do not need to investigate them right now.

Step 4: Add debugging support to launchers

When we created our launch extensions, we just allowed run modes. To add debug launch targets open com.codeandme.debugger.textinterpreter.ui/plugin.xml and switch to Extensions.
  • First go to the launchConfigurationType and set modes to "run,debug"
  • Next locate our launchConfigurationTabGroup and add a new launchMode. There set mode to "debug" and perspective to "org.eclipse.debug.ui.DebugPerspective".
  • Finally open the launchShortcut and set modes to "run,debug".
Step 5: Adding launch implementation

Open TextLaunchDelegate.java. Locate the launch method at the very end of the file and replace it with following code:
 private void launch(final IFile file, final ILaunchConfiguration configuration, final String mode, final ILaunch launch, final IProgressMonitor monitor) {
  // create new interpreter
  TextInterpreter interpreter = new TextInterpreter();

  try {
   if (ILaunchManager.DEBUG_MODE.equals(mode)) {

    // create debugger
    TextDebugger debugger = new TextDebugger(interpreter);
    interpreter.setDebugger(debugger);

    // create debugTarget
    TextDebugTarget debugTarget = new TextDebugTarget(launch, file);

    // create & launch dispatcher
    EventDispatchJob dispatcher = new EventDispatchJob(debugTarget, debugger);

    // attach dispatcher to debugger & debugTarget
    debugger.setEventDispatcher(dispatcher);
    debugTarget.setEventDispatcher(dispatcher);

    // add debug target to launch
    launch.addDebugTarget(debugTarget);
   }

   interpreter.setCode(toString(file));
   interpreter.schedule();

  } catch (Exception e) {
   // TODO handle this exception (but for now, at least know it
   // happened)
   throw new RuntimeException(e);
  }
 }
The basic components we need are:
  • a debugger implementation (lines 9-10)
    The debugger is attached to our interpreter and handles all kinds of debug interactions on client side.
  • A DebugTarget (line 13)
    The DebugTarget is the representative of our debugger on Eclipse side. Whenever the Eclipse Debug Framework needs to interact with our debugger, it calls the DebugTarget to process the request.
  • An asynchronous event dispatcher (16-17)
    As Eclipse debug requests (like resume, step over, ...) originate from the UI, they need to be handled asynchronously. The event dispatcher handles these requests in its own thread.
Step 6: Running the debugger

To track how the debugger works I left some trace messages inside the code. To enable trace output, open your launch configuration, switch to the Tracing tab and select Enable tracing. Then activate com.codeandme.debugger.textinterpreter.debugger and on the right hand side the debug node.

When you run the debugger it will suspend after setting up the interpreter and debugger. When you hit the resume button, you should end up with following console output:
Debugger  : new DebuggerStartedEvent
Dispatcher: [+] DebuggerStartedEvent
Dispatcher: debugger -> DebuggerStartedEvent -> model
Model     : process DebuggerStartedEvent
Model     : new ResumeRequest
Dispatcher: [+] ResumeRequest
Dispatcher: debugger <- ResumeRequest <- model
Debugger  : process ResumeRequest
Debugger  : new ResumedEvent
Dispatcher: [+] ResumedEvent
Dispatcher: debugger -> ResumedEvent -> model
Model     : process ResumedEvent
Hello World
name = Christian
Christian
Debugger  : new TerminatedEvent
Dispatcher: [+] TerminatedEvent
Dispatcher: debugger -> TerminatedEvent -> model
Model     : process TerminatedEvent
After we received the DebuggerStartedEvent, our debugger suspends and waits for a ResumeRequest triggered by eclipse. So make sure you hit the resume button to continue execution.

Not much of a debugger so far, but all the basic components are in place. In the following tutorial we will start to fill our model with some spice.