mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-04-29 19:45:01 +02:00
Bug 293679: Allow multi-select when attaching to processes
This commit is contained in:
parent
d7092b12c9
commit
079dc72df6
4 changed files with 234 additions and 48 deletions
|
@ -1,5 +1,5 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2008, 2009 Ericsson and others.
|
||||
* Copyright (c) 2008, 2011 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
|
||||
|
@ -7,12 +7,15 @@
|
|||
*
|
||||
* Contributors:
|
||||
* Ericsson - initial API and implementation
|
||||
* Marc Khouzam (Ericsson) - Add support for multi-attach (Bug 293679)
|
||||
*******************************************************************************/
|
||||
package org.eclipse.cdt.dsf.gdb.internal.ui.actions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
|
@ -21,6 +24,7 @@ import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
|
|||
import org.eclipse.cdt.dsf.concurrent.DsfExecutor;
|
||||
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
|
||||
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
|
||||
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
|
||||
import org.eclipse.cdt.dsf.concurrent.Query;
|
||||
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
|
||||
import org.eclipse.cdt.dsf.datamodel.IDMContext;
|
||||
|
@ -31,6 +35,7 @@ import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService;
|
|||
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext;
|
||||
import org.eclipse.cdt.dsf.gdb.actions.IConnect;
|
||||
import org.eclipse.cdt.dsf.gdb.internal.ui.GdbUIPlugin;
|
||||
import org.eclipse.cdt.dsf.gdb.internal.ui.launching.LaunchUIMessages;
|
||||
import org.eclipse.cdt.dsf.gdb.internal.ui.launching.ProcessPrompter.PrompterInfo;
|
||||
import org.eclipse.cdt.dsf.gdb.launching.IProcessExtendedInfo;
|
||||
import org.eclipse.cdt.dsf.gdb.launching.LaunchMessages;
|
||||
|
@ -41,8 +46,10 @@ import org.eclipse.cdt.dsf.gdb.service.SessionType;
|
|||
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
|
||||
import org.eclipse.cdt.dsf.service.DsfSession;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Path;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.core.runtime.jobs.Job;
|
||||
import org.eclipse.debug.core.DebugPlugin;
|
||||
|
@ -58,6 +65,14 @@ public class GdbConnectCommand implements IConnect {
|
|||
private final DsfExecutor fExecutor;
|
||||
private final DsfServicesTracker fTracker;
|
||||
|
||||
// A map of processName to path, that allows us to remember the path to the binary file
|
||||
// for a process with a particular name. We can then re-use the same binary for another
|
||||
// process with the same name. This allows a user to connect to multiple processes
|
||||
// with the same name without having to be prompted each time for a path.
|
||||
// This map is associated to the current debug session only, therefore the user can
|
||||
// reset it by using a new debug session.
|
||||
private Map<String, String> fProcessNameToBinaryMap = new HashMap<String, String>();
|
||||
|
||||
public GdbConnectCommand(DsfSession session) {
|
||||
fExecutor = session.getExecutor();
|
||||
fTracker = new DsfServicesTracker(GdbUIPlugin.getBundleContext(), session.getId());
|
||||
|
@ -94,7 +109,12 @@ public class GdbConnectCommand implements IConnect {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Need a job because prompter.handleStatus will block
|
||||
/**
|
||||
* This job will prompt the user to select a set of processes
|
||||
* to attach too.
|
||||
* We need a job because prompter.handleStatus will block and
|
||||
* we don't want to block the executor.
|
||||
*/
|
||||
protected class PromptForPidJob extends Job {
|
||||
|
||||
// The list of processes used in the case of an ATTACH session
|
||||
|
@ -131,7 +151,7 @@ public class GdbConnectCommand implements IConnect {
|
|||
Object result = prompter.handleStatus(processPromptStatus, info);
|
||||
if (result == null) {
|
||||
fRequestMonitor.cancel();
|
||||
} else if (result instanceof Integer || result instanceof String) {
|
||||
} else if (result instanceof IProcessExtendedInfo[] || result instanceof String) {
|
||||
fRequestMonitor.setData(result);
|
||||
} else {
|
||||
fRequestMonitor.setStatus(NO_PID_STATUS);
|
||||
|
@ -145,32 +165,49 @@ public class GdbConnectCommand implements IConnect {
|
|||
}
|
||||
};
|
||||
|
||||
// Need a job to free the executor while we prompt the user for a binary path
|
||||
// Bug 344892
|
||||
/**
|
||||
* This job will prompt the user for a path to the binary to use,
|
||||
* and then will attach to the process.
|
||||
* We need a job to free the executor while we prompt the user for
|
||||
* a binary path. Bug 344892
|
||||
*/
|
||||
private class PromptAndAttachToProcessJob extends UIJob {
|
||||
private final String fPid;
|
||||
private final RequestMonitor fRm;
|
||||
private final String fTitle;
|
||||
private final String fProcName;
|
||||
|
||||
public PromptAndAttachToProcessJob(String pid, RequestMonitor rm) {
|
||||
public PromptAndAttachToProcessJob(String pid, String title, String procName, RequestMonitor rm) {
|
||||
super(""); //$NON-NLS-1$
|
||||
fPid = pid;
|
||||
fTitle = title;
|
||||
fProcName = procName;
|
||||
fRm = rm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IStatus runInUIThread(IProgressMonitor monitor) {
|
||||
String binaryPath = null;
|
||||
|
||||
Shell shell = Display.getCurrent().getActiveShell();
|
||||
if (shell != null) {
|
||||
FileDialog fd = new FileDialog(shell, SWT.NONE);
|
||||
binaryPath = fd.open();
|
||||
}
|
||||
// Have we already see the binary for a process with this name?
|
||||
String binaryPath = fProcessNameToBinaryMap.get(fProcName);
|
||||
|
||||
if (binaryPath == null) {
|
||||
// prompt for the binary path
|
||||
Shell shell = Display.getCurrent().getActiveShell();
|
||||
if (shell != null) {
|
||||
FileDialog fd = new FileDialog(shell, SWT.NONE);
|
||||
fd.setText(fTitle);
|
||||
binaryPath = fd.open();
|
||||
}
|
||||
}
|
||||
|
||||
if (binaryPath == null) {
|
||||
// The user pressed the cancel button, so we cancel the attach gracefully
|
||||
fRm.done();
|
||||
} else {
|
||||
// Store the path of the binary so we can use it again for another process
|
||||
// with the same name
|
||||
fProcessNameToBinaryMap.put(fProcName, binaryPath);
|
||||
|
||||
final String finalBinaryPath = binaryPath;
|
||||
fExecutor.execute(new DsfRunnable() {
|
||||
public void run() {
|
||||
|
@ -232,7 +269,7 @@ public class GdbConnectCommand implements IConnect {
|
|||
@Override
|
||||
protected void handleSuccess() {
|
||||
new PromptForPidJob(
|
||||
"Prompt for Process", newProcessSupported, procInfoList.toArray(new IProcessExtendedInfo[0]), //$NON-NLS-1$
|
||||
LaunchUIMessages.getString("ProcessPrompter.PromptJob"), newProcessSupported, procInfoList.toArray(new IProcessExtendedInfo[0]), //$NON-NLS-1$
|
||||
new DataRequestMonitor<Object>(fExecutor, rm) {
|
||||
@Override
|
||||
protected void handleCancel() {
|
||||
|
@ -241,36 +278,15 @@ public class GdbConnectCommand implements IConnect {
|
|||
}
|
||||
@Override
|
||||
protected void handleSuccess() {
|
||||
// New cycle, look for service again
|
||||
final IGDBProcesses procService = fTracker.getService(IGDBProcesses.class);
|
||||
if (procService != null) {
|
||||
Object data = getData();
|
||||
if (data instanceof String) {
|
||||
// User wants to start a new process
|
||||
String binaryPath = (String)data;
|
||||
procService.debugNewProcess(
|
||||
controlCtx, binaryPath,
|
||||
// khouzam, maybe we should at least pass stopOnMain?
|
||||
new HashMap<String, Object>(), new DataRequestMonitor<IDMContext>(fExecutor, rm));
|
||||
} else if (data instanceof Integer) {
|
||||
String pidStr = Integer.toString((Integer)data);
|
||||
final IGDBBackend backend = fTracker.getService(IGDBBackend.class);
|
||||
if (backend != null && backend.getSessionType() == SessionType.REMOTE) {
|
||||
// For remote attach, we must set the binary first so we need to prompt the user.
|
||||
// Because the prompt is a very long operation, we need to run outside the
|
||||
// executor, so we don't lock it.
|
||||
// Bug 344892
|
||||
new PromptAndAttachToProcessJob(pidStr, rm).schedule();
|
||||
} else {
|
||||
// For a local attach, GDB can figure out the binary automatically,
|
||||
// so we don't need to prompt for it.
|
||||
IProcessDMContext procDmc = procService.createProcessContext(controlCtx, pidStr);
|
||||
procService.attachDebuggerToProcess(procDmc, null, new DataRequestMonitor<IDMContext>(fExecutor, rm));
|
||||
}
|
||||
} else {
|
||||
rm.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR, "Invalid return type for process prompter", null)); //$NON-NLS-1$
|
||||
rm.done();
|
||||
}
|
||||
Object data = getData();
|
||||
if (data instanceof String) {
|
||||
// User wants to start a new process
|
||||
startNewProcess(controlCtx, (String)data, rm);
|
||||
} else if (data instanceof IProcessExtendedInfo[]) {
|
||||
attachToProcesses(controlCtx, (IProcessExtendedInfo[])data, rm);
|
||||
} else {
|
||||
rm.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR, "Invalid return type for process prompter", null)); //$NON-NLS-1$
|
||||
rm.done();
|
||||
}
|
||||
}
|
||||
}).schedule();
|
||||
|
@ -350,4 +366,93 @@ public class GdbConnectCommand implements IConnect {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startNewProcess(ICommandControlDMContext controlDmc, String binaryPath, RequestMonitor rm) {
|
||||
final IGDBProcesses procService = fTracker.getService(IGDBProcesses.class);
|
||||
procService.debugNewProcess(
|
||||
controlDmc, binaryPath,
|
||||
new HashMap<String, Object>(), new DataRequestMonitor<IDMContext>(fExecutor, rm));
|
||||
}
|
||||
|
||||
private void attachToProcesses(final ICommandControlDMContext controlDmc, IProcessExtendedInfo[] processes, final RequestMonitor rm) {
|
||||
|
||||
// For a local attach, GDB can figure out the binary automatically,
|
||||
// so we don't need to prompt for it.
|
||||
final IGDBProcesses procService = fTracker.getService(IGDBProcesses.class);
|
||||
final IGDBBackend backend = fTracker.getService(IGDBBackend.class);
|
||||
|
||||
if (procService != null && backend != null) {
|
||||
// Attach to each process in a sequential fashion. We must do this
|
||||
// to be able to check if we are allowed to attach to the next process.
|
||||
// Attaching to all of them in parallel would assume that all attach are supported.
|
||||
|
||||
// Create a list of all our processes so we can attach to one at a time.
|
||||
// We need to create a new list so that we can remove elements from it.
|
||||
final List<IProcessExtendedInfo> procList = new ArrayList<IProcessExtendedInfo>(Arrays.asList(processes));
|
||||
|
||||
class AttachToProcessRequestMonitor extends DataRequestMonitor<IDMContext> {
|
||||
public AttachToProcessRequestMonitor() {
|
||||
super(ImmediateExecutor.getInstance(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
if (!isSuccess()) {
|
||||
// Failed to attach to a process. Just ignore it and move on.
|
||||
}
|
||||
|
||||
// Check that we have a process to attach to
|
||||
if (procList.size() > 0) {
|
||||
|
||||
// Check that we can actually attach to the process.
|
||||
// This is because some backends may not support multi-process.
|
||||
// If the backend does not support multi-process, we only attach to the first process.
|
||||
procService.isDebuggerAttachSupported(controlDmc, new DataRequestMonitor<Boolean>(ImmediateExecutor.getInstance(), null) {
|
||||
@Override
|
||||
protected void handleCompleted() {
|
||||
if (isSuccess() && getData()) {
|
||||
// Can attach to process
|
||||
|
||||
// Remove process from list and attach to it.
|
||||
IProcessExtendedInfo process = procList.remove(0);
|
||||
String pidStr = Integer.toString(process.getPid());
|
||||
|
||||
if (backend.getSessionType() == SessionType.REMOTE) {
|
||||
// For remote attach, we must set the binary first so we need to prompt the user.
|
||||
// Because the prompt is a very long operation, we need to run outside the
|
||||
// executor, so we don't lock it.
|
||||
// Bug 344892
|
||||
IPath processPath = new Path(process.getName());
|
||||
String processShortName = processPath.lastSegment();
|
||||
new PromptAndAttachToProcessJob(pidStr,
|
||||
LaunchUIMessages.getString("ProcessPrompterDialog.TitlePrefix") + process.getName(), //$NON-NLS-1$
|
||||
processShortName, new AttachToProcessRequestMonitor()).schedule();
|
||||
} else {
|
||||
IProcessDMContext procDmc = procService.createProcessContext(controlDmc, pidStr);
|
||||
procService.attachDebuggerToProcess(procDmc, null, new AttachToProcessRequestMonitor());
|
||||
}
|
||||
} else {
|
||||
// Not allowed to attach to another process. Just stop.
|
||||
rm.done();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// No other process to attach to
|
||||
rm.done();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Trigger the first attach.
|
||||
new AttachToProcessRequestMonitor().done();
|
||||
|
||||
} else {
|
||||
rm.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR, "Cannot find service", null)); //$NON-NLS-1$
|
||||
rm.done();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -226,5 +226,6 @@ LocalCDILaunchDelegate.10=Failed to set program arguments, environment or workin
|
|||
|
||||
ProcessPrompter.Core=core
|
||||
ProcessPrompter.Cores=cores
|
||||
ProcessPrompter.PromptJob=Prompt for Process
|
||||
ProcessPrompterDialog.New=New...
|
||||
|
||||
ProcessPrompterDialog.TitlePrefix=Choose binary for process:
|
|
@ -8,6 +8,7 @@
|
|||
* Contributors:
|
||||
* QNX Software Systems - initial API and implementation
|
||||
* Ericsson - Modified for DSF
|
||||
* Marc Khouzam (Ericsson) - Add support for multi-attach (Bug 293679)
|
||||
*******************************************************************************/
|
||||
package org.eclipse.cdt.dsf.gdb.internal.ui.launching;
|
||||
|
||||
|
@ -150,6 +151,9 @@ public class ProcessPrompter implements IStatusHandler {
|
|||
dialog.setTitle(LaunchMessages.getString("LocalAttachLaunchDelegate.Select_Process")); //$NON-NLS-1$
|
||||
dialog.setMessage(LaunchMessages.getString("LocalAttachLaunchDelegate.Select_Process_to_attach_debugger_to")); //$NON-NLS-1$
|
||||
|
||||
// Allow for multiple selection
|
||||
dialog.setMultipleSelection(true);
|
||||
|
||||
dialog.setElements(plist);
|
||||
if (dialog.open() == Window.OK) {
|
||||
// First check if the user pressed the New button
|
||||
|
@ -158,9 +162,13 @@ public class ProcessPrompter implements IStatusHandler {
|
|||
return binaryPath;
|
||||
}
|
||||
|
||||
IProcessExtendedInfo processInfo = (IProcessExtendedInfo)dialog.getFirstResult();
|
||||
if (processInfo != null) {
|
||||
return new Integer(processInfo.getPid());
|
||||
Object[] results = dialog.getResult();
|
||||
if (results != null) {
|
||||
IProcessExtendedInfo[] processes = new IProcessExtendedInfo[results.length];
|
||||
for (int i=0; i<processes.length; i++) {
|
||||
processes[i] = (IProcessExtendedInfo)results[i];
|
||||
}
|
||||
return processes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,45 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2011 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
|
||||
*
|
||||
* Contributors:
|
||||
* Marc Khouzam (Ericsson) - initial API and implementation
|
||||
* Marc Khouzam (Ericsson) - Add support for multi-attach (Bug 293679)
|
||||
*******************************************************************************/
|
||||
package org.eclipse.cdt.dsf.gdb.internal.ui.launching;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jface.viewers.ILabelProvider;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Event;
|
||||
import org.eclipse.swt.widgets.FileDialog;
|
||||
import org.eclipse.swt.widgets.Listener;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.swt.widgets.Table;
|
||||
import org.eclipse.ui.dialogs.TwoPaneElementSelector;
|
||||
|
||||
/**
|
||||
* Process prompter that allows the user to select one or more entries
|
||||
* in the top pane. Those entries are displayed in the bottom pane.
|
||||
* No selection is allowed in the bottom pane. The result returned
|
||||
* is the list of all selections of the top pane (shown in the bottom
|
||||
* pane).
|
||||
*
|
||||
* The dialog also has a "New..." button that allows to start a new
|
||||
* process. If the method getBinaryPath() returns a non-null string,
|
||||
* it implies that a new process should be created and the return
|
||||
* string indicates the location of the binary.
|
||||
*
|
||||
* Note that getBinaryPath() should be checked before calling getResult()
|
||||
* as it takes precedence over it.
|
||||
*
|
||||
*/
|
||||
public class ProcessPrompterDialog extends TwoPaneElementSelector {
|
||||
private static final int NEW_BUTTON_ID = 9876;
|
||||
private String fBinaryPath;
|
||||
|
@ -43,4 +75,44 @@ public class ProcessPrompterDialog extends TwoPaneElementSelector {
|
|||
return fBinaryPath;
|
||||
}
|
||||
|
||||
/*
|
||||
* The result should be every selected element.
|
||||
*/
|
||||
@Override
|
||||
protected void computeResult() {
|
||||
setResult(Arrays.asList(getSelectedElements()));
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable the ability to select items in the bottom pane.
|
||||
*/
|
||||
@Override
|
||||
protected Table createLowerList(Composite parent) {
|
||||
final Table list = super.createLowerList(parent);
|
||||
|
||||
// First remove listeners such as the double click.
|
||||
// We don't want the user to trigger the action by
|
||||
// double-clicking on the bottom pane.
|
||||
int[] events = { SWT.Selection, SWT.MouseDoubleClick };
|
||||
for (int event : events) {
|
||||
Listener[] selectionListeners = list.getListeners(event);
|
||||
for (Listener listener : selectionListeners) {
|
||||
list.removeListener(event, listener);
|
||||
}
|
||||
}
|
||||
|
||||
// Now add a listener to prevent selection
|
||||
list.addListener(SWT.EraseItem, new Listener() {
|
||||
public void handleEvent(Event event) {
|
||||
if ((event.detail & SWT.SELECTED) != 0) {
|
||||
event.detail &= ~SWT.SELECTED;
|
||||
// Removing the SELECTED event did not work properly.
|
||||
// The foreground text became invisible.
|
||||
// Let's simply deselect everything
|
||||
list.deselectAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue