Home Tutorials Training Consulting Products Books Company Donate Contact us









NOW Hiring

Quick links

Share

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.

Debug model in the UI

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.

Class hierarchy of IBreakpoint

2.2. Registering custom breakpoints

Eclipse provides the org.eclipse.debug.core.breakpoints extension point for registering custom IBreakpoint classes.

IBreakpoint extension point

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.

IToggleBreakpointsTarget type hierarchy

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.
Vogella launch UI

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.

Debug toolbar buttons

When passing in "vogella" as String the result should look similar to this:

Debug view result

4. About this website

5. Eclipse Debug Framework Resources

5.1. vogella GmbH training and consulting support

TRAINING SERVICE & SUPPORT

The vogella company provides comprehensive training and education services from experts in the areas of Eclipse RCP, Android, Git, Java, Gradle and Spring. We offer both public and inhouse training. Whichever course you decide to take, you are guaranteed to experience what many before you refer to as “The best IT class I have ever attended”.

The vogella company offers expert consulting services, development support and coaching. Our customers range from Fortune 100 corporations to individual developers.

Copyright © 2012-2016 vogella GmbH. Free use of the software examples is granted under the terms of the EPL License. This tutorial is published under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Germany license.

See Licence.