/******************************************************************************* * Copyright (c) 2006, 2010 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 * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Wind River Systems - initial API and implementation * Ericsson - Modified for handling of multiple execution contexts *******************************************************************************/ package org.eclipse.cdt.dsf.mi.service; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Hashtable; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.eclipse.cdt.core.IAddress; import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor; import org.eclipse.cdt.dsf.concurrent.RequestMonitor; import org.eclipse.cdt.dsf.datamodel.AbstractDMContext; import org.eclipse.cdt.dsf.datamodel.DMContexts; import org.eclipse.cdt.dsf.datamodel.IDMContext; import org.eclipse.cdt.dsf.debug.service.ICachingService; import org.eclipse.cdt.dsf.debug.service.IRunControl; import org.eclipse.cdt.dsf.debug.service.IRunControl.IExecutionDMContext; import org.eclipse.cdt.dsf.debug.service.IRunControl.IResumedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent; import org.eclipse.cdt.dsf.debug.service.IRunControl.StateChangeReason; import org.eclipse.cdt.dsf.debug.service.IStack; 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.ICommand; import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService; import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin; import org.eclipse.cdt.dsf.gdb.service.IGDBTraceControl.ITraceRecordSelectedChangedDMEvent; import org.eclipse.cdt.dsf.mi.service.command.CommandFactory; import org.eclipse.cdt.dsf.mi.service.command.events.IMIDMEvent; import org.eclipse.cdt.dsf.mi.service.command.events.MIStoppedEvent; import org.eclipse.cdt.dsf.mi.service.command.output.MIArg; import org.eclipse.cdt.dsf.mi.service.command.output.MIFrame; import org.eclipse.cdt.dsf.mi.service.command.output.MIStackInfoDepthInfo; import org.eclipse.cdt.dsf.mi.service.command.output.MIStackListArgumentsInfo; import org.eclipse.cdt.dsf.mi.service.command.output.MIStackListFramesInfo; import org.eclipse.cdt.dsf.mi.service.command.output.MIStackListLocalsInfo; 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.Addr32; import org.eclipse.cdt.utils.Addr64; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.osgi.framework.BundleContext; public class MIStack extends AbstractDsfService implements IStack, ICachingService { protected static class MIFrameDMC extends AbstractDMContext implements IFrameDMContext { private final int fLevel; public MIFrameDMC(String sessionId, IExecutionDMContext execDmc, int level) { super(sessionId, new IDMContext[] { execDmc }); fLevel = level; } public int getLevel() { return fLevel; } @Override public boolean equals(Object other) { return super.baseEquals(other) && ((MIFrameDMC)other).fLevel == fLevel; } @Override public int hashCode() { return super.baseHashCode() ^ fLevel; } @Override public String toString() { return baseToString() + ".frame[" + fLevel + "]"; //$NON-NLS-1$ //$NON-NLS-2$ } } protected static class MIVariableDMC extends AbstractDMContext implements IVariableDMContext { public enum Type { ARGUMENT, LOCAL } final private Type fType; final private int fIndex; public MIVariableDMC(MIStack service, IFrameDMContext frame, Type type, int index) { super(service, new IDMContext[] { frame }); fIndex = index; fType = type; } public int getIndex() { return fIndex; } public Type getType() { return fType; } @Override public boolean equals(Object other) { return super.baseEquals(other) && ((MIVariableDMC)other).fType == fType && ((MIVariableDMC)other).fIndex == fIndex; } @Override public int hashCode() { int typeFactor = 0; if (fType == Type.LOCAL) typeFactor = 2; else if (fType == Type.ARGUMENT) typeFactor = 3; return super.baseHashCode() ^ typeFactor ^ fIndex; } @Override public String toString() { return baseToString() + ".variable(" + fType + ")[" + fIndex + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } /** * Class to track stack depth requests for our internal cache */ private class StackDepthInfo { // The maximum depth we requested public int maxDepthRequested; // The actual depth we received public int returnedDepth; StackDepthInfo(int requested, int returned) { maxDepthRequested = requested; returnedDepth = returned; } } /** * A HashMap for our StackDepth cache, that can clear based on a context. */ @SuppressWarnings("serial") private class StackDepthHashMap<V,T> extends HashMap<V,T> { public void clear(IDMContext context) { final IMIExecutionDMContext execDmc = DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class); if (execDmc != null) { remove(execDmc.getThreadId()); } else { clear(); }; } } private CommandCache fMICommandCache; private CommandFactory fCommandFactory; // Two commands such as // -stack-info-depth 11 // -stack-info-depth 2 // would both be sent to GDB because the command cache sees them as different. // This stackDepthCache allows us to know that if we already ask for a stack depth // we can potentially re-use the answer. private StackDepthHashMap<Integer, StackDepthInfo> fStackDepthCache = new StackDepthHashMap<Integer, StackDepthInfo>(); private MIStoppedEvent fCachedStoppedEvent; private IRunControl fRunControl; /** * Indicates that we are currently visualizing trace data. * In this case, some errors should not be reported. */ private boolean fTraceVisualization; public MIStack(DsfSession session) { super(session); } @Override protected BundleContext getBundleContext() { return GdbPlugin.getBundleContext(); } @Override public void initialize(final RequestMonitor rm) { super.initialize( new RequestMonitor(ImmediateExecutor.getInstance(), rm) { @Override protected void handleSuccess() { doInitialize(rm); } }); } private void doInitialize(RequestMonitor rm) { ICommandControlService commandControl = getServicesTracker().getService(ICommandControlService.class); BufferedCommandControl bufferedCommandControl = new BufferedCommandControl(commandControl, getExecutor(), 2); // This cache stores the result of a command when received; also, this cache // is 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 fMICommandCache = new CommandCache(getSession(), bufferedCommandControl); fMICommandCache.setContextAvailable(commandControl.getContext(), true); fRunControl = getServicesTracker().getService(IRunControl.class); fCommandFactory = getServicesTracker().getService(IMICommandControl.class).getCommandFactory(); getSession().addServiceEventListener(this, null); register(new String[]{IStack.class.getName(), MIStack.class.getName()}, new Hashtable<String,String>()); rm.done(); } @Override public void shutdown(RequestMonitor rm) { unregister(); getSession().removeServiceEventListener(this); fMICommandCache.reset(); super.shutdown(rm); } /** * Creates a frame context. This method is intended to be used by other MI * services and sub-classes which need to create a frame context directly. * <p> * Sub-classes can override this method to provide custom stack frame * context implementation. * </p> * @param execDmc Execution context that this frame is to be a child of. * @param level Level of the new context. * @return A new frame context. */ public IFrameDMContext createFrameDMContext(IExecutionDMContext execDmc, int level) { return new MIFrameDMC(getSession().getId(), execDmc, level); } public void getFrames(final IDMContext ctx, final DataRequestMonitor<IFrameDMContext[]> rm) { getFrames(ctx, 0, ALL_FRAMES, rm); } public void getFrames(final IDMContext ctx, final int startIndex, final int endIndex, final DataRequestMonitor<IFrameDMContext[]> rm) { if (startIndex < 0 || endIndex > 0 && endIndex < startIndex) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid stack frame range [" + startIndex + ',' + endIndex + ']', null)); //$NON-NLS-1$ rm.done(); return; } final IMIExecutionDMContext execDmc = DMContexts.getAncestorOfType(ctx, IMIExecutionDMContext.class); if (execDmc == null) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid context " + ctx, null)); //$NON-NLS-1$ rm.done(); return; } // Make sure the thread is stopped but only if we are not visualizing trace data if (!fTraceVisualization && !fRunControl.isSuspended(execDmc)) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Context is running: " + ctx, null)); //$NON-NLS-1$ rm.done(); return; } if (startIndex == 0 && endIndex == 0) { // Try to retrieve the top stack frame from the cached stopped event. if (fCachedStoppedEvent != null && fCachedStoppedEvent.getFrame() != null && execDmc.equals(fCachedStoppedEvent.getDMContext())) { rm.setData(new IFrameDMContext[] { createFrameDMContext(execDmc, fCachedStoppedEvent.getFrame().getLevel()) }); rm.done(); return; } } final ICommand<MIStackListFramesInfo> miStackListCmd; // firstIndex is the first index retrieved final int firstIndex; if (endIndex >= 0) { miStackListCmd = fCommandFactory.createMIStackListFrames(execDmc, startIndex, endIndex); firstIndex = startIndex; } else { miStackListCmd = fCommandFactory.createMIStackListFrames(execDmc); firstIndex = 0; } fMICommandCache.execute( miStackListCmd, new DataRequestMonitor<MIStackListFramesInfo>(getExecutor(), rm) { @Override protected void handleSuccess() { rm.setData(getFrames(execDmc, getData(), firstIndex, endIndex, startIndex)); rm.done(); } }); } public void getTopFrame(final IDMContext ctx, final DataRequestMonitor<IFrameDMContext> rm) { final IMIExecutionDMContext execDmc = DMContexts.getAncestorOfType(ctx, IMIExecutionDMContext.class); if (execDmc == null) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid context" + ctx, null)); //$NON-NLS-1$ rm.done(); return; } // Try to retrieve the top stack frame from the cached stopped event. if (fCachedStoppedEvent != null && fCachedStoppedEvent.getFrame() != null && execDmc.equals(fCachedStoppedEvent.getDMContext())) { rm.setData(createFrameDMContext(execDmc, fCachedStoppedEvent.getFrame().getLevel())); rm.done(); return; } // If stopped event is not available or doesn't contain frame info, // query top stack frame getFrames( ctx, 0, 0, new DataRequestMonitor<IFrameDMContext[]>(getExecutor(), rm) { @Override protected void handleSuccess() { rm.setData(getData()[0]); rm.done(); } }); } private IFrameDMContext[] getFrames(IMIExecutionDMContext execDmc, MIStackListFramesInfo info, int firstIndex, int lastIndex, int startIndex) { int length = info.getMIFrames().length; if (lastIndex > 0) { int limit= lastIndex - startIndex + 1; if (limit < length) { length = limit; } } IFrameDMContext[] frameDMCs = new MIFrameDMC[length]; for (int i = 0; i < length; i++) { //frameDMCs[i] = new MIFrameDMC(this, info.getMIFrames()[i].getLevel()); final MIFrame frame= info.getMIFrames()[i + startIndex - firstIndex]; assert startIndex + i == frame.getLevel(); frameDMCs[i] = createFrameDMContext(execDmc, frame.getLevel()); } return frameDMCs; } public void getFrameData(final IFrameDMContext frameDmc, final DataRequestMonitor<IFrameDMData> rm) { if (!(frameDmc instanceof MIFrameDMC)) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid context type " + frameDmc, null)); //$NON-NLS-1$ rm.done(); return; } final MIFrameDMC miFrameDmc = (MIFrameDMC)frameDmc; final IMIExecutionDMContext execDmc = DMContexts.getAncestorOfType(frameDmc, IMIExecutionDMContext.class); if (execDmc == null) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "No execution context found in " + frameDmc, null)); //$NON-NLS-1$ rm.done(); return; } /** * Base class for the IFrameDMData object that uses an MIFrame object to * provide the data. Sub-classes must provide the MIFrame object */ abstract class FrameData implements IFrameDMData { abstract protected MIFrame getMIFrame(); public IAddress getAddress() { String addr = getMIFrame().getAddress(); if (addr == null || addr.length() == 0) { return new Addr32(0); } if (addr.startsWith("0x")) { //$NON-NLS-1$ addr = addr.substring(2); } if (addr.length() <= 8) { return new Addr32(getMIFrame().getAddress()); } else { return new Addr64(getMIFrame().getAddress()); } } public int getColumn() { return 0; } public String getFile() { return getMIFrame().getFile(); } public int getLine() { return getMIFrame().getLine(); } public String getFunction() { return getMIFrame().getFunction(); } public String getModule() { return ""; }//$NON-NLS-1$ @Override public String toString() { return getMIFrame().toString(); } } // If requested frame is the top stack frame, try to retrieve it from // the stopped event data. class FrameDataFromStoppedEvent extends FrameData { private final MIStoppedEvent fEvent; FrameDataFromStoppedEvent(MIStoppedEvent event) { fEvent = event; } @Override protected MIFrame getMIFrame() { return fEvent.getFrame(); } } // Retrieve the top stack frame from the stopped event only if the selected thread is the one on which stopped event // is raised if (miFrameDmc.fLevel == 0) { if (fCachedStoppedEvent != null && fCachedStoppedEvent.getFrame() != null && (execDmc.equals(fCachedStoppedEvent.getDMContext()) || fTraceVisualization)) { rm.setData(new FrameDataFromStoppedEvent(fCachedStoppedEvent)); rm.done(); return; } } // If not, retrieve the full list of frame data. class FrameDataFromMIStackFrameListInfo extends FrameData { private MIStackListFramesInfo fFrameDataCacheInfo; private int fFrameIndex; FrameDataFromMIStackFrameListInfo(MIStackListFramesInfo info, int index) { fFrameDataCacheInfo = info; fFrameIndex = index; } @Override protected MIFrame getMIFrame() { return fFrameDataCacheInfo.getMIFrames()[fFrameIndex]; } } fMICommandCache.execute( fCommandFactory.createMIStackListFrames(execDmc), new DataRequestMonitor<MIStackListFramesInfo>(getExecutor(), rm) { @Override protected void handleSuccess() { // Find the index to the correct MI frame object. int idx = findFrameIndex(getData().getMIFrames(), miFrameDmc.fLevel); if (idx == -1) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid frame " + frameDmc, null)); //$NON-NLS-1$ rm.done(); return; } // Create the data object. rm.setData(new FrameDataFromMIStackFrameListInfo(getData(), idx)); rm.done(); } @Override protected void handleError() { // We're seeing gdb in some cases fail when it's // being asked for the stack depth or stack // frames, but the same command succeeds if // the request is limited to one frame. So try // again with a limit of 1. It's better to show // just one frame than none at all if (miFrameDmc.fLevel == 0) { fMICommandCache.execute( fCommandFactory.createMIStackListFrames(execDmc, 0, 0), new DataRequestMonitor<MIStackListFramesInfo>(getExecutor(), rm) { @Override protected void handleSuccess() { // Find the index to the correct MI frame object. int idx = findFrameIndex(getData().getMIFrames(), miFrameDmc.fLevel); if (idx == -1) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid frame " + frameDmc, null)); //$NON-NLS-1$ rm.done(); return; } // Create the data object. rm.setData(new FrameDataFromMIStackFrameListInfo(getData(), idx)); rm.done(); } }); } else { super.handleError(); } } }); } public void getArguments(final IFrameDMContext frameDmc, final DataRequestMonitor<IVariableDMContext[]> rm) { final IMIExecutionDMContext execDmc = DMContexts.getAncestorOfType(frameDmc, IMIExecutionDMContext.class); if (execDmc == null) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "No execution context found in " + frameDmc, null)); //$NON-NLS-1$ rm.done(); return; } // If requested frame is the top stack frame, try to retrieve it from // the stopped event data. if (frameDmc.getLevel() == 0 && fCachedStoppedEvent != null && fCachedStoppedEvent.getFrame() != null && execDmc.equals(fCachedStoppedEvent.getDMContext()) && fCachedStoppedEvent.getFrame().getArgs() != null) { rm.setData(makeVariableDMCs( frameDmc, MIVariableDMC.Type.ARGUMENT, fCachedStoppedEvent.getFrame().getArgs())); rm.done(); return; } // If not, retrieve the full list of frame data. Although we only need one frame // for this call, it will be stored the cache and made available for other calls. fMICommandCache.execute( // We don't actually need to ask for the values in this case, but since // we will ask for them right after, it is more efficient to ask for them now // so as to cache the result. If the command fails, then we will ask for // the result without the values // Don't ask for value when we are visualizing trace data, since some // data will not be there, and the command will fail fCommandFactory.createMIStackListArguments(execDmc, true), new DataRequestMonitor<MIStackListArgumentsInfo>(getExecutor(), rm) { @Override protected void handleSuccess() { // Find the index to the correct MI frame object. // Note: this is a short-cut, but it won't work once we implement retrieving // partial lists of stack frames. int idx = frameDmc.getLevel(); if (idx == -1 || idx >= getData().getMIFrames().length) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Invalid frame " + frameDmc, null)); //$NON-NLS-1$ rm.done(); return; } // Create the variable array out of MIArg array. MIArg[] args = getData().getMIFrames()[idx].getArgs(); if (args == null) args = new MIArg[0]; rm.setData(makeVariableDMCs(frameDmc, MIVariableDMC.Type.ARGUMENT, args)); rm.done(); } @Override protected void handleError() { // If the command fails it can be because we asked for values. // This can happen with uninitialized values and pretty printers (bug 307614). // Since asking for values was simply an optimization // to store the command in the cache, let's retry the command without asking for values. fMICommandCache.execute( fCommandFactory.createMIStackListArguments(execDmc, false), new DataRequestMonitor<MIStackListArgumentsInfo>(getExecutor(), rm) { @Override protected void handleSuccess() { // Find the index to the correct MI frame object. // Note: this is a short-cut, but it won't work once we implement retrieving // partial lists of stack frames. int idx = frameDmc.getLevel(); if (idx == -1 || idx >= getData().getMIFrames().length) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_STATE, "Invalid frame " + frameDmc, null)); //$NON-NLS-1$ rm.done(); return; } // Create the variable array out of MIArg array. MIArg[] args = getData().getMIFrames()[idx].getArgs(); if (args == null) args = new MIArg[0]; rm.setData(makeVariableDMCs(frameDmc, MIVariableDMC.Type.ARGUMENT, args)); rm.done(); } }); } }); } public void getVariableData(IVariableDMContext variableDmc, final DataRequestMonitor<IVariableDMData> rm) { if (!(variableDmc instanceof MIVariableDMC)) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid context type " + variableDmc, null)); //$NON-NLS-1$ rm.done(); return; } final MIVariableDMC miVariableDmc = (MIVariableDMC)variableDmc; // Extract the frame DMC from the variable DMC. final MIFrameDMC frameDmc = DMContexts.getAncestorOfType(variableDmc, MIFrameDMC.class); if (frameDmc == null) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "No frame context found in " + variableDmc, null)); //$NON-NLS-1$ rm.done(); return; } final IMIExecutionDMContext execDmc = DMContexts.getAncestorOfType(frameDmc, IMIExecutionDMContext.class); if (execDmc == null) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "No execution context found in " + frameDmc, null)); //$NON-NLS-1$ rm.done(); return; } /** * Same as with frame objects, this is a base class for the IVariableDMData object that uses an MIArg object to * provide the data. Sub-classes must supply the MIArg object. */ class VariableData implements IVariableDMData { private MIArg dsfMIArg; VariableData(MIArg arg){ dsfMIArg = arg; } public String getName() { return dsfMIArg.getName(); } public String getValue() { return dsfMIArg.getValue(); } @Override public String toString() { return dsfMIArg.toString(); } } // Check if the stopped event can be used to extract the variable value. if (execDmc != null && miVariableDmc.fType == MIVariableDMC.Type.ARGUMENT && frameDmc.fLevel == 0 && fCachedStoppedEvent != null && fCachedStoppedEvent.getFrame() != null && execDmc.equals(fCachedStoppedEvent.getDMContext()) && fCachedStoppedEvent.getFrame().getArgs() != null) { if (miVariableDmc.fIndex >= fCachedStoppedEvent.getFrame().getArgs().length) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1, "Invalid variable " + miVariableDmc, null)); //$NON-NLS-1$ rm.done(); return; } rm.setData(new VariableData(fCachedStoppedEvent.getFrame().getArgs()[miVariableDmc.fIndex])); rm.done(); return; } if (miVariableDmc.fType == MIVariableDMC.Type.ARGUMENT){ fMICommandCache.execute( // Don't ask for value when we are visualizing trace data, since some // data will not be there, and the command will fail fCommandFactory.createMIStackListArguments(execDmc, true), new DataRequestMonitor<MIStackListArgumentsInfo>(getExecutor(), rm) { @Override protected void handleSuccess() { // Find the correct frame and argument if ( frameDmc.fLevel >= getData().getMIFrames().length || miVariableDmc.fIndex >= getData().getMIFrames()[frameDmc.fLevel].getArgs().length ) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid variable " + miVariableDmc, null)); //$NON-NLS-1$ rm.done(); return; } // Create the data object. rm.setData(new VariableData(getData().getMIFrames()[frameDmc.fLevel].getArgs()[miVariableDmc.fIndex])); rm.done(); } @Override protected void handleError() { // Unable to get the values. This can happen with uninitialized values and pretty printers (bug 307614) // Let's try to ask for the arguments without their values, which is better than nothing fMICommandCache.execute( fCommandFactory.createMIStackListArguments(execDmc, false), new DataRequestMonitor<MIStackListArgumentsInfo>(getExecutor(), rm) { @Override protected void handleSuccess() { // Find the correct frame and argument if ( frameDmc.fLevel >= getData().getMIFrames().length || miVariableDmc.fIndex >= getData().getMIFrames()[frameDmc.fLevel].getArgs().length ) { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid variable " + miVariableDmc, null)); //$NON-NLS-1$ rm.done(); return; } // Create the data object. rm.setData(new VariableData(getData().getMIFrames()[frameDmc.fLevel].getArgs()[miVariableDmc.fIndex])); rm.done(); } }); } }); }//if if (miVariableDmc.fType == MIVariableDMC.Type.LOCAL){ fMICommandCache.execute( // Don't ask for value when we are visualizing trace data, since some // data will not be there, and the command will fail fCommandFactory.createMIStackListLocals(frameDmc, !fTraceVisualization), new DataRequestMonitor<MIStackListLocalsInfo>(getExecutor(), rm) { @Override protected void handleSuccess() { // Create the data object. MIArg[] locals = getData().getLocals(); if (locals.length > miVariableDmc.fIndex) { rm.setData(new VariableData(locals[miVariableDmc.fIndex])); } else { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid variable " + miVariableDmc, null)); //$NON-NLS-1$ } rm.done(); } @Override protected void handleError() { // Unable to get the value. This can happen with uninitialized values and pretty printers (bug 307614). // Let's try to ask for the variables without their values, which is better than nothing fMICommandCache.execute( fCommandFactory.createMIStackListLocals(frameDmc, false), new DataRequestMonitor<MIStackListLocalsInfo>(getExecutor(), rm) { @Override protected void handleSuccess() { // Create the data object. MIArg[] locals = getData().getLocals(); if (locals.length > miVariableDmc.fIndex) { rm.setData(new VariableData(locals[miVariableDmc.fIndex])); } else { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid variable " + miVariableDmc, null)); //$NON-NLS-1$ } rm.done(); } }); } }); }//if } private MIVariableDMC[] makeVariableDMCs(IFrameDMContext frame, MIVariableDMC.Type type, MIArg[] miArgs) { // Use LinkedHashMap in order to keep the original ordering. // We don't currently support variables with the same name in the same frame, // so we only keep the first one. // Bug 327621 and 328573 Map<String, MIVariableDMC> variableNames = new LinkedHashMap<String, MIVariableDMC>(); for (int i = 0; i < miArgs.length; i++) { String name = miArgs[i].getName(); MIVariableDMC var = variableNames.get(name); if (var == null) { variableNames.put(name, new MIVariableDMC(this, frame, type, i)); } } return variableNames.values().toArray(new MIVariableDMC[0]); } private int findFrameIndex(MIFrame[] frames, int level) { for (int idx = 0; idx < frames.length; idx++) { if (frames[idx].getLevel() == level) { return idx; } } return -1; } public void getLocals(final IFrameDMContext frameDmc, final DataRequestMonitor<IVariableDMContext[]> rm) { final List<IVariableDMContext> localsList = new ArrayList<IVariableDMContext>(); final CountingRequestMonitor countingRm = new CountingRequestMonitor(getExecutor(), rm) { @Override protected void handleSuccess() { rm.setData( localsList.toArray(new IVariableDMContext[localsList.size()]) ); rm.done(); } }; countingRm.setDoneCount(2); getArguments( frameDmc, new DataRequestMonitor<IVariableDMContext[]>(getExecutor(), countingRm) { @Override protected void handleSuccess() { localsList.addAll( Arrays.asList(getData()) ); countingRm.done(); } }); fMICommandCache.execute( // We don't actually need to ask for the values in this case, but since // we will ask for them right after, it is more efficient to ask for them now // so as to cache the result. If the command fails, then we will ask for // the result without the values // Don't ask for value when we are visualizing trace data, since some // data will not be there, and the command will fail fCommandFactory.createMIStackListLocals(frameDmc, !fTraceVisualization), new DataRequestMonitor<MIStackListLocalsInfo>(getExecutor(), countingRm) { @Override protected void handleSuccess() { localsList.addAll( Arrays.asList( makeVariableDMCs(frameDmc, MIVariableDMC.Type.LOCAL, getData().getLocals())) ); countingRm.done(); } @Override protected void handleError() { // If the command fails it can be because we asked for values. // This can happen with uninitialized values and pretty printers (bug 307614). // Since asking for values was simply an optimization // to store the command in the cache, let's retry the command without asking for values. fMICommandCache.execute( fCommandFactory.createMIStackListLocals(frameDmc, false), new DataRequestMonitor<MIStackListLocalsInfo>(getExecutor(), countingRm) { @Override protected void handleSuccess() { localsList.addAll( Arrays.asList( makeVariableDMCs(frameDmc, MIVariableDMC.Type.LOCAL, getData().getLocals())) ); countingRm.done(); } }); } }); } public void getStackDepth(final IDMContext dmc, final int maxDepth, final DataRequestMonitor<Integer> rm) { final IMIExecutionDMContext execDmc = DMContexts.getAncestorOfType(dmc, IMIExecutionDMContext.class); if (execDmc != null) { // Make sure the thread is stopped if (!fTraceVisualization && !fRunControl.isSuspended(execDmc)) { rm.setData(0); rm.done(); return; } // Check our internal cache first because different commands can // still be re-used. StackDepthInfo cachedDepth = fStackDepthCache.get(execDmc.getThreadId()); if (cachedDepth != null) { if (cachedDepth.maxDepthRequested == 0 || (maxDepth != 0 && cachedDepth.maxDepthRequested >= maxDepth)) { rm.setData(cachedDepth.returnedDepth); rm.done(); return; } } ICommand<MIStackInfoDepthInfo> depthCommand = null; if (maxDepth > 0) depthCommand = fCommandFactory.createMIStackInfoDepth(execDmc, maxDepth); else depthCommand = fCommandFactory.createMIStackInfoDepth(execDmc); fMICommandCache.execute( depthCommand, new DataRequestMonitor<MIStackInfoDepthInfo>(getExecutor(), rm) { @Override protected void handleSuccess() { // Store result in our internal cache fStackDepthCache.put(execDmc.getThreadId(), new StackDepthInfo(maxDepth, getData().getDepth())); rm.setData(getData().getDepth()); rm.done(); } @Override protected void handleError() { if (fTraceVisualization) { // when visualizing trace data with GDB 7.2, the command // -stack-info-depth will return an error if we ask for any level // that GDB does not know about. We would have to iteratively // try different depths until we found the deepest that succeeds. // That is too much of a hack, especially since GDB 7.3 answers correctly. // For 7.2, we can safely say we have one stack // frame, which is going to be the case for 95% of the cases. // To have more stack frames, the user would have to have collected // the registers and enough stack memory for GDB to build another frame. rm.setData(1); rm.done(); } else { // We're seeing gdb in some cases fail when it's // being asked for the stack depth or stack // frames, but the same command succeeds if // the request is limited to one frame. So try // again with a limit of 1. It's better to show // just one frame than none at all if (maxDepth != 1) { getStackDepth(dmc, 1, rm); } else { super.handleError(); } } } }); } else { rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid context", null)); //$NON-NLS-1$ rm.done(); } } /** * @nooverride This method is not intended to be re-implemented or extended by clients. * @noreference This method is not intended to be referenced by clients. */ @DsfServiceEventHandler public void eventDispatched(IResumedDMEvent e) { fMICommandCache.setContextAvailable(e.getDMContext(), false); if (e.getReason() != StateChangeReason.STEP) { fCachedStoppedEvent = null; fMICommandCache.reset(); fStackDepthCache.clear(); } } /** * @nooverride This method is not intended to be re-implemented or extended by clients. * @noreference This method is not intended to be referenced by clients. * @since 1.1 */ @DsfServiceEventHandler public void eventDispatched(ISuspendedDMEvent e) { fMICommandCache.setContextAvailable(e.getDMContext(), true); fMICommandCache.reset(); fStackDepthCache.clear(); } /** * @nooverride This method is not intended to be re-implemented or extended by clients. * @noreference This method is not intended to be referenced by clients. * @since 1.1 */ @DsfServiceEventHandler public void eventDispatched(IMIDMEvent e) { if (e.getMIEvent() instanceof MIStoppedEvent) { fCachedStoppedEvent = (MIStoppedEvent)e.getMIEvent(); } } /** @since 3.0 */ @DsfServiceEventHandler public void eventDispatched(ITraceRecordSelectedChangedDMEvent e) { if (e.isVisualizationModeEnabled()) { fTraceVisualization = true; } else { fTraceVisualization = false; fCachedStoppedEvent = null; } } /** * {@inheritDoc} * @since 1.1 */ public void flushCache(IDMContext context) { fMICommandCache.reset(context); fStackDepthCache.clear(context); fCachedStoppedEvent = null; } }