Thursday, November 21, 2013

Debugger 9. Variables support

Lets continue our little debugger example by adding support to display variables.
  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.


Step 1: Implementing variable classes

Variables are attached to StackFrames and will only be queries when the debugger is in suspended state. To implement them, we need a variable description stored in TextVariable:
package com.codeandme.textinterpreter.debugger.model;

public class TextVariable extends TextDebugElement implements IVariable {

 private final String mName;
 private IValue mValue;

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

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

 @Override
 public void setValue(IValue value) {
  mValue = value;
 }

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

 @Override
 public boolean verifyValue(String expression) throws DebugException {
  return false;
 }

 @Override
 public boolean verifyValue(IValue value) throws DebugException {
  return false;
 }

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

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

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

 @Override
 public boolean hasValueChanged() throws DebugException {
  return false;
 }
}
The implementation is straight forward and does not allow to modify a variables content.

An actual value is stored in a different class:
package com.codeandme.textinterpreter.debugger.model;

import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IValue;
import org.eclipse.debug.core.model.IVariable;

public class TextValue extends TextDebugElement implements IValue {

 private final String mValue;

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

  mValue = value;
 }

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

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

 @Override
 public boolean isAllocated() throws DebugException {
  return true;
 }

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

public class TextStackFrame extends TextDebugElement implements IStackFrame {

 private final List<TextVariable> mVariables = new ArrayList<TextVariable>();
 private boolean mDirtyVariables = true;

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

  return mVariables.toArray(new IVariable[mVariables.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 : mVariables) {
    if (variable.getName().equals(name)) {
     // variable exists
     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));
    mVariables.add(textVariable);
    textVariable.fireCreationEvent();
   }
  }
 }

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

  super.fireChangeEvent(detail);
 }
}

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().

 
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.

No comments:

Post a Comment