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:

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.

No comments:

Post a Comment