/*******************************************************************************
* Copyright (c) 2010, 2016 Texas Instruments, Freescale Semiconductor 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:
* Texas Instruments, Freescale Semiconductor - initial API and implementation
* Alvaro Sanchez-Leon (Ericsson AB) - Each memory context needs a different MemoryRetrieval (Bug 250323)
* Alvaro Sanchez-Leon (Ericsson AB) - [Memory] Support 16 bit addressable size (Bug 426730)
* Anders Dahlberg (Ericsson) - Need additional API to extend support for memory spaces (Bug 431627)
* Alvaro Sanchez-Leon (Ericsson AB) - Need additional API to extend support for memory spaces (Bug 431627)
*******************************************************************************/
package org.eclipse.cdt.dsf.gdb.internal.memory;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;
import org.eclipse.cdt.debug.core.model.provisional.IMemorySpaceAwareMemoryBlock;
import org.eclipse.cdt.debug.core.model.provisional.IMemorySpaceAwareMemoryBlockRetrieval;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
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.model.DsfMemoryBlock;
import org.eclipse.cdt.dsf.debug.model.DsfMemoryBlockRetrieval;
import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryDMContext;
import org.eclipse.cdt.dsf.debug.service.IMemorySpaces;
import org.eclipse.cdt.dsf.debug.service.IMemorySpaces.IMemorySpaceDMContext;
import org.eclipse.cdt.dsf.debug.service.IMemorySpaces2;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.internal.memory.GdbMemoryBlock.MemorySpaceDMContext;
import org.eclipse.cdt.dsf.gdb.service.IGDBMemory;
import org.eclipse.cdt.dsf.gdb.service.IGDBMemory2;
import org.eclipse.cdt.dsf.service.DsfServices;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.model.IMemoryBlock;
import org.eclipse.debug.core.model.IMemoryBlockExtension;
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;
import com.ibm.icu.text.MessageFormat;
/**
* A specialization of the DSF memory block retrieval implementation supporting
* memory spaces. The memory space support is provisional, thus this class is
* internal.
*
* @author Alain Lee and John Cortell
*/
public class GdbMemoryBlockRetrieval extends DsfMemoryBlockRetrieval implements
IMemorySpaceAwareMemoryBlockRetrieval {
private final ServiceTracker<IMemorySpaces, IMemorySpaces> fMemorySpaceServiceTracker;
// No need to use the constants in our base class. Serializing and
// recreating the blocks is done entirely by us
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 = "gdbmemoryBlockExpression"; //$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$
private static final String ATTR_MEMORY_BLOCK_MEMORY_SPACE_ID = "memorySpaceID"; //$NON-NLS-1$
/** see comment in base class */
private static final String CONTEXT_RESERVED = "reserved-for-future-use"; //$NON-NLS-1$
/**
* Constructor
*/
public GdbMemoryBlockRetrieval(String modelId, ILaunchConfiguration config,
DsfSession session) throws DebugException {
super(modelId, config, session);
BundleContext bundle = GdbPlugin.getBundleContext();
// Create a tracker for the memory spaces service
String filter = DsfServices.createServiceFilter(IMemorySpaces.class, session.getId());
try {
fMemorySpaceServiceTracker = new ServiceTracker<>(
bundle, bundle.createFilter(filter), null);
} catch (InvalidSyntaxException e) {
throw new DebugException(new Status(IStatus.ERROR,
GdbPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR,
"Error creating service filter.", e)); //$NON-NLS-1$
}
fMemorySpaceServiceTracker.open();
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.model.DsfMemoryBlockRetrieval#getExtendedMemoryBlock(java.lang.String, java.lang.Object)
*/
@Override
public IMemoryBlockExtension getExtendedMemoryBlock(String expression, Object context) throws DebugException {
String memorySpaceID = null;
// Determine if the expression has memory space information
IDMContext dmc = null;
if (context instanceof IDMContext) {
dmc = (IDMContext) context;
} else {
if (context instanceof IAdaptable) {
dmc = ((IAdaptable)context).getAdapter(IDMContext.class);
}
}
if (dmc != null) {
DecodeResult result = decodeMemorySpaceExpression(dmc, expression);
expression = result.getExpression();
memorySpaceID = result.getMemorySpaceId();
}
return getMemoryBlock(expression, context, memorySpaceID);
}
/* (non-Javadoc)
* @see org.eclipse.cdt.debug.internal.core.model.provisional.IMemorySpaceAwareMemoryBlockRetrieval#getExtendedMemoryBlock(java.lang.String, java.lang.Object, java.lang.String)
*/
@Override
public IMemorySpaceAwareMemoryBlock getMemoryBlock(String expression, Object context, String memorySpaceID) throws DebugException {
// Drill for the actual DMC
IMemoryDMContext memoryDmc = null;
IDMContext dmc = null;
if (context instanceof IAdaptable) {
dmc = ((IAdaptable)context).getAdapter(IDMContext.class);
if (dmc != null) {
memoryDmc = DMContexts.getAncestorOfType(dmc, IMemoryDMContext.class);
}
}
if (memoryDmc == null) {
return null;
}
//Adjust the memory context to use memory spaces when available
if (memoryDmc instanceof IMemorySpaceDMContext) {
// The memory space ids should match
assert(memorySpaceID != null);
assert(memorySpaceID.equals(((IMemorySpaceDMContext)memoryDmc).getMemorySpaceId()));
} else {
if (memorySpaceID != null && memorySpaceID.length() > 0) {
memoryDmc = new MemorySpaceDMContext(getSession().getId(), memorySpaceID, memoryDmc);
}
}
// 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;
}
}
// check for block address exceeding maximum allowed address value
int addressSize = getAddressSize(memoryDmc);
BigInteger endAddress = BigInteger.ONE.shiftLeft(addressSize*8).subtract(BigInteger.ONE);
if (endAddress.compareTo(blockAddress) < 0) {
throw new DebugException(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, -1,
MessageFormat.format(Messages.Err_ExceedsMaxAddress, expression, endAddress.toString(16)), 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 GdbMemoryBlock(this, memoryDmc, getModelId(), expression, blockAddress, getAddressableSize(memoryDmc), 0, memorySpaceID);
}
/*
* implementation of
* @see org.eclipse.cdt.debug.internal.core.model.provisional.IMemorySpaceManagement#getMemorySpaces(Object context)
*/
@Override
public void getMemorySpaces(final Object context, final GetMemorySpacesRequest request) {
try {
getExecutor().execute(new DsfRunnable() {
@Override
public void run() {
IDMContext dmc = null;
if (context instanceof IAdaptable) {
dmc = ((IAdaptable)context).getAdapter(IDMContext.class);
if (dmc != null) {
IMemorySpaces service = fMemorySpaceServiceTracker.getService();
if (service != null) {
service.getMemorySpaces(
dmc,
new DataRequestMonitor<String[]>(getExecutor(), null) {
@Override
protected void handleCompleted() {
// Store the result
if (isSuccess()) {
request.setMemorySpaces(getData());
}
else {
request.setStatus(getStatus());
}
request.done();
}
});
return;
}
}
}
// If we get here, something didn't work as expected
request.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR, "Unable to get memory spaces", null)); //$NON-NLS-1$
request.done();
}
});
} catch (RejectedExecutionException e) {
request.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR, "Unable to get memory spaces", null)); //$NON-NLS-1$
request.done();
}
}
/* (non-Javadoc)
* @see org.eclipse.cdt.debug.internal.core.model.provisional.IMemorySpaceAwareMemoryBlockRetrieval#encodeAddress(java.lang.String, java.lang.String)
*/
@Override
public String encodeAddress(String expression, String memorySpaceID) {
String result = null;
IMemorySpaces service = fMemorySpaceServiceTracker.getService();
if (service != null) {
// the service can tell us to use our default encoding by returning null
result = service.encodeAddress(expression, memorySpaceID);
}
if (result == null) {
// default encoding
result = memorySpaceID + ':' + expression;
}
return result;
}
/* (non-Javadoc)
* @see org.eclipse.cdt.debug.internal.core.model.provisional.IMemorySpaceAwareMemoryBlockRetrieval#decodeAddress(java.lang.String)
*/
@Override
public DecodeResult decodeAddress(String str) throws CoreException {
IMemorySpaces service = fMemorySpaceServiceTracker.getService();
if (service != null) {
final IMemorySpaces.DecodeResult result = service.decodeAddress(str);
if (result != null) { // service can return null to tell use to use default decoding
return new DecodeResult() {
@Override
public String getMemorySpaceId() { return result.getMemorySpaceId(); }
@Override
public String getExpression() { return result.getExpression(); }
};
}
}
// default decoding
final String memorySpaceID;
final String expression;
int index = str.indexOf(':');
if (index == -1) {
//Unknown parsing, may not use memory spaces
memorySpaceID = null;
expression = str;
} else {
memorySpaceID = str.substring(0, index);
expression = (index < str.length()-1) ? str.substring(index+1) : ""; //$NON-NLS-1$
}
return new DecodeResult() {
@Override
public String getMemorySpaceId() { return memorySpaceID; }
@Override
public String getExpression() { return expression; }
};
}
/**
* Decode the received expression by
* First, decoding the string directly
* Second, if the memory space is not found in the expression string, use the Memory service to use some help from gdb
*/
private DecodeResult decodeMemorySpaceExpression(final IDMContext dmc, final String expression) throws DebugException {
DecodeResult decodeResult;
try {
decodeResult = decodeAddress(expression);
} catch (CoreException e1) {
throw new DebugException(e1.getStatus());
}
if (decodeResult.getMemorySpaceId() != null) {
//memory space found in expression
return decodeResult;
}
//
final IMemorySpaces service = fMemorySpaceServiceTracker.getService();
if (service instanceof IMemorySpaces2) {
final IMemorySpaces2 memSpaceService = (IMemorySpaces2) service;
Query<IMemorySpaces.DecodeResult> query = new Query<IMemorySpaces.DecodeResult>() {
@Override
protected void execute(final DataRequestMonitor<IMemorySpaces.DecodeResult> drm) {
memSpaceService.decodeExpression(dmc, expression, drm);
}
};
getExecutor().execute(query);
try {
final IMemorySpaces.DecodeResult result = query.get();
decodeResult = new DecodeResult() {
@Override
public String getMemorySpaceId() {
return result.getMemorySpaceId();
}
@Override
public String getExpression() {
return result.getExpression();
}
};
} catch (InterruptedException e) {
throw new DebugException(new Status(IStatus.ERROR,
GdbPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR,
"Error evaluating memory space expression (InterruptedException).", e)); //$NON-NLS-1$
} catch (ExecutionException e) {
throw new DebugException(new Status(IStatus.ERROR,
GdbPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR,
"Error evaluating memory space expression (ExecutionException).", e)); //$NON-NLS-1$
}
}
return decodeResult;
}
ServiceTracker<IMemorySpaces, IMemorySpaces> getMemorySpaceServiceTracker() {
return fMemorySpaceServiceTracker;
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.model.DsfMemoryBlockRetrieval#getMemento()
*/
@Override
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, CONTEXT_RESERVED);
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_ADDRESS, memoryBlock.getBigBaseAddress().toString());
if (block instanceof IMemorySpaceAwareMemoryBlock) {
String memorySpaceID = ((IMemorySpaceAwareMemoryBlock)memoryBlock).getMemorySpaceID();
if (memorySpaceID != null) {
expression.setAttribute(ATTR_MEMORY_BLOCK_MEMORY_SPACE_ID, memorySpaceID);
// What we return from GdbMemoryBlock#getExpression()
// is the encoded representation. We need to decode it
// to get the original expression used to create the block
DecodeResult result = ((IMemorySpaceAwareMemoryBlockRetrieval)memoryBlock.getMemoryBlockRetrieval()).decodeAddress(memoryBlock.getExpression());
expression.setAttribute(ATTR_MEMORY_BLOCK_EXPR_LABEL, result.getExpression());
}
else {
expression.setAttribute(ATTR_MEMORY_BLOCK_EXPR_LABEL, memoryBlock.getExpression());
}
}
else {
assert false; // should never happen (see getExtendedMemoryBlock()), but we can handle it.
expression.setAttribute(ATTR_MEMORY_BLOCK_EXPR_LABEL, memoryBlock.getExpression());
}
expressionList.appendChild(expression);
}
}
document.appendChild(expressionList);
return DebugPlugin.serializeDocument(document);
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.model.DsfMemoryBlockRetrieval#createBlocksFromConfiguration(org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryDMContext, java.lang.String)
*/
@Override
protected void createBlocksFromConfiguration(final 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, GdbPlugin.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(CONTEXT_RESERVED)) {
List<IMemoryBlock> blocks = new ArrayList<IMemoryBlock>();
NodeList expressionList = root.getChildNodes();
int length = expressionList.getLength();
for (int i = 0; i < length; ++i) {
IMemoryDMContext memoryContext = memoryCtx;
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);
String memorySpaceID = null;
if (entry.hasAttribute(ATTR_MEMORY_BLOCK_MEMORY_SPACE_ID)) {
memorySpaceID = entry.getAttribute(ATTR_MEMORY_BLOCK_MEMORY_SPACE_ID);
if (memorySpaceID.length() == 0) {
memorySpaceID = null;
assert false : "should have either no memory space or a valid (non-empty) ID"; //$NON-NLS-1$
} else {
if (memoryContext instanceof IMemorySpaceDMContext) {
//The context is already a memory space context, make sure the ids are consistent
assert(((IMemorySpaceDMContext) memoryContext).getMemorySpaceId().equals(memorySpaceID));
} else {
//Use a memory space context if the memory space id is valid
memoryContext = new MemorySpaceDMContext(getSession().getId(), memorySpaceID, memoryContext);
}
}
}
BigInteger blockAddress = new BigInteger(address);
DsfMemoryBlock block = new GdbMemoryBlock(this, memoryContext, getModelId(), label, blockAddress, getAddressableSize(memoryContext), 0, memorySpaceID);
blocks.add(block);
}
}
}
DebugPlugin.getDefault().getMemoryBlockManager().addMemoryBlocks( blocks.toArray(new IMemoryBlock[blocks.size()]));
}
}
/* (non-Javadoc)
* @see org.eclipse.cdt.debug.core.model.provisional.IMemorySpaceAwareMemoryBlockRetrieval#creatingBlockRequiresMemorySpaceID()
*/
@Override
public boolean creatingBlockRequiresMemorySpaceID() {
IMemorySpaces service = fMemorySpaceServiceTracker.getService();
if (service != null) {
return service.creatingBlockRequiresMemorySpaceID();
}
return false;
}
private int getAddressableSize(IMemoryDMContext context) {
IGDBMemory2 memoryService = (IGDBMemory2) getServiceTracker()
.getService();
if (memoryService != null && context != null) {
return memoryService.getAddressableSize(context);
}
return super.getAddressableSize();
}
private int getAddressSize(IMemoryDMContext context) {
IGDBMemory memoryService = (IGDBMemory)getServiceTracker().getService();
if (memoryService != null && context != null) {
return memoryService.getAddressSize(context);
}
return super.getAddressSize();
}
}