1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-07-23 17:05:26 +02:00

bug 498782 - [debug] synchronize selection between the DV and GDB

This adds a new service, GDBFocusSynchronizer, that helps keep the
internal GDB selection and the Debug View selection synchronized.

Change-Id: I021b3f65d61e82f6971bdb9232369b6fdf58ea5b
This commit is contained in:
Marc Dumais 2016-06-17 12:58:48 -04:00 committed by Gerrit Code Review @ Eclipse.org
parent e9beafae10
commit dc6e3a06ff
19 changed files with 1363 additions and 8 deletions

View file

@ -212,6 +212,9 @@ implements IConsoleView, IDebuggerConsoleView, IConsoleListener, IPropertyChange
fConsoleToPart.put(registered, part);
fPartToConsole.put(part, registered);
partActivated(part);
if (console instanceof IDebuggerConsole) {
display((IDebuggerConsole)console);
}
break;
}
}
@ -258,6 +261,8 @@ implements IConsoleView, IDebuggerConsoleView, IConsoleListener, IPropertyChange
DebuggerConsoleWorkbenchPart part = fConsoleToPart.get(console);
if (part != null) {
partActivated(part);
// let the console know it's being activated
fActiveConsole.consoleSelected();
}
}

View file

@ -35,4 +35,10 @@ public interface IDebuggerConsole extends IConsole {
* Request a re-computation of the name of the console.
*/
void resetName();
/**
* This console has become selected, the implementation shall use this
* notification to e.g. keep other views in sync with the context of the console
*/
public void consoleSelected();
}

View file

@ -14,6 +14,7 @@ package org.eclipse.cdt.dsf.gdb.internal.ui;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.internal.ui.console.GdbCliConsoleManager;
import org.eclipse.cdt.dsf.gdb.internal.ui.console.TracingConsoleManager;
import org.eclipse.cdt.dsf.gdb.internal.ui.sync.GdbDebugContextSyncManager;
import org.eclipse.cdt.dsf.gdb.launching.GdbLaunch;
import org.eclipse.cdt.dsf.gdb.launching.LaunchMessages;
import org.eclipse.core.runtime.IStatus;
@ -48,6 +49,8 @@ public class GdbUIPlugin extends AbstractUIPlugin {
private static TracingConsoleManager fTracingConsoleManager;
private static GdbCliConsoleManager fGdbConsoleManager;
private static GdbDebugContextSyncManager fGdbSelectionSyncManager;
private static IPreferenceStore fCorePreferenceStore;
@ -72,6 +75,9 @@ public class GdbUIPlugin extends AbstractUIPlugin {
fGdbConsoleManager = new GdbCliConsoleManager();
fGdbConsoleManager.startup();
fGdbSelectionSyncManager = new GdbDebugContextSyncManager();
fGdbSelectionSyncManager.startup();
}
/*
@ -82,6 +88,7 @@ public class GdbUIPlugin extends AbstractUIPlugin {
public void stop(BundleContext context) throws Exception {
fTracingConsoleManager.shutdown();
fGdbConsoleManager.shutdown();
fGdbSelectionSyncManager.shutdown();
disposeAdapterSets();
plugin = null;
@ -93,6 +100,10 @@ public class GdbUIPlugin extends AbstractUIPlugin {
return fGdbConsoleManager;
}
public static GdbDebugContextSyncManager getGdbSelectionSyncManager() {
return fGdbSelectionSyncManager;
}
/**
* Dispose adapter sets for all launches.
*/

View file

@ -9,7 +9,6 @@ package org.eclipse.cdt.dsf.gdb.internal.ui.console;
import java.io.IOException;
import org.eclipse.cdt.debug.ui.debuggerconsole.IDebuggerConsole;
import org.eclipse.cdt.debug.ui.debuggerconsole.IDebuggerConsoleView;
import org.eclipse.cdt.dsf.gdb.IGdbDebugPreferenceConstants;
import org.eclipse.cdt.dsf.gdb.internal.ui.GdbUIPlugin;
@ -37,7 +36,7 @@ import org.eclipse.ui.part.IPageBookViewPage;
* towards GDB. It is used whenever {@link IGDBBackend#isFullGdbConsoleSupported()}
* returns false.
*/
public class GdbBasicCliConsole extends IOConsole implements IDebuggerConsole {
public class GdbBasicCliConsole extends IOConsole implements IGDBDebuggerConsole {
/**
* A conversion factor used to resolve number of characters from number of lines

View file

@ -13,7 +13,6 @@ import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.cdt.debug.ui.debuggerconsole.IDebuggerConsole;
import org.eclipse.cdt.debug.ui.debuggerconsole.IDebuggerConsoleView;
import org.eclipse.cdt.utils.pty.PTY;
import org.eclipse.core.runtime.CoreException;
@ -36,7 +35,7 @@ import org.eclipse.ui.part.IPageBookViewPage;
* full-featured CLI interface. This is only supported with GDB >= 7.12
* and if IGDBBackend.isFullGdbConsoleSupported() returns true.
*/
public class GdbFullCliConsole extends AbstractConsole implements IDebuggerConsole {
public class GdbFullCliConsole extends AbstractConsole implements IGDBDebuggerConsole {
private final ILaunch fLaunch;
private final String fLabel;
private final PTY fGdbPty;
@ -206,4 +205,5 @@ public class GdbFullCliConsole extends AbstractConsole implements IDebuggerConso
public IGdbTerminalControlConnector getTerminalControlConnector() {
return fTerminalConnector;
}
}

View file

@ -0,0 +1,57 @@
/*******************************************************************************
* Copyright (c) 2016 Ericsson and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.internal.ui.console;
import org.eclipse.cdt.debug.ui.debuggerconsole.IDebuggerConsole;
import org.eclipse.cdt.dsf.gdb.internal.service.IGDBFocusSynchronizer;
import org.eclipse.cdt.dsf.gdb.internal.ui.GdbUIPlugin;
import org.eclipse.cdt.dsf.gdb.launching.GdbLaunch;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.ui.DebugUITools;
/**
* GDB specifics to IDebuggerConsole e.g. default implementations
*/
public interface IGDBDebuggerConsole extends IDebuggerConsole {
/*
* (non-Javadoc)
*
* @see org.eclipse.cdt.debug.ui.debuggerconsole.IDebuggerConsole#consoleSelected()
*/
@Override
public default void consoleSelected() {
DsfSession session = ((GdbLaunch)getLaunch()).getSession();
if (!session.isActive()) {return;}
// only trigger the DV selection if the current selection is in
// a different session
IAdaptable context = DebugUITools.getDebugContext();
if (context != null) {
ILaunch selectedLaunch = context.getAdapter(ILaunch.class);
if (getLaunch().equals(selectedLaunch)) {
return;
}
}
session.getExecutor().execute(new Runnable() {
@Override
public void run() {
DsfServicesTracker tracker = new DsfServicesTracker(GdbUIPlugin.getBundleContext(), session.getId());
IGDBFocusSynchronizer gdbSync = tracker.getService(IGDBFocusSynchronizer.class);
tracker.dispose();
if (gdbSync != null) {
gdbSync.sessionSelected();
}
}
});
}
}

View file

@ -0,0 +1,78 @@
/*******************************************************************************
* Copyright (c) 2016 Ericsson AB and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.internal.ui.sync;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext;
import org.eclipse.cdt.dsf.gdb.internal.service.IGDBFocusSynchronizer;
import org.eclipse.cdt.dsf.gdb.internal.ui.GdbUIPlugin;
import org.eclipse.cdt.dsf.mi.service.IMIExecutionDMContext;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.contexts.DebugContextEvent;
import org.eclipse.debug.ui.contexts.IDebugContextListener;
/**
* This instance propagates the selection of debug context elements e.g. Thread to the back end GDB
*/
public class GdbDebugContextSyncManager implements IDebugContextListener {
public void startup() {
DebugUITools.getDebugContextManager().addDebugContextListener(this);
}
public void shutdown() {
DebugUITools.getDebugContextManager().removeDebugContextListener(this);
}
@Override
public void debugContextChanged(DebugContextEvent event) {
// Make sure that it's a change of selection that caused the event
if ((event.getFlags() != DebugContextEvent.ACTIVATED)) {
return;
}
// Get selected element in the Debug View
IAdaptable context = DebugUITools.getDebugContext();
if (context != null) {
final IDMContext dmc = context.getAdapter(IDMContext.class);
if (dmc instanceof IMIExecutionDMContext || dmc instanceof IFrameDMContext) {
// A thread or stack frame was selected. In either case, have GDB switch to the new
// corresponding thread, if required.
// Resolve the debug session
String eventSessionId = dmc.getSessionId();
if (!(DsfSession.isSessionActive(eventSessionId))) {
return;
}
DsfSession session = DsfSession.getSession(eventSessionId);
// order GDB to switch thread
session.getExecutor().execute(new Runnable() {
@Override
public void run() {
DsfServicesTracker tracker = new DsfServicesTracker(GdbUIPlugin.getBundleContext(),
eventSessionId);
IGDBFocusSynchronizer gdbSync = tracker.getService(IGDBFocusSynchronizer.class);
tracker.dispose();
if (gdbSync != null) {
gdbSync.setFocus(new IDMContext[] {dmc}, new ImmediateRequestMonitor() {});
}
}
});
}
}
}
}

View file

@ -0,0 +1,121 @@
/*******************************************************************************
* Copyright (c) 2016 Ericsson and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.internal.ui.viewmodel.launch;
import java.util.List;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl;
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.launch.StackFramesVMNode;
import org.eclipse.cdt.dsf.gdb.internal.service.IGDBFocusSynchronizer.IGDBFocusChangedEvent;
import org.eclipse.cdt.dsf.mi.service.IMIExecutionDMContext;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.ui.viewmodel.VMChildrenUpdate;
import org.eclipse.cdt.dsf.ui.viewmodel.VMDelta;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.AbstractDMVMProvider;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
public class GdbStackFramesVMNode extends StackFramesVMNode {
public GdbStackFramesVMNode(AbstractDMVMProvider provider, DsfSession session) {
super(provider, session);
}
@Override
public int getDeltaFlags(Object e) {
if (e instanceof IGDBFocusChangedEvent) {
return IModelDelta.SELECT;
}
return super.getDeltaFlags(e);
}
@Override
public void buildDelta(final Object e, final VMDelta parentDelta, final int nodeOffset, final RequestMonitor rm) {
if (e instanceof IGDBFocusChangedEvent) {
buildDeltaForFocusChangedEvent((IGDBFocusChangedEvent)e, parentDelta, rm);
}
else {
super.buildDelta(e, parentDelta, nodeOffset, rm);
}
}
private void buildDeltaForFocusChangedEvent(IGDBFocusChangedEvent event, VMDelta parentDelta, RequestMonitor rm) {
getSession().getExecutor().execute(new Runnable() {
@Override
public void run() {
IDMContext ctx = event.getDMContext();
// Is IGDBFocusChangedEvent pertinent for this VMNode?
if (ctx instanceof IFrameDMContext) {
IFrameDMContext newFrameFocus = (IFrameDMContext)ctx;
IMIExecutionDMContext execDmc = DMContexts.getAncestorOfType(newFrameFocus, IMIExecutionDMContext.class);
if (execDmc == null) {
rm.done();
return;
}
IRunControl runControl = getServicesTracker().getService(IRunControl.class);
if (runControl == null) {
// Required services have not initialized yet. Ignore the event.
rm.done();
return;
}
if (runControl.isSuspended(execDmc) || runControl.isStepping(execDmc)) {
// find the VMC index for the frame that switched, so we can select it correctly.
getVMCIndexForDmc(
GdbStackFramesVMNode.this,
newFrameFocus,
parentDelta,
new DataRequestMonitor<Integer>(getExecutor(), rm) {
@Override
protected void handleSuccess() {
// change to frameOffset
final int frameOffset = getData();
// Retrieve the list of stack frames
getVMProvider().updateNode(GdbStackFramesVMNode.this,
new VMChildrenUpdate(parentDelta,
getVMProvider().getPresentationContext(), -1,
-1, new DataRequestMonitor<List<Object>>(
getExecutor(), rm) {
@Override
public void handleSuccess() {
final List<Object> data = getData();
if (data != null && data.size() != 0) {
// create the delta to select the
// current stack frame
parentDelta.addNode(
data.get(frameOffset),
frameOffset,
IModelDelta.SELECT | IModelDelta.FORCE
);
}
rm.done();
}
}));
}
});
} else {
// thread is running - no delta to produce for the stack frame node
rm.done();
}
} else {
// context not a frame - nothing to do here
rm.done();
}
}
});
}
}

View file

@ -25,7 +25,6 @@ import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommand
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlShutdownDMEvent;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.launch.AbstractLaunchVMProvider;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.launch.LaunchRootVMNode;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.launch.StackFramesVMNode;
import org.eclipse.cdt.dsf.gdb.internal.ui.GdbUIPlugin;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceRecordSelectedChangedDMEvent;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITracingStartedDMEvent;
@ -73,7 +72,7 @@ public class LaunchVMProvider extends AbstractLaunchVMProvider
IVMNode threadsNode = new ThreadVMNode(this, getSession());
addChildNodes(containerNode, new IVMNode[] { threadsNode });
IVMNode stackFramesNode = new StackFramesVMNode(this, getSession());
IVMNode stackFramesNode = new GdbStackFramesVMNode(this, getSession());
addChildNodes(threadsNode, new IVMNode[] { stackFramesNode });
}

View file

@ -18,8 +18,11 @@ import java.util.Map;
import org.eclipse.cdt.debug.internal.ui.pinclone.PinCloneUtils;
import org.eclipse.cdt.debug.ui.IPinProvider.IPinElementColorDescriptor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IProcesses;
import org.eclipse.cdt.dsf.debug.service.IProcesses.IThreadDMContext;
@ -32,6 +35,7 @@ import org.eclipse.cdt.dsf.debug.ui.viewmodel.launch.AbstractThreadVMNode;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.launch.ExecutionContextLabelText;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.launch.ILaunchVMConstants;
import org.eclipse.cdt.dsf.gdb.IGdbDebugPreferenceConstants;
import org.eclipse.cdt.dsf.gdb.internal.service.IGDBFocusSynchronizer.IGDBFocusChangedEvent;
import org.eclipse.cdt.dsf.gdb.internal.ui.GdbPinProvider;
import org.eclipse.cdt.dsf.gdb.internal.ui.GdbUIPlugin;
import org.eclipse.cdt.dsf.gdb.service.IGDBProcesses.IGdbThreadDMData;
@ -373,6 +377,9 @@ public class ThreadVMNode extends AbstractThreadVMNode
// being displayed.
return IModelDelta.CONTENT;
}
else if (e instanceof IGDBFocusChangedEvent) {
return IModelDelta.SELECT;
}
return super.getDeltaFlags(e);
}
@ -395,10 +402,49 @@ public class ThreadVMNode extends AbstractThreadVMNode
ancestorDelta.setFlags(ancestorDelta.getFlags() | IModelDelta.CONTENT);
}
rm.done();
} else if (e instanceof IGDBFocusChangedEvent) {
buildDeltaForFocusChangedEvent((IGDBFocusChangedEvent)e, parentDelta, nodeOffset, rm);
} else {
super.buildDelta(e, parentDelta, nodeOffset, rm);
}
}
private void buildDeltaForFocusChangedEvent(IGDBFocusChangedEvent event, VMDelta parentDelta, int nodeOffset, RequestMonitor rm) {
getSession().getExecutor().execute(new DsfRunnable() {
@Override
public void run() {
// can we find a thread context in the hierarchy of the IGDBFocusChangedEvent's context?
IDMContext thread = DMContexts.getAncestorOfType(event.getDMContext(), IMIExecutionDMContext.class);
final IDMContext newThreadFocus = thread;
if (newThreadFocus != null) {
// we need to find the VMC index for the thread that switched, so we can
// select it correctly.
getVMCIndexForDmc(
ThreadVMNode.this,
newThreadFocus,
parentDelta,
new DataRequestMonitor<Integer>(getExecutor(), rm) {
@Override
protected void handleSuccess() {
final int threadOffset = getData();
// Create a delta for the thread node - Select it whether it's running or not
// this way the thread will be visible in the DV even if we end-up selecting one
// of its frame. Using the FORCE flag to override the sticky selection
// policy.
parentDelta.addNode(createVMContext(newThreadFocus), nodeOffset + threadOffset,
IModelDelta.SELECT | IModelDelta.FORCE);
rm.done();
}
});
} else {
// context not a thread - nothing to do here
rm.done();
}
}
});
}
private static final String MEMENTO_NAME = "THREAD_MEMENTO_NAME"; //$NON-NLS-1$
/*

View file

@ -28,6 +28,7 @@ Export-Package: org.eclipse.cdt.dsf.gdb,
org.eclipse.cdt.examples.dsf.gdb",
org.eclipse.cdt.dsf.gdb.internal.commands;x-friends:="org.eclipse.cdt.dsf.gdb.ui",
org.eclipse.cdt.dsf.gdb.internal.memory;x-friends:="org.eclipse.cdt.dsf.gdb.ui",
org.eclipse.cdt.dsf.gdb.internal.service;x-friends:="org.eclipse.cdt.dsf.gdb.ui,org.eclipse.cdt.tests.dsf.gdb",
org.eclipse.cdt.dsf.gdb.internal.service.command.commands;x-internal:=true,
org.eclipse.cdt.dsf.gdb.internal.service.command.events;x-internal:=true,
org.eclipse.cdt.dsf.gdb.internal.service.command.output;x-internal:=true,

View file

@ -0,0 +1,367 @@
/*******************************************************************************
* Copyright (c) 2016 Ericsson and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.internal.service;
import java.util.Hashtable;
import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.AbstractDMEvent;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.DataModelInitializedEvent;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IProcesses.IProcessDMContext;
import org.eclipse.cdt.dsf.debug.service.IProcesses.IThreadDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext;
import org.eclipse.cdt.dsf.debug.service.IStack;
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext;
import org.eclipse.cdt.dsf.debug.service.command.ICommand;
import org.eclipse.cdt.dsf.debug.service.command.IEventListener;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.service.IGDBProcesses;
import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl;
import org.eclipse.cdt.dsf.mi.service.IMIExecutionDMContext;
import org.eclipse.cdt.dsf.mi.service.command.CommandFactory;
import org.eclipse.cdt.dsf.mi.service.command.output.MIConst;
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MINotifyAsyncOutput;
import org.eclipse.cdt.dsf.mi.service.command.output.MIOOBRecord;
import org.eclipse.cdt.dsf.mi.service.command.output.MIOutput;
import org.eclipse.cdt.dsf.mi.service.command.output.MIResult;
import org.eclipse.cdt.dsf.mi.service.command.output.MITuple;
import org.eclipse.cdt.dsf.mi.service.command.output.MIValue;
import org.eclipse.cdt.dsf.service.AbstractDsfService;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.osgi.framework.BundleContext;
/**
* This service keeps synchronized the CDT Debug View selection and GDB's
* internal focus.
*
* To keep the Debug View selection synchronized to CDT's selection, the service keeps
* track of what is the current GDB focus, by listening to the GDB MI notification
* "=thread-selected". When this notification is received, the service orders a change
* to CDT's Debug View selection to match, by sending an IGDBFocusChangedEvent.
*
* To keep GDB's focus synchronized to the Debug View selections, the UI listens to
* platform 'Debug Selection changed' events, and then uses this service, to order GDB
* to change focus to match the selection.
*
* Note: the mapping between the DV selection and GDB focus is not 1 to 1; there can
* be multiple debug sessions at one time, all shown in the DV. There is however a single
* effective DV selection. On the other end, each debug session has a dedicated instance
* of GDB, having its own unique focus, at any given time. Also not all DV selections map
* to a valid GDB focus.
*
* @since 5.2
*/
public class GDBFocusSynchronizer extends AbstractDsfService implements IGDBFocusSynchronizer, IEventListener
{
/** This service's opinion of what is the current GDB focus - it can be
* a thread or stack frame context */
private IDMContext fCurrentGDBFocus;
private IStack fStackService;
private IGDBProcesses fProcesses;
private IGDBControl fGdbcontrol;
private CommandFactory fCommandFactory;
// default initial values
private static final String THREAD_ID_DEFAULT = "1"; //$NON-NLS-1$
public GDBFocusSynchronizer(DsfSession session) {
super(session);
}
private class GDBFocusChangedEvent extends AbstractDMEvent<IDMContext>
implements IGDBFocusChangedEvent
{
public GDBFocusChangedEvent(IDMContext ctx) {
super(ctx);
}
}
@Override
protected BundleContext getBundleContext() {
return GdbPlugin.getBundleContext();
}
@Override
public void initialize(final RequestMonitor requestMonitor) {
super.initialize(new ImmediateRequestMonitor(requestMonitor) {
@Override
protected void handleSuccess() {
doInitialize(requestMonitor);
}
});
}
private void doInitialize(RequestMonitor requestMonitor) {
// obtain reference to a few needed services
fProcesses = getServicesTracker().getService(IGDBProcesses.class);
fStackService = getServicesTracker().getService(IStack.class);
fGdbcontrol = getServicesTracker().getService(IGDBControl.class);
fCommandFactory = fGdbcontrol.getCommandFactory();
register(new String[] { IGDBFocusSynchronizer.class.getName()},
new Hashtable<String, String>());
fGdbcontrol.addEventListener(this);
getSession().addServiceEventListener(this, null);
// set a sane initial value for current GDB focus.
// This value will be updated when the session has finished launching.
// See updateContexts() below.
fCurrentGDBFocus = createThreadContextFromThreadId(THREAD_ID_DEFAULT);
requestMonitor.done();
}
@Override
public void shutdown(RequestMonitor requestMonitor) {
fGdbcontrol.removeEventListener(this);
getSession().removeServiceEventListener(this);
unregister();
super.shutdown(requestMonitor);
}
@Override
public void setFocus(final IDMContext[] focus, RequestMonitor rm) {
assert focus != null;
// new Debug View thread or stack frame selection
IDMContext elem = focus[0];
// new selection is a frame?
if (elem instanceof IFrameDMContext) {
final IFrameDMContext finalFrameCtx = (IFrameDMContext)elem;
setFrameFocus(finalFrameCtx, new ImmediateRequestMonitor(rm) {
@Override
public void handleSuccess() {
// update the current focus, to match new GDB focus
fCurrentGDBFocus = finalFrameCtx;
rm.done();
}
});
}
// new selection is a thread?
else if (elem instanceof IMIExecutionDMContext) {
final IMIExecutionDMContext finalThreadCtx = (IMIExecutionDMContext)elem;
setThreadFocus(finalThreadCtx, new ImmediateRequestMonitor(rm) {
@Override
protected void handleSuccess() {
// update the current focus, to match new GDB focus
fCurrentGDBFocus = finalThreadCtx;
rm.done();
}
});
}
else {
assert false;
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID,
INVALID_HANDLE, "Invalid context to set focus to", null)); //$NON-NLS-1$);
return;
}
}
protected void setThreadFocus(IMIExecutionDMContext newThread, RequestMonitor rm) {
if (newThread == null) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID,
INVALID_HANDLE, "GdbFocusSynchronizer unable to resolve thread context for the selected element", null)); //$NON-NLS-1$
return;
}
// Create a mi-thread-select and send the command
ICommand<MIInfo> command = fCommandFactory.createMIThreadSelect(fGdbcontrol.getContext(), newThread.getThreadId());
fGdbcontrol.queueCommand(command, new ImmediateDataRequestMonitor<MIInfo> (rm));
}
protected void setFrameFocus(IFrameDMContext newFrame, RequestMonitor rm) {
if (newFrame == null) {
rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID,
INVALID_HANDLE, "GdbFocusSynchronizer unable to resolve frame context for the selected element", null)); //$NON-NLS-1$
return;
}
// We must specify the thread for which we want to set the frame in the -stack-select-frame command
IMIExecutionDMContext threadDmc = DMContexts.getAncestorOfType(newFrame, IMIExecutionDMContext.class);
if (isThreadSuspended(threadDmc)) {
// Create a mi-stack-select-frame and send the command
ICommand<MIInfo> command = fCommandFactory.createMIStackSelectFrame(threadDmc, newFrame.getLevel());
fGdbcontrol.queueCommand(command, new ImmediateDataRequestMonitor<MIInfo>(rm));
}
else {
rm.done();
}
}
private boolean isThreadSuspended(IExecutionDMContext ctx) {
assert ctx != null;
IRunControl runControl = getServicesTracker().getService(IRunControl.class);
if (runControl != null) {
return runControl.isSuspended(ctx);
}
else {
return false;
}
}
/**
* Parses gdb output for the =thread-selected notification.
* When this is detected, generate a DSF event to notify listeners
*
* example :
* =thread-selected,id="7",frame={level="0",addr="0x000000000041eab0",func="main",args=[]}
*/
@Override
public void eventReceived(Object output) {
for (MIOOBRecord oobr : ((MIOutput)output).getMIOOBRecords()) {
if (oobr instanceof MINotifyAsyncOutput) {
MINotifyAsyncOutput out = (MINotifyAsyncOutput) oobr;
String miEvent = out.getAsyncClass();
if ("thread-selected".equals(miEvent)) { //$NON-NLS-1$
// extract tid
MIResult[] results = out.getMIResults();
String tid = null;
String frameLevel = null;
for (int i = 0; i < results.length; i++) {
String var = results[i].getVariable();
MIValue val = results[i].getMIValue();
if (var.equals("frame") && val instanceof MITuple) { //$NON-NLS-1$
// dig deeper to get the frame level
MIResult[] res = ((MITuple)val).getMIResults();
for (int j = 0; j < res.length; j++) {
var = res[j].getVariable();
val = res[j].getMIValue();
if (var.equals("level")) { //$NON-NLS-1$
if (val instanceof MIConst) {
frameLevel = ((MIConst) val).getString();
}
}
}
}
else {
if (var.equals("id")) { //$NON-NLS-1$
if (val instanceof MIConst) {
tid = ((MIConst) val).getString();
}
}
}
}
// tid should never be null
assert (tid != null);
if (tid == null) {
return;
}
// update current focus
if (frameLevel == null) {
// thread running - current focus is a thread
fCurrentGDBFocus = createThreadContextFromThreadId(tid);
createAndDispatchGDBFocusChangedEvent();
}
else {
// thread suspended - current focus is a stack frame
int intFrameNum = 0;
try {
intFrameNum = Integer.parseInt(frameLevel);
}
catch (NumberFormatException e){
GdbPlugin.log(e);
}
String finalTid = tid;
fStackService.getFrames(
createThreadContextFromThreadId(finalTid),
intFrameNum, intFrameNum,
new ImmediateDataRequestMonitor<IFrameDMContext[]>() {
@Override
protected void handleCompleted() {
if (isSuccess() && getData().length > 0) {
fCurrentGDBFocus = getData()[0];
} else {
fCurrentGDBFocus = createThreadContextFromThreadId(finalTid);
}
createAndDispatchGDBFocusChangedEvent();
}
});
}
}
}
}
}
private void createAndDispatchGDBFocusChangedEvent() {
assert fCurrentGDBFocus != null;
fGdbcontrol.getSession().dispatchEvent(new GDBFocusChangedEvent(fCurrentGDBFocus),
fGdbcontrol.getProperties());
}
/**
* Creates an execution context from a thread id
*
* @param tid The thread id on which the execution context is based
*/
private IMIExecutionDMContext createThreadContextFromThreadId(String tid) {
assert tid != null;
IContainerDMContext parentContainer =
fProcesses.createContainerContextFromThreadId(fGdbcontrol.getContext(), tid);
IProcessDMContext processDmc = DMContexts.getAncestorOfType(parentContainer, IProcessDMContext.class);
IThreadDMContext threadDmc = fProcesses.createThreadContext(processDmc, tid);
return fProcesses.createExecutionContext(parentContainer, threadDmc, tid);
}
@Override
public void sessionSelected() {
// get debug view to select this session's current thread/frame
createAndDispatchGDBFocusChangedEvent();
}
@Override
public IDMContext[] getFocus() {
return new IDMContext[] { fCurrentGDBFocus };
}
@DsfServiceEventHandler
public void updateContexts(DataModelInitializedEvent event) {
// the debug session has finished launching - update the current focus
// to something sane. i.e. thread1 or thread1->frame0
IMIExecutionDMContext threadCtx = createThreadContextFromThreadId(THREAD_ID_DEFAULT);
if (!isThreadSuspended(threadCtx)) {
fCurrentGDBFocus = threadCtx;
}
else {
fStackService.getTopFrame(threadCtx, new ImmediateDataRequestMonitor<IFrameDMContext>() {
@Override
protected void handleCompleted() {
if (isSuccess()) {
fCurrentGDBFocus = getData();
} else {
fCurrentGDBFocus = threadCtx;
}
}
});
}
}
}

