blob: a6b615577f3e06876c3bba0dd3c74270b1469de5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2015 Wind River Systems and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Wind River Systems - initial API and implementation
* Ericsson AB - expanded from initial stub
* Ericsson AB - added support for event handling
* Ericsson AB - added memory cache
* Vladimir Prus (CodeSourcery) - support for -data-read-memory-bytes (bug 322658)
* John Dallaway - support for -data-write-memory-bytes (bug 387793)
* John Dallaway - memory cache update fix (bug 387688)
* Alvaro Sanchez-Leon (Ericsson AB) - [Memory] Support 16 bit addressable size (Bug 426730)
* Alvaro Sanchez-Leon (Ericsson AB) - [Memory] Memory space cache not being reset (Bug 432963)
*******************************************************************************/
package org.eclipse.cdt.dsf.mi.service;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
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.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.AbstractDMEvent;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.ICachingService;
import org.eclipse.cdt.dsf.debug.service.IExpressions;
import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionChangedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionDMAddress;
import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionDMContext;
import org.eclipse.cdt.dsf.debug.service.IMemory;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerResumedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerSuspendedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.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.command.BufferedCommandControl;
import org.eclipse.cdt.dsf.debug.service.command.CommandCache;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl;
import org.eclipse.cdt.dsf.mi.service.MIExpressions.ExpressionChangedEvent;
import org.eclipse.cdt.dsf.mi.service.command.CommandFactory;
import org.eclipse.cdt.dsf.mi.service.command.output.MIDataReadMemoryBytesInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIDataReadMemoryInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIDataWriteMemoryInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIInfo;
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.Addr64;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.model.MemoryByte;
import org.osgi.framework.BundleContext;
/**
* Memory service implementation
*/
public class MIMemory extends AbstractDsfService implements IMemory, ICachingService {
private static final String READ_MEMORY_BYTES_FEATURE = "data-read-memory-bytes"; //$NON-NLS-1$
//data-read-memory write is deprecated, its description could be ambiguous for e.g. 16 bit addressable systems
private static final String DATA_WRITE_MEMORY_16_NOT_SUPPORTED = "data-write-memory with word-size != 1 not supported"; //$NON-NLS-1$
public class MemoryChangedEvent extends AbstractDMEvent<IMemoryDMContext> implements IMemoryChangedEvent {
private IAddress[] fAddresses;
public MemoryChangedEvent(IMemoryDMContext context, IAddress[] addresses) {
super(context);
fAddresses = addresses;
}
@Override
public IAddress[] getAddresses() {
return fAddresses;
}
}
// Back-end commands cache
private CommandCache fCommandCache;
private CommandFactory fCommandFactory;
// Map of memory caches
private Map<IMemoryDMContext, MIMemoryCache> fMemoryCaches;
/** @since 4.2 */
protected MIMemoryCache getMemoryCache(IMemoryDMContext memoryDMC) {
MIMemoryCache cache = fMemoryCaches.get(memoryDMC);
if (cache == null) {
cache = new MIMemoryCache();
fMemoryCaches.put(memoryDMC, cache);
}
return cache;
}
// Whether the -data-read-memory-bytes should be used
// instead of -data-read-memory
private boolean fDataReadMemoryBytes;
/**
* Constructor
*/
public MIMemory(DsfSession session) {
super(session);
}
///////////////////////////////////////////////////////////////////////////
// AbstractDsfService overrides
///////////////////////////////////////////////////////////////////////////
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.service.AbstractDsfService#initialize(org.eclipse.cdt.dsf.concurrent.RequestMonitor)
*/
@Override
public void initialize(final RequestMonitor requestMonitor) {
super.initialize(new ImmediateRequestMonitor(requestMonitor) {
@Override
protected void handleSuccess() {
doInitialize(requestMonitor);
}
});
}
private void doInitialize(final RequestMonitor requestMonitor) {
IGDBControl commandControl = getServicesTracker().getService(IGDBControl.class);
BufferedCommandControl bufferedCommandControl = new BufferedCommandControl(commandControl, getExecutor(), 2);
fDataReadMemoryBytes = commandControl.getFeatures().contains(READ_MEMORY_BYTES_FEATURE);
fCommandFactory = getServicesTracker().getService(IMICommandControl.class).getCommandFactory();
// 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
fCommandCache = new CommandCache(getSession(), bufferedCommandControl);
fCommandCache.setContextAvailable(commandControl.getContext(), true);
register(new String[] { MIMemory.class.getName(), IMemory.class.getName() }, new Hashtable<String, String>());
fMemoryCaches = new HashMap<>();
getSession().addServiceEventListener(this, null);
requestMonitor.done();
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.service.AbstractDsfService#shutdown(org.eclipse.cdt.dsf.concurrent.RequestMonitor)
*/
@Override
public void shutdown(final RequestMonitor requestMonitor) {
unregister();
getSession().removeServiceEventListener(this);
super.shutdown(requestMonitor);
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.service.AbstractDsfService#getBundleContext()
*/
@Override
protected BundleContext getBundleContext() {
return GdbPlugin.getBundleContext();
}
///////////////////////////////////////////////////////////////////////////
// IMemory
///////////////////////////////////////////////////////////////////////////
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.service.IMemory#getMemory(org.eclipse.cdt.dsf.datamodel.IDMContext, org.eclipse.cdt.core.IAddress, long, int, org.eclipse.cdt.dsf.concurrent.DataRequestMonitor)
*/
@Override
public void getMemory(IMemoryDMContext memoryDMC, IAddress address, long offset, int wordSize, int wordCount,
DataRequestMonitor<MemoryByte[]> drm) {
if (memoryDMC == null) {
drm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Unknown context type", null)); //$NON-NLS-1$);
drm.done();
return;
}
if (wordSize < 1) {
drm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Word size not supported (< 1)", //$NON-NLS-1$
null));
drm.done();
return;
}
if (wordCount < 0) {
drm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR,
"Invalid word count (< 0)", null)); //$NON-NLS-1$
drm.done();
return;
}
getMemoryCache(memoryDMC).getMemory(memoryDMC, address.add(offset), wordSize, wordCount, drm);
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.service.IMemory#setMemory(org.eclipse.cdt.dsf.datamodel.IDMContext, org.eclipse.cdt.core.IAddress, long, int, byte[], org.eclipse.cdt.dsf.concurrent.RequestMonitor)
*/
@Override
public void setMemory(IMemoryDMContext memoryDMC, IAddress address, long offset, int wordSize, int wordCount,
byte[] buffer, RequestMonitor rm) {
if (memoryDMC == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Unknown context type", null)); //$NON-NLS-1$);
rm.done();
return;
}
if (wordSize < 1) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Word size not supported (< 1)", //$NON-NLS-1$
null));
rm.done();
return;
}
if (wordCount < 0) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR,
"Invalid word count (< 0)", null)); //$NON-NLS-1$
rm.done();
return;
}
if (buffer.length < wordCount * wordSize) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR,
"Buffer too short", null)); //$NON-NLS-1$
rm.done();
return;
}
getMemoryCache(memoryDMC).setMemory(memoryDMC, address, offset, wordSize, wordCount, buffer, rm);
}
/* (non-Javadoc)
* @see org.eclipse.cdt.dsf.debug.service.IMemory#fillMemory(org.eclipse.cdt.dsf.datamodel.IDMContext, org.eclipse.cdt.core.IAddress, long, int, byte[], org.eclipse.cdt.dsf.concurrent.RequestMonitor)
*/
@Override
public void fillMemory(IMemoryDMContext memoryDMC, IAddress address, long offset, int wordSize, int count,
byte[] pattern, RequestMonitor rm) {
if (memoryDMC == null) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INTERNAL_ERROR, "Unknown context type", null)); //$NON-NLS-1$);
rm.done();
return;
}
if (wordSize < 1) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, NOT_SUPPORTED, "Word size not supported (< 1)", //$NON-NLS-1$
null));
rm.done();
return;
}
if (count < 0) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR,
"Invalid repeat count (< 0)", null)); //$NON-NLS-1$
rm.done();
return;
}
if (pattern.length < 1) {
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR,
"Empty pattern", null)); //$NON-NLS-1$
rm.done();
return;
}
// Create an aggregate buffer so we can write in 1 shot
int length = pattern.length;
byte[] buffer = new byte[count * length];
for (int i = 0; i < count; i++) {
System.arraycopy(pattern, 0, buffer, i * length, length);
}
int word_count = buffer.length / wordSize;
if (buffer.length % wordSize != 0) {
word_count++;
}
// All is clear: go for it
getMemoryCache(memoryDMC).setMemory(memoryDMC, address, offset, wordSize, word_count, buffer, rm);
}
///////////////////////////////////////////////////////////////////////
// Back-end functions
///////////////////////////////////////////////////////////////////////
/**
* @param memoryDMC
* @param address
* @param offset
* @param wordSize
* @param wordCount in addressable units
* @param drm
*
* @since 1.1
*/
protected void readMemoryBlock(IDMContext dmc, IAddress address, final long offset, final int wordSize,
final int wordCount, final DataRequestMonitor<MemoryByte[]> drm) {
if (fDataReadMemoryBytes) {
fCommandCache.execute(
fCommandFactory.createMIDataReadMemoryBytes(dmc, address.toString(), offset, wordCount, wordSize),
new DataRequestMonitor<MIDataReadMemoryBytesInfo>(getExecutor(), drm) {
@Override
protected void handleSuccess() {
// Retrieve the memory block
drm.setData(getData().getMIMemoryBlock());
drm.done();
}
@Override
protected void handleFailure() {
drm.setData(createInvalidBlock(wordSize * wordCount));
drm.done();
}
});
} else {
if (wordSize != 1) {
//The word-size is specified within the resulting command data-read-memory
//The word-size is defined in bytes although in the MI interface it's not clear if the meaning is
//octets or system dependent bytes (minimum addressable memory).
//As this command is deprecated there is no good reason to augment the support for word sizes != 1
drm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED,
DATA_WRITE_MEMORY_16_NOT_SUPPORTED, null));
drm.done();
return;
}
/* To simplify the parsing of the MI result, we request the output to
* be on 1 row of [count] columns, no char interpretation.
*/
int mode = MIFormat.HEXADECIMAL;
int nbRows = 1;
int nbCols = wordCount;
Character asChar = null;
fCommandCache
.execute(
fCommandFactory.createMIDataReadMemory(dmc, offset, address.toString(), mode, wordSize,
nbRows, nbCols, asChar),
new DataRequestMonitor<MIDataReadMemoryInfo>(getExecutor(), drm) {
@Override
protected void handleSuccess() {
// Retrieve the memory block
drm.setData(getData().getMIMemoryBlock());
drm.done();
}
@Override
protected void handleFailure() {
drm.setData(createInvalidBlock(wordSize * wordCount));
drm.done();
}
});
}
}
private MemoryByte[] createInvalidBlock(int size) {
// Bug234289: If memory read fails, return a block marked as invalid
MemoryByte[] block = new MemoryByte[size];
for (int i = 0; i < block.length; i++) {
block[i] = new MemoryByte((byte) 0, (byte) 0);
}
return block;
}
/**
* @param memoryDMC
* @param address
* @param offset
* @param wordSize
* @param wordCount in addressable units
* @param buffer
* @param rm
*
* @since 1.1
*/
protected void writeMemoryBlock(final IDMContext dmc, final IAddress address, final long offset, final int wordSize,
final int wordCount, final byte[] buffer, final RequestMonitor rm) {
if (fDataReadMemoryBytes) {
// Use -data-write-memory-bytes for performance,
fCommandCache.execute(
fCommandFactory.createMIDataWriteMemoryBytes(dmc, address.add(offset).toString(),
(buffer.length == wordCount * wordSize) ? buffer
: Arrays.copyOf(buffer, wordCount * wordSize)),
new DataRequestMonitor<MIInfo>(getExecutor(), rm));
} else {
if (wordSize != 1) {
//The word-size is specified within the resulting command data-write-memory
//The word-size is defined in bytes although in the MI interface it's not clear if the meaning is
//octets or system dependent bytes (minimum addressable memory).
//As this command is deprecated there is no good reason to augment the support for word sizes != 1
rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, REQUEST_FAILED,
DATA_WRITE_MEMORY_16_NOT_SUPPORTED, null));
rm.done();
return;
}
// Each byte is written individually (GDB power...)
// so we need to keep track of the count
final CountingRequestMonitor countingRM = new CountingRequestMonitor(getExecutor(), rm);
countingRM.setDoneCount(wordCount);
// We will format the individual bytes in decimal
int format = MIFormat.DECIMAL;
String baseAddress = address.toString();
// Issue an MI request for each byte to write
for (int i = 0; i < wordCount; i++) {
String value = Byte.toString(buffer[i]);
fCommandCache.execute(
fCommandFactory.createMIDataWriteMemory(dmc, offset + i, baseAddress, format, wordSize, value),
new DataRequestMonitor<MIDataWriteMemoryInfo>(getExecutor(), countingRM));
}
}
}
//////////////////////////////////////////////////////////////////////////
// Event handlers
//////////////////////////////////////////////////////////////////////////
@DsfServiceEventHandler
public void eventDispatched(IResumedDMEvent e) {
if (e instanceof IContainerResumedDMEvent) {
fCommandCache.setContextAvailable(e.getDMContext(), false);
}
if (e.getReason() != StateChangeReason.STEP) {
IMemoryDMContext memoryDMC = DMContexts.getAncestorOfType(e.getDMContext(), IMemoryDMContext.class);
// It is the memory context we want to clear, not only the context that resumed. The resumed context
// is probably a thread but that running thread could have changed any memory within the memory
// context.
if (memoryDMC != null) {
fCommandCache.reset(memoryDMC);
memoryCacheReset(memoryDMC);
}
}
}
@DsfServiceEventHandler
public void eventDispatched(ISuspendedDMEvent e) {
if (e instanceof IContainerSuspendedDMEvent) {
fCommandCache.setContextAvailable(e.getDMContext(), true);
}
IMemoryDMContext memoryDMC = DMContexts.getAncestorOfType(e.getDMContext(), IMemoryDMContext.class);
// It is the memory context we want to clear, not only the context that stopped. The stopped context
// is probably a thread but that thread that ran could have changed any memory within the memory
// context.
if (memoryDMC != null) {
fCommandCache.reset(memoryDMC);
memoryCacheReset(memoryDMC);
}
}
/**
* @deprecated Replaced by the generic {@link #eventDispatched(IExpressionChangedDMEvent)}
*/
@Deprecated
public void eventDispatched(ExpressionChangedEvent e) {
}
/**
* @noreference This method is not intended to be referenced by clients.
* @since 4.2
*/
@DsfServiceEventHandler
public void eventDispatched(IExpressionChangedDMEvent e) {
// Get the context and expression service handle
final IExpressionDMContext context = e.getDMContext();
IExpressions expressionService = getServicesTracker().getService(IExpressions.class);
// Get the variable information and update the corresponding memory locations
if (expressionService != null) {
expressionService.getExpressionAddressData(context,
new DataRequestMonitor<IExpressionDMAddress>(getExecutor(), null) {
@Override
protected void handleSuccess() {
// Figure out which memory area was modified
IExpressionDMAddress expression = getData();
IAddress expAddress = expression.getAddress();
if (expAddress != IExpressions.IExpressionDMLocation.INVALID_ADDRESS) {
final int count = expression.getSize();
final Addr64 address;
if (expAddress instanceof Addr64) {
address = (Addr64) expAddress;
} else {
address = new Addr64(expAddress.getValue());
}
final IMemoryDMContext memoryDMC = DMContexts.getAncestorOfType(context,
IMemoryDMContext.class);
getMemoryCache(memoryDMC).refreshMemory(memoryDMC, address, 0,
getAddressableSize(memoryDMC), count, true,
new RequestMonitor(getExecutor(), null));
}
}
});
}
}
/**
* The default addressable size is set to 1 octet, to be overridden by sub-classes supporting different values
* @since 4.4
*/
protected int getAddressableSize(IMemoryDMContext context) {
return 1;
}
///////////////////////////////////////////////////////////////////////////
// SortedLinkedlist
///////////////////////////////////////////////////////////////////////////
// This class is really the equivalent of a C struct (old habits die hard...)
// For simplicity, everything is public.
private static class MemoryBlock {
public IAddress fAddress;
public long fLengthInAddressableUnits;
public long fLengthInOctets;
public MemoryByte[] fBlock;
public MemoryBlock(IAddress address, long lengthInOctets, long lengthInAddressableUnits, MemoryByte[] block) {
// A memory block is expected to be populated with the contents of a defined range of addresses
// therefore the number of octets shall be divisible by the number of addresses
assert (lengthInOctets % lengthInAddressableUnits == 0);
fAddress = address;
fLengthInAddressableUnits = lengthInAddressableUnits;
fLengthInOctets = lengthInOctets;
fBlock = block;
}
}
// Address-ordered data structure to cache the memory blocks.
// Contiguous blocks are merged if possible.
@SuppressWarnings("serial")
private static class SortedMemoryBlockList extends LinkedList<MemoryBlock> {
public SortedMemoryBlockList() {
super();
}
// Insert the block in the sorted linked list and merge contiguous
// blocks if necessary
@Override
public boolean add(MemoryBlock block) {
// If the list is empty, just store the block
if (isEmpty()) {
addFirst(block);
return true;
}
// Insert the block at the correct location and then
// merge the blocks if possible
ListIterator<MemoryBlock> it = listIterator();
while (it.hasNext()) {
int index = it.nextIndex();
MemoryBlock item = it.next();
if (block.fAddress.compareTo(item.fAddress) < 0) {
add(index, block);
compact(index);
return true;
}
}
// Put at the end of the list and merge if necessary
addLast(block);
compact(size() - 1);
return true;
}
// Merge this block with its contiguous neighbors (if any)
// Note: Merge is not performed if resulting block size would exceed MAXINT
private void compact(int index) {
MemoryBlock newBlock = get(index);
// Case where the block is to be merged with the previous block
if (index > 0) {
MemoryBlock prevBlock = get(index - 1);
IAddress endOfPreviousBlock = prevBlock.fAddress.add(prevBlock.fLengthInAddressableUnits);
if (endOfPreviousBlock.distanceTo(newBlock.fAddress).longValue() == 0) {
long newLengthInOctets = prevBlock.fLengthInOctets + newBlock.fLengthInOctets;
long newLengthInAddressableUnits = prevBlock.fLengthInAddressableUnits
+ newBlock.fLengthInAddressableUnits;
if (newLengthInOctets <= Integer.MAX_VALUE) {
MemoryByte[] block = new MemoryByte[(int) newLengthInOctets];
System.arraycopy(prevBlock.fBlock, 0, block, 0, (int) prevBlock.fLengthInOctets);
System.arraycopy(newBlock.fBlock, 0, block, (int) prevBlock.fLengthInOctets,
(int) newBlock.fLengthInOctets);
newBlock = new MemoryBlock(prevBlock.fAddress, newLengthInOctets, newLengthInAddressableUnits,
block);
remove(index);
index -= 1;
set(index, newBlock);
}
}
}
// Case where the block is to be merged with the following block
int lastIndex = size() - 1;
if (index < lastIndex) {
MemoryBlock nextBlock = get(index + 1);
IAddress endOfNewBlock = newBlock.fAddress.add(newBlock.fLengthInAddressableUnits);
if (endOfNewBlock.distanceTo(nextBlock.fAddress).longValue() == 0) {
long newLength = newBlock.fLengthInOctets + nextBlock.fLengthInOctets;
long newAddressesLength = newBlock.fLengthInAddressableUnits + nextBlock.fLengthInAddressableUnits;
if (newLength <= Integer.MAX_VALUE) {
MemoryByte[] block = new MemoryByte[(int) newLength];
System.arraycopy(newBlock.fBlock, 0, block, 0, (int) newBlock.fLengthInOctets);
System.arraycopy(nextBlock.fBlock, 0, block, (int) newBlock.fLengthInOctets,
(int) nextBlock.fLengthInOctets);
newBlock = new MemoryBlock(newBlock.fAddress, newLength, newAddressesLength, block);
set(index, newBlock);
remove(index + 1);
}
}
}
}
}
///////////////////////////////////////////////////////////////////////////
// MIMemoryCache
///////////////////////////////////////////////////////////////////////////
/** @since 4.2 */
protected class MIMemoryCache {
// The memory cache data structure
private SortedMemoryBlockList fMemoryBlockList;
public MIMemoryCache() {
// Create the memory block cache
fMemoryBlockList = new SortedMemoryBlockList();
}
public void reset() {
// Clear the memory cache
fMemoryBlockList.clear();
}
/**
* This function walks the address-sorted memory block list to identify
* the 'missing' blocks (i.e. the holes) that need to be fetched on the target.
*
* The idea is fairly simple but an illustration could perhaps help.
* Assume the cache holds a number of cached memory blocks with gaps i.e.
* there is un-cached memory areas between blocks A, B and C:
*
* +---------+ +---------+ +---------+
* + A + + B + + C +
* +---------+ +---------+ +---------+
* : : : : : :
* [a] : : [b] : : [c] : : [d]
* : : : : : :
* [e---+--] : [f--+---------+--] : :
* [g---+---------+------+---------+------+---------+----]
* : : : : : :
* : [h] : : [i----+--] : :
*
*
* We have the following cases to consider.The requested block [a-i] either:
*
* [1] Fits entirely before A, in one of the gaps, or after C
* with no overlap and no contiguousness (e.g. [a], [b], [c] and [d])
* -> Add the requested block to the list of blocks to fetch
*
* [2] Starts before an existing block but overlaps part of it, possibly
* spilling in the gap following the cached block (e.g. [e], [f] and [g])
* -> Determine the length of the missing part (< count)
* -> Add a request to fill the gap before the existing block
* -> Update the requested block for the next iteration:
* - Start address to point just after the end of the cached block
* - Count reduced by cached block length (possibly becoming negative, e.g. [e])
* At this point, the updated requested block starts just beyond the cached block
* for the next iteration.
*
* [3] Starts at or into an existing block and overlaps part of it ([h] and [i])
* -> Update the requested block for the next iteration:
* - Start address to point just after the end of the cached block
* - Count reduced by length to end of cached block (possibly becoming negative, e.g. [h])
* At this point, the updated requested block starts just beyond the cached block
* for the next iteration.
*
* We iterate over the cached blocks list until there is no entry left or until
* the remaining requested block count is <= 0, meaning the result list contains
* only the sub-blocks needed to fill the gap(s), if any.
*
* (As is often the case, it takes much more typing to explain it than to just do it :-)
*
* What is missing is a parameter that indicates the minimal block size that is worth fetching.
* This is target-specific and straight in the realm of the coalescing function...
*
* @param reqBlockStart The address of the requested block
* @param count Its length
* @return A list of the sub-blocks to fetch in order to fill enough gaps in the memory cache
* to service the request
*/
private List<MemoryBlock> getListOfMissingBlocks(IAddress reqBlockStart, int wordCount, int wordSize) {
int octetCount = wordCount * wordSize;
LinkedList<MemoryBlock> list = new LinkedList<>();
ListIterator<MemoryBlock> it = fMemoryBlockList.listIterator();
// Look for holes in the list of memory blocks
while (it.hasNext() && octetCount > 0) {
MemoryBlock cachedBlock = it.next();
IAddress cachedBlockStart = cachedBlock.fAddress;
IAddress cachedBlockEnd = cachedBlock.fAddress.add(cachedBlock.fLengthInAddressableUnits);
// Case where we miss a block before the cached block
if (reqBlockStart.distanceTo(cachedBlockStart).longValue() >= 0) {
int lengthInOctets = (int) Math
.min(reqBlockStart.distanceTo(cachedBlockStart).longValue() * wordSize, octetCount);
// If both blocks start at the same location, no need to create a new cached block
if (lengthInOctets > 0) {
int lengthInAddressableUnits = lengthInOctets / wordSize;
MemoryBlock newBlock = new MemoryBlock(reqBlockStart, lengthInOctets, lengthInAddressableUnits,
new MemoryByte[0]);
list.add(newBlock);
}
// Adjust request block start and length for the next iteration
reqBlockStart = cachedBlockEnd;
octetCount -= lengthInOctets + cachedBlock.fLengthInOctets;
}
// Case where the requested block starts somewhere in the cached block
else if (cachedBlockStart.distanceTo(reqBlockStart).longValue() > 0
&& reqBlockStart.distanceTo(cachedBlockEnd).longValue() >= 0) {
// Start of the requested block already in cache
// Adjust request block start and length for the next iteration
octetCount -= reqBlockStart.distanceTo(cachedBlockEnd).longValue() * wordSize;
reqBlockStart = cachedBlockEnd;
}
}
// Case where we miss a block at the end of the cache
if (octetCount > 0) {
int addressesLength = octetCount / wordSize;
MemoryBlock newBlock = new MemoryBlock(reqBlockStart, octetCount, addressesLength, new MemoryByte[0]);
list.add(newBlock);
}
return list;
}
/**
* This function walks the address-sorted memory block list to get the
* cached memory bytes (possibly from multiple contiguous blocks).
* This function is called *after* the missing blocks have been read from
* the back end i.e. the requested memory is all cached.
*
* Again, this is fairly simple. As we loop over the address-ordered list,
* There are really only 2 cases:
*
* [1] The requested block fits entirely in the cached block ([a] or [b])
* [2] The requested block starts in a cached block and ends in the
* following (contiguous) one ([c]) in which case it is treated
* as 2 contiguous requests ([c'] and [c"])
*
* +--------------+--------------+
* + A + B +
* +--------------+--------------+
* : [a----] : [b-----] :
* : : :
* : [c-----+------] :
* : [c'---]+[c"---] :
*
* @param reqBlockStart The address of the requested block
* @param count Its length
* @return The cached memory content
*/
private MemoryByte[] getMemoryBlockFromCache(IAddress reqBlockStart, int wordCount, int wordSize) {
int count = wordCount * wordSize;
IAddress reqBlockEnd = reqBlockStart.add(wordCount);
MemoryByte[] resultBlock = new MemoryByte[count];
ListIterator<MemoryBlock> iter = fMemoryBlockList.listIterator();
while (iter.hasNext()) {
MemoryBlock cachedBlock = iter.next();
IAddress cachedBlockStart = cachedBlock.fAddress;
IAddress cachedBlockEnd = cachedBlock.fAddress.add(cachedBlock.fLengthInAddressableUnits);
// Case where the cached block overlaps completely the requested memory block
if (cachedBlockStart.distanceTo(reqBlockStart).longValue() >= 0
&& reqBlockEnd.distanceTo(cachedBlockEnd).longValue() >= 0) {
int pos = (int) cachedBlockStart.distanceTo(reqBlockStart).longValue() * wordSize;
System.arraycopy(cachedBlock.fBlock, pos, resultBlock, 0, count);
}
// Case where the beginning of the cached block is within the requested memory block
else if (reqBlockStart.distanceTo(cachedBlockStart).longValue() >= 0
&& cachedBlockStart.distanceTo(reqBlockEnd).longValue() > 0) {
int pos = (int) reqBlockStart.distanceTo(cachedBlockStart).longValue() * wordSize;
int length = (int) Math.min(cachedBlock.fLengthInOctets, count - pos);
System.arraycopy(cachedBlock.fBlock, 0, resultBlock, pos, length);
}
// Case where the end of the cached block is within the requested memory block
else if (cachedBlockStart.distanceTo(reqBlockStart).longValue() >= 0
&& reqBlockStart.distanceTo(cachedBlockEnd).longValue() > 0) {
int pos = (int) cachedBlockStart.distanceTo(reqBlockStart).longValue() * wordSize;
int length = (int) Math.min(cachedBlock.fLengthInOctets - pos, count);
System.arraycopy(cachedBlock.fBlock, pos, resultBlock, 0, length);
}
}
return resultBlock;
}
/**
* This function walks the address-sorted memory block list and updates
* the content with the actual memory just read from the target.
*
* @param modBlockStart
* @param wordCount - Number of addressable units
* @param modBlock
* @param wordSize - Number of octets per addressable unit
*/
private void updateMemoryCache(IAddress modBlockStart, int wordCount, MemoryByte[] modBlock, int wordSize) {
IAddress modBlockEnd = modBlockStart.add(wordCount);
ListIterator<MemoryBlock> iter = fMemoryBlockList.listIterator();
int count = wordCount * wordSize;
while (iter.hasNext()) {
MemoryBlock cachedBlock = iter.next();
IAddress cachedBlockStart = cachedBlock.fAddress;
IAddress cachedBlockEnd = cachedBlock.fAddress.add(cachedBlock.fLengthInAddressableUnits);
// For now, we only bother to update bytes already cached.
// Note: In a better implementation (v1.1), we would augment
// the cache with the missing memory blocks since we went
// through the pains of reading them in the first place.
// (this is left as an exercise to the reader :-)
// Case where the modified block is completely included in the cached block
if (cachedBlockStart.distanceTo(modBlockStart).longValue() >= 0
&& modBlockEnd.distanceTo(cachedBlockEnd).longValue() >= 0) {
int pos = (int) cachedBlockStart.distanceTo(modBlockStart).longValue() * wordSize;
System.arraycopy(modBlock, 0, cachedBlock.fBlock, pos, count);
}
// Case where the cached block is completely included in the modified block
else if (modBlockStart.distanceTo(cachedBlockStart).longValue() >= 0
&& cachedBlockEnd.distanceTo(modBlockEnd).longValue() >= 0) {
int pos = (int) modBlockStart.distanceTo(cachedBlockStart).longValue() * wordSize;
System.arraycopy(modBlock, pos, cachedBlock.fBlock, 0, (int) cachedBlock.fLengthInOctets);
}
// Case where the beginning of the modified block is within the cached block
else if (cachedBlockStart.distanceTo(modBlockStart).longValue() >= 0
&& modBlockStart.distanceTo(cachedBlockEnd).longValue() > 0) {
int pos = (int) cachedBlockStart.distanceTo(modBlockStart).longValue() * wordSize;
int length = (int) modBlockStart.distanceTo(cachedBlockEnd).longValue() * wordSize;
System.arraycopy(modBlock, 0, cachedBlock.fBlock, pos, length);
}
// Case where the end of the modified block is within the cached block
else if (cachedBlockStart.distanceTo(modBlockEnd).longValue() > 0
&& modBlockEnd.distanceTo(cachedBlockEnd).longValue() >= 0) {
int pos = (int) modBlockStart.distanceTo(cachedBlockStart).longValue() * wordSize;
int length = (int) cachedBlockStart.distanceTo(modBlockEnd).longValue() * wordSize;
System.arraycopy(modBlock, pos, cachedBlock.fBlock, 0, length);
}
}
return;
}
/**
* @param memoryDMC
* @param address the memory block address (on the target)
* @param wordSize the size, in bytes, of an addressable item
* @param wordCount the number of addressable units to read
* @param drm the asynchronous data request monitor
*/
public void getMemory(IMemoryDMContext memoryDMC, final IAddress address, final int wordSize,
final int wordCount, final DataRequestMonitor<MemoryByte[]> drm) {
// Determine the number of read requests to issue
List<MemoryBlock> missingBlocks = getListOfMissingBlocks(address, wordCount, wordSize);
int numberOfRequests = missingBlocks.size();
// A read request will be issued for each block needed
// so we need to keep track of the count
final CountingRequestMonitor countingRM = new CountingRequestMonitor(getExecutor(), drm) {
@Override
protected void handleSuccess() {
// We received everything so read the result from the memory cache
drm.setData(getMemoryBlockFromCache(address, wordCount, wordSize));
drm.done();
}
};
countingRM.setDoneCount(numberOfRequests);
// Issue the read requests
for (int i = 0; i < numberOfRequests; i++) {
MemoryBlock block = missingBlocks.get(i);
final IAddress startAddress = block.fAddress;
final int length = (int) block.fLengthInAddressableUnits;
readMemoryBlock(memoryDMC, startAddress, 0, wordSize, length,
new DataRequestMonitor<MemoryByte[]>(getSession().getExecutor(), drm) {
@Override
protected void handleSuccess() {
MemoryByte[] block = getData();
int lenghtInaddressableUnits = block.length / wordSize;
MemoryBlock memoryBlock = new MemoryBlock(startAddress, block.length,
lenghtInaddressableUnits, block);
fMemoryBlockList.add(memoryBlock);
countingRM.done();
}
});
}
}
/**
* @param memoryDMC
* @param address the memory block address (on the target)
* @param offset the offset from the start address
* @param wordSize the size, in bytes, of an addressable item
* @param wordCount the number of addressable units to write
* @param buffer the source buffer
* @param rm the asynchronous request monitor
*/
public void setMemory(final IMemoryDMContext memoryDMC, final IAddress address, final long offset,
final int wordSize, final int wordCount, final byte[] buffer, final RequestMonitor rm) {
writeMemoryBlock(memoryDMC, address, offset, wordSize, wordCount, buffer,
new RequestMonitor(getSession().getExecutor(), rm) {
@Override
protected void handleSuccess() {
// Clear the command cache (otherwise we can't guarantee
// that the subsequent memory read will be correct)
fCommandCache.reset();
// Re-read the modified memory block to asynchronously update of the memory cache
readMemoryBlock(memoryDMC, address, offset, wordSize, wordCount,
new DataRequestMonitor<MemoryByte[]>(getExecutor(), rm) {
@Override
protected void handleSuccess() {
updateMemoryCache(address.add(offset), wordCount, getData(), wordSize);
// Send the MemoryChangedEvent
IAddress[] addresses = new IAddress[wordCount];
for (int i = 0; i < wordCount; i++) {
addresses[i] = address.add(offset + i);
}
getSession().dispatchEvent(new MemoryChangedEvent(memoryDMC, addresses),
getProperties());
rm.done();
}
});
}
});
}
/**
* @param memoryDMC
* @param address
* @param offset
* @param wordSize
* @param wordCount
* @param sendMemoryEvent Indicates if a IMemoryChangedEvent should be sent if the memory cache has changed.
* @param rm
*/
public void refreshMemory(final IMemoryDMContext memoryDMC, final IAddress address, final long offset,
final int wordSize, final int wordCount, final boolean sendMemoryEvent, final RequestMonitor rm) {
// Check if we already cache part of this memory area (which means it
// is used by a memory service client that will have to be updated)
List<MemoryBlock> list = getListOfMissingBlocks(address, wordCount, wordSize);
int sizeToRead = 0;
for (MemoryBlock block : list) {
sizeToRead += block.fLengthInAddressableUnits;
}
// If none of the requested memory is in cache, just get out
if (sizeToRead == wordCount) {
rm.done();
return;
}
// Read the corresponding memory block
fCommandCache.reset();
readMemoryBlock(memoryDMC, address, offset, wordSize, wordCount,
new DataRequestMonitor<MemoryByte[]>(getExecutor(), rm) {
@Override
protected void handleSuccess() {
MemoryByte[] oldBlock = getMemoryBlockFromCache(address, wordCount, wordSize);
MemoryByte[] newBlock = getData();
boolean blocksDiffer = false;
for (int i = 0; i < oldBlock.length; i++) {
if (oldBlock[i].getValue() != newBlock[i].getValue()) {
blocksDiffer = true;
break;
}
}
if (blocksDiffer) {
updateMemoryCache(address.add(offset), wordCount, newBlock, wordSize);
if (sendMemoryEvent) {
// Send the MemoryChangedEvent
final IAddress[] addresses = new IAddress[wordCount];
for (int i = 0; i < wordCount; i++) {
addresses[i] = address.add(offset + i);
}
getSession().dispatchEvent(new MemoryChangedEvent(memoryDMC, addresses),
getProperties());
}
}
rm.done();
}
});
}
}
/**
* {@inheritDoc}
* @since 1.1
*/
@Override
public void flushCache(IDMContext context) {
fCommandCache.reset(context);
IMemoryDMContext memoryDMC = DMContexts.getAncestorOfType(context, IMemoryDMContext.class);
if (memoryDMC != null) {
memoryCacheReset(memoryDMC);
}
}
/**
* Reset the cache for the given memory context or any of its associated
* child memory space contexts (see Bug 432963)
*/
private void memoryCacheReset(IMemoryDMContext memoryDMC) {
for (IMemoryDMContext ctx : fMemoryCaches.keySet()) {
if (ctx != null && ctx.equals(memoryDMC) || DMContexts.isAncestorOf(ctx, memoryDMC)) {
fMemoryCaches.get(ctx).reset();
}
}
}
}