/*******************************************************************************
* Copyright (c) 2007, 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 Communication - upgrade IF to IMemoryBlockRetrievalExtension
* Ericsson Communication - added Expression evaluation
* Ericsson Communication - added support for 64 bit processors
* Ericsson Communication - added support for event handling
*******************************************************************************/
package org.eclipse.cdt.dsf.debug.model;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfExecutor;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.Query;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IExpressions;
import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionDMContext;
import org.eclipse.cdt.dsf.debug.service.IFormattedValues;
import org.eclipse.cdt.dsf.debug.service.IFormattedValues.FormattedValueDMContext;
import org.eclipse.cdt.dsf.debug.service.IFormattedValues.FormattedValueDMData;
import org.eclipse.cdt.dsf.debug.service.IMemory;
import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryDMContext;
import org.eclipse.cdt.dsf.internal.DsfPlugin;
import org.eclipse.cdt.dsf.service.DsfServices;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.service.IDsfService;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IMemoryBlock;
import org.eclipse.debug.core.model.IMemoryBlockExtension;
import org.eclipse.debug.core.model.IMemoryBlockRetrievalExtension;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.util.tracker.ServiceTracker;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Implementation of memory access API of the Eclipse standard debug model.
*
* The DsfMemoryBlockRetrieval is not an actual memory block but rather a
* reference to the memory address space for an execution context (e.g. a
* process) within a debug session. From this debug 'context', memory blocks
* can then be read/written.
*
* Note: For the reference application, The IMemoryBlockRetrievalExtension
* is implemented. This will result in getExtendedMemoryBlock() being called
* when a memory block is selected from the platform memory pane.
*
* However, if the 'simpler' IMemoryBlockRetrieval is to be implemented, the
* code will still be functional after some trivial adjustments.
*
* @since 1.0
*/
public class DsfMemoryBlockRetrieval extends PlatformObject implements IMemoryBlockRetrievalExtension
{
private final String fModelId;
private final DsfSession fSession;
private final DsfExecutor fExecutor;
private final String fContextString;
private final ServiceTracker fMemoryServiceTracker;
private final ServiceTracker fExpressionServiceTracker;
private final ILaunchConfiguration fLaunchConfig;
private final ILaunch fLaunch;
private final IDebugTarget fDebugTarget;
private final boolean fSupportsValueModification;
private final boolean fSupportBaseAddressModification;
private final int fAddressSize;
private final int fWordSize; // Number of bytes per address
/**
* Constructor
*
* @param modelId
* @param dmc
* @throws DebugException
*/
public DsfMemoryBlockRetrieval(String modelId, ILaunchConfiguration config, DsfSession session) throws DebugException {
// DSF stuff
fModelId = modelId;
// FIXME: (Bug228573) Currently memory contexts are differentiated by
// sessionID so there is no way to guarantee the memory blocks will be
// reinstated in the correct memory space.
// Need a way to create deterministically the context ID from a unique
// target, ideally from the launch configuration (or derived from it).
// For the time being, just put some constant. This will work until we
// support multiple targets in the same launch.
// fContextString = fContext.toString();
fContextString = "Context string"; //$NON-NLS-1$
fSession = session;
if (fSession == null) {
throw new IllegalArgumentException(
"Session " + session + " is not active"); //$NON-NLS-1$ //$NON-NLS-2$
}
fExecutor = fSession.getExecutor();
BundleContext bundle = DsfPlugin.getBundleContext();
// Here we chose to use 2 distinct service trackers instead of an
// amalgamated one because it is less error prone (and we are lazy).
// Create a tracker for the MemoryService
String memoryServiceFilter = DsfServices.createServiceFilter(IMemory.class, session.getId());
try {
fMemoryServiceTracker = new ServiceTracker(
bundle, bundle.createFilter(memoryServiceFilter), null);
} catch (InvalidSyntaxException e) {
throw new DebugException(new Status(IStatus.ERROR,
DsfPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR,
"Error creating service filter.", e)); //$NON-NLS-1$
}
fMemoryServiceTracker.open();
// Create a tracker for the ExpressionService
String expressionServiceFilter = "(&" + //$NON-NLS-1$
"(OBJECTCLASS=" //$NON-NLS-1$
+ IExpressions.class.getName()
+ ")" + //$NON-NLS-1$
"(" + IDsfService.PROP_SESSION_ID //$NON-NLS-1$
+ "=" + session.getId() + ")" + //$NON-NLS-1$//$NON-NLS-2$
")"; //$NON-NLS-1$
try {
fExpressionServiceTracker = new ServiceTracker(
bundle, bundle.createFilter(expressionServiceFilter), null);
} catch (InvalidSyntaxException e) {
throw new DebugException(new Status(IStatus.ERROR,
DsfPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR,
"Error creating service filter.", e)); //$NON-NLS-1$
}
fExpressionServiceTracker.open();
// Launch configuration information
fLaunchConfig = config;
fLaunch = null;
fDebugTarget = null;
fAddressSize = 4; // Get this from the launch configuration
fWordSize = 1; // Get this from the launch configuration
fSupportsValueModification = true; // Get this from the launch configuration
fSupportBaseAddressModification = false; // Get this from the launch configuration
}
///////////////////////////////////////////////////////////////////////////
// Memory monitors persistence
///////////////////////////////////////////////////////////////////////////
/*
* In the launch configuration file, the memory block entry is structured
* as follows (note: this differs from CDI):
*
* <stringAttribute
* key="org.eclipse.dsf.launch.MEMORY_BLOCKS"
* value="<?xml version="1.0" encoding="UTF-8" standalone="no"?>
* <memoryBlockExpressionList context=[memory context ID]>
* <memoryBlockExpression label=[monitor label] address=[base address]/>
* <memoryBlockExpression ...>
* ...
* </memoryBlockExpressionList>
* ...
* <memoryBlockExpressionList context=...>
* ...
* </memoryBlockExpressionList>"
* />
*/
//-------------------------------------------------------------------------
// Memory blocks memento tags
//-------------------------------------------------------------------------
// These 2 really belong in the DSF launch configuration class...
private static final String DSF_LAUNCH_ID = "org.eclipse.dsf.launch"; //$NON-NLS-1$
private static final String ATTR_DEBUGGER_MEMORY_BLOCKS = DSF_LAUNCH_ID + ".MEMORY_BLOCKS"; //$NON-NLS-1$
private static final String MEMORY_BLOCK_EXPRESSION_LIST = "memoryBlockExpressionList"; //$NON-NLS-1$
private static final String ATTR_EXPRESSION_LIST_CONTEXT = "context"; //$NON-NLS-1$
private static final String MEMORY_BLOCK_EXPRESSION = "memoryBlockExpression"; //$NON-NLS-1$
private static final String ATTR_MEMORY_BLOCK_EXPR_LABEL = "label"; //$NON-NLS-1$
private static final String ATTR_MEMORY_BLOCK_EXPR_ADDRESS = "address"; //$NON-NLS-1$
//-------------------------------------------------------------------------
// Install persisted memory monitors
//-------------------------------------------------------------------------
/**
* Restore the memory monitors from the memento in the launch configuration
*/
public void initialize(final IMemoryDMContext memoryCtx) {
try {
final String memento = fLaunchConfig.getAttribute(ATTR_DEBUGGER_MEMORY_BLOCKS, ""); //$NON-NLS-1$
if (memento != null && memento.trim().length() != 0) {
// Submit the runnable to install the monitors on dispatch thread.
getExecutor().submit(new Runnable() {
public void run() {
try {
createBlocksFromConfiguration(memoryCtx, memento);
} catch (CoreException e) {
DsfPlugin.getDefault().getLog().log(e.getStatus());
}
}
});
}
} catch (CoreException e) {
DsfPlugin.getDefault().getLog().log(e.getStatus());
}
}
/**
* Create memory blocks based on the given memento (obtained from the launch
* configuration) and add them to the platform's IMemoryBlockManager. The
* memento was previously created by {@link #getMemento()}
*
* @since 2.1
*/
protected void createBlocksFromConfiguration(IMemoryDMContext memoryCtx, String memento) throws CoreException {
// Parse the memento and validate its type
Element root = DebugPlugin.parseDocument(memento);
if (!root.getNodeName().equalsIgnoreCase(MEMORY_BLOCK_EXPRESSION_LIST)) {
IStatus status = new Status(IStatus.ERROR, DsfPlugin.PLUGIN_ID, DebugPlugin.INTERNAL_ERROR,
"Memory monitor initialization: invalid memento", null);//$NON-NLS-1$
throw new CoreException(status);
}
// Process the block list specific to this memory context
// FIXME: (Bug228573) We only process the first entry...
if (root.getAttribute(ATTR_EXPRESSION_LIST_CONTEXT).equals(fContextString)) {
List<IMemoryBlock> blocks = new ArrayList<IMemoryBlock>();
NodeList expressionList = root.getChildNodes();
int length = expressionList.getLength();
for (int i = 0; i < length; ++i) {
Node node = expressionList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element entry = (Element) node;
if (entry.getNodeName().equalsIgnoreCase(MEMORY_BLOCK_EXPRESSION)) {
String label = entry.getAttribute(ATTR_MEMORY_BLOCK_EXPR_LABEL);
String address = entry.getAttribute(ATTR_MEMORY_BLOCK_EXPR_ADDRESS);
BigInteger blockAddress = new BigInteger(address);
DsfMemoryBlock block = new DsfMemoryBlock(this, memoryCtx, fModelId, label, blockAddress, fWordSize, 0);
blocks.add(block);
}
}
}
DebugPlugin.getDefault().getMemoryBlockManager().addMemoryBlocks( blocks.toArray(new IMemoryBlock[blocks.size()]));
}
}
// FIXME: (Bug228573) Each retrieval overwrites the previous one :-(
// In theory, we should make this a Job since we are writing to the file system.
// However, this would cause the same racing condition as Bug228308. Finally, we
// don't care too much about the UI responsiveness since we are in the process of
// shutting down :-)
public void saveMemoryBlocks() {
try {
ILaunchConfigurationWorkingCopy wc = fLaunchConfig.getWorkingCopy();
wc.setAttribute(ATTR_DEBUGGER_MEMORY_BLOCKS, getMemento());
wc.doSave();
}
catch( CoreException e ) {
DsfPlugin.getDefault().getLog().log(e.getStatus());
}
}
/**
* Create a memento to represent all active blocks created by this retrieval
* object (blocks currently registered with the platform's
* IMemoryBlockManager). We will be expected to recreate the blocks in
* {@link #createBlocksFromConfiguration(IMemoryDMContext, String)}.
*
* @return a string memento
* @throws CoreException
*/
public String getMemento() throws CoreException {
IMemoryBlock[] blocks = DebugPlugin.getDefault().getMemoryBlockManager().getMemoryBlocks(this);
Document document = DebugPlugin.newDocument();
Element expressionList = document.createElement(MEMORY_BLOCK_EXPRESSION_LIST);
expressionList.setAttribute(ATTR_EXPRESSION_LIST_CONTEXT, fContextString);
for (IMemoryBlock block : blocks) {
if (block instanceof IMemoryBlockExtension) {
IMemoryBlockExtension memoryBlock = (IMemoryBlockExtension) block;
Element expression = document.createElement(MEMORY_BLOCK_EXPRESSION);
expression.setAttribute(ATTR_MEMORY_BLOCK_EXPR_LABEL, memoryBlock.getExpression());
expression.setAttribute(ATTR_MEMORY_BLOCK_EXPR_ADDRESS, memoryBlock.getBigBaseAddress().toString());
expressionList.appendChild(expression);
}
}
document.appendChild(expressionList);
return DebugPlugin.serializeDocument(document);
}
///////////////////////////////////////////////////////////////////////////
// Accessors
///////////////////////////////////////////////////////////////////////////
public DsfSession getSession() {
return fSession;
}
public DsfExecutor getExecutor() {
return fExecutor;
}
public ServiceTracker getServiceTracker() {
return fMemoryServiceTracker;
}
///////////////////////////////////////////////////////////////////////////
// Launch/Target specific information
///////////////////////////////////////////////////////////////////////////
public ILaunch getLaunch() {
return fLaunch;
}
public IDebugTarget getDebugTarget() {
return fDebugTarget;
}
public int getAddressSize() {
return fAddressSize;
}
public int getAddressableSize() {
return fWordSize;
}
public boolean supportsValueModification() {
return fSupportsValueModification;
}
public boolean supportBaseAddressModification() {
return fSupportBaseAddressModification;
}
///////////////////////////////////////////////////////////////////////////
// IMemoryBlockRetrieval - obsoleted by IMemoryBlockRetrievalExtension
///////////////////////////////////////////////////////////////////////////
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IMemoryBlockRetrieval#supportsStorageRetrieval()
*/
public boolean supportsStorageRetrieval() {
return true;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IMemoryBlockRetrieval#getMemoryBlock(long, long)
*/
public IMemoryBlock getMemoryBlock(final long startAddress, final long length) throws DebugException {
throw new DebugException(new Status(
IStatus.ERROR, DsfPlugin.PLUGIN_ID, DebugException.NOT_SUPPORTED,
"getMemoryBlock() not supported, use getExtendedMemoryBlock()", null)); //$NON-NLS-1$
}
///////////////////////////////////////////////////////////////////////////
// IMemoryBlockRetrievalExtension
///////////////////////////////////////////////////////////////////////////
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.IMemoryBlockRetrievalExtension#getExtendedMemoryBlock(java.lang.String,
* java.lang.Object)
*/
public IMemoryBlockExtension getExtendedMemoryBlock(String expression, Object context) throws DebugException {
// Drill for the actual DMC
IMemoryDMContext memoryDmc = null;
IDMContext dmc = null;
if (context instanceof IAdaptable) {
dmc = (IDMContext)((IAdaptable)context).getAdapter(IDMContext.class);
if (dmc != null) {
memoryDmc = DMContexts.getAncestorOfType(dmc, IMemoryDMContext.class);
}
}
if (memoryDmc == null) {
return null;
}
// The block start address (supports 64-bit processors)
BigInteger blockAddress;
/*
* See if the expression is a simple numeric value; if it is, we can
* avoid some costly processing (calling the back-end to resolve the
* expression and obtain an address)
*/
try {
// First, assume a decimal address
int base = 10;
int offset = 0;
// Check for "hexadecimality"
if (expression.startsWith("0x") || expression.startsWith("0X")) { //$NON-NLS-1$//$NON-NLS-2$
base = 16;
offset = 2;
}
// Check for "binarity"
else if (expression.startsWith("0b")) { //$NON-NLS-1$
base = 2;
offset = 2;
}
// Check for "octality"
else if (expression.startsWith("0")) { //$NON-NLS-1$
base = 8;
offset = 1;
}
// Now, try to parse the expression. If a NumberFormatException is
// thrown, then it wasn't a simple numerical expression and we go
// to plan B (attempt an expression evaluation)
blockAddress = new BigInteger(expression.substring(offset), base);
} catch (NumberFormatException nfexc) {
// OK, expression is not a simple, absolute numeric value;
// try to resolve as an expression.
// In case of failure, simply return 'null'
// Resolve the expression
blockAddress = resolveMemoryAddress(dmc, expression);
if (blockAddress == null) {
return null;
}
}
/*
* At this point, we only resolved the requested memory block
* start address and we have no idea of the block's length.
*
* The renderer will provide this information when it calls
* getBytesFromAddress() i.e. after the memory block holder has
* been instantiated.
*
* The down side is that every time we switch renderer, for the
* same memory block, a trip to the target could result. However,
* the memory request cache should save the day.
*/
return new DsfMemoryBlock(this, memoryDmc, fModelId, expression, blockAddress, fWordSize, 0);
}
///////////////////////////////////////////////////////////////////////////
// Helper functions
///////////////////////////////////////////////////////////////////////////
/**
* @since 2.1
*/
protected BigInteger resolveMemoryAddress(final IDMContext dmc, final String expression) throws DebugException {
// Use a Query to "synchronize" the downstream calls
Query<BigInteger> query = new Query<BigInteger>() {
@Override
protected void execute(final DataRequestMonitor<BigInteger> drm) {
// Lookup for the ExpressionService
final IExpressions expressionService = (IExpressions) fExpressionServiceTracker.getService();
if (expressionService != null) {
// Create the expression
final IExpressionDMContext expressionDMC = expressionService.createExpression(dmc, expression);
String formatId = IFormattedValues.HEX_FORMAT;
FormattedValueDMContext valueDmc = expressionService.getFormattedValueContext(expressionDMC, formatId);
expressionService.getFormattedExpressionValue(
valueDmc,
new DataRequestMonitor<FormattedValueDMData>(getExecutor(), drm) {
@Override
protected void handleSuccess() {
// Store the result
try {
String value = getData().getFormattedValue().substring(2); // Strip the "0x"
drm.setData(new BigInteger(value, 16));
} catch (IndexOutOfBoundsException e) {
setFormatError(e);
} catch (NumberFormatException e) {
setFormatError(e);
}
drm.done();
}
private void setFormatError(Exception e) {
drm.setStatus(new Status(
IStatus.ERROR, DsfPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR,
"The result of expression evaluation \"" + getData().getFormattedValue() + "\" is not formatted correctly.", e)); //$NON-NLS-1$ //$NON-NLS-2$
}
}
);
}
}
};
fExecutor.execute(query);
try {
// The happy case
return query.get();
} catch (InterruptedException e) {
throw new DebugException(new Status(IStatus.ERROR,
DsfPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR,
"Error evaluating memory address (InterruptedException).", e)); //$NON-NLS-1$
} catch (ExecutionException e) {
throw new DebugException(new Status(IStatus.ERROR,
DsfPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR,
"Error evaluating memory address (ExecutionException).", e)); //$NON-NLS-1$
}
}
/**
* Return the model ID specified at construction
*
* @since 2.1
*/
protected String getModelId() {
return fModelId;
}
}