1
0
Fork 0
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:
Marc Khouzam 2011-06-28 16:48:59 -04:00
parent d7092b12c9
commit 079dc72df6
4 changed files with 234 additions and 48 deletions

View file

@ -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();
}
}
}

View file

@ -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:

View file

@ -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;
}
}
}

View file

@ -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;
}
}