View file

@ -0,0 +1,53 @@
/*******************************************************************************
* Copyright (c) 2016 Ericsson and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.internal.service;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.datamodel.IDMEvent;
import org.eclipse.cdt.dsf.service.IDsfService;
/**
* This service keeps synchronized the CDT debug view selection and GDB's
* internal focus - GDB's current thread, stack frame, and (implicitly) inferior.
*
* @since 5.2
*/
public interface IGDBFocusSynchronizer extends IDsfService {
/**
* Returns an array of contexts, representing the current synchronized focus
*/
IDMContext[] getFocus();
/**
* Sets the service's current focus and propagate it to the GDB corresponding to this
* service's instance, when appropriate.
*
* @param focus An array of IDMContext, each context representing a focus'ed element
* from the Debug View
* @param rm the request monitor
*/
void setFocus(IDMContext[] focus, RequestMonitor rm);
/**
* The service sends this event to indicate that GDB has changed its focus, as a
* result of an event not triggered by CDT. For example a console command typed by
* the user.
* Note: the full focus might not be reflected in the included context. The service
* can be queried to get the complete picture.
*/
interface IGDBFocusChangedEvent extends IDMEvent<IDMContext> {}
/**
* This tells the synchronizer that the session, corresponding to this service's
* instance, has been selected. This can be called, for example, when a specific
* debugger console has become active, so that the synchronizer will reflect this
* in the Debug View selection.
*/
void sessionSelected();
}

