/******************************************************************************* * Copyright (c) 2007, 2015 Wind River Systems and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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<IMemoryDMContext, MIMemoryCache>(); 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)", null)); //$NON-NLS-1$ 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)", null)); //$NON-NLS-1$ 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)", null)); //$NON-NLS-1$ 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<MemoryBlock>(); 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(); } } } }