Contents


Interfacing with the CDT debugger, Part 1

Understand the C/C++ debugger interface

Add custom tools to the CDT debugger framework

Comments

Content series:

This content is part # of # in the series: Interfacing with the CDT debugger, Part 1

Stay tuned for additional content in this series.

This content is part of the series:Interfacing with the CDT debugger, Part 1

Stay tuned for additional content in this series.

A command-line interface is a serviceable tool for debugging, but nothing exudes professionalism and polish like a well-crafted graphical environment. It takes considerable time and pain to build a full-featured debugging environment from scratch, but there's an alternative: the Eclipse C/C++ Development Tooling (CDT). The CDT's extensibility allows you to connect its graphical debugging capabilities to your custom debugger. This doesn't require a lot of code, but you need to understand the CDT's extension points and the CDI.

The CDI is a Java™-based Application Programming Interface (API) whose classes and interfaces make it possible to access the CDT's debugging framework. An Eclipse plug-in that uses the CDI can add new debuggers to the CDT's operation and display the debugging results in the Eclipse/CDT debug perspective. This article covers the CDI in detail. Part 2 of this "Interfacing with the CDT debugger" series shows how the CDI is specialized to interface the GNU Debugger (gdb) through its Machine Interface (MI)..

Example CDI plug-in

The best way to learn how CDT debuggers work is to see and experiment with actual code. This article explains how to build a feature-poor plug-in that extends the CDT to provide a basic debugging capability. There is no actual debugger executable, but you can use this code as a basis for adding your own custom debugger to the CDT.

This example plug-in consists of three extensions to the CDT and the Eclipse Debug Framework:

org.eclipse.debug.core.launchConfigurationTypes
Creates a separate launcher to debug C/C++ applications
org.eclipse.debug.ui.launchConfigurationTabGroups
Receives debug configuration parameters from the user
org.eclipse.cdt.debug.core.CDebugger
Creates the debug session for the launched C/C++ application

This article explains each of these and provides example code showing how they work. Then it delves into the operation of the CDI, covering the CDI model and investigating how the CDI makes breakpoints and watchpoints possible.

Create a custom launch-configuration type

In Eclipse, the process of starting an application is called launching the application in Run mode. A debug session is referred to as a launch in Debug mode. Once you choose a launch mode, the next step is to choose a launch-configuration type. This tells Eclipse precisely how the application should be executed or debugged. For example, a launch-configuration type for Debug mode defines the debugger executable, default debug options, and how the debug output should be presented in Eclipse. Figure 1 shows configuration types presented in the CDT Debug Configurations window.

Figure 1. CDT Launch Configuration window
CDT Launch Configuration window
CDT Launch Configuration window

To interface a new debugger with Eclipse, the first step is to create a new launch-configuration type. This requires a plug-in that extends the org.eclipse.debug.core.launchConfigurationTypes extension point. In Figure 1, you can see Example Configuration Type at left in the window. Listing 1 presents the extension that defines this new type.

Listing 1. Example LaunchConfigurationType extension
   <extension
         point="org.eclipse.debug.core.launchConfigurationTypes">
      <launchConfigurationType
            name="Example Configuration Type"
            delegate="org.dworks.debugexample.ExampleConfigurationDelegate"
            modes="debug"
            public="true"
            sourceLocatorId="org.eclipse.cdt.debug.core.sourceLocator"
            sourcePathComputerId="org.eclipse.cdt.debug.core.sourcePathComputer"
            id="org.dworks.debug.example.ExampleLaunch">
      </launchConfigurationType>
   </extension>

The Example Configuration Type is placed in the Debug window because the modes field is set to debug, and the public field is set to true. The most important field is delegate, which identifies the class that manages the launch process. This class must implement the ILaunchConfigurationDelegate interface, and coding a delegate from scratch can be complex. Thankfully, the CDT makes your life easier by providing four pre-built delegate classes:

LocalRunLaunchDelegate
Runs a local C/C++ application
LocalAttachLaunchDelegate
Debugs local applications
CoreFileLaunchDelegate
Analyzes core files after execution
LocalCDILaunchDelegate
Interfaces a local CDI debugger

The CDT uses the first three for local software launches. The last delegate was created for tools outside the CDT and serves as the focus of this discussion. When the LocalCDILaunchDelegate is called upon to launch in debug mode, it obtains information about the executable to be debugged and the parameters to be sent to the debugger. This information must be packaged in an ILaunchConfiguration object, and a suitable UI is required to obtain this information from the user.

Adding a tab group for the launch configuration

Before an Eclipse application can be debugged, you must select a launch-configuration type and, by double- or right-clicking, create a new launch configuration. At this point, a graphical panel appears on the right and requests information related to the launch. This panel is called a launch configuration tab group; an example tab group is shown at right in Figure 1. This tab group consists of multiple tabs, and the visible tab in Figure 1 accepts the names of the executable to be debugged and its project.

Each launch must have its own tab group, and this must be identified in plug-in.xml as an extension of org.eclipse.debug.ui.launchConfigurationTabGroups. Listing 2 shows what this extension looks like in this example.

Listing 2. Declaring the example tab group
   <extension
         point="org.eclipse.debug.ui.launchConfigurationTabGroups">
      <launchConfigurationTabGroup
            type="org.dworks.debug.example.ExampleLaunch"
            class="org.dworks.debug.example.ExampleTabGroup"
            id="org.dworks.debug.example.ExampleTabGroup">
      </launchConfigurationTabGroup>
   </extension>

This extension is easy to understand. The type field identifies the launch configuration, the class identifies the class that creates the tab group, and id gives the tab group a unique name. The class identified by class must implement the ILaunchConfigurationTabGroup interface, and its job is to create one or more ILaunchConfigurationTabs. These tabs provide the debugger with the information it needs to operate, including debugging flags, the location of the source code, and memory addresses. To configure an ILaunchConfigurationTab to do its job, you have to implement two important methods:

  • createControl(Composite parent)— Creates the user interface for the debugger tab
  • performApply(ILaunchConfigurationWorkingCopy configuration)— Configures the debugger parameters

The second method is particularly important. It functions by assigning values to attributes in a LaunchConfigurationWorkingCopy. This data object holds the information required for debugging, and it's sent to the debugger when the debug session starts. Attribute names are listed in the ICDTLaunchConfigurationConstants interface. Important attributes include:

ATTR_DEBUGGER_ID
ID of the debugger to be launched
ATTR_DEBUGGER_SPECIFIC_ATTRS_MAP
Attributes supplied to the debugger session
ATTR_PROJECT_NAME
Name of the project being debugged
ATTR_PROGRAM_NAME
Name of the program being debugged
ATTR_PROGRAM_ARGUMENTS
Parameters supplied to the running program
ATTR_PLATFORM
Operating system running the program

The ExampleConfigurationTabGroup code creates a single configuration tab: ExampleTab. This tab displays six pre-initialized text boxes — one for each of the attributes detailed previously. Figure 2 shows what the ExampleTab looks like. Normally, you should create multiple tabs with a variety of controls. The CDT provides the CDebuggerTab class for configuring a C/C++ debugger.

Figure 2. Example launch-configuration tab
Example launch-configuration tab
Example launch-configuration tab

The code in ExampleTab initializes debug attributes with the setDefaults() method. This method identifies the example debugger as the debugger to use and searches selected CDT resources to find the project and program to debug. Performing this search doesn't require new code because ExampleConfigurationTab extends the CDT's CLaunchConfigurationTab class. This abstract class provides two important methods: getContext(), which returns a CDT resource, and initializeCProject(), which initializes the ATTR_PROJECT_NAME attribute with the name of the project.

Creating the custom debugger

When you click Debug, Eclipse searches for the debugger identified by the ATTR_DEBUGGER_ID attribute. In the example project, this is set to org.dworks.debugexample.ExampleDebugger, which corresponds to the id field in the example project's extension of org.eclipse.cdt.debug.core.CDebugger. Listing 3 presents the full extension.

Listing 3. The example debugger extension point
   <extension point="org.eclipse.cdt.debug.core.CDebugger">
      <debugger
            platform="*"
            name="Example Debugger"
            modes="run"
            cpu="*"
            class="org.dworks.debugexample.ExampleDebugger"
            id="org.dworks.debugexample.ExampleDebugger">
      </debugger>
   </extension>

When the debugger starts, Eclipse checks to see if the processing environment is supported by the debugger's platform and cpu elements. If so, it checks to make sure the current launch mode is supported by the debugger's mode element. If all the checks pass, Eclipse finds the class identified in the class field. This class must implement the ICDIDebugger2 interface and must, therefore, provide an implementation of the createSession() method.

