[
Date Prev][
Date Next][
Thread Prev][
Thread Next][
Date Index][
Thread Index]
[
List Home]
[cdt-debug-dev] Adding a Target....
|
Hi!
So here's the break down on how we got a custom target working for us on a
not quite golden version of 1.0.1. (Will move to golden here shortly.)
A little background first, Tensilica has three different remote target
types: boards with a serial port, boards through a jtag connecter fronted
by a proprietary bit of server software and MP simulators written with a
system-C like library that we supply called XTMP. There is a local target
as well which is a single processor simulator. That's the default run
environment and it works just fine with the GDB extension points already
supplied.
All of these targets potentially have different gdb target commands. XTMP
and the jtag server are implicitly multi-processing targets, meaning that
they support connection to more than one processor simultaneously.
As if it were not bad enough, it's even a little more complex than that.
Because we are a configurable processor company, each of the processors can
be different. Generally speaking GDB covers the abstraction of the
differences, however we have to point GDB to some magic files so that it
knows about the specifics of the core it is debugging. We take care of all
this stuff so that our customers don't have to.
The first milestone was to support XTMP MP debugging for heterogeneous
processor configurations. So one target, multiple processors that can be
different.
When the team is lean and I have to implement, I've found that I tend to do
the things that no one else wants to do. Oh the joys of being in
management. I personally did the implementation here and can speak to the
details. As a starting comment I have to say that I think that debug
abstraction is quite good. The interfaces are great! The implementation of
those interfaces is good but not always ideal for reuse. I ended up making
some very small changes to the core so as not to have to copy about 700
lines of code.
Here's how the solution ended up working. (The code I used is attached,
though clearly is example, not actually useful.) I trust that
Getting a target sent to GDB turns out to be a matter of getting your own
ICDITarget. ICDITarget is the interface that is used by the higher level
tools to do execution control of the debug target. With GDB that means it's
sending commands to the MISession object. The supplied implementation of
this class is CTarget and is about 500 - 600 lines of gdb commands.
At first it looks like a cake walk. CTarget handles the restart() method by
sending an -exec-run command. With an mi1 debugger We want to replace it
with a Target that sends -target-select followed by -exec-continue. It was
not quite that easy however.
Turns out that the session (a CSession) is creating and suppling the
CTarget. CSession is a 200ish line module that's has a collection of the
objects that comprise a C debugging session. CSession in turn is supplied
by the debugger object (ICDebugger) which knows how to create sessions.
To recap the interface, ICDebugger creates ICDISession which hasPart
ICDITarget. In the implementation GDBDebugger creates a CSession which
creates and makes available a CTarget.
So off we go. Starting at the top of the food chain, we had to create our
own debugger object to be able to create the session. (Also to be able to
add those peksy command line parameters to GDB I mentioned.) That's the
XtGDB class. You will notice that only launch sessions are supported at the
moment. The key hook here for target adding is the call to
XtensaCDebugModel.createCSession.
The only differences between XtensaCDebugModel.createCSession and
MIPlugin.createCSession is support for additional GDB command line
arguments and creating our new ICDISession class, XtensaCSession.
Remember the goal: we want to supply our own ICDITarget object. We are only
creating a custom ICDISession to be able to add the target. I wanted to use
the existing CSession object and just override the constructor to replace
the CTarget ICDITarget with my ICDITarget subclass. Unfortunately the
instance variable holding the Target is private.
No worries, bad news does not improve with age and I'd rather change the
base code in a simple way that will break cleanly on mis-integration than
copy it and have it quietly go bad on some update where CSession
implementation is changed for some reason. The tensilica version of
CSession now says "protected CTarget ctarget" rather than just "CTarget
ctarget."
With the session hooked to put our target in, it was time to look at
actually implementing the target command in the ICDITarget object.
Again, the interface here is outstanding. You can read through ICDITarget
and quickly get a feel that resume and restart are the interesting methods.
If you look at the implementation if CTarget resume you see that it
delegates to restart in the cases where you need to issue a target XXX
command to gdb. Restart is the interesting method.
CTarget is 500 lines of quality mi1 GDB controlling. I had no desire to
reproduce it, I subclassed it with XtensaCTarget. The restart method to
support targets is pretty straight forward and probably speaks for itself.
I should mention that the commented out continue code does work but MAY be
behavior I don't want. We will know in another couple of weeks after the
next usability release to our applications group.
There was one issue with the base class. I have no idea who uses
lastExecutionToken, but it is made available from CTarget. It's also
private with no set method. With the same logic in mind, it is now
protected in the version in the Tensilica database so that I didn't have to
copy the lovely 500 lines of quality GDB control.
So, that's how it ended up working. Interesting classes attached. There was
probably an easier way and I always like hearing about that.
Thanks,
-Chris
package com.tensilica.xide.cdt.debugger;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.cdt.debug.core.ICDebugger;
import org.eclipse.cdt.debug.core.cdi.CDIException;
import org.eclipse.cdt.debug.core.cdi.ICDISession;
import org.eclipse.cdt.debug.mi.core.cdi.CSession;
import org.eclipse.cdt.debug.mi.core.cdi.SourceManager;
import org.eclipse.cdt.debug.mi.core.IMILaunchConfigurationConstants;
import org.eclipse.cdt.debug.mi.core.MIPlugin;
import org.eclipse.cdt.debug.mi.core.MIException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.debug.core.ILaunchConfiguration;
/**
* Xtensa version of the GDB debugger. This class just knows
* how to create debugger sessions.
*
* @author songer
*/
public class XtGDB implements ICDebugger {
public CSession session;
void initializeLibraries(ILaunchConfiguration config, CSession session) throws CDIException {
try {
SourceManager mgr = (SourceManager)session.getSourceManager();
boolean autolib = config.getAttribute(IMILaunchConfigurationConstants.ATTR_AUTO_SOLIB, false);
if (autolib) {
mgr.setAutoSolib();
}
List p = config.getAttribute(IMILaunchConfigurationConstants.ATTR_SOLIB_PATH, new ArrayList(1));
if (p.size() > 0) {
String[] paths = (String[])p.toArray(new String[0]);
mgr.setLibraryPaths(paths);
}
} catch (CoreException e) {
throw new CDIException("Error initializing: " + e.getMessage());
}
}
public ICDISession createLaunchSession(ILaunchConfiguration config,
IFile exe) throws CDIException
{
return createLaunchSession( config, exe, 0 );
}
public ICDISession createLaunchSession(ILaunchConfiguration config,
IFile exe, int portNumber) throws CDIException {
try {
String gdb = config.getAttribute(IMILaunchConfigurationConstants.ATTR_DEBUG_NAME, "gdb");
List l = config.getAttribute( "xide.gdbargs", (List)null );
session = (CSession)XtensaCDebugModel.createCSession(gdb,
exe.getLocation().toOSString(), l, portNumber);
initializeLibraries(config, session);
return session;
} catch (IOException e) {
throw new CDIException("Error initializing: " + e.getMessage());
} catch (MIException e) {
throw new CDIException("Error initializing: " + e.getMessage());
} catch (CoreException e) {
throw new CDIException("Error initializing: " + e.getMessage());
}
}
public ICDISession createLaunchSession(ILaunchConfiguration config,
IFile exe, List l, int portNumber) throws CDIException {
try {
String gdb = config.getAttribute(IMILaunchConfigurationConstants.ATTR_DEBUG_NAME, "gdb");
session = (CSession)XtensaCDebugModel.createCSession(gdb,
exe.getLocation().toOSString(), l, portNumber);
initializeLibraries(config, session);
return session;
} catch (IOException e) {
throw new CDIException("Error initializing: " + e.getMessage());
} catch (MIException e) {
throw new CDIException("Error initializing: " + e.getMessage());
} catch (CoreException e) {
throw new CDIException("Error initializing: " + e.getMessage());
}
}
public ICDISession createAttachSession(ILaunchConfiguration config, IFile exe, int pid) throws CDIException {
throw new NullPointerException();
/*
try {
String gdb = config.getAttribute(IMILaunchConfigurationConstants.ATTR_DEBUG_NAME, "gdb");
session = (CSession)MIPlugin.getDefault().createCSession(gdb, exe.getLocation().toOSString(), pid, null);
initializeLibraries(config, session);
return session;
} catch (IOException e) {
throw new CDIException("Error initializing: " + e.getMessage());
} catch (MIException e) {
throw new CDIException("Error initializing: " + e.getMessage());
} catch (CoreException e) {
throw new CDIException("Error initializing: " + e.getMessage());
}
*/
}
public ICDISession createCoreSession(ILaunchConfiguration config, IFile exe, IPath corefile) throws CDIException {
throw new NullPointerException();
/*
try {
String gdb = config.getAttribute(IMILaunchConfigurationConstants.ATTR_DEBUG_NAME, "gdb");
session = (CSession)MIPlugin.getDefault().createCSession(gdb, exe.getLocation().toOSString(), corefile.toOSString());
initializeLibraries(config, session);
return session;
} catch (IOException e) {
throw new CDIException("Error initializing: " + e.getMessage());
} catch (MIException e) {
throw new CDIException("Error initializing: " + e.getMessage());
} catch (CoreException e) {
throw new CDIException("Error initializing: " + e.getMessage());
}
*/
}
}
package com.tensilica.xide.cdt.debugger;
import org.eclipse.cdt.debug.mi.core.cdi.CSession;
import org.eclipse.cdt.debug.mi.core.*;
import org.eclipse.cdt.debug.core.cdi.model.*;
/**
* Clone of the CSession object. We clone this to be able to override
* the target creation.
*
* @author songer
*/
public class XtensaCSession extends CSession {
/** just like CSessions except that we put in our own
* cTarget */
public XtensaCSession(MISession s, boolean attach, int portNum)
{
super( s, attach );
ctarget = new XtensaCTarget(this, portNum );
}
/** just like Csession's except that we put in our own
* cTarget */
public XtensaCSession( MISession s)
{
super( s );
ctarget = new XtensaCTarget( this, 0 );
}
}
package com.tensilica.xide.cdt.debugger;
import org.eclipse.cdt.debug.mi.core.cdi.CTarget;
import org.eclipse.cdt.debug.mi.core.cdi.*;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.cdt.debug.core.cdi.*;
import org.eclipse.cdt.debug.core.cdi.model.*;
import org.eclipse.cdt.debug.mi.core.*;
import org.eclipse.cdt.debug.mi.core.command.*;
import org.eclipse.cdt.debug.mi.core.event.MIDetachedEvent;
import org.eclipse.cdt.debug.mi.core.event.MIThreadExitEvent;
import org.eclipse.cdt.debug.mi.core.output.MIDataEvaluateExpressionInfo;
import org.eclipse.cdt.debug.mi.core.output.MIInfo;
import org.eclipse.cdt.debug.mi.core.output.MIInfoThreadsInfo;
import org.eclipse.cdt.debug.mi.core.output.MIThreadSelectInfo;
/**
* We hook this class to be able to insert our own target commands
* for certain methods.
*
* @author songer
*/
public class XtensaCTarget extends CTarget {
/** our GDB port number */
protected int portNum;
/** all is done by the super class */
public XtensaCTarget(CSession s, int portNum)
{
super( s );
this.portNum = portNum;
}
/**
* Handles restarts by connecting to the target and then
* sending a continue.
*/
public void restart() throws CDIException
{
/** not a remote connection */
if( portNum == 0 )
{
super.restart();
return;
}
MISession mi = session.getMISession();
CommandFactory factory = mi.getCommandFactory();
String selectArray[] = new String[] { "remote",
"localhost:" + Integer.toString(portNum) };
/* try the connect. Note that we may need to try multiple times
* because there is a race condition between Eclipse and the
* XTMP simulator (like Java is going to lose a race with C, but
* who can tell what happens -- especially on windows.) */
boolean loser = true;
int repCount = 0;
while( loser )
{
MITargetSelect targetSelect = factory.createMITargetSelect(selectArray);
try
{
mi.postCommand(targetSelect);
MIInfo info = targetSelect.getMIInfo();
if (info == null)
throw new CDIException("No answer");
loser = false;
lastExecutionToken = targetSelect.getToken();
}
catch(MIException e )
{
if( repCount >= 10 )
throw new MI2CDIException( e );
}
repCount++;
}
mi.getMIInferior().setSuspended();
/*
MIExecContinue cont = factory.createMIExecContinue();
try
{
mi.postCommand( cont );
MIInfo info = cont.getMIInfo();
if( info == null )
throw new CDIException( "No Answer" );
lastExecutionToken = cont.getToken();
}
catch( MIException e )
{
throw new MI2CDIException( e );
}
*/
}
}
package com.tensilica.xide.cdt.debugger;
import org.eclipse.debug.core.model.*;
import org.eclipse.debug.core.*;
import org.eclipse.cdt.debug.core.cdi.model.*;
import org.eclipse.cdt.debug.core.model.*;
import org.eclipse.cdt.debug.core.cdi.*;
import org.eclipse.cdt.debug.core.*;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.cdt.debug.internal.core.model.*;
import org.eclipse.cdt.utils.spawner.*;
import org.eclipse.cdt.utils.pty.*;
import org.eclipse.cdt.debug.mi.core.*;
import org.eclipse.cdt.debug.mi.core.command.*;
import org.eclipse.cdt.debug.mi.core.output.*;
import java.text.MessageFormat;
import java.io.IOException;
import java.util.List;
import java.util.Iterator;
/**
* Utility classes. At the moment really just a slightly changed clone
* of what's in CDebugModel in the CDT.
*
* @author songer
*/
public class XtensaCDebugModel
{
/**
* Creates an argument string array from the known args (given in
* an array) and unknown string (given in a list). The list args
* are put right after the first base arg.
*
* @param base array of arguments, must be valid
* @param l list of additional arguments, may be null
* @return the new command string
*/
protected static String[] createArgs( String base[], List l )
{
if( l == null )
return base;
String ret[] = new String[ base.length + l.size()];
ret[0] = base[0];
Iterator i = l.iterator();
int count = 1;
while( i.hasNext() )
ret[count++] = (String)i.next();
for( int j = 1 ; j < base.length ; j++, count++ )
{
ret[count] = base[j];
}
return ret;
}
/**
* Method createCSession.
* @param gdb string to the GDB
* @param program the program that we are debugging.
* @param l list of additional arguments for GDB.
* @param portNumber the portnumber to connect on.
* @return ICDISession
* @throws MIException
*/
public static ICDISession createCSession(String gdb,
String program, List l, int portNumber) throws IOException, MIException {
PTY pty = null;
try {
pty = new PTY();
} catch (IOException e) {
}
return createCSession(gdb, program, pty, l, portNumber);
}
/**
* Method createCSession.
* @param program
* @return ICDISession
* @throws IOException
*/
public static ICDISession createCSession(String gdb,
String program, PTY pty, List l, int portNumber) throws IOException, MIException {
if (gdb == null || gdb.length() == 0) {
gdb = "gdb";
}
String[] args;
if (pty != null)
{
args = new String[] {gdb, "-q", "-nw", "-tty", pty.getSlaveName(), "-i", "mi1", program};
args = createArgs( args, l );
}
else
{
args = new String[] {gdb, "-q", "-nw", "-i", "mi1", program};
args = createArgs( args, l );
}
Process pgdb = ProcessFactory.getFactory().exec(args);
MISession session = MIPlugin.getDefault().createMISession(pgdb, pty, MISession.PROGRAM);
// For windows we need to start the inferior in a new console window
// to separate the Inferior std{in,out,err} from gdb std{in,out,err}
try {
CommandFactory factory = session.getCommandFactory();
MIGDBSet set = factory.createMIGDBSet(new String[]{"new-console"});
session.postCommand(set);
MIInfo info = set.getMIInfo();
if (info == null) {
throw new MIException("No answer");
}
} catch (MIException e) {
// We ignore this exception, for example
// on GNU/Linux the new-console is an error.
}
return new XtensaCSession(session, false, portNumber);
}
public static IDebugTarget newDebugTarget( final ILaunch launch,
final ICDITarget cdiTarget,
final String name,
final IProcess debuggeeProcess,
final IProcess debuggerProcess,
final IFile file,
final boolean allowTerminate,
final boolean allowDisconnect,
final boolean stopInMain ) throws DebugException
{
final IDebugTarget[] target = new IDebugTarget[1];
IWorkspaceRunnable r = new IWorkspaceRunnable()
{
public void run( IProgressMonitor m )
{
target[0] = new XtensaCDebugTarget( launch,
ICDebugTargetType.TARGET_TYPE_LOCAL_RUN,
cdiTarget,
name,
debuggeeProcess,
debuggerProcess,
file,
allowTerminate,
allowDisconnect );
}
};
try
{
ResourcesPlugin.getWorkspace().run( r, null );
}
catch( CoreException e )
{
throw new DebugException( e.getStatus() );
}
ICDIConfiguration config = cdiTarget.getSession().getConfiguration();
if ( config.supportsBreakpoints() && stopInMain )
{
stopInMain( (XtensaCDebugTarget)target[0] );
}
if ( config.supportsResume() )
{
target[0].resume();
}
return target[0];
}
private static void stopInMain( XtensaCDebugTarget target ) throws DebugException
{
ICDILocation location = target.getCDISession().getBreakpointManager().createLocation( "", "main", 0 );
try
{
target.setInternalTemporaryBreakpoint( location );
}
catch( DebugException e )
{
/*
String message = MessageFormat.format( "Unable to set temporary breakpoint in main.\nReason: {0}\nContinue?", new String[] { e.getStatus().getMessage() } );
IStatus newStatus = new Status( IStatus.WARNING,
e.getStatus().getPlugin(),
null,
message,
null );
if ( !CDebugUtils.question( newStatus, target ) )
{
target.terminate();
throw new DebugException( new Status( IStatus.OK,
e.getStatus().getPlugin(),
e.getStatus().getCode(),
e.getStatus().getMessage(),
null ) );
}
*/
}
}
}