View file

@ -30,6 +30,7 @@ import org.eclipse.cdt.dsf.debug.service.ISourceLookup.ISourceLookupDMContext;
import org.eclipse.cdt.dsf.debug.service.IStack;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService;
import org.eclipse.cdt.dsf.gdb.service.IGDBHardwareAndOS;
import org.eclipse.cdt.dsf.gdb.internal.service.IGDBFocusSynchronizer;
import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl;
import org.eclipse.cdt.dsf.mi.service.CSourceLookup;
import org.eclipse.cdt.dsf.mi.service.IMIBackend;
@ -128,6 +129,10 @@ public class ServicesLaunchSequence extends Sequence {
public void execute(final RequestMonitor requestMonitor) {
fLaunch.getServiceFactory().createService(MIBreakpointsSynchronizer.class, fSession).initialize(requestMonitor);
}},
new Step() { @Override
public void execute(final RequestMonitor requestMonitor) {
fLaunch.getServiceFactory().createService(IGDBFocusSynchronizer.class, fSession).initialize(requestMonitor);
}},
};
public ServicesLaunchSequence(DsfSession session, GdbLaunch launch, IProgressMonitor pm) {

View file

@ -38,6 +38,8 @@ import org.eclipse.cdt.dsf.debug.service.ISourceLookup;
import org.eclipse.cdt.dsf.debug.service.IStack;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControl;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.internal.service.GDBFocusSynchronizer;
import org.eclipse.cdt.dsf.gdb.internal.service.IGDBFocusSynchronizer;
import org.eclipse.cdt.dsf.gdb.launching.GdbLaunch;
import org.eclipse.cdt.dsf.gdb.launching.LaunchUtils;
import org.eclipse.cdt.dsf.gdb.service.command.CommandFactory_6_8;
@ -169,7 +171,10 @@ public class GdbDebugServicesFactory extends AbstractDsfDebugServicesFactory {
}
else if (MIBreakpointsSynchronizer.class.isAssignableFrom(clazz)) {
return (V)createBreakpointsSynchronizerService(session);
}
}
else if (IGDBFocusSynchronizer.class.isAssignableFrom(clazz)) {
return (V)createFocusSynchronizerService(session);
}
return super.createService(clazz, session, optionalArguments);
}
@ -369,6 +374,13 @@ public class GdbDebugServicesFactory extends AbstractDsfDebugServicesFactory {
return new MIBreakpointsSynchronizer(session);
}
/**
* @since 5.2
*/
protected IGDBFocusSynchronizer createFocusSynchronizerService(DsfSession session) {
return new GDBFocusSynchronizer(session);
}
/**
* Compares the GDB version of the current debug session with the one specified by
* parameter 'version'. Returns -1, 0, or 1 if the current version is less than,

View file

@ -16,6 +16,7 @@ import org.eclipse.cdt.tests.dsf.gdb.tests.nonstop.MIExpressionsNonStopTest;
import org.eclipse.cdt.tests.dsf.gdb.tests.nonstop.MIRunControlNonStopTargetAvailableTest;
import org.eclipse.cdt.tests.dsf.gdb.tests.nonstop.OperationsWhileTargetIsRunningNonStopTest;
import org.eclipse.cdt.tests.dsf.gdb.tests.nonstop.StepIntoSelectionNonStopTest;
import org.eclipse.cdt.tests.dsf.gdb.tests.nonstop.ThreadStackFrameSyncTest;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@ -57,6 +58,7 @@ import org.junit.runners.Suite;
GDBProcessesTest.class,
PostMortemCoreTest.class,
CommandTimeoutTest.class,
ThreadStackFrameSyncTest.class,
/* Add your test class here */
})
public class SuiteGdb {

View file

@ -0,0 +1,556 @@
/*******************************************************************************
* Copyright (c) 2016 Ericsson and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.cdt.tests.dsf.gdb.tests.nonstop;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Query;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.datamodel.IDMEvent;
import org.eclipse.cdt.dsf.debug.service.IMultiRunControl;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext;
import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants;
import org.eclipse.cdt.dsf.gdb.internal.service.IGDBFocusSynchronizer;
import org.eclipse.cdt.dsf.gdb.internal.service.IGDBFocusSynchronizer.IGDBFocusChangedEvent;
import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl;
import org.eclipse.cdt.dsf.mi.service.IMIExecutionDMContext;
import org.eclipse.cdt.dsf.mi.service.IMIProcesses;
import org.eclipse.cdt.dsf.mi.service.command.commands.CLICommand;
import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent;
import org.eclipse.cdt.dsf.mi.service.command.output.CLIThreadInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIConsoleStreamOutput;
import org.eclipse.cdt.dsf.mi.service.command.output.MIConst;
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MINotifyAsyncOutput;
import org.eclipse.cdt.dsf.mi.service.command.output.MIOOBRecord;
import org.eclipse.cdt.dsf.mi.service.command.output.MIOutput;
import org.eclipse.cdt.dsf.mi.service.command.output.MIResult;
import org.eclipse.cdt.dsf.mi.service.command.output.MITuple;
import org.eclipse.cdt.dsf.mi.service.command.output.MIValue;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.tests.dsf.gdb.framework.BaseParametrizedTestCase;
import org.eclipse.cdt.tests.dsf.gdb.framework.ServiceEventWaitor;
import org.eclipse.cdt.tests.dsf.gdb.framework.SyncUtil;
import org.eclipse.cdt.tests.dsf.gdb.launching.TestsPlugin;
import org.eclipse.cdt.tests.dsf.gdb.tests.ITestConstants;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
public class ThreadStackFrameSyncTest extends BaseParametrizedTestCase {
final static private int DEFAULT_TIMEOUT = 1000;
private DsfServicesTracker fServicesTracker;
private IMultiRunControl fMultiRunControl;
private IGDBControl fCommandControl;
private IGDBFocusSynchronizer fGdbSync;
private DsfSession fSession;
private List<IDMEvent<? extends IDMContext>> fEventsReceived = new ArrayList<IDMEvent<? extends IDMContext>>();
// Breakpoint tags in MultiThread.cc
public static final String[] LINE_TAGS = new String[] {
"LINE_MAIN_BEFORE_THREAD_START", // Just before StartThread
"LINE_MAIN_AFTER_THREAD_START", // Just after StartThread
"LINE_MAIN_ALL_THREADS_STARTED", // Where all threads are guaranteed to be started.
};
/*
* Name of the executable
*/
private static final String EXEC_NAME = "MultiThread.exe";
private static final String SOURCE_NAME = "MultiThread.cc";
@BeforeClass
public static void beforeClass() {
Assume.assumeTrue(supportsNonStop());
}
@Override
public void doBeforeTest() throws Exception {
assumeGdbVersionAtLeast(ITestConstants.SUFFIX_GDB_7_12);
super.doBeforeTest();
resolveLineTagLocations(SOURCE_NAME, LINE_TAGS);
fSession = getGDBLaunch().getSession();
Assert.assertNotNull(fSession);
Runnable runnable = new Runnable() {
@Override
public void run() {
fServicesTracker = new DsfServicesTracker(TestsPlugin.getBundleContext(), fSession.getId());
Assert.assertTrue(fServicesTracker != null);
fCommandControl = fServicesTracker.getService(IGDBControl.class);
Assert.assertTrue(fCommandControl != null);
fMultiRunControl = fServicesTracker.getService(IMultiRunControl.class);
Assert.assertTrue(fMultiRunControl != null);
fGdbSync = fServicesTracker.getService(IGDBFocusSynchronizer.class);
Assert.assertTrue(fGdbSync != null);
IMIProcesses procService = fServicesTracker.getService(IMIProcesses.class);
Assert.assertTrue(procService != null);
// Register to receive DSF events
fSession.addServiceEventListener(ThreadStackFrameSyncTest.this, null);
}
};
fSession.getExecutor().submit(runnable).get();
}
@Override
protected void setLaunchAttributes() {
super.setLaunchAttributes();
setLaunchAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME,
EXEC_PATH + EXEC_NAME);
// Multi run control only makes sense for non-stop mode
setLaunchAttribute(IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_NON_STOP, true);
}
@Override
public void doAfterTest() throws Exception {
super.doAfterTest();
if (fSession != null) {
fSession.getExecutor().submit(() -> fSession.removeServiceEventListener(ThreadStackFrameSyncTest.this))
.get();
}
fEventsReceived.clear();
if (fServicesTracker!=null) fServicesTracker.dispose();
}
//////////////////////////////////////////////////////////////////////////////////////
// Start of tests
//////////////////////////////////////////////////////////////////////////////////////
/**
* This test verifies that changing the active thread, in GDB, in CLI,
* triggers a GDB notification that a new thread has been selected.
*/
@Test
public void testChangingCurrentThreadCLINotification() throws Throwable {
ServiceEventWaitor<MIStoppedEvent> eventWaitor =
new ServiceEventWaitor<MIStoppedEvent>(fMultiRunControl.getSession(), MIStoppedEvent.class);
// add a breakpoint in main
SyncUtil.addBreakpoint(SOURCE_NAME + ":" + getLineForTag("LINE_MAIN_ALL_THREADS_STARTED"), false);
// add a breakpoint in thread code
SyncUtil.addBreakpoint("36", false);
// Run program
SyncUtil.resumeAll();
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000)); // Wait for first thread to stop
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000)); // Wait for second thread to stop
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000));
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000));
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000));
// *** at this point all 5 threads should be stopped
// Try some thread switching - SwitchThreadAndCaptureThreadSwitchedEvent will
// capture the "=thread-selected" event and return the newly selected thread
// for us to compare to what we ordered
for (int i = 0; i < 2; i++) {
assertEquals("2",switchThreadAndCaptureThreadSwitchedEvent("2"));
assertEquals("3",switchThreadAndCaptureThreadSwitchedEvent("3"));
assertEquals("4",switchThreadAndCaptureThreadSwitchedEvent("4"));
assertEquals("5",switchThreadAndCaptureThreadSwitchedEvent("5"));
assertEquals("1",switchThreadAndCaptureThreadSwitchedEvent("1"));
}
}
/**
* This test verifies that changing the active frame, in GDB, in CLI,
* triggers a GDB notification that a new frame has been selected.
*/
@Test
public void testChangingCurrentFrameCLINotification() throws Throwable {
ServiceEventWaitor<MIStoppedEvent> eventWaitor =
new ServiceEventWaitor<MIStoppedEvent>(fMultiRunControl.getSession(), MIStoppedEvent.class);
// add a breakpoint in main
SyncUtil.addBreakpoint(SOURCE_NAME + ":" + getLineForTag("LINE_MAIN_ALL_THREADS_STARTED"), false);
// add a breakpoint in thread code
SyncUtil.addBreakpoint("36", false);
// Run program
SyncUtil.resumeAll();
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000)); // Wait for first thread to stop
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000)); // Wait for second thread to stop
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000));
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000));
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000));
// *** at this point all 5 threads should be stopped
// switch to a thread that has some stack frames
assertEquals("2",switchThreadAndCaptureThreadSwitchedEvent("2"));
// Try some stack frame switching - SwitchFrameAndCaptureThreadSwitchedEvent will
// capture the "=thread-selected" event and return the newly selected stack frame,
// for us to compare to what we ordered
for (int i = 0; i < 5; i++) {
assertEquals("1",switchFrameAndCaptureStackFrameSwitchedEvent("1"));
assertEquals("0",switchFrameAndCaptureStackFrameSwitchedEvent("0"));
}
}
/**
* This test verifies that the GDB Synchronizer service is able to set
* the current GDB thread
*/
@Test
public void testGdbSyncServiceCanSwitchGDBThread() throws Throwable {
ServiceEventWaitor<MIStoppedEvent> eventWaitor =
new ServiceEventWaitor<MIStoppedEvent>(fMultiRunControl.getSession(), MIStoppedEvent.class);
// add a breakpoint in main
SyncUtil.addBreakpoint(SOURCE_NAME + ":" + getLineForTag("LINE_MAIN_ALL_THREADS_STARTED"), false);
// add a breakpoint in thread code
SyncUtil.addBreakpoint("36", false);
// Run program
SyncUtil.resumeAll();
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000)); // Wait for first thread to stop
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000)); // Wait for second thread to stop
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000));
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000));
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000));
// *** at this point all 5 threads should be stopped
// have the sync service set GDB current tid to thread 5
fGdbSync.setFocus(new IDMContext[] {getContextForThreadId(5)}, new ImmediateRequestMonitor());
assertEquals("5", getCurrentThread());
fGdbSync.setFocus(new IDMContext[] {getContextForThreadId(4)}, new ImmediateRequestMonitor());
assertEquals("4", getCurrentThread());
fGdbSync.setFocus(new IDMContext[] {getContextForThreadId(3)}, new ImmediateRequestMonitor());
assertEquals("3", getCurrentThread());
fGdbSync.setFocus(new IDMContext[] {getContextForThreadId(2)}, new ImmediateRequestMonitor());
assertEquals("2", getCurrentThread());
fGdbSync.setFocus(new IDMContext[] {getContextForThreadId(1)}, new ImmediateRequestMonitor());
assertEquals("1", getCurrentThread());
}
/**
* This test verifies that the GDB Synchronizer service is able to set
* the current GDB stack frame
*/
@Test
public void testGdbSyncServiceCanSwitchGDBStackFrame() throws Throwable {
ServiceEventWaitor<MIStoppedEvent> eventWaitor =
new ServiceEventWaitor<MIStoppedEvent>(fMultiRunControl.getSession(), MIStoppedEvent.class);
// add a breakpoint in main
SyncUtil.addBreakpoint(SOURCE_NAME + ":" + getLineForTag("LINE_MAIN_ALL_THREADS_STARTED"), false);
// add a breakpoint in thread code
SyncUtil.addBreakpoint("36", false);
// Run program
SyncUtil.resumeAll();
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000)); // Wait for first thread to stop
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000)); // Wait for second thread to stop
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000));
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000));
eventWaitor.waitForEvent(TestsPlugin.massageTimeout(2000));
// *** at this point all 5 threads should be stopped
final IFrameDMContext frame1 = SyncUtil.getStackFrame(1, 1);
final IFrameDMContext frame0 = SyncUtil.getStackFrame(1, 0);
// do a few of times
for (int i = 0; i < 50; i++) {
// have the sync service switch stack frame to 1
fSession.getExecutor().execute(new Runnable() {
@Override
public void run() {
fGdbSync.setFocus(new IDMContext[] {frame1}, new ImmediateRequestMonitor());
}
});
Thread.sleep(100);
assertEquals("1", getCurrentStackFrameLevel());
// have the sync service switch stack frame to 0
fSession.getExecutor().execute(new Runnable() {
@Override
public void run() {
fGdbSync.setFocus(new IDMContext[] {frame0}, new ImmediateRequestMonitor());
}
});
Thread.sleep(100);
assertEquals("0", getCurrentStackFrameLevel());
}
}
//////////////////////////////////////////////////////////////////////////////////////
// End of tests
//////////////////////////////////////////////////////////////////////////////////////
// SyncUtil.getExecutionContext() takes the index of the
// array of all threads, so it will return a thread off by one.
// We compensate for this in this method
private IMIExecutionDMContext getContextForThreadId(int tid) throws InterruptedException, ExecutionException, TimeoutException {
return SyncUtil.getExecutionContext(tid -1);
}
/**
* This is a wrapper around selectGdbThread(), that waits and captures the
* expected "=thread-selected" event, and returns the thread id from it.
* @throws Throwable
*/
private String switchThreadAndCaptureThreadSwitchedEvent(String tid) throws Throwable {
Thread.sleep(100);
fEventsReceived.clear();
selectGdbThread(tid);
IDMContext ctx = waitForEvent(IGDBFocusChangedEvent.class).getDMContext();
if (ctx instanceof IMIExecutionDMContext) {
IMIExecutionDMContext execDmc = (IMIExecutionDMContext) ctx;
return execDmc.getThreadId();
}
else if (ctx instanceof IFrameDMContext) {
IMIExecutionDMContext execDmc = DMContexts.getAncestorOfType(ctx, IMIExecutionDMContext.class);
return execDmc.getThreadId();
}
return "unknown";
}
/**
* Waits and captures the expected "=thread-selected" event, and returns the frame id from it.
* @throws Throwable
*/
private String switchFrameAndCaptureStackFrameSwitchedEvent(String frameLevel) throws Throwable {
IFrameDMContext newFrame = null;
Thread.sleep(100);
fEventsReceived.clear();
selectGdbStackFrame(frameLevel);
Object[] elems = fGdbSync.getFocus();
for (Object elem : elems) {
if (elem instanceof IFrameDMContext) {
newFrame = (IFrameDMContext)elem;
break;
}
}
return newFrame != null ? Integer.toString(newFrame.getLevel()) : null;
}
/**
* Changes the current thread, using the CLI command "thread <tid>"
* @param tid: the thread id of the thread to switch-to. If empty,
* the command will simply report the current thread.
* @return the tid of the (possibly newly) currently selected gdb thread
* @throws Exception
*/
private String sendCLIThread(String tid) throws Exception {
IContainerDMContext containerDmc = SyncUtil.getContainerContext();
Query<CLIThreadInfo> query = new Query<CLIThreadInfo>() {
@Override
protected void execute(DataRequestMonitor<CLIThreadInfo> rm) {
fCommandControl.queueCommand(new CLICommand<CLIThreadInfo>(containerDmc,"thread " + tid) {
@Override
public CLIThreadInfo getResult(MIOutput output) {
return new CLIThreadInfo(output);
}
}, rm);
}
};
fCommandControl.getExecutor().execute(query);
CLIThreadInfo info = query.get();
return info.getCurrentThread();
}
private String getCurrentThread() throws Exception {
return sendCLIThread("");
}
/**
* Changes the current stack frame, using the CLI command "frame <level>". Then parses
* the output to extract the current frame.
* @param level the frame level wanted. If empty, the command will report the current level
* @return newly set level.
* @throws Exception
*/
private String sendCLIFrame(String level) throws Exception {
IContainerDMContext containerDmc = SyncUtil.getContainerContext();
Query<MIInfo> query = new Query<MIInfo>() {
@Override
protected void execute(DataRequestMonitor<MIInfo> rm) {
fCommandControl.queueCommand(new CLICommand<MIInfo>(containerDmc,"frame " + level) {
@Override
public CLIThreadInfo getResult(MIOutput output) {
return new CLIThreadInfo(output);
}
}, rm);
}
};
fCommandControl.getExecutor().execute(query);
String frameLevel = null;
for (MIOOBRecord oobr : query.get().getMIOutput().getMIOOBRecords()) {
// if frame changed, we'll get this printout:
if (oobr instanceof MINotifyAsyncOutput) {
// example of output:
// =thread-selected,id="2",frame={level="1",addr="0x00007ffff7bc4184",func="start_thread",args=[],from="/lib/x86_64-linux-gnu/libpthread.so.0"}
MINotifyAsyncOutput out = (MINotifyAsyncOutput) oobr;
String miEvent = out.getAsyncClass();
if ("thread-selected".equals(miEvent)) { //$NON-NLS-1$
// parse =thread-selected to extract current stack frame
MIResult[] results = out.getMIResults();
for (int i = 0; i < results.length; i++) {
String var = results[i].getVariable();
MIValue val = results[i].getMIValue();
if (var.equals("frame") && val instanceof MITuple) { //$NON-NLS-1$
// dig deeper to get the frame level
MIResult[] res = ((MITuple)val).getMIResults();
for (int j = 0; j < res.length; j++) {
var = res[j].getVariable();
val = res[j].getMIValue();
if (var.equals("level")) { //$NON-NLS-1$
if (val instanceof MIConst) {
frameLevel = ((MIConst) val).getString();
}
}
}
}
}
}
}
// if frame command was not given a parameter or the parameter is already
// the current frame, we'll get this version of the printout:
else if (oobr instanceof MIConsoleStreamOutput) {
// example of output (here frame = 0):
// ~"#0 main (argc=1 ...
String printout = ((MIConsoleStreamOutput) oobr).getCString();
int index1 = printout.indexOf('#');
int index2 = printout.indexOf(' ');
if (index1 != -1 && index2 != -1) {
frameLevel = printout.substring(index1 + 1, index2);
break;
}
}
}
return frameLevel;
}
private String getCurrentStackFrameLevel() throws Throwable {
return sendCLIFrame("");
}
@DsfServiceEventHandler
public void eventDispatched(IDMEvent<? extends IDMContext> e) {
synchronized(this) {
fEventsReceived.add(e);
notifyAll();
}
}
private void selectGdbThread(String tid) throws Throwable {
queueConsoleCommand(String.format("thread %s", tid));
}
private void selectGdbStackFrame(String frameLevel) throws Throwable {
queueConsoleCommand(String.format("frame %s", frameLevel));
}
private void queueConsoleCommand(String command) throws Throwable {
queueConsoleCommand(command, TestsPlugin.massageTimeout(DEFAULT_TIMEOUT), TimeUnit.MILLISECONDS);
}
private void queueConsoleCommand(final String command, int timeout, TimeUnit unit) throws Throwable {
Query<MIInfo> query = new Query<MIInfo>() {
@Override
protected void execute(DataRequestMonitor<MIInfo> rm) {
fCommandControl.queueCommand(
fCommandControl.getCommandFactory().createMIInterpreterExecConsole(
fCommandControl.getContext(),
command),
rm);
}
};
fSession.getExecutor().execute(query);
query.get(timeout, unit);
}
private <V extends IDMEvent<? extends IDMContext>> V waitForEvent(Class<V> eventType) throws Exception {
return waitForEvent(eventType, TestsPlugin.massageTimeout(DEFAULT_TIMEOUT));
}
@SuppressWarnings("unchecked")
private <V extends IDMEvent<? extends IDMContext>> V waitForEvent(Class<V> eventType, int timeout) throws Exception {
IDMEvent<?> event = getEvent(eventType);
if (event == null) {
synchronized(this) {
try {
wait(timeout);
}
catch (InterruptedException ex) {
}
}
event = getEvent(eventType);
if (event == null) {
throw new Exception(String.format("Timed out waiting for '%s' to occur.", eventType.getName()));
}
}
return (V)event;
}
@SuppressWarnings("unchecked")
private synchronized <V extends IDMEvent<? extends IDMContext>> V getEvent(Class<V> eventType) {
for (IDMEvent<?> e : fEventsReceived) {
if (eventType.isAssignableFrom(e.getClass())) {
fEventsReceived.remove(e);
return (V)e;
}
}
return null;
}
}

View file

@ -10,6 +10,7 @@
*******************************************************************************/
package org.eclipse.cdt.dsf.ui.viewmodel.datamodel;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor;
@ -27,6 +28,7 @@ import org.eclipse.cdt.dsf.ui.viewmodel.AbstractVMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.AbstractVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.VMChildrenUpdate;
import org.eclipse.cdt.dsf.ui.viewmodel.VMDelta;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
@ -347,4 +349,39 @@ abstract public class AbstractDMVMNode extends AbstractVMNode implements IVMNode
return retVal;
}
/**
* This method looks for a specific DMC, used in a IVMNode type. If found, its index is returned, else
* index 0.
*
* @param nodeType the node to search on
* @param wantedCtx the dmc we are looking-for
* @param parentDelta delta for the parent VMNode
* @param rm request monitor
*/
protected void getVMCIndexForDmc(IVMNode nodetype, IDMContext wantedCtx, VMDelta parentDelta, DataRequestMonitor<Integer> rm) {
final int indexFailed = 0;
getVMProvider().updateNode(nodetype, new VMChildrenUpdate(
parentDelta, getVMProvider().getPresentationContext(), -1, -1,
new DataRequestMonitor<List<Object>>(getExecutor(), rm) {
@Override
protected void handleSuccess() {
boolean found = false;
for (int i = 0; i < getData().size(); i++) {
if (getData().get(i) instanceof IDMVMContext) {
IDMVMContext vmc = (IDMVMContext)getData().get(i);
if (vmc.getDMContext().equals(wantedCtx)) {
rm.setData(i);
found = true;
break;
}
}
}
if (!found) {
rm.setData(indexFailed);
}
rm.done();
}
}));
}
}

View file

@ -1,4 +1,4 @@
Manifest-Version: 1.0
Manifest-Version: 1.02.8.0-SNAPSHOT
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-Vendor: %providerName