From 079dc72df68e4ea3751aff0582d42e5075edbd3d Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Tue, 28 Jun 2011 16:48:59 -0400 Subject: [PATCH] Bug 293679: Allow multi-select when attaching to processes --- .../ui/actions/GdbConnectCommand.java | 193 ++++++++++++++---- .../ui/launching/LaunchUIMessages.properties | 3 +- .../ui/launching/ProcessPrompter.java | 14 +- .../ui/launching/ProcessPrompterDialog.java | 72 +++++++ 4 files changed, 234 insertions(+), 48 deletions(-) diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/actions/GdbConnectCommand.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/actions/GdbConnectCommand.java index 60c921422dd..c7ce9d8e65b 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/actions/GdbConnectCommand.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/actions/GdbConnectCommand.java @@ -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 fProcessNameToBinaryMap = new HashMap(); + 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(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(), new DataRequestMonitor(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(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(), new DataRequestMonitor(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 procList = new ArrayList(Arrays.asList(processes)); + + class AttachToProcessRequestMonitor extends DataRequestMonitor { + 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(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(); + } + + } } + + diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/launching/LaunchUIMessages.properties b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/launching/LaunchUIMessages.properties index 5262c153f75..9c90e6e21ea 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/launching/LaunchUIMessages.properties +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/launching/LaunchUIMessages.properties @@ -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: \ No newline at end of file diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/launching/ProcessPrompter.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/launching/ProcessPrompter.java index 8d75ccf1ba5..666bbfebba7 100644 --- a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/launching/ProcessPrompter.java +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/launching/ProcessPrompter.java @@ -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