The goal of createSession() is to construct an object that implements the ICDISession interface. This object enables the CDT to access the debugger as it operates. This access is provided with a number of methods, and three of the most important are as follows:

  • getSessionProcess()— Returns the process of the running debugger
  • getTargets()— Returns an array of targets being debugged
  • getEventManager()— Returns a manager object that adds and removes event listeners

The first of these methods is straightforward and returns a Process object corresponding to the debugger executable. The next two methods, getTargets() and getEventManager(), are more involved; the rest of this article explains them. Because these methods depend on the debugger executable, and because the example plug-in doesn't have one, the ExampleDebugger class leaves these methods empty.

Targets, events, and the CDI Model

When the CDT calls getTargets(), the session must provide an ICDITarget for each process being debugged. The ICDITarget's job is to receive debugging commands, translate them for the debugger, and send them to the debugger. For example, when you click the Step or Resume button in the CDT debug perspective, the CDT calls the target's stepOver() or resume() method, respectively. The CDI doesn't make any requirements as to how the debugger target interface should work, but Part 2 explains how gdb interfaces a target using the MI protocol.

In addition to sending commands to the debugger, the ICDITarget also transmits the debugger's output to any object interested in receiving it. This is accomplished through CDI events, which is where the session's getEventManager() method comes in. When any CDI object, such as a RegisterView, wants to be informed of debugger events, it calls getEventManager() to access the session's ICDIEventManager. Then it calls the manager's addEventListener() method to add itself as a listener. When the debugger produces output, the target calls on the manager to alert all of its listeners. The manager does this by calling each listener's handleDebugEvents() method, which must be implemented by all ICDIEventListeners.

The CDI provides a standard set of ICDIEvents. Every debugger's response or change in the debug environment must be packaged into one of the following:

ICDIBreakpointMovedEvent
Fired when the user chooses a new breakpoint
ICDIChangedEvent
Fired when an aspect of the target, such as a variable, register, or portion of memory, changes value
ICDICreationEvent/ICDIDestroyedEvent
Fired when an object, such as a breakpoint, is created or removed
ICDIExitedEvent
Fired when the program has finished
ICDIRestartedEvent/ICDIDisconnectedEvent
Fired when the target restarts or disconnects
ICDIExecutableReloadedEvent
Fired when a program is reloaded, such as after a new build
ICDISuspendedEvent/ICDIRestartedEvent/ICDIResumedEvent
Fired when the executable is stopped, restarted, or resumed

Part 2 details how gdb output is converted into MIEvents and how MIEvents become ICDIEvents.

The ICDIChangedEvent is particularly important because it's fired every time the target changes. Each changeable aspect of the target is represented by an ICDIObject, and these objects form a hierarchy called the CDI Model. They include ICDIRegisters, ICDIVariables, ICDIMemoryBlocks, ICDIThreads, and many more. The ICDITarget is the root and container of these CDI Model objects. The only method that each ICDIObject must implement is getTarget().

CDI breakpoints and watchpoints

Two of the most important ways of controlling the debugger use breakpoints and watchpoints. A breakpoint halts the debugger once it reaches a specific location or once a condition is met. A watchpoint halts the debugger when a specific variable is read from or written to. The CDI provides two interfaces to represent these: ICDIBreakpoint and its subinterface, ICDIWatchpoint. The ICDIBreakpoint can be regular, temporary, or hardware-assisted; and an ICDIWatchpoint can be read-type, write-type, or both.

You create and delete breakpoints and watchpoints through an instance of ICDIBreakpointManagement. This presents many related methods, such as setLineBreakpoint(), deleteBreakpoints(), and setWatchpoint(). But it's important to remember that the plug-in class of the org.eclipse.debug.core plug-in provides its own breakpoint manager, IBreakpointManager, which you can access with DebugPlugin.getDefault().getBreakpointManager(). In many cases, you may need to access this and convert IBreakpoints into ICDIBreakpoints.

Conclusion

Building a graphical debugging environment from scratch is a Herculean task: Not only do you have to know the target processor inside and out but you also have to communicate with the debugger and deliver its output to a graphical user environment. For those who aren't Hercules, Eclipse provides an API for adding custom debuggers to the CDT framework. This API is called the C/C++ Debugger Interface (CDI), and this article has covered the basics of its operation and use. Part 2 of this "Interfacing with the CDT debugger" series explains how the CDT uses the CDI to interface the finest open source debugger of them all: the GNU Debugger (gdb).


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=312452
ArticleTitle=Interfacing with the CDT debugger, Part 1: Understand the C/C++ debugger interface
publish-date=06102008