1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-07-21 16:05:25 +02:00

Bug 407340 - Keep showing in the Debug view a process that has exited

Change-Id: Ib4a1c80d99b1a311c781ea1f481943982cdf0cbd
Signed-off-by: Marc Khouzam <marc.khouzam@ericsson.com>
This commit is contained in:
Marc Khouzam 2015-03-30 15:05:08 -04:00 committed by Gerrit Code Review @ Eclipse.org
parent a19bbeb962
commit d533adec3c
11 changed files with 563 additions and 79 deletions

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2006, 2012 Ericsson and others.
* Copyright (c) 2006, 2015 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
@ -10,6 +10,7 @@
* Wind River Systems - Factored out AbstractContainerVMNode
* Patrick Chuong (Texas Instruments) - Add support for icon overlay in the debug view (Bug 334566)
* Marc Khouzam (Ericsson) - Respect the "Show Full Path" option for the process name (Bug 378418)
* Marc Khouzam (Ericsson) - Support for exited processes in the debug view (bug 407340)
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.internal.ui.viewmodel.launch;
@ -25,6 +26,7 @@ import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.datamodel.IDMEvent;
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;
@ -41,7 +43,8 @@ import org.eclipse.cdt.dsf.gdb.IGdbDebugPreferenceConstants;
import org.eclipse.cdt.dsf.gdb.internal.ui.GdbPinProvider;
import org.eclipse.cdt.dsf.gdb.internal.ui.GdbUIPlugin;
import org.eclipse.cdt.dsf.gdb.service.IGDBProcesses.IGdbThreadDMData;
import org.eclipse.cdt.dsf.internal.ui.DsfUIPlugin;
import org.eclipse.cdt.dsf.gdb.service.IGDBProcesses.IGdbThreadExitedDMData;
import org.eclipse.cdt.dsf.gdb.service.IGDBProcesses.IThreadRemovedDMEvent;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.ui.concurrent.ViewerCountingRequestMonitor;
import org.eclipse.cdt.dsf.ui.concurrent.ViewerDataRequestMonitor;
@ -58,6 +61,7 @@ import org.eclipse.cdt.dsf.ui.viewmodel.properties.VMDelegatingPropertiesUpdate;
import org.eclipse.cdt.ui.CDTSharedImages;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementCompareRequest;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementLabelProvider;
@ -72,7 +76,6 @@ import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.IMemento;
@SuppressWarnings("restriction")
public class ContainerVMNode extends AbstractContainerVMNode
implements IElementLabelProvider, IElementMementoProvider
{
@ -115,6 +118,30 @@ public class ContainerVMNode extends AbstractContainerVMNode
provider.setColumnInfo(
PropertiesBasedLabelProvider.ID_COLUMN_NO_COLUMNS,
new LabelColumnInfo(new LabelAttribute[] {
/* EXITED CONTAINER LABEL */
new GdbExecutionContextLabelText(
MessagesForGdbLaunchVM.ContainerVMNode_No_columns__exited_format,
new String[] {
ExecutionContextLabelText.PROP_NAME_KNOWN,
PROP_NAME,
ExecutionContextLabelText.PROP_ID_KNOWN,
ILaunchVMConstants.PROP_ID,
IGdbLaunchVMConstants.PROP_EXIT_CODE_KNOWN,
IGdbLaunchVMConstants.PROP_EXIT_CODE }) {
@Override
public boolean isEnabled(IStatus status, Map<String, Object> properties) {
Boolean exited = (Boolean) properties.get(IGdbLaunchVMConstants.PROP_THREAD_EXITED);
return Boolean.TRUE.equals(exited);
}
},
/* EXITED CONTAINER IMAGE */
new LabelImage(DebugUITools.getImageDescriptor(IDebugUIConstants.IMG_OBJS_OS_PROCESS_TERMINATED)) {
{ setPropertyNames(new String[] {
IGdbLaunchVMConstants.PROP_THREAD_EXITED }); }
},
/* ACTIVE CONTAINER LABEL */
new GdbExecutionContextLabelText(
MessagesForGdbLaunchVM.ContainerVMNode_No_columns__text_format,
new String[] {
@ -126,6 +153,7 @@ public class ContainerVMNode extends AbstractContainerVMNode
IGdbLaunchVMConstants.PROP_CORES_ID,
IGdbLaunchVMConstants.PROP_THREAD_SUMMARY_KNOWN,
IGdbLaunchVMConstants.PROP_THREAD_SUMMARY }),
new LabelText(MessagesForGdbLaunchVM.ContainerVMNode_No_columns__Error__label, new String[0]),
/* RUNNING CONTAINER - RED PIN */
@ -285,14 +313,15 @@ public class ContainerVMNode extends AbstractContainerVMNode
if (update.getProperties().contains(PROP_NAME) ||
update.getProperties().contains(ILaunchVMConstants.PROP_ID) ||
update.getProperties().contains(IGdbLaunchVMConstants.PROP_CORES_ID))
update.getProperties().contains(IGdbLaunchVMConstants.PROP_CORES_ID) ||
update.getProperties().contains(IGdbLaunchVMConstants.PROP_THREAD_EXITED) ||
update.getProperties().contains((IGdbLaunchVMConstants.PROP_EXIT_CODE)))
{
IProcesses processService = getServicesTracker().getService(IProcesses.class);
final IProcessDMContext procDmc = findDmcInPath(update.getViewerInput(), update.getElementPath(), IProcessDMContext.class);
if (processService == null || procDmc == null) {
update.setStatus(DsfUIPlugin.newErrorStatus(IDsfStatusConstants.INVALID_HANDLE, "Service or handle invalid", null)); //$NON-NLS-1$
update.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_HANDLE, "Service or handle invalid", null)); //$NON-NLS-1$
} else {
processService.getExecutionData(
procDmc,
@ -347,6 +376,15 @@ public class ContainerVMNode extends AbstractContainerVMNode
}
}
update.setProperty(IGdbLaunchVMConstants.PROP_CORES_ID, coresStr);
if (data instanceof IGdbThreadExitedDMData) {
update.setProperty(IGdbLaunchVMConstants.PROP_THREAD_EXITED, true);
Integer exitCode = ((IGdbThreadExitedDMData)data).getExitCode();
if (exitCode != null) {
update.setProperty(IGdbLaunchVMConstants.PROP_EXIT_CODE, exitCode);
}
}
}
protected void fillThreadSummary(final IPropertiesUpdate update, final RequestMonitor rm) {
@ -361,7 +399,7 @@ public class ContainerVMNode extends AbstractContainerVMNode
final IContainerDMContext procDmc = findDmcInPath(update.getViewerInput(), update.getElementPath(), IContainerDMContext.class);
if (processService == null || procDmc == null) {
update.setStatus(DsfUIPlugin.newErrorStatus(IDsfStatusConstants.INVALID_HANDLE, "Service or handle invalid", null)); //$NON-NLS-1$
update.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_HANDLE, "Service or handle invalid", null)); //$NON-NLS-1$
} else {
// Fetch all the threads
processService.getProcessesBeingDebugged(
@ -373,7 +411,7 @@ public class ContainerVMNode extends AbstractContainerVMNode
if (!isSuccess() ||
!(getData() instanceof IExecutionDMContext[]) ||
runControl == null) {
update.setStatus(DsfUIPlugin.newErrorStatus(IDsfStatusConstants.INVALID_HANDLE, "Unable to get threads summary", null)); //$NON-NLS-1$
update.setStatus(new Status(IStatus.ERROR, GdbUIPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_HANDLE, "Unable to get threads summary", null)); //$NON-NLS-1$
rm.done();
return;
}
@ -400,6 +438,13 @@ public class ContainerVMNode extends AbstractContainerVMNode
if (e instanceof ICommandControlShutdownDMEvent) {
return IModelDelta.CONTENT;
}
if (e instanceof IThreadRemovedDMEvent) {
IDMContext dmc = e instanceof IDMEvent<?> ? ((IDMEvent<?>)e).getDMContext() : null;
if (dmc instanceof IProcessDMContext) {
return IModelDelta.CONTENT;
}
return IModelDelta.NO_CHANGE;
}
return super.getDeltaFlags(e);
}
@ -407,6 +452,12 @@ public class ContainerVMNode extends AbstractContainerVMNode
public void buildDelta(Object e, final VMDelta parentDelta, final int nodeOffset, final RequestMonitor requestMonitor) {
if (e instanceof ICommandControlShutdownDMEvent) {
parentDelta.setFlags(parentDelta.getFlags() | IModelDelta.CONTENT);
} else if (e instanceof IThreadRemovedDMEvent) {
IDMContext dmc = e instanceof IDMEvent<?> ? ((IDMEvent<?>)e).getDMContext() : null;
if (dmc instanceof IProcessDMContext) {
// A process was removed, refresh the parent
parentDelta.setFlags(parentDelta.getFlags() | IModelDelta.CONTENT);
}
} else {
super.buildDelta(e, parentDelta, nodeOffset, requestMonitor);
return;

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2008, 2012 Wind River Systems and others.
* Copyright (c) 2008, 2015 Wind River Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@ -7,6 +7,7 @@
*
* Contributors:
* Wind River Systems - initial API and implementation
* Marc Khouzam (Ericsson) - Support for exited processes in the debug view (bug 407340)
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.internal.ui.viewmodel.launch;
@ -35,6 +36,9 @@ public class GdbExecutionContextLabelText extends ExecutionContextLabelText {
if (IGdbLaunchVMConstants.PROP_THREAD_SUMMARY_KNOWN.equals(propertyName)) {
return properties.get(IGdbLaunchVMConstants.PROP_THREAD_SUMMARY) != null ? 1 : 0;
}
if (IGdbLaunchVMConstants.PROP_EXIT_CODE_KNOWN.equals(propertyName)) {
return properties.get(IGdbLaunchVMConstants.PROP_EXIT_CODE) != null ? 1 : 0;
}
return super.getPropertyValue(propertyName, status, properties);
}
@ -43,12 +47,11 @@ public class GdbExecutionContextLabelText extends ExecutionContextLabelText {
if (IGdbLaunchVMConstants.PROP_OS_ID_KNOWN.equals(propertyName) ||
IGdbLaunchVMConstants.PROP_OS_ID.equals(propertyName) ||
IGdbLaunchVMConstants.PROP_CORES_ID_KNOWN.equals(propertyName) ||
IGdbLaunchVMConstants.PROP_CORES_ID.equals(propertyName))
{
return true;
}
if (IGdbLaunchVMConstants.PROP_THREAD_SUMMARY_KNOWN.equals(propertyName) ||
IGdbLaunchVMConstants.PROP_THREAD_SUMMARY.equals(propertyName))
IGdbLaunchVMConstants.PROP_CORES_ID.equals(propertyName) ||
IGdbLaunchVMConstants.PROP_THREAD_SUMMARY_KNOWN.equals(propertyName) ||
IGdbLaunchVMConstants.PROP_THREAD_SUMMARY.equals(propertyName) ||
IGdbLaunchVMConstants.PROP_EXIT_CODE_KNOWN.equals(propertyName) ||
IGdbLaunchVMConstants.PROP_EXIT_CODE.equals(propertyName))
{
return true;
}

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2008, 2010 Wind River Systems and others.
* Copyright (c) 2008, 2015 Wind River Systems 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
@ -8,6 +8,7 @@
* Contributors:
* Wind River Systems - initial API and implementation
* Patrick Chuong (Texas Instruments) - Add support for icon overlay in the debug view (Bug 334566)
* Marc Khouzam (Ericsson) - Support for exited processes in the debug view (bug 407340)
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.internal.ui.viewmodel.launch;
@ -42,4 +43,18 @@ public interface IGdbLaunchVMConstants {
public static final String PROP_THREAD_SUMMARY_KNOWN = "thread_summary_known"; //$NON-NLS-1$
public static final String PROP_THREAD_SUMMARY = "thread_summary"; //$NON-NLS-1$
/**
* If this property is set, it indicates the process or thread should be shown as exited.
*/
public static final String PROP_THREAD_EXITED = "thread_exited"; //$NON-NLS-1$
/**
* Value <code>0</code> means it's not known. Value <code>1</code>, means it's known.
*/
public static final String PROP_EXIT_CODE_KNOWN = "exit_code_known"; //$NON-NLS-1$
/**
* If set, the value of the property indicates the exit code returned.
*/
public static final String PROP_EXIT_CODE = "exit_code"; //$NON-NLS-1$
}

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2008, 2012 Wind River Systems and others.
* Copyright (c) 2008, 2015 Wind River Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@ -7,6 +7,7 @@
*
* Contributors:
* Wind River Systems - initial API and implementation
* Marc Khouzam (Ericsson) - Support for exited processes in the debug view (bug 407340)
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.internal.ui.viewmodel.launch;
@ -20,6 +21,7 @@ public class MessagesForGdbLaunchVM extends NLS {
public static String ThreadVMNode_No_columns__text_format;
public static String ThreadVMNode_No_columns__Error__label;
public static String ContainerVMNode_No_columns__text_format;
public static String ContainerVMNode_No_columns__exited_format;
public static String ContainerVMNode_No_columns__Error__label;
/** since 2.3 */
public static String ContainerVMNode_filtered_running_threads;

View file

@ -1,5 +1,5 @@
###############################################################################
# Copyright (c) 2007, 2014 Wind River Systems and others.
# Copyright (c) 2007, 2015 Wind River Systems 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
@ -9,6 +9,7 @@
# Wind River Systems - initial API and implementation
# Marc Khouzam (Ericsson) - Update format of thread string to better display
# Thread name (Bug 378154)
# Marc Khouzam (Ericsson) - Support for exited processes in the debug view (bug 407340)
###############################################################################
# {0} - name available, 0=not available/1=available
@ -38,6 +39,14 @@ ThreadVMNode_No_columns__Error__label=<unavailable>
# {7} - Thread summary in text format
ContainerVMNode_No_columns__text_format={0,choice,0#Process|1#{1}}{2,choice,0#|1# [{3}]}{4,choice,0#|1# [cores: {5}]}{6,choice,0#|1# {7}}
# {0} - name available, 0=not available/1=available
# {1} - name
# {2} - ID available, 0=not available/1=available
# {3} - ID
# {4} - Exit code available, 0=not available/1=available
# {5} - Exit code
ContainerVMNode_No_columns__exited_format=<terminated{4,choice,0#|1#, exit value: {5}}>{0,choice,0#Process|1#{1}}{2,choice,0#|1# [{3}]}
ContainerVMNode_No_columns__Error__label=<unavailable>
ContainerVMNode_filtered_running_threads=filtered running threads

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2008, 2014 Ericsson and others.
* Copyright (c) 2008, 2015 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
@ -13,16 +13,22 @@
* 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.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 org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.IProcessInfo;
@ -101,6 +107,12 @@ import org.osgi.framework.BundleContext;
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:
@ -346,6 +358,54 @@ public class GDBProcesses_7_0 extends AbstractDsfService
}
}
/**
* 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.
*/
@ -371,6 +431,39 @@ public class GDBProcesses_7_0 extends AbstractDsfService
}
}
/**
* 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.
@ -449,6 +542,17 @@ public class GDBProcesses_7_0 extends AbstractDsfService
}
}
/**
* @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.
*/
@ -483,7 +587,72 @@ public class GDBProcesses_7_0 extends AbstractDsfService
// 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<String, String>();
private Map<String, String> fDebuggedProcessesAndNames = new HashMap<>();
/**
* 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$
@ -626,6 +795,16 @@ public class GDBProcesses_7_0 extends AbstractDsfService
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
@ -651,6 +830,23 @@ public class GDBProcesses_7_0 extends AbstractDsfService
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,
@ -672,7 +868,6 @@ public class GDBProcesses_7_0 extends AbstractDsfService
// 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!!!
// khouzam
if (getThreadToGroupMap().isEmpty()) {
groupId = MIProcesses.UNIQUE_GROUP_ID;
} else {
@ -713,6 +908,11 @@ public class GDBProcesses_7_0 extends AbstractDsfService
@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<IMIExecutionDMContext>();
Iterator<Map.Entry<String, String>> iterator = getThreadToGroupMap().entrySet().iterator();
@ -732,6 +932,20 @@ public class GDBProcesses_7_0 extends AbstractDsfService
@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;
@ -811,7 +1025,11 @@ public class GDBProcesses_7_0 extends AbstractDsfService
@Override
public void getDebuggingContext(IThreadDMContext dmc, DataRequestMonitor<IDMContext> rm) {
if (dmc instanceof MIProcessDMC) {
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);
@ -970,17 +1188,33 @@ public class GDBProcesses_7_0 extends AbstractDsfService
return fNumConnected > 0;
}
private boolean isExitedProcess(IDMContext dmc) {
return DMContexts.getAncestorOfType(dmc, MIExitedProcessDMC.class) != null;
}
@Override
public void canDetachDebuggerFromProcess(IDMContext dmc, DataRequestMonitor<Boolean> rm) {
rm.setData(doCanDetachDebuggerFromProcess());
rm.done();
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, simply remove the entry from our table to stop showing it.
getExitedProcesses().remove(exitedProc.getGroupId());
getSession().dispatchEvent(new ProcessRemovedDMEvent(exitedProc), null);
return;
}
ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class);
IMIProcessDMContext procDmc = DMContexts.getAncestorOfType(dmc, IMIProcessDMContext.class);
final IMIProcessDMContext procDmc = DMContexts.getAncestorOfType(dmc, IMIProcessDMContext.class);
if (controlDmc != null && procDmc != null) {
if (!doCanDetachDebuggerFromProcess()) {
@ -994,9 +1228,24 @@ public class GDBProcesses_7_0 extends AbstractDsfService
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));
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();
@ -1005,7 +1254,12 @@ public class GDBProcesses_7_0 extends AbstractDsfService
@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();
}
@ -1057,6 +1311,12 @@ public class GDBProcesses_7_0 extends AbstractDsfService
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) {
@ -1067,13 +1327,34 @@ public class GDBProcesses_7_0 extends AbstractDsfService
}
});
} 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(), rm) {
new DataRequestMonitor<MIListThreadGroupsInfo>(getExecutor(), addExitedDRM) {
@Override
protected void handleSuccess() {
rm.setData(makeContainerDMCs(controlDmc, getData().getGroupList()));
rm.done();
addExitedDRM.done(makeContainerDMCs(controlDmc, getData().getGroupList()));
}
@Override
protected void handleFailure() {
@ -1083,8 +1364,7 @@ public class GDBProcesses_7_0 extends AbstractDsfService
for (String groupId : getGroupToPidMap().keySet()) {
containerDmcs[i++] = createContainerContextFromGroupId(controlDmc, groupId);
}
rm.setData(containerDmcs);
rm.done();
addExitedDRM.done(containerDmcs);
}
});
}
@ -1244,9 +1524,13 @@ public class GDBProcesses_7_0 extends AbstractDsfService
@Override
public void terminate(IThreadDMContext thread, final RequestMonitor rm) {
if (thread instanceof MIExitedProcessDMC) {
// For an exited process, simply remove the entry from our table to stop showing it.
getExitedProcesses().remove(((MIExitedProcessDMC)thread).getGroupId());
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
if (fBackend.getSessionType() == SessionType.CORE) {
fCommandControl.terminate(rm);
} else if (thread instanceof IMIProcessDMContext) {
getDebuggingContext(
@ -1350,6 +1634,10 @@ public class GDBProcesses_7_0 extends AbstractDsfService
if (!isSuccess()) {
fProcRestarting = false;
}
// In case the process we restarted was already exited, remove it from our list
getExitedProcesses().remove(groupId);
setData(getData());
super.handleCompleted();
};
@ -1449,6 +1737,7 @@ public class GDBProcesses_7_0 extends AbstractDsfService
if (e instanceof ContainerStartedDMEvent) {
fContainerCommandCache.reset();
fNumConnected++;
fProcRestarting = false;
} else {
fThreadCommandCache.reset();
}
@ -1475,7 +1764,6 @@ public class GDBProcesses_7_0 extends AbstractDsfService
fCommandControl.terminate(new ImmediateRequestMonitor());
}
}
fProcRestarting = false;
} else {
fThreadCommandCache.reset();
}
@ -1617,8 +1905,13 @@ public class GDBProcesses_7_0 extends AbstractDsfService
if (groupId != null) {
String pId = getGroupToPidMap().remove(groupId);
// GDB is no longer debugging this process. Remove it from our list.
fDebuggedProcessesAndNames.remove(pId);
// GDB is no longer debugging this process. Remove it from our list
String name = fDebuggedProcessesAndNames.remove(pId);
if (!fProcRestarting && !getDetachedProcesses().remove(groupId)) {
// If the process is not restarting and 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

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2010, 2014 Ericsson and others.
* Copyright (c) 2010, 2015 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
@ -10,6 +10,7 @@
* Andy Jin (QNX) - Not output thread osId as a string when it is null (Bug 397039)
* Alvaro Sanchez-Leon - Bug 451396 - Improve extensibility to process MI "-thread-info" results
* Simon Marchi (Ericsson) - Bug 378154 - Pass thread name from MIThread to the data model
* Marc Khouzam (Ericsson) - Support for exited processes in the debug view (bug 407340)
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.service;
@ -136,6 +137,12 @@ public class GDBProcesses_7_1 extends GDBProcesses_7_0 {
protected void handleSuccess() {
final IThreadDMData firstLevelData = getData();
// No need to go further if we are dealing with an exited process
if (firstLevelData instanceof IGdbThreadExitedDMData) {
rm.done(firstLevelData);
return;
}
ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class);
final String groupId = getGroupFromPid(((IMIProcessDMContext)dmc).getProcId());
@ -217,7 +224,7 @@ public class GDBProcesses_7_1 extends GDBProcesses_7_0 {
String name = thread.getName();
String core = thread.getCore();
return new MIThreadDMData_7_1(name == null ? "" : name, id, core == null ? null : new String[] { core });
return new MIThreadDMData_7_1(name == null ? "" : name, id, core == null ? null : new String[] { core }); //$NON-NLS-1$
}
@DsfServiceEventHandler

View file

@ -495,6 +495,12 @@ public class GDBProcesses_7_2 extends GDBProcesses_7_1 implements IMultiTerminat
@Override
public void detachDebuggerFromProcess(IDMContext dmc, final RequestMonitor rm) {
MIExitedProcessDMC exitedProc = DMContexts.getAncestorOfType(dmc, MIExitedProcessDMC.class);
if (exitedProc != null) {
super.detachDebuggerFromProcess(dmc, rm);
return;
}
ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class);
final IMIContainerDMContext containerDmc = DMContexts.getAncestorOfType(dmc, IMIContainerDMContext.class);
@ -510,6 +516,9 @@ public class GDBProcesses_7_2 extends GDBProcesses_7_1 implements IMultiTerminat
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
getDetachedProcesses().add(containerDmc.getGroupId());
fCommandControl.queueCommand(
fCommandFactory.createMITargetDetach(controlDmc, containerDmc.getGroupId()),
new DataRequestMonitor<MIInfo>(getExecutor(), rm) {
@ -529,7 +538,14 @@ public class GDBProcesses_7_2 extends GDBProcesses_7_1 implements IMultiTerminat
// Also, with GDB 7.2, removing the inferior does not work because of another bug, so we just don't do it.
fCommandControl.queueCommand(
fCommandFactory.createMITargetDetach(containerDmc),
new DataRequestMonitor<MIInfo>(getExecutor(), rm));
new DataRequestMonitor<MIInfo>(getExecutor(), rm) {
@Override
protected void handleFailure() {
// Detach failed
getDetachedProcesses().remove(containerDmc.getGroupId());
super.handleFailure();
};
});
}
}
});

View file

@ -16,6 +16,8 @@ import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfExecutor;
import org.eclipse.cdt.dsf.concurrent.Sequence;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
import org.eclipse.cdt.dsf.mi.service.command.events.MIThreadGroupExitedEvent;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
/**
@ -35,5 +37,25 @@ public class GDBProcesses_7_3 extends GDBProcesses_7_2_1 {
DataRequestMonitor<IContainerDMContext> rm) {
return new StartOrRestartProcessSequence_7_3(executor, containerDmc, attributes, restart, rm);
}
@Override
@DsfServiceEventHandler
public void eventDispatched(MIThreadGroupExitedEvent e) {
super.eventDispatched(e);
// Cache the exit code if there is one
String groupId = e.getGroupId();
String exitCode = e.getExitCode();
if (groupId != null && exitCode != null) {
ExitedProcInfo info = getExitedProcesses().get(groupId);
if (info != null) {
try {
// Must use 'decode' since GDB returns an octal value
info.setExitCode(Integer.decode(exitCode));
} catch (NumberFormatException exception) {
}
}
}
}
}

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2008, 2011 Ericsson and others.
* Copyright (c) 2008, 2015 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
@ -8,6 +8,7 @@
* Contributors:
* Ericsson - initial API and implementation
* Ericsson - added support for core-awareness
* Marc Khouzam (Ericsson) - Support for exited processes in the debug view (bug 407340)
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.service;
@ -15,6 +16,7 @@ import java.util.Map;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.datamodel.IDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerDMContext;
import org.eclipse.cdt.dsf.mi.service.IMIContainerDMContext;
import org.eclipse.cdt.dsf.mi.service.IMIExecutionDMContext;
@ -29,7 +31,7 @@ public interface IGDBProcesses extends IMIProcesses {
*
* @since 4.0
*/
public interface IGdbThreadDMData extends IThreadDMData {
interface IGdbThreadDMData extends IThreadDMData {
/**
* @return The list of identifiers of the cores on which the thread
* or process is currently located. A thread will typically
@ -47,6 +49,27 @@ public interface IGDBProcesses extends IMIProcesses {
String getOwner();
}
/**
* This interface describes an exited thread/process.
*
* @since 4.7
*/
interface IGdbThreadExitedDMData extends IThreadDMData {
/**
* @return The exit code of this process.
* Returns null if the exit code is not known.
*/
Integer getExitCode();
}
/**
* Indicates that a process or thread is no longer being tracked by
* the session. This event usually refers to exited elements that
* were still being shown to the user but that have now been removed.
* @since 4.7
*/
interface IThreadRemovedDMEvent extends IDMEvent<IThreadDMContext> {}
/**
* Get a list of all execution contexts belonging to a container. This call is synchronous,
* unlike the call to getProcessesBeingDebugged(). However, some services may not be able

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2008, 2014 Ericsson and others.
* Copyright (c) 2008, 2015 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
@ -9,6 +9,7 @@
* Ericsson - Initial API and implementation
* Xavier Raynaud (Kalray) - MIThread can be overridden (Bug 429124)
* Alvaro Sanchez-Leon - Bug 451396 - Improve extensibility to process MI "-thread-info" results
* Marc Khouzam (Ericsson) - Support new "exit-code" MI parameter (Bug 407340)
*******************************************************************************/
package org.eclipse.cdt.dsf.mi.service.command.output;
@ -164,6 +165,14 @@ import org.eclipse.cdt.dsf.concurrent.Immutable;
* -list-thread-groups --available
* ^done,groups=[{id="19445",type="process",description="gdb.7.2 -i mi testing/a.out",user="lmckhou"},{id="19451",type="process",description="/local/lmckhou/testing/a.out",user="lmckhou"},{id="19462",type="process",description="sleep 5",user="lmckhou"}]
*
* GDB 7.9
*
* -list-thread-groups
* ^done,groups=[{id="i1",type="process",exit-code="0",executable="/home/lmckhou/runtime-TestDSF/DSFTestApp/Debug/DSFTestApp"}]
*
* -list-thread-groups
* ^done,groups=[{id="i1",type="process",exit-code="03",executable="/home/lmckhou/runtime-TestDSF/DSFTestApp/Debug/DSFTestApp"}]
*
* @since 1.1
*/
@ -186,6 +195,13 @@ public class MIListThreadGroupsInfo extends MIInfo {
String[] getCores();
/**@since 4.0 */
String getExecutable();
/**
* @return the exit code of this thread group.
* null if not applicable or not available.
* @since 4.7
*/
Integer getExitCode();
}
/**
@ -207,9 +223,16 @@ public class MIListThreadGroupsInfo extends MIInfo {
final String[] fCores;
final String fExecutable;
final MIThread[] fThreadList;
final Integer fExitCode;
public ThreadGroupInfo(String id, String description, String type, String pid,
String user, String[] cores, String exec, MIThread[] threads) {
this(id, description, type, pid, user, cores, exec, threads, null);
}
/** @since 4.7 */
public ThreadGroupInfo(String id, String description, String type, String pid,
String user, String[] cores, String exec, MIThread[] threads, Integer exitCode) {
fGroupId = id;
fDescription = description;
fType = type;
@ -222,6 +245,8 @@ public class MIListThreadGroupsInfo extends MIInfo {
fName = parseName(fDescription);
fThreadList = threads;
fExitCode = exitCode;
}
protected String parseName(String desc) {
@ -287,6 +312,12 @@ public class MIListThreadGroupsInfo extends MIInfo {
@Override
public MIThread[] getThreads() { return fThreadList; }
/** @since 4.7 */
@Override
public Integer getExitCode() {
return fExitCode;
}
}
@ -346,6 +377,7 @@ public class MIListThreadGroupsInfo extends MIInfo {
String id, desc, type, pid, exec, user;
id = desc = type = pid = exec = user = "";//$NON-NLS-1$
MIThread[] threads = null;
Integer exitCode = null;
String[] cores = null;
@ -382,24 +414,35 @@ public class MIListThreadGroupsInfo extends MIInfo {
user = str.trim();
}
} else if (var.equals("cores")) { //$NON-NLS-1$
// Staring with GDB 7.1
// Starting with GDB 7.1
MIValue value = result.getMIValue();
if (value instanceof MIList) {
cores = parseCores((MIList)value);
}
} else if (var.equals("executable")) { //$NON-NLS-1$
// Staring with GDB 7.2
// Starting with GDB 7.2
MIValue value = result.getMIValue();
if (value instanceof MIConst) {
String str = ((MIConst)value).getCString();
exec = str.trim();
}
} else if (var.equals("threads")) { //$NON-NLS-1$
// Staring with GDB 7.1
// Starting with GDB 7.1
MIValue value = result.getMIValue();
if (value instanceof MIList) {
threads = parseThreads(((MIList)value));
}
} else if (var.equals("exit-code")) { //$NON-NLS-1$
// Starting with GDB 7.9
MIValue value = result.getMIValue();
if (value instanceof MIConst) {
String str = ((MIConst)value).getCString();
try {
// Must use 'decode' as GDB returns the value in octal format
exitCode = Integer.decode(str.trim());
} catch (NumberFormatException e) {
}
}
}
}
// In the case of -list-thread-groups --available, the pid field is not present, but the
@ -410,7 +453,7 @@ public class MIListThreadGroupsInfo extends MIInfo {
if (pid.equals("") && !desc.equals("")) { //$NON-NLS-1$ //$NON-NLS-2$
pid = id;
}
fGroupList[i] = new ThreadGroupInfo(id, desc, type, pid, user, cores, exec, threads);
fGroupList[i] = new ThreadGroupInfo(id, desc, type, pid, user, cores, exec, threads, exitCode);
}
}