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.

2 comments:

  1. Great tutorials. Thank you very much. I hope to see more of this kind of excellent tutorials on Planet Eclipse.

    ReplyDelete
  2. Thank you very much! It is really great. I didnt see such a extended eclipse plugin tutorial in web before

    ReplyDelete