Monday, November 11, 2013

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:

  1. A fictional interpreter
  2. The launch framework
  3. A tale of debuggers, processes and threads
  4. Stepping, suspending and other actions
  5. UI breakpoint integration
  6. Debugger breakpoint integration
  7. Display current source code - source lookup
  8. Run to line support
  9. Displaying variables
  10. Displaying memory areas

Source code for this tutorial is available on googlecode as a single zip archive, as a Team Project Set or you can checkout the SVN projects directly.


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 framework 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.

Decoupled communication between the model and the debugger might be avoided if the debugger is run in the eclipse context. As this is often not the case we will use events here, too. Typical real world implementations might use TCP communication with an external process. 

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.


Step 1: Add debugging support to launchers

When we created our launch extensions, we just allowed run modes. To add debug launch targets open com.codeandme.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 2: Adding launch implementation

Open TextLaunchDelegate.java and exchange the launch method 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);
   dispatcher.schedule();

   // 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 3: The debugger

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

We already defined in the launcher code an EventDispatcher that delivers events between the debugger and the DebugTarget. The code is not very interesting, so I leave its study to you.

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:
package com.codeandme.textinterpreter.debugger;

public class TextDebugger implements IDebugger, IEventProcessor {

 // FIXME for full source code see http://codeandme.googlecode.com/svn/trunk/blog/debugger/03/com.codeandme.textinterpreter.debugger/src/com/codeandme/textinterpreter/debugger/TextDebugger.java

 private EventDispatchJob mDispatcher;
 private final TextInterpreter mInterpreter;

 @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) {
  System.out.println("Debugger.handleEvent() " + event);

  if (event instanceof ResumeEvent)
   mInterpreter.resume();
 }

 private void fireEvent(final IDebugEvent event) {
  System.out.println("Debugger.fireEvent() " + event);

  mDispatcher.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. This allows the model to 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 4: 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.
package com.codeandme.textinterpreter.debugger.model;

public class TextDebugTarget extends TextDebugElement implements IDebugTarget, IEventProcessor {

 // FIXME for full source code see http://codeandme.googlecode.com/svn/trunk/blog/debugger/03/com.codeandme.textinterpreter.debugger/src/com/codeandme/textinterpreter/debugger/model/TextDebugTarget.java 

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

 private EventDispatchJob mDispatcher;

 private State mState = State.NOT_STARTED;

 private final IFile mFile;

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

  fireCreationEvent();

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

 void fireModelEvent(final IDebugEvent event) {
  System.out.println("Target.fireEvent() " + event);

  mDispatcher.addEvent(event);
 }

 @Override
 public void handleEvent(final IDebugEvent event) {

  if (!isDisconnected()) {
   System.out.println("Target.handleEvent() " + event);

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

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

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

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

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

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

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

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

 @Override
 public String getName() {
  return "Text DebugTarget";
 }

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

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

 @Override
 public boolean isSuspended() {
  return (mState == 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 13, 46, 52, ...). This is necessary to enable/disable debugging options on the toolbar.

In the constructor we fire a creation event (line 20). 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 Process 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.

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


Step 5: Running the debugger

I left some sysout statements in the code to track all the events that are passed between model and debugger. When you run the debugger you should see following output:
Debugger.fireEvent() DebuggerStartedEvent
Target.handleEvent() DebuggerStartedEvent
Target.fireEvent() ResumeRequest
Debugger.handleEvent() ResumeRequest
Debugger.fireEvent() ResumedEvent
Target.handleEvent() ResumedEvent
>>> hello world <<<
>>> first name = Christian <<<
>>> we just defined a variable <<<
>>>  <<<
>>> counter = 23 <<<
>>>  <<<
>>> we are running <<<
>>> our interpreter <<<
Debugger.fireEvent() TerminatedEvent
Target.handleEvent() TerminatedEvent
After we received the DebuggerStartedEvent, our debugger suspends and waits for a resume event 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.

No comments:

Post a Comment