diff options
Diffstat (limited to 'dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/commands/GdbConnectCommand.java')
-rw-r--r-- | dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/commands/GdbConnectCommand.java | 528 |
1 files changed, 528 insertions, 0 deletions
diff --git a/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/commands/GdbConnectCommand.java b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/commands/GdbConnectCommand.java new file mode 100644 index 00000000000..d90dc5752ac --- /dev/null +++ b/dsf-gdb/org.eclipse.cdt.dsf.gdb.ui/src/org/eclipse/cdt/dsf/gdb/internal/ui/commands/GdbConnectCommand.java @@ -0,0 +1,528 @@ +/******************************************************************************* + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Ericsson - initial API and implementation + * Marc Khouzam (Ericsson) - Add support for multi-attach (Bug 293679) + *******************************************************************************/ +package org.eclipse.cdt.dsf.gdb.internal.ui.commands; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.RejectedExecutionException; + +import org.eclipse.cdt.debug.core.model.IConnectHandler; +import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor; +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.ImmediateDataRequestMonitor; +import org.eclipse.cdt.dsf.concurrent.Query; +import org.eclipse.cdt.dsf.concurrent.RequestMonitor; +import org.eclipse.cdt.dsf.datamodel.IDMContext; +import org.eclipse.cdt.dsf.debug.service.IProcesses; +import org.eclipse.cdt.dsf.debug.service.IProcesses.IProcessDMContext; +import org.eclipse.cdt.dsf.debug.service.IProcesses.IThreadDMData; +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.actions.ProcessInfo; +import org.eclipse.cdt.dsf.gdb.internal.ui.launching.LaunchUIMessages; +import org.eclipse.cdt.dsf.gdb.internal.ui.launching.ProcessPrompter; +import org.eclipse.cdt.dsf.gdb.internal.ui.launching.ProcessPrompter.PrompterInfo; +import org.eclipse.cdt.dsf.gdb.launching.GdbLaunch; +import org.eclipse.cdt.dsf.gdb.launching.IProcessExtendedInfo; +import org.eclipse.cdt.dsf.gdb.launching.LaunchMessages; +import org.eclipse.cdt.dsf.gdb.service.IGDBBackend; +import org.eclipse.cdt.dsf.gdb.service.IGDBProcesses; +import org.eclipse.cdt.dsf.gdb.service.IGDBProcesses.IGdbThreadDMData; +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.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext; +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.debug.core.IRequest; +import org.eclipse.debug.core.commands.AbstractDebugCommand; +import org.eclipse.debug.core.commands.IDebugCommandRequest; +import org.eclipse.debug.core.commands.IEnabledStateRequest; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.progress.UIJob; + +public class GdbConnectCommand extends AbstractDebugCommand implements IConnectHandler, 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. + // This map is only needed for remote sessions, since we don't need to specify + // the binary location for a local attach session. + private Map<String, String> fProcessNameToBinaryMap = new HashMap<String, String>(); + + public GdbConnectCommand(DsfSession session) { + fExecutor = session.getExecutor(); + fTracker = new DsfServicesTracker(GdbUIPlugin.getBundleContext(), session.getId()); + } + + public void dispose() { + fTracker.dispose(); + } + + @Override + protected boolean isExecutable(Object[] targets, IProgressMonitor monitor, IEnabledStateRequest request) + throws CoreException + { + return canConnect(); + } + + /* + * This method should not be called from the UI thread. + * (non-Javadoc) + * @see org.eclipse.cdt.dsf.gdb.actions.IConnect#canConnect() + */ + @Override + public boolean canConnect() { + Query<Boolean> canConnectQuery = new Query<Boolean>() { + @Override + public void execute(DataRequestMonitor<Boolean> rm) { + IProcesses procService = fTracker.getService(IProcesses.class); + ICommandControlService commandControl = fTracker.getService(ICommandControlService.class); + + if (procService != null && commandControl != null) { + procService.isDebuggerAttachSupported(commandControl.getContext(), rm); + } else { + rm.setData(false); + rm.done(); + } + } + }; + try { + fExecutor.execute(canConnectQuery); + return canConnectQuery.get(); + } catch (InterruptedException e) { + } catch (ExecutionException e) { + } catch (RejectedExecutionException e) { + // Can be thrown if the session is shutdown + } + + return false; + } + + /** + * This job will prompt the user to select a set of processes + * to attach too. + * We need a job because the ProcessPrompter will block and + * we don't want to block the executor. + */ + protected class PromptForPidJob extends UIJob { + + // The list of processes used in the case of an ATTACH session + IProcessExtendedInfo[] fProcessList = null; + DataRequestMonitor<Object> fRequestMonitor; + boolean fNewProcessSupported; + + public PromptForPidJob(String name, boolean newProcessSupported, IProcessExtendedInfo[] procs, DataRequestMonitor<Object> rm) { + super(name); + fNewProcessSupported = newProcessSupported; + fProcessList = procs; + fRequestMonitor = rm; + } + + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + final Status NO_PID_STATUS = new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, -1, + LaunchMessages.getString("LocalAttachLaunchDelegate.No_Process_ID_selected"), //$NON-NLS-1$ + null); + + try { + PrompterInfo info = new PrompterInfo(fNewProcessSupported, fProcessList); + Object result = new ProcessPrompter().handleStatus(null, info); + if (result == null) { + fRequestMonitor.cancel(); + } else if (result instanceof IProcessExtendedInfo[] || result instanceof String) { + fRequestMonitor.setData(result); + } else { + fRequestMonitor.setStatus(NO_PID_STATUS); + } + } catch (CoreException e) { + fRequestMonitor.setStatus(NO_PID_STATUS); + } + fRequestMonitor.done(); + + return Status.OK_STATUS; + } + }; + + /** + * 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, String title, String procName, RequestMonitor rm) { + super(""); //$NON-NLS-1$ + fPid = pid; + fTitle = title; + fProcName = procName; + fRm = rm; + } + + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + + // 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 { + + final String finalBinaryPath = binaryPath; + fExecutor.execute(new DsfRunnable() { + @Override + public void run() { + IGDBProcesses procService = fTracker.getService(IGDBProcesses.class); + ICommandControlService commandControl = fTracker.getService(ICommandControlService.class); + + if (procService != null && commandControl != null) { + IProcessDMContext procDmc = procService.createProcessContext(commandControl.getContext(), fPid); + procService.attachDebuggerToProcess(procDmc, finalBinaryPath, new DataRequestMonitor<IDMContext>(fExecutor, fRm) { + @Override + protected void handleSuccess() { + // Store the path of the binary so we can use it again for another process + // with the same name. Only do this on success, to avoid being stuck with + // a path that is invalid + fProcessNameToBinaryMap.put(fProcName, finalBinaryPath); + fRm.done(); + }; + }); + + } else { + fRm.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR, "Cannot find services", null)); //$NON-NLS-1$ + fRm.done(); + } + } + }); + } + + return Status.OK_STATUS; + } + } + + @Override + protected void doExecute(Object[] targets, IProgressMonitor monitor, IRequest request) throws CoreException { + Query<Boolean> connectQuery = new Query<Boolean>() { + @Override + public void execute(DataRequestMonitor<Boolean> rm) { + connect(rm); + } + }; + try { + fExecutor.execute(connectQuery); + connectQuery.get(); + } catch (InterruptedException e) { + } catch (ExecutionException e) { + } catch (CancellationException e) { + // Nothing to do, just ignore the command since the user + // cancelled it. + } catch (RejectedExecutionException e) { + // Can be thrown if the session is shutdown + } + } + + /* + * This method should not be called from the UI thread. + * (non-Javadoc) + * @see org.eclipse.cdt.dsf.gdb.actions.IConnect#canConnect() + */ + @Override + public void connect(final RequestMonitor rm) + { + fExecutor.execute(new DsfRunnable() { + @Override + public void run() { + final IProcesses procService = fTracker.getService(IProcesses.class); + ICommandControlService commandControl = fTracker.getService(ICommandControlService.class); + + if (procService != null && commandControl != null) { + final ICommandControlDMContext controlCtx = commandControl.getContext(); + + // First check if the "New..." button should be enabled. + procService.isDebugNewProcessSupported(controlCtx, new DataRequestMonitor<Boolean>(fExecutor, rm) { + @Override + protected void handleCompleted() { + final boolean newProcessSupported = isSuccess() && getData(); + + // Now get the list of all processes + procService.getRunningProcesses( + controlCtx, + new DataRequestMonitor<IProcessDMContext[]>(fExecutor, rm) { + @Override + protected void handleSuccess() { + + final List<IProcessExtendedInfo> procInfoList = new ArrayList<IProcessExtendedInfo>(); + + final CountingRequestMonitor countingRm = + new CountingRequestMonitor(fExecutor, rm) { + @Override + protected void handleSuccess() { + // Prompt the user to choose one or more processes, or to start a new one + new PromptForPidJob( + LaunchUIMessages.getString("ProcessPrompter.PromptJob"), //$NON-NLS-1$ + newProcessSupported, + procInfoList.toArray(new IProcessExtendedInfo[procInfoList.size()]), + new DataRequestMonitor<Object>(fExecutor, rm) { + @Override + protected void handleCancel() { + rm.cancel(); + rm.done(); + } + @Override + protected void handleSuccess() { + 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.done(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR, "Invalid return type for process prompter", null)); //$NON-NLS-1$ + } + } + }).schedule(); + } + }; + + if (getData().length > 0 && getData()[0] instanceof IThreadDMData) { + // The list of running processes also contains the name of the processes + // This is much more efficient. Let's use it. + for (IProcessDMContext processCtx : getData()) { + IThreadDMData processData = (IThreadDMData) processCtx; + int pid = 0; + try { + pid = Integer.parseInt(processData.getId()); + } catch (NumberFormatException e) { + } + String[] cores = null; + String owner = null; + if (processData instanceof IGdbThreadDMData) { + cores = ((IGdbThreadDMData)processData).getCores(); + owner = ((IGdbThreadDMData)processData).getOwner(); + } + procInfoList.add(new ProcessInfo(pid, processData.getName(), cores, owner)); + } + + // Re-use the counting monitor and trigger it right away. + // No need to call done() in this case. + countingRm.setDoneCount(0); + } else { + // The list of running processes does not contain the names, so + // we must obtain it individually + + // For each process, obtain its name + // Once all the names are obtained, prompt the user for the pid to use + + // New cycle, look for service again + final IProcesses procService = fTracker.getService(IProcesses.class); + + if (procService != null) { + countingRm.setDoneCount(getData().length); + + for (IProcessDMContext processCtx : getData()) { + procService.getExecutionData( + processCtx, + new DataRequestMonitor<IThreadDMData> (fExecutor, countingRm) { + @Override + protected void handleSuccess() { + IThreadDMData processData = getData(); + int pid = 0; + try { + pid = Integer.parseInt(processData.getId()); + } catch (NumberFormatException e) { + } + String[] cores = null; + String owner = null; + if (processData instanceof IGdbThreadDMData) { + cores = ((IGdbThreadDMData)processData).getCores(); + owner = ((IGdbThreadDMData)processData).getOwner(); + } + procInfoList.add(new ProcessInfo(pid, processData.getName(), cores, owner)); + countingRm.done(); + } + }); + } + } else { + // Trigger right away. No need to call done() in this case. + countingRm.setDoneCount(0); + } + } + } + }); + } + }); + } else { + rm.done(); + } + } + }); + } + + private void startNewProcess(ICommandControlDMContext controlDmc, String binaryPath, RequestMonitor rm) { + 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 ImmediateDataRequestMonitor<IDMContext> { + public AttachToProcessRequestMonitor() { + super(); + } + + @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 ImmediateDataRequestMonitor<Boolean>() { + @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. + + // If this is the very first attach of a remote session, check if the user + // specified the binary in the launch. If so, let's add it to our map to + // avoid having to prompt the user for that binary. + // This would be particularly annoying since we didn't use to have + // to do that before we supported multi-process. + // Must do this here to be in the executor + // Bug 350365 + if (fProcessNameToBinaryMap.isEmpty()) { + IPath binaryPath = backend.getProgramPath(); + if (binaryPath != null && !binaryPath.isEmpty()) { + fProcessNameToBinaryMap.put(binaryPath.lastSegment(), binaryPath.toOSString()); + } + } + + // 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 { + // For a local attach, we can attach directly without looking for the binary + // since GDB will figure it out by itself + 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.done(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR, "Cannot find service", null)); //$NON-NLS-1$ + } + + } + + @Override + protected Object getTarget(Object element) { + if (element instanceof GdbLaunch || + element instanceof IDMVMContext) { + return element; + } + return null; + } + + @Override + protected boolean isRemainEnabled(IDebugCommandRequest request) { + return false; + } +} + + |