diff --git a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/debuggerconsole/DebuggerConsoleView.java b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/debuggerconsole/DebuggerConsoleView.java index 0af01357613..d5f316a7705 100644 --- a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/debuggerconsole/DebuggerConsoleView.java +++ b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/internal/ui/views/debuggerconsole/DebuggerConsoleView.java @@ -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(); } } diff --git a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/ui/debuggerconsole/IDebuggerConsole.java b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/ui/debuggerconsole/IDebuggerConsole.java index 86f83f23f46..fe5bfa2d74e 100644 --- a/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/ui/debuggerconsole/IDebuggerConsole.java +++ b/debug/org.eclipse.cdt.debug.ui/src/org/eclipse/cdt/debug/ui/debuggerconsole/IDebuggerConsole.java @@ -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(); } diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/GdbUIPlugin.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/GdbUIPlugin.java index b65ba1b147b..c10b7404eb2 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/GdbUIPlugin.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/GdbUIPlugin.java @@ -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. */ diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbBasicCliConsole.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbBasicCliConsole.java index 0d01b85a646..46233f7ab29 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbBasicCliConsole.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbBasicCliConsole.java @@ -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 diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbFullCliConsole.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbFullCliConsole.java index 274d3da7287..e1c0daf854c 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbFullCliConsole.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/GdbFullCliConsole.java @@ -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; } + } diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/IGDBDebuggerConsole.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/IGDBDebuggerConsole.java new file mode 100644 index 00000000000..74b0715b426 --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/console/IGDBDebuggerConsole.java @@ -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(); + } + } + }); + } +} diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/sync/GdbDebugContextSyncManager.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/sync/GdbDebugContextSyncManager.java new file mode 100644 index 00000000000..ffa0e6ddc6d --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/sync/GdbDebugContextSyncManager.java @@ -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() {}); + } + } + }); + } + } + } +} diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/viewmodel/launch/GdbStackFramesVMNode.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/viewmodel/launch/GdbStackFramesVMNode.java new file mode 100644 index 00000000000..c5b1328783a --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/viewmodel/launch/GdbStackFramesVMNode.java @@ -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(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>( + getExecutor(), rm) { + @Override + public void handleSuccess() { + final List 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(); + } + } + }); + } +} diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/viewmodel/launch/LaunchVMProvider.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/viewmodel/launch/LaunchVMProvider.java index 461cdba7ed0..72e45b63e0a 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/viewmodel/launch/LaunchVMProvider.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/viewmodel/launch/LaunchVMProvider.java @@ -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 }); } diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/viewmodel/launch/ThreadVMNode.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/viewmodel/launch/ThreadVMNode.java index 9efd65e70b9..abaabbc1c17 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/viewmodel/launch/ThreadVMNode.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/viewmodel/launch/ThreadVMNode.java @@ -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(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$ /* diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/META-INF/MANIFEST.MF b/dsf-gdb/org.eclipse.cdt.dsf.gdb/META-INF/MANIFEST.MF index 15bca41d8b1..e5dcb6b5bca 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/META-INF/MANIFEST.MF +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/META-INF/MANIFEST.MF @@ -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, diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/internal/service/GDBFocusSynchronizer.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/internal/service/GDBFocusSynchronizer.java new file mode 100644 index 00000000000..179496044a8 --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/internal/service/GDBFocusSynchronizer.java @@ -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 + 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()); + + 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 command = fCommandFactory.createMIThreadSelect(fGdbcontrol.getContext(), newThread.getThreadId()); + fGdbcontrol.queueCommand(command, new ImmediateDataRequestMonitor (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 command = fCommandFactory.createMIStackSelectFrame(threadDmc, newFrame.getLevel()); + fGdbcontrol.queueCommand(command, new ImmediateDataRequestMonitor(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() { + @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() { + @Override + protected void handleCompleted() { + if (isSuccess()) { + fCurrentGDBFocus = getData(); + } else { + fCurrentGDBFocus = threadCtx; + } + } + }); + } + } +} diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/internal/service/IGDBFocusSynchronizer.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/internal/service/IGDBFocusSynchronizer.java new file mode 100644 index 00000000000..bd2a33f27a3 --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/internal/service/IGDBFocusSynchronizer.java @@ -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 {} + + /** + * 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(); +} diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/launching/ServicesLaunchSequence.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/launching/ServicesLaunchSequence.java index 58b96107bf9..f0397a00e46 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/launching/ServicesLaunchSequence.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/launching/ServicesLaunchSequence.java @@ -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) { diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GdbDebugServicesFactory.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GdbDebugServicesFactory.java index e553286ec35..c1358b13e65 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GdbDebugServicesFactory.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb/src/org/eclipse/cdt/dsf/gdb/service/GdbDebugServicesFactory.java @@ -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, diff --git a/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/SuiteGdb.java b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/SuiteGdb.java index 3575445bcbe..70eb5012ae7 100644 --- a/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/SuiteGdb.java +++ b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/SuiteGdb.java @@ -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 { diff --git a/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/nonstop/ThreadStackFrameSyncTest.java b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/nonstop/ThreadStackFrameSyncTest.java new file mode 100644 index 00000000000..9c87502126d --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.tests.dsf.gdb/src/org/eclipse/cdt/tests/dsf/gdb/tests/nonstop/ThreadStackFrameSyncTest.java @@ -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> fEventsReceived = new ArrayList>(); + + // 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 eventWaitor = + new ServiceEventWaitor(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 eventWaitor = + new ServiceEventWaitor(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 eventWaitor = + new ServiceEventWaitor(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 eventWaitor = + new ServiceEventWaitor(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 " + * @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 query = new Query() { + @Override + protected void execute(DataRequestMonitor rm) { + fCommandControl.queueCommand(new CLICommand(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 ". 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 query = new Query() { + @Override + protected void execute(DataRequestMonitor rm) { + fCommandControl.queueCommand(new CLICommand(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 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 query = new Query() { + @Override + protected void execute(DataRequestMonitor rm) { + fCommandControl.queueCommand( + fCommandControl.getCommandFactory().createMIInterpreterExecConsole( + fCommandControl.getContext(), + command), + rm); + } + }; + fSession.getExecutor().execute(query); + query.get(timeout, unit); + } + + private > V waitForEvent(Class eventType) throws Exception { + return waitForEvent(eventType, TestsPlugin.massageTimeout(DEFAULT_TIMEOUT)); + } + + @SuppressWarnings("unchecked") + private > V waitForEvent(Class 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 getEvent(Class eventType) { + for (IDMEvent e : fEventsReceived) { + if (eventType.isAssignableFrom(e.getClass())) { + fEventsReceived.remove(e); + return (V)e; + } + } + return null; + } + + + +} diff --git a/dsf/org.eclipse.cdt.dsf.ui/src/org/eclipse/cdt/dsf/ui/viewmodel/datamodel/AbstractDMVMNode.java b/dsf/org.eclipse.cdt.dsf.ui/src/org/eclipse/cdt/dsf/ui/viewmodel/datamodel/AbstractDMVMNode.java index a07c56ee95c..3a607a666f3 100644 --- a/dsf/org.eclipse.cdt.dsf.ui/src/org/eclipse/cdt/dsf/ui/viewmodel/datamodel/AbstractDMVMNode.java +++ b/dsf/org.eclipse.cdt.dsf.ui/src/org/eclipse/cdt/dsf/ui/viewmodel/datamodel/AbstractDMVMNode.java @@ -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 rm) { + final int indexFailed = 0; + getVMProvider().updateNode(nodetype, new VMChildrenUpdate( + parentDelta, getVMProvider().getPresentationContext(), -1, -1, + new DataRequestMonitor>(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(); + } + })); + } } diff --git a/dsf/org.eclipse.cdt.dsf/META-INF/MANIFEST.MF b/dsf/org.eclipse.cdt.dsf/META-INF/MANIFEST.MF index 6f521704eb1..d8f944036aa 100644 --- a/dsf/org.eclipse.cdt.dsf/META-INF/MANIFEST.MF +++ b/dsf/org.eclipse.cdt.dsf/META-INF/MANIFEST.MF @@ -1,4 +1,4 @@ -Manifest-Version: 1.0 +Manifest-Version: 1.02.8.0-SNAPSHOT Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-Vendor: %providerName