| /******************************************************************************* |
| * Copyright (c) 2008, 2016 Ericsson and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Ericsson - initial API and implementation |
| * Onur Akdemir (TUBITAK BILGEM-ITI) - Multi-process debugging (Bug 237306) |
| * John Dallaway - GDB 7.x MI thread details field ignored (Bug 325556) |
| * Marc Khouzam (Ericsson) - Make each thread an IDisassemblyDMContext (bug 352748) |
| * Andy Jin (QNX) - Not output thread osId as a string when it is null (Bug 397039) |
| * Marc Khouzam (Ericsson) - Move IBreakpointsTargetDMContext from MIContainerDMC |
| * to GDBContainerDMC to ease inheritance (Bug 389945) |
| * Marc Khouzam (Ericsson) - Support for exited processes in the debug view (bug 407340) |
| *******************************************************************************/ |
| package org.eclipse.cdt.dsf.gdb.service; |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.concurrent.TimeUnit; |
| |
| import org.eclipse.cdt.core.CCorePlugin; |
| import org.eclipse.cdt.core.IProcessInfo; |
| import org.eclipse.cdt.core.IProcessList; |
| 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.ImmediateDataRequestMonitor; |
| import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor; |
| import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor; |
| import org.eclipse.cdt.dsf.concurrent.Immutable; |
| import org.eclipse.cdt.dsf.concurrent.RequestMonitor; |
| import org.eclipse.cdt.dsf.concurrent.Sequence; |
| import org.eclipse.cdt.dsf.datamodel.AbstractDMContext; |
| import org.eclipse.cdt.dsf.datamodel.AbstractDMEvent; |
| import org.eclipse.cdt.dsf.datamodel.DMContexts; |
| import org.eclipse.cdt.dsf.datamodel.IDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext; |
| import org.eclipse.cdt.dsf.debug.service.ICachingService; |
| import org.eclipse.cdt.dsf.debug.service.IDisassembly.IDisassemblyDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IProcesses; |
| 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.IContainerResumedDMEvent; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerSuspendedDMEvent; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl.IExitedDMEvent; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl.IResumedDMEvent; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl.IStartedDMEvent; |
| import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent; |
| import org.eclipse.cdt.dsf.debug.service.command.BufferedCommandControl; |
| import org.eclipse.cdt.dsf.debug.service.command.CommandCache; |
| import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext; |
| import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlShutdownDMEvent; |
| import org.eclipse.cdt.dsf.debug.service.command.IEventListener; |
| import org.eclipse.cdt.dsf.gdb.IGDBLaunchConfigurationConstants; |
| import org.eclipse.cdt.dsf.gdb.IGdbDebugConstants; |
| import org.eclipse.cdt.dsf.gdb.IGdbDebugPreferenceConstants; |
| import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; |
| import org.eclipse.cdt.dsf.gdb.launching.InferiorRuntimeProcess; |
| import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl; |
| import org.eclipse.cdt.dsf.mi.service.IMICommandControl; |
| import org.eclipse.cdt.dsf.mi.service.IMIContainerDMContext; |
| import org.eclipse.cdt.dsf.mi.service.IMIExecutionDMContext; |
| import org.eclipse.cdt.dsf.mi.service.IMIProcessDMContext; |
| import org.eclipse.cdt.dsf.mi.service.IMIProcesses; |
| import org.eclipse.cdt.dsf.mi.service.IMIRunControl; |
| import org.eclipse.cdt.dsf.mi.service.IMIRunControl.MIRunMode; |
| import org.eclipse.cdt.dsf.mi.service.MIBreakpointsManager; |
| import org.eclipse.cdt.dsf.mi.service.MIProcesses; |
| import org.eclipse.cdt.dsf.mi.service.command.CommandFactory; |
| import org.eclipse.cdt.dsf.mi.service.command.MIInferiorProcess; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadGroupCreatedEvent; |
| import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadGroupExitedEvent; |
| 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.MIListThreadGroupsInfo; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIListThreadGroupsInfo.IThreadGroupInfo; |
| 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.MIThread; |
| import org.eclipse.cdt.dsf.mi.service.command.output.MIThreadInfoInfo; |
| 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.cdt.utils.pty.PTY; |
| import org.eclipse.cdt.utils.pty.PersistentPTY; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.debug.core.DebugPlugin; |
| import org.eclipse.debug.core.ILaunch; |
| import org.eclipse.debug.core.model.IProcess; |
| import org.osgi.framework.BundleContext; |
| |
| /** |
| * This class implements the IProcesses interface for GDB 7.0 |
| * which supports the new -list-thread-groups command. |
| */ |
| public class GDBProcesses_7_0 extends AbstractDsfService implements IGDBProcesses, ICachingService, IEventListener { |
| |
| /** |
| * The maximum amount of exited processes we can show. |
| * Each one is shown in the debug view. |
| */ |
| private final static int MAX_NUMBER_EXITED_PROCESS = 5; |
| |
| // Below is the context hierarchy that is implemented between the |
| // MIProcesses service and the MIRunControl service for the MI |
| // implementation of DSF: |
| // |
| // MIControlDMContext (ICommandControlDMContext) |
| // | |
| // MIProcessDMC (IProcess) |
| // / \ |
| // / \ |
| // MIContainerDMC MIThreadDMC (IThread) |
| // (IContainer) / |
| // \ / |
| // MIExecutionDMC |
| // (IExecution) |
| // |
| |
| /** |
| * Context representing a thread in GDB/MI |
| */ |
| @Immutable |
| private static class MIExecutionDMC extends AbstractDMContext |
| implements IMIExecutionDMContext, IDisassemblyDMContext { |
| /** |
| * String ID that is used to identify the thread in the GDB/MI protocol. |
| */ |
| private final String fThreadId; |
| |
| /** |
| * Constructor for the context. It should not be called directly by clients. |
| * Instead clients should call {@link IMIProcesses#createExecutionContext()} |
| * to create instances of this context based on the thread ID. |
| * <p/> |
| * |
| * @param sessionId Session that this context belongs to. |
| * @param containerDmc The container that this context belongs to. |
| * @param threadDmc The thread context parents of this context. |
| * @param threadId GDB/MI thread identifier. |
| */ |
| protected MIExecutionDMC(String sessionId, IContainerDMContext containerDmc, IThreadDMContext threadDmc, |
| String threadId) { |
| super(sessionId, |
| containerDmc == null && threadDmc == null ? new IDMContext[0] |
| : containerDmc == null ? new IDMContext[] { threadDmc } |
| : threadDmc == null ? new IDMContext[] { containerDmc } |
| : new IDMContext[] { containerDmc, threadDmc }); |
| fThreadId = threadId; |
| } |
| |
| /** |
| * Returns the GDB/MI thread identifier of this context. |
| * @return |
| */ |
| @Override |
| public String getThreadId() { |
| return fThreadId; |
| } |
| |
| /* Unused; reintroduce if needed |
| public String getId(){ |
| return fThreadId; |
| } |
| */ |
| |
| @Override |
| public String toString() { |
| return baseToString() + ".thread[" + fThreadId + "]"; //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return baseEquals(obj) && ((MIExecutionDMC) obj).fThreadId.equals(fThreadId); |
| } |
| |
| @Override |
| public int hashCode() { |
| return baseHashCode() ^ fThreadId.hashCode(); |
| } |
| } |
| |
| /** |
| * Context representing a thread group of GDB/MI. |
| */ |
| @Immutable |
| static class MIContainerDMC extends AbstractDMContext implements IMIContainerDMContext, IDisassemblyDMContext { |
| /** |
| * String ID that is used to identify the thread group in the GDB/MI protocol. |
| */ |
| private final String fId; |
| |
| /** |
| * Constructor for the context. It should not be called directly by clients. |
| * Instead clients should call {@link IMIProcesses#createContainerContext |
| * to create instances of this context based on the group name. |
| * |
| * @param sessionId Session that this context belongs to. |
| * @param processDmc The process context that is the parent of this context. |
| * @param groupId GDB/MI thread group identifier. |
| */ |
| public MIContainerDMC(String sessionId, IProcessDMContext processDmc, String groupId) { |
| super(sessionId, processDmc == null ? new IDMContext[0] : new IDMContext[] { processDmc }); |
| fId = groupId; |
| } |
| |
| /** |
| * Returns the GDB/MI thread group identifier of this context. |
| */ |
| @Override |
| public String getGroupId() { |
| return fId; |
| } |
| |
| @Override |
| public String toString() { |
| return baseToString() + ".threadGroup[" + fId + "]"; //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return baseEquals(obj) |
| && (((MIContainerDMC) obj).fId == null ? fId == null : ((MIContainerDMC) obj).fId.equals(fId)); |
| } |
| |
| @Override |
| public int hashCode() { |
| return baseHashCode() ^ (fId == null ? 0 : fId.hashCode()); |
| } |
| } |
| |
| private static class GDBContainerDMC extends MIContainerDMC |
| implements IMemoryDMContext, IBreakpointsTargetDMContext { |
| public GDBContainerDMC(String sessionId, IProcessDMContext processDmc, String groupId) { |
| super(sessionId, processDmc, groupId); |
| } |
| } |
| |
| /** |
| * Context representing a thread. |
| * @since 4.0 |
| */ |
| @Immutable |
| protected static class MIThreadDMC extends AbstractDMContext implements IThreadDMContext { |
| /** |
| * ID used by GDB to refer to threads. |
| */ |
| private final String fId; |
| |
| /** |
| * Constructor for the context. It should not be called directly by clients. |
| * Instead clients should call {@link IMIProcesses#createThreadContext} |
| * to create instances of this context based on the thread ID. |
| * <p/> |
| * |
| * @param sessionId Session that this context belongs to. |
| * @param processDmc The process that this thread belongs to. |
| * @param id thread identifier. |
| */ |
| public MIThreadDMC(String sessionId, IProcessDMContext processDmc, String id) { |
| super(sessionId, processDmc == null ? new IDMContext[0] : new IDMContext[] { processDmc }); |
| fId = id; |
| } |
| |
| /** |
| * Returns the thread identifier of this context. |
| * @return |
| */ |
| public String getId() { |
| return fId; |
| } |
| |
| @Override |
| public String toString() { |
| return baseToString() + ".OSthread[" + fId + "]"; //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return baseEquals(obj) |
| && (((MIThreadDMC) obj).fId == null ? fId == null : ((MIThreadDMC) obj).fId.equals(fId)); |
| } |
| |
| @Override |
| public int hashCode() { |
| return baseHashCode() ^ (fId == null ? 0 : fId.hashCode()); |
| } |
| } |
| |
| @Immutable |
| private static class MIProcessDMC extends AbstractDMContext implements IMIProcessDMContext { |
| /** |
| * ID given by the OS. |
| */ |
| private final String fId; |
| |
| /** |
| * Constructor for the context. It should not be called directly by clients. |
| * Instead clients should call {@link IMIProcesses#createProcessContext} |
| * to create instances of this context based on the PID. |
| * <p/> |
| * |
| * @param sessionId Session that this context belongs to. |
| * @param controlDmc The control context parent of this process. |
| * @param id process identifier. |
| */ |
| public MIProcessDMC(String sessionId, ICommandControlDMContext controlDmc, String id) { |
| super(sessionId, controlDmc == null ? new IDMContext[0] : new IDMContext[] { controlDmc }); |
| fId = id; |
| } |
| |
| @Override |
| public String getProcId() { |
| return fId; |
| } |
| |
| @Override |
| public String toString() { |
| return baseToString() + ".proc[" + fId + "]"; //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| // We treat the UNKNOWN_PROCESS_ID as a wildcard. Any processId (except null) will be considered |
| // equal to the UNKNOWN_PROCESS_ID. This is important because before starting a process, we don't |
| // have a pid yet, but we still need to create a process context, and we must use UNKNOWN_PROCESS_ID. |
| // Bug 336890 |
| |
| if (!baseEquals(obj)) { |
| return false; |
| } |
| |
| MIProcessDMC other = (MIProcessDMC) obj; |
| if (fId == null || other.fId == null) { |
| return fId == null && other.fId == null; |
| } |
| |
| // Now that we know neither is null, check for UNKNOWN_PROCESS_ID wildcard |
| if (fId.equals(MIProcesses.UNKNOWN_PROCESS_ID) || other.fId.equals(MIProcesses.UNKNOWN_PROCESS_ID)) { |
| return true; |
| } |
| |
| return fId.equals(other.fId); |
| } |
| |
| @Override |
| public int hashCode() { |
| // We cannot use fId in the hashCode. This is because we support |
| // the wildCard MIProcesses.UNKNOWN_PROCESS_ID which is equal to any other fId. |
| // But we also need the hashCode of the wildCard to be the same |
| // as the one of all other fIds, which is why we need a constant hashCode |
| // See bug 336890 |
| return baseHashCode(); |
| } |
| } |
| |
| /** |
| * A process context representing a process that has exited. |
| * Since an exited process no longer has a pid, we need another way |
| * of characterizing it. We use the groupId instead. |
| * Note that with GDB 7.0 and 7.1, the groupId is the pid, so that |
| * does not help us, but since we only handle single-process debugging |
| * for those versions of GDB, we don't need any id to know we are |
| * dealing with our single process. |
| * Starting with GDB 7.2, we handle multi-process, but then we |
| * can use the groupId as a persistent identifier of each process, |
| * even an exited one. |
| * @since 4.7 |
| */ |
| @Immutable |
| protected static class MIExitedProcessDMC extends MIProcessDMC { |
| private final String fGroupId; |
| |
| public MIExitedProcessDMC(String sessionId, ICommandControlDMContext controlDmc, String pid, String groupId) { |
| super(sessionId, controlDmc, pid); |
| fGroupId = groupId; |
| } |
| |
| public String getGroupId() { |
| return fGroupId; |
| } |
| |
| @Override |
| public String toString() { |
| return super.toString() + ".group[" + getGroupId() + "]"; //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (!super.equals(obj)) { |
| return false; |
| } |
| |
| MIExitedProcessDMC other = (MIExitedProcessDMC) obj; |
| if (fGroupId == null || other.fGroupId == null) { |
| return fGroupId == null && other.fGroupId == null; |
| } |
| |
| return fGroupId.equals(other.fGroupId); |
| } |
| |
| @Override |
| public int hashCode() { |
| return super.hashCode() ^ (fGroupId == null ? 0 : fGroupId.hashCode()); |
| } |
| } |
| |
| /** |
| * The data of a corresponding thread or process. |
| */ |
| @Immutable |
| protected static class MIThreadDMData implements IThreadDMData { |
| final String fName; |
| final String fId; |
| |
| public MIThreadDMData(String name, String id) { |
| fName = name; |
| fId = id; |
| } |
| |
| @Override |
| public String getId() { |
| return fId; |
| } |
| |
| @Override |
| public String getName() { |
| return fName; |
| } |
| |
| @Override |
| public boolean isDebuggerAttached() { |
| return true; |
| } |
| } |
| |
| /** |
| * The data corresponding to an exited process. |
| * @since 4.7 |
| */ |
| @Immutable |
| protected static class MIExitedProcessDMData implements IGdbThreadExitedDMData { |
| final String fName; |
| final String fId; |
| final Integer fExitCode; |
| |
| public MIExitedProcessDMData(String name, String id, Integer exitCode) { |
| fName = name; |
| fId = id; |
| fExitCode = exitCode; |
| } |
| |
| @Override |
| public String getId() { |
| return fId; |
| } |
| |
| @Override |
| public String getName() { |
| return fName; |
| } |
| |
| @Override |
| public boolean isDebuggerAttached() { |
| return false; |
| } |
| |
| @Override |
| public Integer getExitCode() { |
| return fExitCode; |
| } |
| } |
| |
| /** |
| * This class provides an implementation of both a process context and process data. |
| * It is used to be able to return a list of processes including their data all at once. |
| * @since 4.0 |
| */ |
| @Immutable |
| protected static class MIProcessDMCAndData extends MIProcessDMC implements IGdbThreadDMData2 { |
| final String fName; |
| // Note that cores are only available from GDB 7.1. |
| final String[] fCores; |
| final String fOwner; |
| final String fDescription; |
| |
| public MIProcessDMCAndData(String sessionId, ICommandControlDMContext controlDmc, String id, String name, |
| String[] cores, String owner) { |
| this(sessionId, controlDmc, id, name, cores, owner, null); |
| } |
| |
| /** |
| * @since 5.6 |
| */ |
| public MIProcessDMCAndData(String sessionId, ICommandControlDMContext controlDmc, String id, String name, |
| String[] cores, String owner, String description) { |
| super(sessionId, controlDmc, id); |
| fName = name; |
| fCores = cores; |
| fOwner = owner; |
| fDescription = description; |
| } |
| |
| @Override |
| public String getId() { |
| return getProcId(); |
| } |
| |
| @Override |
| public String getName() { |
| return fName; |
| } |
| |
| /** |
| * @since 5.6 |
| */ |
| @Override |
| public String getDescription() { |
| return fDescription; |
| } |
| |
| @Override |
| public boolean isDebuggerAttached() { |
| return true; |
| } |
| |
| @Override |
| public String[] getCores() { |
| return fCores; |
| } |
| |
| @Override |
| public String getOwner() { |
| return fOwner; |
| } |
| |
| @Override |
| public String toString() { |
| return baseToString() + ".proc[" + getId() + "," + getName() + "," + getOwner() + "]"; //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$ |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return super.equals(obj) |
| && (((MIProcessDMCAndData) obj).fName == null ? fName == null |
| : ((MIProcessDMCAndData) obj).fName.equals(fName)) |
| && (((MIProcessDMCAndData) obj).fOwner == null ? fOwner == null |
| : ((MIProcessDMCAndData) obj).fOwner.equals(fOwner)); |
| } |
| |
| @Override |
| public int hashCode() { |
| return super.hashCode() ^ (fName == null ? 0 : fName.hashCode()) ^ (fOwner == null ? 0 : fOwner.hashCode()); |
| } |
| } |
| |
| /** |
| * Event indicating that an container (debugged process) has started. This event |
| * implements the {@link IStartedMDEvent} from the IRunControl service. |
| */ |
| public static class ContainerStartedDMEvent extends AbstractDMEvent<IExecutionDMContext> |
| implements IStartedDMEvent { |
| public ContainerStartedDMEvent(IContainerDMContext context) { |
| super(context); |
| } |
| } |
| |
| /** |
| * Event indicating that an container is no longer being debugged. This event |
| * implements the {@link IExitedMDEvent} from the IRunControl service. |
| */ |
| public static class ContainerExitedDMEvent extends AbstractDMEvent<IExecutionDMContext> implements IExitedDMEvent { |
| public ContainerExitedDMEvent(IContainerDMContext context) { |
| super(context); |
| } |
| } |
| |
| /** |
| * @since 4.7 |
| */ |
| protected static class ProcessRemovedDMEvent extends AbstractDMEvent<IThreadDMContext> |
| implements IThreadRemovedDMEvent { |
| public ProcessRemovedDMEvent(IProcessDMContext context) { |
| super(context); |
| } |
| } |
| |
| /** |
| * A map of thread id to thread group id. We use this to find out to which threadGroup a thread belongs. |
| */ |
| private Map<String, String> fThreadToGroupMap = new HashMap<>(); |
| /** |
| * A map of thread group id to process id. We use this to find out to which pid a group refers. |
| */ |
| private Map<String, String> fGroupToPidMap = new HashMap<>(); |
| |
| private IGDBControl fCommandControl; |
| private IGDBBackend fBackend; |
| private CommandFactory fCommandFactory; |
| |
| // A cache for commands about the threadGroups |
| private CommandCache fContainerCommandCache; |
| |
| //A cache for commands about the threads |
| private CommandCache fThreadCommandCache; |
| |
| // A temporary cache to avoid using -list-thread-groups --available more than once at the same time. |
| // We cannot cache this command because it lists all available processes, which can |
| // change at any time. However, it is inefficient to send more than one of this command at |
| // the same time. This cache will help us avoid that. The idea is that we cache the command, |
| // but as soon as it returns, we clear the cache. So the cache will only trigger for those |
| // overlapping situations. Using this cache also allows to handle the all-stop case |
| // when the target can be unavailable and instead of hanging, the cache will return an error. |
| private CommandCache fListThreadGroupsAvailableCache; |
| |
| // A map of process id to process names. A name is fetched whenever we start |
| // debugging a process, and removed when we stop. |
| // This allows us to make sure that if a pid is re-used, we will not use an |
| // old name for it. Bug 275497 |
| // This map also serves as a list of processes we are currently debugging. |
| // This is important because we cannot always ask GDB for the list, since it may |
| // be running at the time. Bug 303503 |
| private Map<String, String> fDebuggedProcessesAndNames = new HashMap<>(); |
| |
| /** |
| * A map that keeps track of the PTY associated with an inferior (groupId) |
| */ |
| private Map<String, PTY> fGroupIdToPTYMap = new HashMap<>(); |
| /** |
| * A list of groupIds that have exited. |
| */ |
| private List<String> fExitedGroupId = new ArrayList<>(); |
| |
| /** |
| * Information about an exited process |
| * @since 4.7 |
| */ |
| protected class ExitedProcInfo { |
| private String pid; |
| private String name; |
| private Integer exitCode; |
| |
| public ExitedProcInfo(String aPid, String aName) { |
| pid = aPid; |
| name = aName; |
| } |
| |
| protected String getPid() { |
| return pid; |
| } |
| |
| protected String getName() { |
| return name; |
| } |
| |
| protected Integer getExitCode() { |
| return exitCode; |
| } |
| |
| protected void setExitCode(Integer code) { |
| exitCode = code; |
| } |
| } |
| |
| /** |
| * A LRU (least-recently-used) map that limits the amount of exited process list. |
| * Once the limit is reached, oldest exited processes are automatically removed |
| * when new ones are inserted. This avoids the risk of growing the list |
| * of exited processes too much and showing too many in the debug view. |
| */ |
| private class LRUExitedProcessMap extends LinkedHashMap<String, ExitedProcInfo> { |
| public static final long serialVersionUID = 0; |
| |
| @Override |
| protected boolean removeEldestEntry(Entry<String, ExitedProcInfo> eldest) { |
| return size() > MAX_NUMBER_EXITED_PROCESS; |
| } |
| } |
| |
| /** |
| * Map of groupId to ExitedProcInfo. |
| * This map contains the information of each process that has exited. |
| * Note that we must maintain this information ourselves since GDB |
| * sometimes prunes its list of inferiors, which implies we cannot |
| * count on GDB to keep track of exited processes. |
| */ |
| private Map<String, ExitedProcInfo> fProcExitedMap = new LRUExitedProcessMap(); |
| |
| /** |
| * Set of groupId of processes that we detached from. |
| * The content is very short-lived as it is only kept until |
| * we receive the =thread-group-exited event from GDB |
| * and need to know if the process in question was detached from. |
| * Using this set, we can know if we should store the process |
| * in the fExitedProcesses map or not. |
| */ |
| private Set<String> fProcDetachedSet = new HashSet<>(); |
| |
| private static final String FAKE_THREAD_ID = "0"; //$NON-NLS-1$ |
| |
| /** |
| * Keeps track of how many processes we are currently connected to |
| */ |
| private int fNumConnected; |
| |
| /** |
| * Keeps track if we are dealing with the very first process of GDB. |
| */ |
| private boolean fInitialProcess = true; |
| |
| public GDBProcesses_7_0(DsfSession session) { |
| super(session); |
| } |
| |
| /** |
| * This method initializes this service. |
| * |
| * @param requestMonitor |
| * The request monitor indicating the operation is finished |
| */ |
| @Override |
| public void initialize(final RequestMonitor requestMonitor) { |
| super.initialize(new ImmediateRequestMonitor(requestMonitor) { |
| @Override |
| protected void handleSuccess() { |
| doInitialize(requestMonitor); |
| } |
| }); |
| } |
| |
| /** |
| * This method initializes this service after our superclass's initialize() |
| * method succeeds. |
| * |
| * @param requestMonitor |
| * The call-back object to notify when this service's |
| * initialization is done. |
| */ |
| private void doInitialize(RequestMonitor requestMonitor) { |
| |
| fCommandControl = getServicesTracker().getService(IGDBControl.class); |
| fBackend = getServicesTracker().getService(IGDBBackend.class); |
| BufferedCommandControl bufferedCommandControl = new BufferedCommandControl(fCommandControl, getExecutor(), 2); |
| |
| fCommandFactory = getServicesTracker().getService(IMICommandControl.class).getCommandFactory(); |
| |
| // These caches store the result of a command when received; also, these caches |
| // are manipulated when receiving events. Currently, events are received after |
| // three scheduling of the executor, while command results after only one. This |
| // can cause problems because command results might be processed before an event |
| // that actually arrived before the command result. |
| // To solve this, we use a bufferedCommandControl that will delay the command |
| // result by two scheduling of the executor. |
| // See bug 280461 |
| fContainerCommandCache = new CommandCache(getSession(), bufferedCommandControl); |
| fContainerCommandCache.setContextAvailable(fCommandControl.getContext(), true); |
| fThreadCommandCache = new CommandCache(getSession(), bufferedCommandControl); |
| fThreadCommandCache.setContextAvailable(fCommandControl.getContext(), true); |
| |
| // No need to use the bufferedCommandControl for the listThreadGroups cache |
| // because it is not being affected by events. |
| fListThreadGroupsAvailableCache = new CommandCache(getSession(), fCommandControl); |
| fListThreadGroupsAvailableCache.setContextAvailable(fCommandControl.getContext(), true); |
| |
| getSession().addServiceEventListener(this, null); |
| fCommandControl.addEventListener(this); |
| |
| // Register this service. |
| register(new String[] { IProcesses.class.getName(), IMIProcesses.class.getName(), IGDBProcesses.class.getName(), |
| GDBProcesses_7_0.class.getName() }, new Hashtable<String, String>()); |
| |
| requestMonitor.done(); |
| } |
| |
| /** |
| * This method shuts down this service. It unregisters the service, stops |
| * receiving service events, and calls the superclass shutdown() method to |
| * finish the shutdown process. |
| * |
| * @return void |
| */ |
| @Override |
| public void shutdown(RequestMonitor requestMonitor) { |
| unregister(); |
| getSession().removeServiceEventListener(this); |
| fCommandControl.removeEventListener(this); |
| super.shutdown(requestMonitor); |
| } |
| |
| /** |
| * @return The bundle context of the plug-in to which this service belongs. |
| */ |
| @Override |
| protected BundleContext getBundleContext() { |
| return GdbPlugin.getBundleContext(); |
| } |
| |
| /** @since 4.0 */ |
| protected Map<String, String> getThreadToGroupMap() { |
| return fThreadToGroupMap; |
| } |
| |
| /** @since 4.0 */ |
| protected Map<String, String> getGroupToPidMap() { |
| return fGroupToPidMap; |
| } |
| |
| /** @since 4.0 */ |
| protected int getNumConnected() { |
| return fNumConnected; |
| } |
| |
| /** @since 4.0 */ |
| protected void setNumConnected(int num) { |
| fNumConnected = num; |
| } |
| |
| /** @since 4.0 */ |
| protected boolean isInitialProcess() { |
| return fInitialProcess; |
| } |
| |
| /** @since 4.0 */ |
| protected void setIsInitialProcess(boolean isInitial) { |
| fInitialProcess = isInitial; |
| } |
| |
| /**@since 4.7 */ |
| protected Map<String, ExitedProcInfo> getExitedProcesses() { |
| return fProcExitedMap; |
| } |
| |
| /** @since 4.7 */ |
| protected Set<String> getDetachedProcesses() { |
| return fProcDetachedSet; |
| } |
| |
| /** |
| * Returns the groupId that is associated with the provided pId |
| * @since 4.0 |
| */ |
| protected String getGroupFromPid(String pid) { |
| if (pid == null) |
| return null; |
| |
| for (Map.Entry<String, String> entry : getGroupToPidMap().entrySet()) { |
| if (pid.equals(entry.getValue())) { |
| return entry.getKey(); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public IThreadDMContext createThreadContext(IProcessDMContext processDmc, String threadId) { |
| return new MIThreadDMC(getSession().getId(), processDmc, threadId); |
| } |
| |
| @Override |
| public IProcessDMContext createProcessContext(ICommandControlDMContext controlDmc, String pid) { |
| return new MIProcessDMC(getSession().getId(), controlDmc, pid); |
| } |
| |
| /** |
| * Create a special context describing a process that has exited. |
| * @param controlDmc Its parent context. |
| * @param groupId The GDB groupId to which this process refers to. Since an exited process no longer |
| * has a pid, we use this id to characterize it uniquely. |
| * Note that with GDB 7.0 and 7.1, the groupId is the pid, so that |
| * does not help us, but since we only handle single-process debugging |
| * for those versions of GDB, we don't need any id to know we are |
| * dealing with our single process. |
| * Starting with GDB 7.2, we handle multi-process, but then we |
| * can use the groupId as a persistent identifier of each process, |
| * even an exited one. |
| */ |
| private IProcessDMContext createExitedProcessContext(ICommandControlDMContext controlDmc, String pid, |
| String groupId) { |
| return new MIExitedProcessDMC(getSession().getId(), controlDmc, pid, groupId); |
| } |
| |
| @Override |
| public IMIExecutionDMContext createExecutionContext(IContainerDMContext containerDmc, IThreadDMContext threadDmc, |
| String threadId) { |
| return new MIExecutionDMC(getSession().getId(), containerDmc, threadDmc, threadId); |
| } |
| |
| @Override |
| public IMIContainerDMContext createContainerContext(IProcessDMContext processDmc, String groupId) { |
| return new GDBContainerDMC(getSession().getId(), processDmc, groupId); |
| } |
| |
| @Override |
| public IMIContainerDMContext createContainerContextFromThreadId(ICommandControlDMContext controlDmc, |
| String threadId) { |
| String groupId = getThreadToGroupMap().get(threadId); |
| if (groupId == null) { |
| // this can happen if the threadId was 'all' |
| // In such a case, we choose the first process we find |
| // This works when we run a single process |
| // but will break for multi-process!!! |
| if (getThreadToGroupMap().isEmpty()) { |
| groupId = MIProcesses.UNIQUE_GROUP_ID; |
| } else { |
| Collection<String> values = getThreadToGroupMap().values(); |
| for (String value : values) { |
| groupId = value; |
| break; |
| } |
| } |
| } |
| |
| return createContainerContextFromGroupId(controlDmc, groupId); |
| } |
| |
| /** @since 4.0 */ |
| @Override |
| public IMIContainerDMContext createContainerContextFromGroupId(ICommandControlDMContext controlDmc, |
| String groupId) { |
| if (groupId == null || groupId.length() == 0) { |
| // This happens when we are doing non-attach, so for GDB < 7.2, we know that in that case |
| // we are single process, so lets see if we have the group in our map. |
| assert getGroupToPidMap().size() <= 1 : "More than one process in our map"; //$NON-NLS-1$ |
| if (getGroupToPidMap().size() == 1) { |
| for (String key : getGroupToPidMap().keySet()) { |
| groupId = key; |
| break; |
| } |
| } |
| } |
| |
| String pid = getGroupToPidMap().get(groupId); |
| if (pid == null) { |
| // For GDB 7.0 and 7.1, the groupId is the pid, so we can use it directly |
| pid = groupId; |
| } |
| IProcessDMContext processDmc = createProcessContext(controlDmc, pid); |
| return createContainerContext(processDmc, groupId); |
| } |
| |
| @Override |
| public IMIExecutionDMContext[] getExecutionContexts(IMIContainerDMContext containerDmc) { |
| if (isExitedProcess(containerDmc)) { |
| // No threads for an exited process |
| return new IMIExecutionDMContext[0]; |
| } |
| |
| String groupId = containerDmc.getGroupId(); |
| List<IMIExecutionDMContext> execDmcList = new ArrayList<>(); |
| Iterator<Map.Entry<String, String>> iterator = getThreadToGroupMap().entrySet().iterator(); |
| while (iterator.hasNext()) { |
| Map.Entry<String, String> entry = iterator.next(); |
| if (entry.getValue().equals(groupId)) { |
| String threadId = entry.getKey(); |
| IProcessDMContext procDmc = DMContexts.getAncestorOfType(containerDmc, IProcessDMContext.class); |
| IMIExecutionDMContext execDmc = createExecutionContext(containerDmc, |
| createThreadContext(procDmc, threadId), threadId); |
| execDmcList.add(execDmc); |
| } |
| } |
| return execDmcList.toArray(new IMIExecutionDMContext[0]); |
| } |
| |
| @Override |
| public void getExecutionData(IThreadDMContext dmc, final DataRequestMonitor<IThreadDMData> rm) { |
| if (dmc instanceof MIExitedProcessDMC) { |
| ExitedProcInfo info = getExitedProcesses().get(((MIExitedProcessDMC) dmc).getGroupId()); |
| if (info != null) { |
| rm.done(new MIExitedProcessDMData(info.getName(), info.getPid(), info.getExitCode())); |
| } else { |
| // This can happen for example, when restarting an exited process, |
| // where we've deleted the process from our table, but it has |
| // yet to be cleaned up from the view |
| rm.done(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, |
| "Unavailable info about exited process", null)); //$NON-NLS-1$ |
| } |
| |
| return; |
| } |
| |
| if (dmc instanceof IMIProcessDMContext) { |
| String id = ((IMIProcessDMContext) dmc).getProcId(); |
| String name = null; |
| if (fBackend.getSessionType() == SessionType.CORE || "42000".equals(id)) { //$NON-NLS-1$ |
| // For the Core session, the process is no longer running. |
| // Therefore, we cannot get its name with the -list-thread-groups command. |
| // As for id 42000, it is a special id used by GDB to indicate the real proc |
| // id is not known. This will happen in a Remote session, when we use |
| // -target-select remote instead of -target-select extended-remote. |
| // |
| // So, we take the name from the binary we are using. |
| name = fBackend.getProgramPath().lastSegment(); |
| // Also, the pid we get from GDB is 1 or 42000, which is not correct. |
| // I haven't found a good way to get the pid yet, so let's not show it. |
| id = null; |
| } else { |
| if (fDebuggedProcessesAndNames.containsKey(id)) { |
| name = fDebuggedProcessesAndNames.get(id); |
| assert name != null; |
| if (name == null) { |
| // Should not happen, but just in case...use the |
| // binary file name (absolute path) |
| name = fBackend.getProgramPath().toOSString(); |
| fDebuggedProcessesAndNames.put(id, name); |
| } else if (name.isEmpty()) { |
| // We know of the process but haven't fetched its name yet. |
| // Let's fetch it now. |
| // GDB is debugging a new process. Let's fetch its |
| // name and remember it. In order to get the name, |
| // we have to request all running processes, not |
| // just the ones being debugged. We got a lot more |
| // information when we request all processes. |
| final String finalPId = id; |
| fListThreadGroupsAvailableCache.execute( |
| fCommandFactory.createMIListThreadGroups(fCommandControl.getContext(), true), |
| new DataRequestMonitor<MIListThreadGroupsInfo>(getExecutor(), null) { |
| @Override |
| protected void handleCompleted() { |
| // We cannot actually cache this command since the process |
| // list may change. But this cache allows to avoid overlapping |
| // sending of this command and proper handling if the target is |
| // unavailable. |
| fListThreadGroupsAvailableCache.reset(); |
| |
| // Note that the output of the "-list-thread-groups --available" command |
| // still shows the pid as a groupId, even for GDB 7.2. |
| String name = null; |
| if (isSuccess()) { |
| for (IThreadGroupInfo groupInfo : getData().getGroupList()) { |
| if (groupInfo.getPid().equals(finalPId)) { |
| name = groupInfo.getName(); |
| fDebuggedProcessesAndNames.put(finalPId, name); |
| break; |
| } |
| } |
| } else { |
| // Looks like this gdb doesn't truly support |
| // "-list-thread-groups --available". Get the |
| // process list natively if we're debugging locally |
| if (fBackend.getSessionType() == SessionType.LOCAL) { |
| try { |
| IProcessList list = CCorePlugin.getDefault().getProcessList(); |
| if (list != null) { |
| int pId_int = Integer.parseInt(finalPId); |
| for (IProcessInfo procInfo : list.getProcessList()) { |
| if (procInfo.getPid() == pId_int) { |
| name = procInfo.getName(); |
| fDebuggedProcessesAndNames.put(finalPId, name); |
| break; |
| } |
| } |
| } |
| } catch (Exception e) { |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, |
| REQUEST_FAILED, "Could not get process name", e)); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| if (name == null) { |
| // No way to get the name right now, so use the binary file name (absolute path) |
| name = fBackend.getProgramPath().toOSString(); |
| fDebuggedProcessesAndNames.put(finalPId, name); |
| } |
| rm.done(new MIThreadDMData(name, finalPId)); |
| } |
| }); |
| return; |
| } |
| } else { |
| // We don't have the name in our map. This could happen |
| // if a process has terminated but the |
| // debug session is not terminated because the preference |
| // to keep GDB running has been selected or because there |
| // are other processes part of that session. |
| name = "Unknown name"; //$NON-NLS-1$ |
| } |
| } |
| rm.setData(new MIThreadDMData(name, id)); |
| rm.done(); |
| } else if (dmc instanceof MIThreadDMC) { |
| final MIThreadDMC threadDmc = (MIThreadDMC) dmc; |
| |
| ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class); |
| fThreadCommandCache.execute(fCommandFactory.createMIThreadInfo(controlDmc, threadDmc.getId()), |
| new DataRequestMonitor<MIThreadInfoInfo>(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| IThreadDMData threadData = null; |
| if (getData().getThreadList().length != 0) { |
| MIThread thread = getData().getThreadList()[0]; |
| if (thread.getThreadId().equals(threadDmc.getId())) { |
| String id = ""; //$NON-NLS-1$ |
| if (thread.getOsId() != null) { |
| id = thread.getOsId(); |
| } |
| // append thread details (if any) to the thread ID |
| // as for GDB 6.x with CLIInfoThreadsInfo#getOsId() |
| final String details = thread.getDetails(); |
| if (details != null && !details.isEmpty()) { |
| if (!id.isEmpty()) |
| id += " "; //$NON-NLS-1$ |
| id += "(" + details + ")"; //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| // We must indicate and empty id by using null |
| if (id.isEmpty()) |
| id = null; |
| |
| threadData = new MIThreadDMData("", id); //$NON-NLS-1$ |
| } |
| } |
| |
| if (threadData != null) { |
| rm.setData(threadData); |
| } else { |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, |
| "Could not get thread info", null)); //$NON-NLS-1$ |
| } |
| rm.done(); |
| } |
| }); |
| } else { |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid DMC type", null)); //$NON-NLS-1$ |
| rm.done(); |
| } |
| } |
| |
| @Override |
| public void getDebuggingContext(IThreadDMContext dmc, DataRequestMonitor<IDMContext> rm) { |
| if (dmc instanceof MIExitedProcessDMC) { |
| MIExitedProcessDMC exitedProc = (MIExitedProcessDMC) dmc; |
| IMIContainerDMContext containerDmc = createContainerContext(exitedProc, exitedProc.getGroupId()); |
| rm.setData(containerDmc); |
| } else if (dmc instanceof MIProcessDMC) { |
| MIProcessDMC procDmc = (MIProcessDMC) dmc; |
| IMIContainerDMContext containerDmc = createContainerContext(procDmc, getGroupFromPid(procDmc.getProcId())); |
| rm.setData(containerDmc); |
| } else if (dmc instanceof MIThreadDMC) { |
| MIThreadDMC threadDmc = (MIThreadDMC) dmc; |
| IMIProcessDMContext procDmc = DMContexts.getAncestorOfType(dmc, IMIProcessDMContext.class); |
| IMIContainerDMContext containerDmc = createContainerContext(procDmc, getGroupFromPid(procDmc.getProcId())); |
| rm.setData(createExecutionContext(containerDmc, threadDmc, threadDmc.getId())); |
| } else { |
| rm.setStatus( |
| new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Invalid thread context.", null)); //$NON-NLS-1$ |
| } |
| |
| rm.done(); |
| } |
| |
| /** @since 4.0 */ |
| protected boolean doIsDebuggerAttachSupported() { |
| return fBackend.getIsAttachSession() && fNumConnected == 0; |
| } |
| |
| @Override |
| public void isDebuggerAttachSupported(IDMContext dmc, DataRequestMonitor<Boolean> rm) { |
| rm.setData(doIsDebuggerAttachSupported()); |
| rm.done(); |
| } |
| |
| @Override |
| public void attachDebuggerToProcess(IProcessDMContext procCtx, DataRequestMonitor<IDMContext> rm) { |
| attachDebuggerToProcess(procCtx, null, rm); |
| } |
| |
| /** |
| * @since 4.0 |
| */ |
| @Override |
| public void attachDebuggerToProcess(final IProcessDMContext procCtx, final String binaryPath, |
| final DataRequestMonitor<IDMContext> dataRm) { |
| if (procCtx instanceof IMIProcessDMContext) { |
| if (!doIsDebuggerAttachSupported()) { |
| dataRm.setStatus( |
| new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Attach not supported.", null)); //$NON-NLS-1$ |
| dataRm.done(); |
| return; |
| } |
| |
| // Use a sequence for better control of each step |
| ImmediateExecutor.getInstance().execute(new Sequence(getExecutor(), dataRm) { |
| |
| private IMIContainerDMContext fContainerDmc; |
| |
| private Step[] steps = new Step[] { |
| // For remote attach, we must set the binary first |
| // For a local attach, GDB can figure out the binary automatically, |
| // so we don't specify it. |
| new Step() { |
| @Override |
| public void execute(RequestMonitor rm) { |
| |
| if (isInitialProcess()) { |
| // To be proper, set the initialProcess variable to false |
| // it may be necessary for a class that extends this class |
| setIsInitialProcess(false); |
| } |
| |
| // There is no groupId until we attach, so we can use the default groupId |
| fContainerDmc = createContainerContext(procCtx, MIProcesses.UNIQUE_GROUP_ID); |
| |
| if (binaryPath != null) { |
| fCommandControl.queueCommand( |
| fCommandFactory.createMIFileExecAndSymbols(fContainerDmc, binaryPath), |
| new ImmediateDataRequestMonitor<MIInfo>(rm)); |
| return; |
| } |
| |
| rm.done(); |
| } |
| }, |
| // Attach to the process |
| new Step() { |
| @Override |
| public void execute(RequestMonitor rm) { |
| // For non-stop mode, we do a non-interrupting attach |
| // Bug 333284 |
| boolean shouldInterrupt = true; |
| IMIRunControl runControl = getServicesTracker().getService(IMIRunControl.class); |
| if (runControl != null && runControl.getRunMode() == MIRunMode.NON_STOP) { |
| shouldInterrupt = false; |
| } |
| |
| fCommandControl.queueCommand( |
| fCommandFactory.createMITargetAttach(fContainerDmc, |
| ((IMIProcessDMContext) procCtx).getProcId(), shouldInterrupt), |
| new DataRequestMonitor<MIInfo>(getExecutor(), rm)); |
| } |
| }, new Step() { |
| @Override |
| public void execute(RequestMonitor rm) { |
| // By now, GDB has reported the groupId that was created for this process |
| fContainerDmc = createContainerContext(procCtx, |
| getGroupFromPid(((IMIProcessDMContext) procCtx).getProcId())); |
| |
| // Store the fully formed container context so it can be returned to the caller. |
| dataRm.setData(fContainerDmc); |
| |
| // Initialize memory data for this process. |
| IGDBMemory memory = getServicesTracker().getService(IGDBMemory.class); |
| IMemoryDMContext memContext = DMContexts.getAncestorOfType(fContainerDmc, |
| IMemoryDMContext.class); |
| if (memory == null || memContext == null) { |
| rm.done(); |
| return; |
| } |
| memory.initializeMemoryData(memContext, rm); |
| } |
| }, new Step() { |
| @Override |
| public void execute(RequestMonitor rm) { |
| // Start tracking breakpoints. |
| MIBreakpointsManager bpmService = getServicesTracker() |
| .getService(MIBreakpointsManager.class); |
| bpmService.startTrackingBpForProcess(fContainerDmc, rm); |
| } |
| }, |
| // Turn on reverse debugging if it was enabled as a launch option |
| new Step() { |
| @Override |
| public void execute(RequestMonitor rm) { |
| doReverseDebugStep(procCtx, rm); |
| } |
| }, }; |
| |
| @Override |
| public Step[] getSteps() { |
| return steps; |
| } |
| }); |
| } else { |
| dataRm.setStatus( |
| new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Invalid process context.", null)); //$NON-NLS-1$ |
| dataRm.done(); |
| } |
| } |
| |
| /** @since 5.0 */ |
| protected void doReverseDebugStep(final IProcessDMContext procCtx, RequestMonitor rm) { |
| // Turn on reverse debugging if it was enabled as a launch option |
| IReverseRunControl reverseService = getServicesTracker().getService(IReverseRunControl.class); |
| if (reverseService != null) { |
| ILaunch launch = procCtx.getAdapter(ILaunch.class); |
| if (launch != null) { |
| try { |
| boolean reverseEnabled = launch.getLaunchConfiguration().getAttribute( |
| IGDBLaunchConfigurationConstants.ATTR_DEBUGGER_REVERSE, |
| IGDBLaunchConfigurationConstants.DEBUGGER_REVERSE_DEFAULT); |
| if (reverseEnabled) { |
| reverseService.enableReverseMode(fCommandControl.getContext(), true, rm); |
| return; |
| } |
| } catch (CoreException e) { |
| // Ignore, just don't set reverse |
| } |
| } |
| } |
| rm.done(); |
| } |
| |
| /** @since 4.0 */ |
| protected boolean doCanDetachDebuggerFromProcess() { |
| return fNumConnected > 0; |
| } |
| |
| private boolean isExitedProcess(IDMContext dmc) { |
| return DMContexts.getAncestorOfType(dmc, MIExitedProcessDMC.class) != null; |
| } |
| |
| @Override |
| public void canDetachDebuggerFromProcess(IDMContext dmc, DataRequestMonitor<Boolean> rm) { |
| MIExitedProcessDMC exitedProc = DMContexts.getAncestorOfType(dmc, MIExitedProcessDMC.class); |
| if (exitedProc != null) { |
| // Allow to use the disconnect button to remove an exited process |
| rm.done(true); |
| return; |
| } |
| rm.done(doCanDetachDebuggerFromProcess()); |
| } |
| |
| @Override |
| public void detachDebuggerFromProcess(final IDMContext dmc, final RequestMonitor rm) { |
| MIExitedProcessDMC exitedProc = DMContexts.getAncestorOfType(dmc, MIExitedProcessDMC.class); |
| if (exitedProc != null) { |
| // For an exited process, remove the entry from our table to stop showing it, and |
| // remove the entry from the launch itself to remove the process's console |
| String groupId = exitedProc.getGroupId(); |
| getExitedProcesses().remove(groupId); |
| removeProcessFromLaunch(groupId); |
| getSession().dispatchEvent(new ProcessRemovedDMEvent(exitedProc), null); |
| return; |
| } |
| |
| ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class); |
| final IMIProcessDMContext procDmc = DMContexts.getAncestorOfType(dmc, IMIProcessDMContext.class); |
| |
| if (controlDmc != null && procDmc != null) { |
| if (!doCanDetachDebuggerFromProcess()) { |
| rm.setStatus( |
| new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Detach not supported.", null)); //$NON-NLS-1$ |
| rm.done(); |
| return; |
| } |
| |
| IMIRunControl runControl = getServicesTracker().getService(IMIRunControl.class); |
| if (runControl != null && !runControl.isTargetAcceptingCommands()) { |
| fBackend.interrupt(); |
| } |
| |
| // Remember that this process was detached so we don't show it as an exited process. |
| // We must set this before sending the detach command to gdb to avoid race conditions |
| final IMIContainerDMContext containerDmc = DMContexts.getAncestorOfType(dmc, IMIContainerDMContext.class); |
| if (containerDmc != null) { |
| getDetachedProcesses().add(containerDmc.getGroupId()); |
| } |
| fCommandControl.queueCommand(fCommandFactory.createMITargetDetach(controlDmc, procDmc.getProcId()), |
| new DataRequestMonitor<MIInfo>(getExecutor(), rm) { |
| @Override |
| protected void handleFailure() { |
| // The detach failed |
| if (containerDmc != null) { |
| getDetachedProcesses().remove(containerDmc.getGroupId()); |
| } |
| super.handleFailure(); |
| } |
| }); |
| } else { |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Invalid context.", null)); //$NON-NLS-1$ |
| rm.done(); |
| } |
| } |
| |
| @Override |
| public void canTerminate(IThreadDMContext thread, DataRequestMonitor<Boolean> rm) { |
| if (thread instanceof MIExitedProcessDMC) { |
| // Allow pressing the terminate button to remove an exited process |
| rm.setData(true); |
| } else { |
| rm.setData(true); |
| } |
| rm.done(); |
| } |
| |
| @Override |
| public void isDebugNewProcessSupported(IDMContext dmc, DataRequestMonitor<Boolean> rm) { |
| rm.setData(doIsDebugNewProcessSupported()); |
| rm.done(); |
| } |
| |
| /** @since 4.0 */ |
| protected boolean doIsDebugNewProcessSupported() { |
| return false; |
| } |
| |
| @Override |
| public void debugNewProcess(IDMContext dmc, String file, Map<String, Object> attributes, |
| DataRequestMonitor<IDMContext> rm) { |
| |
| // Store the current value of the initialProcess variable because we will use it later |
| // and we are about to change it. |
| boolean isInitial = isInitialProcess(); |
| if (isInitialProcess()) { |
| setIsInitialProcess(false); |
| } else { |
| // If we are trying to create another process than the initial one, see if we are allowed |
| if (!doIsDebugNewProcessSupported()) { |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, |
| "Not allowed to create a new process", null)); //$NON-NLS-1$ |
| rm.done(); |
| return; |
| } |
| } |
| |
| ImmediateExecutor.getInstance() |
| .execute(getDebugNewProcessSequence(getExecutor(), isInitial, dmc, file, attributes, rm)); |
| } |
| |
| /** |
| * Return the sequence that is to be used to create a new process the specified process. |
| * Allows others to extend more easily. |
| * @since 4.0 |
| */ |
| protected Sequence getDebugNewProcessSequence(DsfExecutor executor, boolean isInitial, IDMContext dmc, String file, |
| Map<String, Object> attributes, DataRequestMonitor<IDMContext> rm) { |
| return new DebugNewProcessSequence(executor, isInitial, dmc, file, attributes, rm); |
| } |
| |
| @Override |
| public void getProcessesBeingDebugged(final IDMContext dmc, final DataRequestMonitor<IDMContext[]> rm) { |
| final ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class); |
| final IMIContainerDMContext containerDmc = DMContexts.getAncestorOfType(dmc, IMIContainerDMContext.class); |
| if (containerDmc != null) { |
| if (isExitedProcess(containerDmc)) { |
| // No threads for an exited process |
| rm.done(new IMIExecutionDMContext[0]); |
| return; |
| } |
| |
| fThreadCommandCache.execute(fCommandFactory.createMIListThreadGroups(controlDmc, containerDmc.getGroupId()), |
| new DataRequestMonitor<MIListThreadGroupsInfo>(getExecutor(), rm) { |
| @Override |
| protected void handleSuccess() { |
| rm.setData(makeExecutionDMCs(containerDmc, getData().getThreadInfo().getThreadList())); |
| rm.done(); |
| } |
| }); |
| } else { |
| |
| final DataRequestMonitor<IMIContainerDMContext[]> addExitedDRM = new ImmediateDataRequestMonitor<IMIContainerDMContext[]>( |
| rm) { |
| @Override |
| protected void handleCompleted() { |
| List<IMIContainerDMContext> containerDmcs = new ArrayList<>(Arrays.asList(getData())); |
| |
| // Add the exited processes to our list in reverse order of insertion so that |
| // the latest exited process is at the top |
| List<Entry<String, ExitedProcInfo>> entries = new ArrayList<>(getExitedProcesses().entrySet()); |
| for (int i = entries.size() - 1; i >= 0; i--) { |
| Entry<String, ExitedProcInfo> entry = entries.get(i); |
| String groupId = entry.getKey(); |
| String pid = entry.getValue().getPid(); |
| IProcessDMContext processDmc = createExitedProcessContext(controlDmc, pid, groupId); |
| containerDmcs.add(createContainerContext(processDmc, groupId)); |
| } |
| |
| rm.done(containerDmcs.toArray(new IMIContainerDMContext[containerDmcs.size()])); |
| } |
| }; |
| |
| fContainerCommandCache.execute(fCommandFactory.createMIListThreadGroups(controlDmc), |
| new DataRequestMonitor<MIListThreadGroupsInfo>(getExecutor(), addExitedDRM) { |
| @Override |
| protected void handleSuccess() { |
| addExitedDRM.done(makeContainerDMCs(controlDmc, getData().getGroupList())); |
| } |
| |
| @Override |
| protected void handleFailure() { |
| // If the target is not available, generate the list ourselves |
| IMIContainerDMContext[] containerDmcs = new IMIContainerDMContext[getGroupToPidMap() |
| .size()]; |
| int i = 0; |
| for (String groupId : getGroupToPidMap().keySet()) { |
| containerDmcs[i++] = createContainerContextFromGroupId(controlDmc, groupId); |
| } |
| addExitedDRM.done(containerDmcs); |
| } |
| }); |
| } |
| } |
| |
| private IExecutionDMContext[] makeExecutionDMCs(IContainerDMContext containerDmc, MIThread[] threadInfos) { |
| final IProcessDMContext procDmc = DMContexts.getAncestorOfType(containerDmc, IProcessDMContext.class); |
| |
| if (threadInfos.length == 0) { |
| // Main thread always exist even if it is not reported by GDB. |
| // So create thread-id = 0 when no thread is reported. |
| // This hack is necessary to prevent AbstractMIControl from issuing a thread-select |
| // because it doesn't work if the application was not compiled with pthread. |
| return new IMIExecutionDMContext[] { createExecutionContext(containerDmc, |
| createThreadContext(procDmc, FAKE_THREAD_ID), FAKE_THREAD_ID) }; |
| } else { |
| IExecutionDMContext[] executionDmcs = new IMIExecutionDMContext[threadInfos.length]; |
| for (int i = 0; i < threadInfos.length; i++) { |
| String threadId = threadInfos[i].getThreadId(); |
| executionDmcs[i] = createExecutionContext(containerDmc, createThreadContext(procDmc, threadId), |
| threadId); |
| } |
| return executionDmcs; |
| } |
| } |
| |
| private IMIContainerDMContext[] makeContainerDMCs(ICommandControlDMContext controlDmc, IThreadGroupInfo[] groups) { |
| // This is a workaround for post-mortem tracing because the early GDB release |
| // does not report a process when we do -list-thread-group |
| // GDB 7.2 will properly report the process so this |
| // code can be removed when GDB 7.2 is released |
| // START OF WORKAROUND |
| if (groups.length == 0 && fBackend.getSessionType() == SessionType.CORE) { |
| return new IMIContainerDMContext[] { |
| createContainerContextFromGroupId(controlDmc, MIProcesses.UNIQUE_GROUP_ID) }; |
| } |
| // END OF WORKAROUND to be removed when GDB 7.2 is available |
| |
| // With GDB 7.1, we can receive a bogus process when we are not debugging anything |
| // -list-thread-groups |
| // ^done,groups=[{id="0",type="process",pid="0"}] |
| // As for GDB 7.2, the pid field is missing altogether in this case |
| // -list-thread-groups |
| // ^done,groups=[{id="i1",type="process"}] |
| // Just ignore that entry |
| List<IMIContainerDMContext> containerDmcs = new ArrayList<>(groups.length); |
| for (IThreadGroupInfo group : groups) { |
| if (group.getPid() == null || group.getPid().isEmpty() || group.getPid().equals("0")) { //$NON-NLS-1$ |
| continue; |
| } |
| String groupId = group.getGroupId(); |
| containerDmcs.add(createContainerContextFromGroupId(controlDmc, groupId)); |
| } |
| return containerDmcs.toArray(new IMIContainerDMContext[containerDmcs.size()]); |
| } |
| |
| @Override |
| public void getRunningProcesses(final IDMContext dmc, final DataRequestMonitor<IProcessDMContext[]> rm) { |
| final ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class); |
| if (controlDmc != null) { |
| fListThreadGroupsAvailableCache.execute(fCommandFactory.createMIListThreadGroups(controlDmc, true), |
| new DataRequestMonitor<MIListThreadGroupsInfo>(getExecutor(), rm) { |
| @Override |
| protected void handleCompleted() { |
| // We cannot actually cache this command since the process |
| // list may change. But this cache allows to avoid overlapping |
| // sending of this command. |
| fListThreadGroupsAvailableCache.reset(); |
| |
| if (isSuccess()) { |
| rm.setData(makeProcessDMCAndData(controlDmc, getData().getGroupList())); |
| } else { |
| // Looks like this gdb doesn't truly support |
| // "-list-thread-groups --available". If we're |
| // debugging locally, resort to getting the |
| // list natively (as we do with gdb 6.8). If |
| // we're debugging remotely, the user is out |
| // of luck |
| if (fBackend.getSessionType() == SessionType.LOCAL) { |
| IProcessList list = null; |
| try { |
| list = CCorePlugin.getDefault().getProcessList(); |
| } catch (CoreException e) { |
| } |
| |
| if (list == null) { |
| rm.setData(new IProcessDMContext[0]); |
| } else { |
| IProcessInfo[] procInfos = list.getProcessList(); |
| rm.setData(makeProcessDMCAndData(controlDmc, procInfos)); |
| } |
| } else { |
| rm.setData(new IProcessDMContext[0]); |
| } |
| } |
| rm.done(); |
| } |
| }); |
| } else { |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Invalid context.", null)); //$NON-NLS-1$ |
| rm.done(); |
| } |
| |
| } |
| |
| /** |
| * Create the joint process DMC and data based on IProcessInfo, which is a local listing. |
| * @since 4.0 |
| */ |
| protected MIProcessDMCAndData[] makeProcessDMCAndData(ICommandControlDMContext controlDmc, |
| IProcessInfo[] processes) { |
| MIProcessDMCAndData[] procDmcs = new MIProcessDMCAndData[processes.length]; |
| for (int i = 0; i < procDmcs.length; i++) { |
| procDmcs[i] = new MIProcessDMCAndData(controlDmc.getSessionId(), controlDmc, |
| Integer.toString(processes[i].getPid()), processes[i].getName(), null, null); |
| } |
| return procDmcs; |
| } |
| |
| /** |
| * Create the joint process DMC and data based on IThreadGroupInfo, which is obtained from GDB. |
| * @since 4.0 |
| */ |
| protected MIProcessDMCAndData[] makeProcessDMCAndData(ICommandControlDMContext controlDmc, |
| IThreadGroupInfo[] processes) { |
| MIProcessDMCAndData[] procDmcs = new MIProcessDMCAndData[processes.length]; |
| int i = 0; |
| for (IThreadGroupInfo process : processes) { |
| procDmcs[i++] = new MIProcessDMCAndData(controlDmc.getSessionId(), controlDmc, process.getGroupId(), |
| process.getName(), process.getCores(), process.getUser(), process.getDesciption()); |
| } |
| return procDmcs; |
| } |
| |
| @Override |
| public void isRunNewProcessSupported(IDMContext dmc, DataRequestMonitor<Boolean> rm) { |
| rm.setData(false); |
| rm.done(); |
| } |
| |
| @Override |
| public void runNewProcess(IDMContext dmc, String file, Map<String, Object> attributes, |
| DataRequestMonitor<IProcessDMContext> rm) { |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Not supported", null)); //$NON-NLS-1$ |
| rm.done(); |
| } |
| |
| @Override |
| public void terminate(IThreadDMContext thread, final RequestMonitor rm) { |
| if (thread instanceof MIExitedProcessDMC) { |
| // For an exited process, remove the entry from our table to stop showing it, and |
| // remove the entry from the launch itself to remove the process's console |
| String groupId = ((MIExitedProcessDMC) thread).getGroupId(); |
| getExitedProcesses().remove(groupId); |
| removeProcessFromLaunch(groupId); |
| getSession().dispatchEvent(new ProcessRemovedDMEvent((IProcessDMContext) thread), null); |
| } else if (fBackend.getSessionType() == SessionType.CORE) { |
| // For a core session, there is no concept of killing the inferior, |
| // so lets kill GDB |
| fCommandControl.terminate(rm); |
| } else if (thread instanceof IMIProcessDMContext) { |
| getDebuggingContext(thread, new ImmediateDataRequestMonitor<IDMContext>(rm) { |
| @Override |
| protected void handleSuccess() { |
| if (getData() instanceof IMIContainerDMContext) { |
| IMIRunControl runControl = getServicesTracker().getService(IMIRunControl.class); |
| if (runControl != null && !runControl.isTargetAcceptingCommands()) { |
| fBackend.interrupt(); |
| } |
| |
| fCommandControl.queueCommand( |
| fCommandFactory.createMIInterpreterExecConsoleKill((IMIContainerDMContext) getData()), |
| new ImmediateDataRequestMonitor<MIInfo>(rm)); |
| } else { |
| rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, |
| "Invalid process context.", null)); //$NON-NLS-1$ |
| rm.done(); |
| } |
| } |
| }); |
| } else { |
| rm.setStatus( |
| new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Invalid process context.", null)); //$NON-NLS-1$ |
| rm.done(); |
| } |
| } |
| |
| /** @since 4.0 */ |
| @Override |
| public void canRestart(IContainerDMContext containerDmc, DataRequestMonitor<Boolean> rm) { |
| if (fBackend.getIsAttachSession() || fBackend.getSessionType() == SessionType.CORE) { |
| rm.setData(false); |
| rm.done(); |
| return; |
| } |
| |
| // Before GDB6.8, the Linux gdbserver would restart a new |
| // process when getting a -exec-run but the communication |
| // with GDB had a bug and everything hung. |
| // with GDB6.8 the program restarts properly one time, |
| // but on a second attempt, gdbserver crashes. |
| // So, lets just turn off the Restart for Remote debugging |
| if (fBackend.getSessionType() == SessionType.REMOTE) { |
| rm.setData(false); |
| rm.done(); |
| return; |
| } |
| |
| rm.setData(true); |
| rm.done(); |
| } |
| |
| /** |
| * Creates the container context that is to be used for the new process that will |
| * be created by the restart operation. |
| * This container does not have its pid yet, while the container of the process |
| * that is being restarted does have its pid. |
| * Also, for GDB 7.0 and 7.1, the groupId being the pid, we cannot use the old |
| * container's groupId, but must use the default groupId until the pid is created. |
| * |
| * @since 4.0 |
| */ |
| protected IMIContainerDMContext createContainerContextForRestart(String groupId) { |
| IProcessDMContext processDmc = createProcessContext(fCommandControl.getContext(), |
| MIProcesses.UNKNOWN_PROCESS_ID); |
| // Don't use the groupId passed in, since it is the old pid. |
| return createContainerContext(processDmc, MIProcesses.UNIQUE_GROUP_ID); |
| } |
| |
| /** @since 4.0 */ |
| @Override |
| public void restart(IContainerDMContext containerDmc, final Map<String, Object> attributes, |
| final DataRequestMonitor<IContainerDMContext> rm) { |
| // Before performing the restart, check if the process is properly suspended. |
| // For such a case, we usually use IMIRunControl.isTargetAcceptingCommands(). |
| // However, in non-stop, although the target is accepting command, a restart |
| // won't work because it needs to be able to set breakpoints. So, to allow |
| // for breakpoints to be set, we make sure process is actually suspended. |
| // |
| // The other way to make this work is to have the restart code set the breakpoints |
| // using the breakpoint service, instead of sending the breakpoint command directly. |
| // This required more changes than suspending the process, so it was not done |
| // just yet. |
| // Bug 246740 |
| |
| final String groupId = ((IMIContainerDMContext) containerDmc).getGroupId(); |
| |
| // This request monitor actually performs the restart |
| RequestMonitor restartRm = new ImmediateRequestMonitor(rm) { |
| @Override |
| protected void handleSuccess() { |
| // For a restart, we are given the container context of the original process. However, we want to start |
| // a new process with a new pid, so we should create a container for it, and not use the old container with the old pid. |
| // Pass in the groupId because starting with GDB 7.2, we must re-use the same groupId. |
| IContainerDMContext newContainerDmc = createContainerContextForRestart(groupId); |
| |
| startOrRestart(newContainerDmc, attributes, true, |
| new ImmediateDataRequestMonitor<IContainerDMContext>(rm) { |
| @Override |
| protected void handleCompleted() { |
| // In case the process we restarted was already exited, remove it from our list |
| // We do this here for GDB 7.1, because we know the proper groupId here which |
| // will change when the new restarted process will start. For GDB >= 7.2 |
| // the groupId is fixed so we don't have to do this right away, but it won't hurt. |
| getExitedProcesses().remove(groupId); |
| |
| setData(getData()); |
| super.handleCompleted(); |
| } |
| }); |
| } |
| }; |
| |
| IRunControl runControl = getServicesTracker().getService(IRunControl.class); |
| if (runControl != null && !runControl.isSuspended(containerDmc)) { |
| // The process is running. Let's suspended it before doing the restart |
| runControl.suspend(containerDmc, restartRm); |
| } else { |
| // The process is already suspended, we can just trigger the restart |
| restartRm.done(); |
| } |
| } |
| |
| /** @since 4.0 */ |
| @Override |
| public void start(IContainerDMContext containerDmc, Map<String, Object> attributes, |
| DataRequestMonitor<IContainerDMContext> rm) { |
| startOrRestart(containerDmc, attributes, false, rm); |
| } |
| |
| /** @since 4.0 */ |
| protected void startOrRestart(IContainerDMContext containerDmc, Map<String, Object> attributes, boolean restart, |
| DataRequestMonitor<IContainerDMContext> rm) { |
| ImmediateExecutor.getInstance() |
| .execute(getStartOrRestartProcessSequence(getExecutor(), containerDmc, attributes, restart, rm)); |
| } |
| |
| /** |
| * Return the sequence that is to be used to start or restart the specified process. |
| * Allows others to extend more easily. |
| * @since 4.0 |
| */ |
| protected Sequence getStartOrRestartProcessSequence(DsfExecutor executor, IContainerDMContext containerDmc, |
| Map<String, Object> attributes, boolean restart, DataRequestMonitor<IContainerDMContext> rm) { |
| return new StartOrRestartProcessSequence_7_0(executor, containerDmc, attributes, restart, rm); |
| } |
| |
| /** |
| * Removes the process with the specified groupId from the launch. |
| * |
| * @return The label used for the console of that process. |
| */ |
| private String removeProcessFromLaunch(String groupId) { |
| ILaunch launch = (ILaunch) getSession().getModelAdapter(ILaunch.class); |
| IProcess[] launchProcesses = launch.getProcesses(); |
| for (IProcess process : launchProcesses) { |
| if (process instanceof InferiorRuntimeProcess) { |
| String groupAttribute = process.getAttribute(IGdbDebugConstants.INFERIOR_GROUPID_ATTR); |
| |
| // if the groupAttribute is not set in the process we know we are dealing |
| // with single process debugging so the one process is the one we want. |
| // If the groupAttribute is set, then we must make sure it is the proper inferior |
| if (groupAttribute == null || groupAttribute.equals(MIProcesses.UNIQUE_GROUP_ID) |
| || groupAttribute.equals(groupId)) { |
| launch.removeProcess(process); |
| return process.getLabel(); |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Add the specified process to the launch. |
| */ |
| private void addProcessToLaunch(Process inferior, String groupId, String label) { |
| // Add the inferior to the launch. |
| // This cannot be done on the executor or things deadlock. |
| DebugPlugin.getDefault().asyncExec(() -> { |
| // Add the inferior |
| // Need to go through DebugPlugin.newProcess so that we can use |
| // the overrideable process factory to allow others to override. |
| // First set attribute to specify we want to create an inferior process. |
| // Bug 210366 |
| ILaunch launch = (ILaunch) getSession().getModelAdapter(ILaunch.class); |
| Map<String, String> attributes = new HashMap<>(); |
| attributes.put(IGdbDebugConstants.PROCESS_TYPE_CREATION_ATTR, |
| IGdbDebugConstants.INFERIOR_PROCESS_CREATION_VALUE); |
| IProcess runtimeInferior = DebugPlugin.newProcess(launch, inferior, label != null ? label : "", //$NON-NLS-1$ |
| attributes); |
| // Now set the inferior groupId |
| runtimeInferior.setAttribute(IGdbDebugConstants.INFERIOR_GROUPID_ATTR, groupId); |
| }); |
| } |
| |
| /** |
| * @since 5.2 |
| */ |
| @Override |
| public void addInferiorToLaunch(IContainerDMContext containerDmc, String label, PTY pty, RequestMonitor rm) { |
| if (containerDmc instanceof IMIContainerDMContext) { |
| String groupId = ((IMIContainerDMContext) containerDmc).getGroupId(); |
| // Create an MIInferiorProcess to track the new instance of the process, |
| // remove the old one from the launch, and add the new one to the launch. |
| Process inferiorProcess; |
| if (pty == null) { |
| inferiorProcess = createInferiorProcess(containerDmc, fBackend.getMIOutputStream()); |
| } else { |
| fGroupIdToPTYMap.put(groupId, pty); |
| inferiorProcess = createInferiorProcess(containerDmc, pty); |
| } |
| addProcessToLaunch(inferiorProcess, groupId, label); |
| } |
| rm.done(); |
| } |
| |
| @DsfServiceEventHandler |
| public void eventDispatched(final MIThreadGroupCreatedEvent e) { |
| IProcessDMContext procDmc = e.getDMContext(); |
| IMIContainerDMContext containerDmc = e.getGroupId() != null ? createContainerContext(procDmc, e.getGroupId()) |
| : null; |
| getSession().dispatchEvent(new ContainerStartedDMEvent(containerDmc), getProperties()); |
| } |
| |
| @DsfServiceEventHandler |
| public void eventDispatched(final MIThreadGroupExitedEvent e) { |
| IProcessDMContext procDmc = e.getDMContext(); |
| IMIContainerDMContext containerDmc = e.getGroupId() != null ? createContainerContext(procDmc, e.getGroupId()) |
| : null; |
| getSession().dispatchEvent(new ContainerExitedDMEvent(containerDmc), getProperties()); |
| } |
| |
| @DsfServiceEventHandler |
| public void eventDispatched(IResumedDMEvent e) { |
| if (e instanceof IContainerResumedDMEvent) { |
| // This will happen in all-stop mode |
| fContainerCommandCache.setContextAvailable(e.getDMContext(), false); |
| fThreadCommandCache.setContextAvailable(e.getDMContext(), false); |
| fListThreadGroupsAvailableCache.setContextAvailable(e.getDMContext(), false); |
| } else { |
| // This will happen in non-stop mode |
| // Keep target available for Container commands |
| } |
| } |
| |
| /** @since 5.2 */ |
| protected MIInferiorProcess createInferiorProcess(IContainerDMContext container, OutputStream outputStream) { |
| return new MIInferiorProcess(container, outputStream); |
| } |
| |
| /** @since 5.2 */ |
| protected MIInferiorProcess createInferiorProcess(IContainerDMContext container, PTY pty) { |
| return new MIInferiorProcess(container, pty); |
| } |
| |
| private void handleRestartingProcess(IMIContainerDMContext containerDmc) { |
| String label = removeProcessFromLaunch(containerDmc.getGroupId()); |
| if (label != null) { |
| // We only add the process to the launch if the original process was part of the launch. |
| // For example, in the attach case, there is no process added to the launch |
| // We re-use the same PTY as the one used before the restart. |
| addInferiorToLaunch(containerDmc, label, fGroupIdToPTYMap.get(containerDmc.getGroupId()), |
| new ImmediateRequestMonitor()); |
| } |
| } |
| |
| @DsfServiceEventHandler |
| public void eventDispatched(ISuspendedDMEvent e) { |
| if (e instanceof IContainerSuspendedDMEvent) { |
| // This will happen in all-stop mode |
| fContainerCommandCache.setContextAvailable(fCommandControl.getContext(), true); |
| fThreadCommandCache.setContextAvailable(fCommandControl.getContext(), true); |
| fListThreadGroupsAvailableCache.setContextAvailable(fCommandControl.getContext(), true); |
| } else { |
| // This will happen in non-stop mode |
| } |
| |
| // If user is debugging a gdb target that doesn't send thread |
| // creation events, make sure we don't use cached thread |
| // information. Reset the cache after every suspend. See bugzilla |
| // 280631 |
| try { |
| if (fBackend.getUpdateThreadListOnSuspend()) { |
| // We need to clear the cache for the context that we use to fill the cache, |
| // and it is the controDMC in this case. |
| ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(e.getDMContext(), |
| ICommandControlDMContext.class); |
| fThreadCommandCache.reset(controlDmc); |
| } |
| } catch (CoreException exc) { |
| } |
| } |
| |
| // Event handler when a thread or threadGroup starts |
| @DsfServiceEventHandler |
| public void eventDispatched(IStartedDMEvent e) { |
| if (e.getDMContext() instanceof IMIContainerDMContext) { |
| String groupId = ((IMIContainerDMContext) e.getDMContext()).getGroupId(); |
| if (fExitedGroupId.remove(groupId)) { |
| // The process in question is restarting. |
| handleRestartingProcess((IMIContainerDMContext) e.getDMContext()); |
| } |
| |
| fContainerCommandCache.reset(); |
| fNumConnected++; |
| } else { |
| fThreadCommandCache.reset(); |
| } |
| } |
| |
| // Event handler when a thread or a threadGroup exits |
| @DsfServiceEventHandler |
| public void eventDispatched(IExitedDMEvent e) { |
| if (e.getDMContext() instanceof IMIContainerDMContext) { |
| fExitedGroupId.add(((IMIContainerDMContext) e.getDMContext()).getGroupId()); |
| |
| fContainerCommandCache.reset(); |
| |
| assert fNumConnected > 0; |
| fNumConnected--; |
| |
| if (fNumConnected == 0 && Platform.getPreferencesService().getBoolean(GdbPlugin.PLUGIN_ID, |
| IGdbDebugPreferenceConstants.PREF_AUTO_TERMINATE_GDB, true, null)) { |
| // If the last process we are debugging finishes and does not restart |
| // let's terminate GDB. We wait a small delay to see if the process will restart. |
| // We also do this for a remote attach session, since the 'auto terminate' preference |
| // is enabled. If users want to keep the session alive to attach to another process, |
| // they can simply disable that preference |
| getExecutor().schedule(new DsfRunnable() { |
| @Override |
| public void run() { |
| // Verify the process didn't restart by checking that we still have nothing connected |
| if (fNumConnected == 0) { |
| fCommandControl.terminate(new ImmediateRequestMonitor()); |
| } |
| } |
| }, 500, TimeUnit.MILLISECONDS); |
| } |
| } else { |
| fThreadCommandCache.reset(); |
| } |
| } |
| |
| /** |
| * @since 5.2 |
| */ |
| @DsfServiceEventHandler |
| public void eventDispatched(ICommandControlShutdownDMEvent e) { |
| // Now that the debug session is over, close the persistent PTY streams |
| for (PTY pty : fGroupIdToPTYMap.values()) { |
| if (pty instanceof PersistentPTY) { |
| try { |
| ((PersistentPTY) pty).closeStreams(); |
| } catch (IOException e1) { |
| } |
| } |
| } |
| fGroupIdToPTYMap.clear(); |
| |
| fExitedGroupId.clear(); |
| } |
| |
| @Override |
| public void flushCache(IDMContext context) { |
| fContainerCommandCache.reset(context); |
| fThreadCommandCache.reset(context); |
| // Not technically needed since we are supposed to have |
| // cleared this cache as soon as the it gets the answer |
| // from GDB; but to be more future-proof, might as well |
| // clear it here also. |
| fListThreadGroupsAvailableCache.reset(context); |
| } |
| |
| /* |
| * Catch =thread-created/exited and =thread-group-exited events to update our |
| * groupId to threadId map. |
| */ |
| @Override |
| public void eventReceived(Object output) { |
| for (MIOOBRecord oobr : ((MIOutput) output).getMIOOBRecords()) { |
| if (oobr instanceof MINotifyAsyncOutput) { |
| MINotifyAsyncOutput exec = (MINotifyAsyncOutput) oobr; |
| String miEvent = exec.getAsyncClass(); |
| if ("thread-created".equals(miEvent) || "thread-exited".equals(miEvent)) { //$NON-NLS-1$ //$NON-NLS-2$ |
| String threadId = null; |
| String groupId = null; |
| |
| MIResult[] results = exec.getMIResults(); |
| for (int i = 0; i < results.length; i++) { |
| String var = results[i].getVariable(); |
| MIValue val = results[i].getMIValue(); |
| if (var.equals("group-id")) { //$NON-NLS-1$ |
| if (val instanceof MIConst) { |
| groupId = ((MIConst) val).getString(); |
| } |
| } else if (var.equals("id")) { //$NON-NLS-1$ |
| if (val instanceof MIConst) { |
| threadId = ((MIConst) val).getString(); |
| } |
| } |
| } |
| |
| if ("thread-created".equals(miEvent)) { //$NON-NLS-1$ |
| // Update the thread to groupId map with the new groupId |
| getThreadToGroupMap().put(threadId, groupId); |
| } else { |
| getThreadToGroupMap().remove(threadId); |
| } |
| // "thread-group-created" was used before GDB 7.2, while "thread-group-started" is used with GDB 7.2 |
| } else if ("thread-group-created".equals(miEvent) || "thread-group-started".equals(miEvent)) { //$NON-NLS-1$ //$NON-NLS-2$ |
| String groupId = null; |
| String pId = null; |
| |
| MIResult[] results = exec.getMIResults(); |
| for (int i = 0; i < results.length; i++) { |
| String var = results[i].getVariable(); |
| MIValue val = results[i].getMIValue(); |
| if (var.equals("id")) { //$NON-NLS-1$ |
| if (val instanceof MIConst) { |
| groupId = ((MIConst) val).getString().trim(); |
| } |
| } else if (var.equals("pid")) { //$NON-NLS-1$ |
| // Available starting with GDB 7.2 |
| if (val instanceof MIConst) { |
| pId = ((MIConst) val).getString().trim(); |
| } |
| } |
| } |
| |
| if (pId == null) { |
| // Before GDB 7.2, the groupId was the pid of the process |
| pId = groupId; |
| } |
| |
| if (groupId != null) { |
| // In case the process that just started was already exited (so we are dealing |
| // with a restart), remove it from our list. |
| // Do this here to handle the restart case triggered by GDB itself |
| // (user typing 'run' from the GDB console). In this case, we don't know yet |
| // we are dealing with a restart, but when we see the process come back, we |
| // know to remove it from the exited list. Note that this won't work |
| // for GDB 7.1 because the groupId of the new process is not the same as the old |
| // one. Not worth fixing for such an old version. |
| getExitedProcesses().remove(groupId); |
| |
| getGroupToPidMap().put(groupId, pId); |
| |
| // Mark that we know this new process, but don't fetch its |
| // name until it is requested. |
| fDebuggedProcessesAndNames.put(pId, ""); //$NON-NLS-1$ |
| } |
| } else if ("thread-group-exited".equals(miEvent)) { //$NON-NLS-1$ |
| String groupId = null; |
| |
| MIResult[] results = exec.getMIResults(); |
| for (int i = 0; i < results.length; i++) { |
| String var = results[i].getVariable(); |
| MIValue val = results[i].getMIValue(); |
| if (var.equals("id")) { //$NON-NLS-1$ |
| if (val instanceof MIConst) { |
| groupId = ((MIConst) val).getString().trim(); |
| } |
| } |
| } |
| |
| if (groupId != null) { |
| String pId = getGroupToPidMap().remove(groupId); |
| |
| // GDB is no longer debugging this process. Remove it from our list |
| String name = fDebuggedProcessesAndNames.remove(pId); |
| if (!getDetachedProcesses().remove(groupId)) { |
| // If the process was not detached, |
| // store it in the list of exited processes. |
| getExitedProcesses().put(groupId, new ExitedProcInfo(pId, name)); |
| } |
| |
| // Remove any entries for that group from our thread to group map |
| // When detaching from a group, we won't have received any thread-exited event |
| // but we don't want to keep those entries. |
| if (getThreadToGroupMap().containsValue(groupId)) { |
| Iterator<Map.Entry<String, String>> iterator = getThreadToGroupMap().entrySet().iterator(); |
| while (iterator.hasNext()) { |
| if (iterator.next().getValue().equals(groupId)) { |
| iterator.remove(); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |