Eclipse Debug Framework. This article describes how to implement debugging behavior in Eclipse.
1. Eclipse Debug Framework
1.1. Prerequisites for this tutorial
This tutorial assumes that you have basic understanding of development for the Eclipse platform. Please see Eclipse RCP tutorial or Eclipse Plug-in tutorial if you need any basic information.
In addition to the basics another requirement is the Defining custom launcher for the Eclipse IDE tutorial, which should be read beforehand.
1.2. Eclipse Debug Model
The Eclipse Debug Model is represented by a hierarchy of model interfaces, which are visualized by a the Debug and Variables view, which can usually be found in the Debug perspective of the Eclipse IDE.
Every debug model element is derived from
org.eclipse.debug.core.model.IDebugElement
interface.
-
org.eclipse.debug.core.model.IDebugTarget
-
org.eclipse.debug.core.model.IThread
-
org.eclipse.debug.core.model.IStackFrame
-
org.eclipse.debug.core.model.IVariable
-
org.eclipse.debug.core.model.IValue
The debug framework also offers an abstract
org.eclipse.debug.core.model.DebugElement
class as default implementation of
IDebugElement
interface, which should should be used for implementing these custom
debug model elements.
|
1.3. Debug Model Communication
The Debug Model Communication is based on events, due to the asynchronous nature of the communication between the model and the interpreter.
For example the
IDebugTarget
implements the
org.eclipse.debug.core.model.ISuspendResume
interface with method like
suspend()
and
resume()
, which are called synchronously, but are immediately returned. So
the actual result comes back asynchronously by sending an
org.eclipse.debug.core.DebugEvent
.
Also custom events may be fired asynchronously, as long as this
is
done in
different threads so that the UI won’t be blocked. But for
convenience the
org.eclipse.debug.core.DebugEvent
objects should be used by deriving the debug model elements from
org.eclipse.debug.core.model.DebugElement
, which offers default implementations for firing
DebugEvents
.
1.4. Attach the IDebugTarget to the launch delegate
IDebugTargets
are added to the launch procedure like this:
@Override
public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor) throws CoreException {
// do launch and anything else...
// ... and add a certain IDebugTarget in case the debug mode is used
if (ILaunchManager.DEBUG_MODE.equals(mode)) {
IDebugTarget target = // instanciate custom IDebugTarget
launch.addDebugTarget(target);
}
}
A general overview concerning the launcher framework can be found in the Defining custom launcher for the Eclipse IDE tutorial.
2. Breakpoints
2.1. Eclipse Breakpoint Debug Model
Breakpoints in general are used to suspend the execution of an application at a certain point.
In Eclipse breakpoint classes are derived from the
org.eclipse.debug.core.model.IBreakpoint
interface. As listed below there are also default implementation for
breakpoints, as well as more specific breakpoints, e.g.,
ILineBreakpoint
, which also informs about a certain position in a document.
2.2. Registering custom breakpoints
Eclipse provides the
org.eclipse.debug.core.breakpoints
extension point for registering custom
IBreakpoint
classes.
In this definition a
markerType
attribute is used, because breakpoints do have a reference to an
IMarker
, which represents the breakpoint in the UI and it is also used for
the persistence of breakpoints.
Therefore the org.eclipse.core.resources.markers extension point is used.
<!-- The id is the same as the referenced "markerType" in the org.eclipse.debug.core.breakpoints
extension point -->
<extension id="example.breakpoint.marker" point="org.eclipse.core.resources.markers">
<!-- persist the marker, so that the referenced breakpoint will be recreated -->
<persistent value="true" />
<!-- Additional attributes for the marker can be defined, which are also
persisted -->
<attribute name="CUSTOM_ADDITIONAL_ATTR_WHICH_IS_ALSO_PERSISTED" />
<!-- The org.eclipse.debug.core.breakpointMarker super type indicated that
this marker is used for a breakpoint -->
<super type="org.eclipse.debug.core.breakpointMarker" />
</extension>
2.3. Creating breakpoints in the Eclipse IDE
The easiest approach to create custom breakpoints is to provide an
implementation of
org.eclipse.debug.ui.actions.IToggleBreakpointsTarget
.
For instance the
IToggleBreakpointsTargetExtension
interface provides a
public boolean canToggleBreakpoints(IWorkbenchPart part,
ISelection selection)
`
method and a
`public void toggleBreakpoints(IWorkbenchPart part, ISelection
selection) throws CoreException;
`
where a breakpoint can be toggled according to the current
`ISelection
and/or
IWorkbenchPart
.
These
IToggleBreakpointsTarget
implementations are often provided as an adapter for custom
IDebugElements
.
<extension point="org.eclipse.core.runtime.adapters">
<factory adaptableType="com.example.CustomDebugElement"
class="com.example.CustomDebugElementAdapterFactory">
<adapter type="org.eclipse.debug.ui.actions.IToggleBreakpointsTarget">
</adapter>
</factory>
</extension>
2.4. Registering breakpoints in the IBreakpointManager
When an
IBreakpoint
is created, e.g., by a
IToggleBreakpointsTarget
, it must be registered to the workspace’s
IBreakpointManager
.
CustomBreakpoint breakpoint = // instanciate custom Breakpoint
DebugPlugin.getDefault().getBreakpointManager().addBreakpoint(breakpoint);
When adding or removing
IBreakpoints
from the
IBreakpointManager
its
IBreakpointListener
will be informed about it and can act upon the added or removed
breakpoint.
An
IDebugTarget
is an instance of
IBreakpointListener
by default and is therefore in charge to manage changes concerning
breakpoints.
3. Exercise: Creating the Debug Model Elements
3.1. Target
In this exercise an example debug model will be created, so that a debug session can be started.
In this debug session a given String will be printed to the console char by char. The running application can be suspended at the char, which is currently in the work queue.
This exercise is based on Launch Configuration UI and will make use of the tab and launch delegate, which is created in the Defining custom launcher for the Eclipse IDE tutorial. |
3.2. Create a common IDebugElement
As mentioned earlier each debug model class is derived from
IDebugElement
and contains methods, which are equal for every debug model class.
import org.eclipse.debug.core.model.DebugElement;
import org.eclipse.debug.core.model.IDebugTarget;
public abstract class ExampleDebugElement extends DebugElement {
public ExampleDebugElement(IDebugTarget target) {
super(target);
}
@Override
public String getModelIdentifier() {
return Activator.PLUGIN_ID;
}
}
A custom model identifier may also be used. This means it is not necessary to use the plug-in’s id to make everything work correctly. |
3.3. Create an IStackFrame
IStackFrames
are only visible when a thread is suspended, which can also be seen
in the following
Create an IThread
section.
For instance when debugging a Java application the list of
IStackFrame
objects under a thread show the currently suspended Java stacktrace.
package com.example.debug.core.model;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IRegisterGroup;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.debug.core.model.IVariable;
public class ExampleStackFrame extends ExampleDebugElement implements IStackFrame {
private IThread thread;
private int charPos;
private String wordToBeSpelled;
public ExampleStackFrame(IThread thread, String wordToBeSpelled, int charPos) {
super(thread.getDebugTarget());
this.thread = thread;
this.wordToBeSpelled = wordToBeSpelled;
this.charPos = charPos;
}
@Override
public boolean canStepInto() {
return thread.canStepInto();
}
@Override
public boolean canStepOver() {
return thread.canStepOver();
}
@Override
public boolean canStepReturn() {
return thread.canStepReturn();
}
@Override
public boolean isStepping() {
return thread.isStepping();
}
@Override
public void stepInto() throws DebugException {
thread.stepInto();
}
@Override
public void stepOver() throws DebugException {
thread.stepOver();
}
@Override
public void stepReturn() throws DebugException {
thread.stepReturn();
}
@Override
public boolean canResume() {
return thread.canResume();
}
@Override
public boolean canSuspend() {
return thread.canSuspend();
}
@Override
public boolean isSuspended() {
return thread.isSuspended();
}
@Override
public void resume() throws DebugException {
thread.resume();
}
@Override
public void suspend() throws DebugException {
thread.suspend();
}
@Override
public boolean canTerminate() {
return thread.canTerminate();
}
@Override
public boolean isTerminated() {
return thread.isTerminated();
}
@Override
public void terminate() throws DebugException {
thread.terminate();
}
@Override
public IThread getThread() {
return thread;
}
@Override
public IVariable[] getVariables() throws DebugException {
return new IVariable[0];
}
@Override
public boolean hasVariables() throws DebugException {
return false;
}
@Override
public int getLineNumber() throws DebugException {
return charPos;
}
@Override
public int getCharStart() throws DebugException {
return charPos;
}
@Override
public int getCharEnd() throws DebugException {
return charPos + 1;
}
@Override
public String getName() throws DebugException {
return "Char '" + wordToBeSpelled.charAt(charPos) + "' position : " + getCharStart();
}
@Override
public IRegisterGroup[] getRegisterGroups() throws DebugException {
return new IRegisterGroup[0];
}
@Override
public boolean hasRegisterGroups() throws DebugException {
return false;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + charPos;
result = prime * result + ((thread == null) ? 0 : thread.hashCode());
result = prime * result + ((wordToBeSpelled == null) ? 0 : wordToBeSpelled.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ExampleStackFrame other = (ExampleStackFrame) obj;
if (charPos != other.charPos)
return false;
if (thread == null) {
if (other.thread != null)
return false;
} else if (!thread.equals(other.thread))
return false;
if (wordToBeSpelled == null) {
if (other.wordToBeSpelled != null)
return false;
} else if (!wordToBeSpelled.equals(other.wordToBeSpelled))
return false;
return true;
}
}
For convenience most of the actions in the ExampleStackFrame are
delegated to the given
IThread
.
For visualization the getName()
method returns the character and position of the word, which should
be
spelled. In later sections IVariables will be added to this
IStackFrame
implementation, so that also available variables are shown in the
Variables
view.
IStackFrame
classes should also override the
equals
and
hashcode
methods, because they are usually compared to each other. See
getStackFrames()
and
getTopStackFrame()
methods in
Create an IThread
.
3.4. Create an IThread
An
IThread
is supposed to be a wrapper around a usual Java thread, which runs
within an
IDebugTarget
.
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IStackFrame;
import org.eclipse.debug.core.model.IThread;
public class ExampleThread extends ExampleDebugElement implements IThread {
// The different states, which this IThread can have
private enum State {
RUNNING, STEPPING, SUSPENDED, TERMINATED
}
private State currentState;
private State requestedState;
private Thread thread;
private int charPos;
private final String wordToBeSpelled;
public ExampleThread(IDebugTarget target, String wordToBeSpelled) {
super(target);
this.wordToBeSpelled = wordToBeSpelled;
thread = new Thread(new Resume());
thread.start();
fireCreationEvent();
}
@Override
public boolean canResume() {
return isSuspended();
}
@Override
public boolean canSuspend() {
return !isTerminated() && !isSuspended();
}
@Override
public boolean isSuspended() {
return State.SUSPENDED.equals(currentState);
}
@Override
public void resume() throws DebugException {
synchronized (this) {
requestedState = State.RUNNING;
thread = new Thread(new Resume());
thread.start();
}
}
@Override
public void suspend() throws DebugException {
synchronized (this) {
requestedState = State.SUSPENDED;
thread.interrupt();
}
}
@Override
public synchronized boolean isStepping() {
return State.STEPPING.equals(currentState);
}
@Override
public boolean canStepOver() {
return isSuspended();
}
@Override
public void stepOver() throws DebugException {
synchronized (this) {
requestedState = State.STEPPING;
thread = new Thread(new StepOver());
thread.start();
}
}
@Override
public boolean canStepInto() {
return false;
}
@Override
public boolean canStepReturn() {
return false;
}
@Override
public void stepInto() throws DebugException {
}
@Override
public void stepReturn() throws DebugException {
}
@Override
public boolean canTerminate() {
return !isTerminated();
}
@Override
public boolean isTerminated() {
return State.TERMINATED.equals(currentState);
}
@Override
public void terminate() throws DebugException {
synchronized (this) {
requestedState = State.TERMINATED;
if (isSuspended()) {
// run to termination
thread = new Thread(new Resume());
thread.start();
} else {
thread.interrupt();
}
}
}
@Override
public IStackFrame getTopStackFrame() throws DebugException {
synchronized (this) {
if (isSuspended()) {
return new ExampleStackFrame(this, wordToBeSpelled, charPos);
}
}
return null;
}
@Override
public IStackFrame[] getStackFrames() throws DebugException {
synchronized (this) {
if (isSuspended()) {
if (charPos < 1) {
return new IStackFrame[] { new ExampleStackFrame(this, wordToBeSpelled, charPos) };
}
IStackFrame[] frames = new IStackFrame[charPos + 1];
for (int i = charPos; i >= 0; i--) {
frames[i] = new ExampleStackFrame(this, wordToBeSpelled, charPos - i);
}
return frames;
}
}
return new IStackFrame[0];
}
@Override
public boolean hasStackFrames() throws DebugException {
// An IThread only has stack frames when it is suspended.
return isSuspended();
}
@Override
public int getPriority() throws DebugException {
return 0;
}
@Override
public String getName() throws DebugException {
return "Example Thread with the word " + wordToBeSpelled;
}
@Override
public IBreakpoint[] getBreakpoints() {
return new IBreakpoint[0];
}
class StepOver implements Runnable {
@Override
public void run() {
synchronized (ExampleThread.this) {
currentState = State.STEPPING;
}
fireResumeEvent(DebugEvent.STEP_OVER);
int event = doNextStep();
int detail = DebugEvent.UNSPECIFIED;
synchronized (ExampleThread.this) {
// update state
switch (event) {
case DebugEvent.BREAKPOINT:
currentState = State.SUSPENDED;
detail = DebugEvent.BREAKPOINT;
break;
case DebugEvent.UNSPECIFIED:
currentState = State.SUSPENDED;
detail = DebugEvent.STEP_END;
break;
case DebugEvent.TERMINATE:
currentState = State.TERMINATED;
break;
case DebugEvent.SUSPEND:
currentState = State.SUSPENDED;
detail = DebugEvent.CLIENT_REQUEST;
break;
}
}
switch (currentState) {
case SUSPENDED:
fireSuspendEvent(detail);
break;
case TERMINATED:
fireTerminateEvent();
ExampleDebugTarget target = (ExampleDebugTarget) getDebugTarget();
target.dispose();
break;
default:
break;
}
}
}
class Resume implements Runnable {
@Override
public void run() {
synchronized (ExampleThread.this) {
currentState = State.RUNNING;
}
fireResumeEvent(DebugEvent.CLIENT_REQUEST);
int detail = DebugEvent.UNSPECIFIED;
int event = DebugEvent.UNSPECIFIED;
while (event == DebugEvent.UNSPECIFIED) {
event = doNextStep();
}
synchronized (ExampleThread.this) {
// update state
switch (event) {
case DebugEvent.BREAKPOINT:
currentState = State.SUSPENDED;
detail = DebugEvent.BREAKPOINT;
break;
case DebugEvent.TERMINATE:
currentState = State.TERMINATED;
break;
case DebugEvent.SUSPEND:
currentState = State.SUSPENDED;
detail = DebugEvent.CLIENT_REQUEST;
break;
}
}
switch (currentState) {
case SUSPENDED:
fireSuspendEvent(detail);
break;
case TERMINATED:
fireTerminateEvent();
ExampleDebugTarget target = (ExampleDebugTarget) getDebugTarget();
target.dispose();
break;
default:
break;
}
}
}
private int doNextStep() {
if (State.TERMINATED.equals(requestedState)) {
return DebugEvent.TERMINATE;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
switch (requestedState) {
case TERMINATED:
return DebugEvent.TERMINATE;
case SUSPENDED:
return DebugEvent.SUSPEND;
default:
break;
}
}
System.out.print(wordToBeSpelled.charAt(charPos));
charPos++;
if (charPos > wordToBeSpelled.length() - 1) {
System.out.println();
charPos = 0;
}
return DebugEvent.UNSPECIFIED;
}
}
3.5. Create an IDebugTarget
The
IDebugTarget
is supposed to be applied to the
org.eclipse.debug.core.ILaunch
object, which is passed to an
org.eclipse.debug.core.model.LaunchConfigurationDelegate
, by using its
addDebugTarget(IDebugTarget target)
method.
.
It can contain several
IThreads
, which run during a debug session.
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IMemoryBlock;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IThread;
public class ExampleDebugTarget extends ExampleDebugElement implements IDebugTarget {
private IProcess process;
private ILaunch launch;
private ExampleThread thread;
// the IProcess and ILaunch are provided by an IDebugTarget and therefore
// they should be passed. The getProcess() and getLaunch() method should be
// overridden accordingly. See DebugElement implementation
public ExampleDebugTarget(IProcess process, ILaunch launch, String wordToBeSpelled) {
super(null);
this.process = process;
this.launch = launch;
this.thread = new ExampleThread(this, wordToBeSpelled);
// notify that this element has been created
fireCreationEvent();
}
// must be overridden since "this" cannot be passed in a constructor
@Override
public IDebugTarget getDebugTarget() {
return this;
}
@Override
public IProcess getProcess() {
return this.process;
}
@Override
public ILaunch getLaunch() {
return launch;
}
@Override
public boolean canTerminate() {
return thread.canTerminate();
}
@Override
public boolean isTerminated() {
return thread.isTerminated();
}
@Override
public void terminate() throws DebugException {
thread.terminate();
}
@Override
public boolean canResume() {
return thread.canResume();
}
@Override
public boolean canSuspend() {
return thread.canSuspend();
}
@Override
public boolean isSuspended() {
return thread.isSuspended();
}
@Override
public void resume() throws DebugException {
thread.resume();
}
@Override
public void suspend() throws DebugException {
thread.suspend();
}
@Override
public void breakpointAdded(IBreakpoint breakpoint) {
}
@Override
public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta) {
}
@Override
public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta) {
}
@Override
public boolean canDisconnect() {
return false;
}
@Override
public void disconnect() throws DebugException {
}
@Override
public boolean isDisconnected() {
return false;
}
@Override
public boolean supportsStorageRetrieval() {
return false;
}
@Override
public IMemoryBlock getMemoryBlock(long startAddress, long length) throws DebugException {
return null;
}
@Override
public IThread[] getThreads() throws DebugException {
if (isTerminated()) {
return new IThread[0];
}
return new IThread[]{thread};
}
@Override
public boolean hasThreads() throws DebugException {
return true;
}
@Override
public String getName() throws DebugException {
return "Example Debug target";
}
@Override
public boolean supportsBreakpoint(IBreakpoint breakpoint) {
return false;
}
public void dispose() {
fireTerminateEvent();
}
}
3.6. Validate
To validate the result start the ExampleLaunchDelegate, switch to the debug perspective and use the toolbar controls below to suspend, resume, step over or terminate the debug session.
When passing in "vogella" as String the result should look similar to this:
4. Eclipse Debug Framework Resources
4.1. vogella Java example code
If you need more assistance we offer Online Training and Onsite training as well as consulting