| /******************************************************************************* |
| * Copyright (c) 2009, 2010 Nokia 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: |
| * Nokia - Initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.cdt.debug.edc.internal.services.dsf; |
| |
| import java.io.IOException; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| |
| import org.eclipse.cdt.core.IAddress; |
| import org.eclipse.cdt.core.IAddressFactory; |
| import org.eclipse.cdt.debug.edc.MemoryUtils; |
| import org.eclipse.cdt.debug.edc.internal.EDCDebugger; |
| import org.eclipse.cdt.debug.edc.internal.EDCTrace; |
| import org.eclipse.cdt.debug.edc.services.IEDCDMContext; |
| import org.eclipse.cdt.debug.edc.snapshot.IAlbum; |
| import org.eclipse.cdt.debug.edc.snapshot.ISnapshotContributor; |
| import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants; |
| import org.eclipse.cdt.dsf.concurrent.RequestMonitor; |
| import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryDMContext; |
| import org.eclipse.cdt.utils.Addr32Factory; |
| import org.eclipse.cdt.utils.Addr64; |
| import org.eclipse.cdt.utils.Addr64Factory; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.debug.core.model.MemoryByte; |
| import org.eclipse.tm.tcf.protocol.IToken; |
| import org.eclipse.tm.tcf.services.IMemory; |
| import org.eclipse.tm.tcf.services.IMemory.DoneGetContext; |
| import org.eclipse.tm.tcf.services.IMemory.DoneMemory; |
| import org.eclipse.tm.tcf.services.IMemory.ErrorOffset; |
| import org.eclipse.tm.tcf.services.IMemory.MemoryContext; |
| import org.eclipse.tm.tcf.services.IMemory.MemoryError; |
| import org.eclipse.tm.tcf.util.TCFTask; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NodeList; |
| |
| /** |
| * This is adapted from |
| * org.eclipse.cdt.dsf.mi.service.MIMemory.MIMemoryCache |
| * |
| */ |
| public class MemoryCache implements ISnapshotContributor { |
| |
| // Timeout waiting for TCF agent reply. |
| final private int TIMEOUT = 6000; // milliseconds |
| private int minimumBlockSize = 0; |
| private final Map<String, MemoryContext> tcfMemoryContexts = Collections.synchronizedMap(new HashMap<String, MemoryContext>()); |
| private final SortedMemoryBlockList memoryBlockList = new SortedMemoryBlockList(); |
| |
| private boolean snapshot = false; |
| |
| /** |
| * @param minimumBlockSize minimum size of memory block to cache. |
| */ |
| public MemoryCache(int minimumBlockSize) { |
| this.minimumBlockSize = minimumBlockSize; |
| } |
| |
| public void reset() { |
| // clear the memory cache |
| synchronized (memoryBlockList) |
| { |
| memoryBlockList.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 LinkedList<MemoryBlock> getListOfMissingBlocks(IAddress reqBlockStart, int count) { |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArgs(new Object[] { reqBlockStart.toHexAddressString(), count })); } |
| LinkedList<MemoryBlock> list = new LinkedList<MemoryBlock>(); |
| |
| synchronized (memoryBlockList) |
| { |
| ListIterator<MemoryBlock> it = memoryBlockList.listIterator(); |
| |
| // Look for holes in the list of memory blocks |
| while (it.hasNext() && count > 0) { |
| MemoryBlock cachedBlock = it.next(); |
| IAddress cachedBlockStart = cachedBlock.fAddress; |
| IAddress cachedBlockEnd = cachedBlock.fAddress.add(cachedBlock.fLength); |
| |
| // Case where we miss a block before the cached block |
| if (reqBlockStart.distanceTo(cachedBlockStart).longValue() >= 0) { |
| int length = (int) Math.min(reqBlockStart.distanceTo(cachedBlockStart).longValue(), count); |
| // If both blocks start at the same location, no need to create |
| // a new cached block |
| if (length > 0) { |
| IAddress blockAddress; |
| if (reqBlockStart instanceof Addr64) { |
| IAddressFactory f = new Addr64Factory(); |
| blockAddress = f.createAddress(reqBlockStart.getValue()); |
| } else { |
| IAddressFactory f = new Addr32Factory(); |
| blockAddress = f.createAddress(reqBlockStart.getValue()); |
| } |
| MemoryBlock newBlock = new MemoryBlock(blockAddress, length, new MemoryByte[0]); |
| list.add(newBlock); |
| } |
| // Adjust request block start and length for the next iteration |
| reqBlockStart = cachedBlockEnd; |
| count -= length + cachedBlock.fLength; |
| } |
| |
| // 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 |
| count -= reqBlockStart.distanceTo(cachedBlockEnd).longValue(); |
| reqBlockStart = cachedBlockEnd; |
| } |
| } |
| |
| // Case where we miss a block at the end of the cache |
| if (count > 0) { |
| IAddress blockAddress; |
| if (reqBlockStart instanceof Addr64) { |
| IAddressFactory f = new Addr64Factory(); |
| blockAddress = f.createAddress(reqBlockStart.getValue()); |
| } else { |
| IAddressFactory f = new Addr32Factory(); |
| blockAddress = f.createAddress(reqBlockStart.getValue()); |
| } |
| MemoryBlock newBlock = new MemoryBlock(blockAddress, count, new MemoryByte[0]); |
| list.add(newBlock); |
| } |
| } |
| |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceExit(null, EDCTrace.fixArg(list)); } |
| 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 count) { |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArgs(new Object[] { reqBlockStart.toHexAddressString(), count })); } |
| |
| MemoryByte[] resultBlock = new MemoryByte[count]; |
| |
| synchronized (memoryBlockList) |
| { |
| IAddress reqBlockEnd = reqBlockStart.add(count); |
| ListIterator<MemoryBlock> iter = memoryBlockList.listIterator(); |
| |
| while (iter.hasNext()) { |
| MemoryBlock cachedBlock = iter.next(); |
| IAddress cachedBlockStart = cachedBlock.fAddress; |
| IAddress cachedBlockEnd = cachedBlock.fAddress.add(cachedBlock.fLength); |
| |
| // Case where the cached block overlaps completely the requested |
| // memory block |
| long cs2rsGap = cachedBlockStart.distanceTo(reqBlockStart).longValue(); |
| long ce2reGap = cachedBlockEnd.distanceTo(reqBlockEnd).longValue(); |
| if (cs2rsGap >= 0 && ce2reGap <= 0) { |
| System.arraycopy(cachedBlock.fBlock, (int)cs2rsGap, resultBlock, 0, count); |
| break; |
| } |
| |
| // Case where the beginning of the cached block is within the |
| // requested memory block |
| long cs2reGap = cachedBlockStart.distanceTo(reqBlockEnd).longValue(); |
| if (cs2rsGap < 0 && cs2reGap > 0) { |
| int rpos = -(int)cs2rsGap; |
| int length = (int) Math.min(cachedBlock.fLength, count - rpos); |
| System.arraycopy(cachedBlock.fBlock, 0, resultBlock, rpos, length); |
| |
| // given that cached blocks are in sorted order, there is no need to |
| // continue if the end of this request block is in this cached block |
| if (count - rpos <= cachedBlock.fLength) // the last one |
| break; |
| |
| } else { |
| // Case where the end of the cached block is within the requested |
| // memory block |
| long ce2rsGap = cachedBlockEnd.distanceTo(reqBlockStart).longValue(); |
| if (cs2rsGap >= 0 && ce2rsGap < 0) { |
| int cpos = (int) cs2rsGap; |
| int length = Math.min((int)cachedBlock.fLength - cpos, count); |
| System.arraycopy(cachedBlock.fBlock, cpos, resultBlock, 0, length); |
| } |
| } |
| } |
| } |
| |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceExit(null, EDCTrace.fixArgs(resultBlock)); } |
| 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 count |
| * @param modBlock |
| */ |
| private void updateMemoryCache(IAddress modBlockStart, int count, MemoryByte[] modBlock) { |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArgs(new Object[] { modBlockStart.toHexAddressString(), count })); } |
| |
| synchronized (memoryBlockList) |
| { |
| IAddress modBlockEnd = modBlockStart.add(count); |
| ListIterator<MemoryBlock> iter = memoryBlockList.listIterator(); |
| |
| while (iter.hasNext()) { |
| MemoryBlock cachedBlock = iter.next(); |
| IAddress cachedBlockStart = cachedBlock.fAddress; |
| IAddress cachedBlockEnd = cachedBlock.fAddress.add(cachedBlock.fLength); |
| |
| // 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(); |
| System.arraycopy(modBlock, 0, cachedBlock.fBlock, pos, count); |
| } |
| |
| // 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(); |
| int length = (int) cachedBlockStart.distanceTo(modBlockEnd).longValue(); |
| 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(); |
| int length = (int) cachedBlockStart.distanceTo(modBlockEnd).longValue(); |
| System.arraycopy(modBlock, pos, cachedBlock.fBlock, 0, length); |
| } |
| } |
| } |
| |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| return; |
| } |
| |
| /** |
| * This function iterates through missing blocks (blocks not currently |
| * cached, but wanted) and reads from the target and creates new cached |
| * blocks. |
| * |
| * @param tcfMemoryService |
| * @param context |
| * @param address |
| * @param word_size |
| * @param count |
| * @param drm |
| * @param timeOutLimit |
| * @throws CoreException |
| */ |
| public MemoryByte[] getMemory(final IMemory tcfMemoryService, final IMemoryDMContext context, final IAddress address, |
| final int word_size, final int count, long timeOutLimit) throws CoreException { |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArgs(new Object[] { context, address.toHexAddressString(), word_size, count })); } |
| |
| if (!snapshot) { |
| // determine number of read requests to issue |
| final LinkedList<MemoryBlock> missingBlocks = getListOfMissingBlocks(address, count); |
| final int numberOfRequests = missingBlocks.size(); |
| |
| if (numberOfRequests >= 1 && tcfMemoryService == null) { |
| throw new CoreException(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, "Fail to read memory.")); |
| } |
| // System.out.printf("MemoryCache.getMemory address=%x count=%d numberOfRequests=%d\n", |
| // address.getValue(), count, numberOfRequests); |
| for (int i = 0; i < numberOfRequests; i++) { |
| MemoryBlock block = missingBlocks.get(i); |
| IAddress blockAddress = block.fAddress; |
| int blockLength = (int) block.fLength; |
| if (blockLength < minimumBlockSize) |
| blockLength = minimumBlockSize; |
| |
| MemoryByte[] result; |
| try { |
| result = readBlock(tcfMemoryService, context, blockAddress, word_size, blockLength, timeOutLimit); |
| MemoryBlock newBlock = new MemoryBlock(blockAddress, blockLength, result); |
| memoryBlockList.add(newBlock); |
| } catch (Exception e) { |
| throw new CoreException(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR, |
| "Fail to read memory.", e.getCause())); //$NON-NLS-1$ |
| } |
| } |
| } |
| MemoryByte[] result = getMemoryBlockFromCache(address, count); |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| return result; |
| } |
| |
| private MemoryContext getTCFMemoryContext(final IMemory tcfMemoryService, final String contextID, long timeOutLimit) throws IOException, InterruptedException, ExecutionException, TimeoutException { |
| |
| MemoryContext ret = tcfMemoryContexts.get(contextID); |
| if (ret != null) |
| return ret; |
| |
| final TCFTask<MemoryContext> tcfTask = new TCFTask<MemoryContext>(TIMEOUT) { |
| |
| public void run() { |
| tcfMemoryService.getContext(contextID, new DoneGetContext() { |
| |
| public void doneGetContext(IToken token, Exception error, MemoryContext context) { |
| if (error == null) { |
| done(context); |
| } else { |
| error(error); |
| } |
| } |
| }); |
| } |
| }; |
| |
| ret = tcfTask.get(timeOutLimit, TimeUnit.MILLISECONDS); |
| |
| if (ret != null) |
| tcfMemoryContexts.put(contextID, ret); |
| |
| return ret; |
| } |
| |
| /** |
| * This function does the actual reading from the target. |
| * |
| * @param tcfMemoryService |
| * @param context |
| * @param address |
| * @param word_size |
| * @param count |
| * @param timeOutLimit |
| * @return |
| * @throws IOException |
| * @throws TimeoutException |
| * @throws ExecutionException |
| * @throws InterruptedException |
| */ |
| private MemoryByte[] readBlock(final IMemory tcfMemoryService, final IMemoryDMContext context, |
| final IAddress address, final int word_size, final int count, long timeOutLimit) throws IOException, InterruptedException, ExecutionException, TimeoutException { |
| |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArgs(new Object[] { context, address.toHexAddressString(), word_size, count })); } |
| |
| final MemoryContext tcfMC = getTCFMemoryContext(tcfMemoryService, ((IEDCDMContext)context).getID(), timeOutLimit); |
| |
| MemoryByte[] result = null; |
| |
| final TCFTask<MemoryByte[]> tcfTask = new TCFTask<MemoryByte[]>(TIMEOUT) { |
| |
| public void run() { |
| Number tcfAddress = address.getValue(); |
| final byte[] buffer = new byte[word_size * count]; |
| tcfMC.get(tcfAddress, word_size, buffer, 0, count * word_size, 0, new DoneMemory() { |
| |
| public void doneMemory(IToken token, MemoryError error) { |
| if (error == null) { |
| MemoryByte[] res = new MemoryByte[buffer.length]; |
| for (int i = 0; i < buffer.length; i++) { |
| res[i] = new MemoryByte(buffer[i]); |
| } |
| done(res); |
| } else if (error instanceof IMemory.ErrorOffset) { |
| boolean someStatusKnown = false; |
| IMemory.ErrorOffset errorOffset = (ErrorOffset) error; |
| MemoryByte[] res = new MemoryByte[buffer.length]; |
| |
| // TODO: figure actual endianness (MemoryByte.BIG_ENDIAN) flag; |
| // we leave out the flag here which defaults to little-endian |
| for (int i = 0; i < buffer.length; i++) { |
| byte flags = MemoryByte.ENDIANESS_KNOWN | MemoryByte.READABLE | MemoryByte.WRITABLE; |
| |
| int st = errorOffset.getStatus(i); |
| if ((st & IMemory.ErrorOffset.BYTE_UNKNOWN) != 0) { |
| flags = 0; |
| } else { |
| someStatusKnown = true; |
| if ((st & IMemory.ErrorOffset.BYTE_INVALID) != 0) { |
| flags &= ~(MemoryByte.READABLE + MemoryByte.WRITABLE); |
| } else { |
| if ((st & IMemory.ErrorOffset.BYTE_CANNOT_READ) != 0) |
| flags &= ~MemoryByte.READABLE; |
| if ((st & IMemory.ErrorOffset.BYTE_CANNOT_WRITE) != 0) |
| flags &= ~MemoryByte.WRITABLE; |
| } |
| } |
| res[i] = new MemoryByte(buffer[i], flags); |
| } |
| if (someStatusKnown) |
| done(res); |
| else |
| error(error); |
| } else { |
| error(error); |
| } |
| } |
| }); |
| } |
| }; |
| |
| result = tcfTask.get(timeOutLimit, TimeUnit.MILLISECONDS); |
| |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| return result; |
| } |
| |
| /** |
| * This function writes a block of memory and then re-reads and updates any |
| * cached blocks. |
| * |
| * @param tcfMemoryService |
| * @param context |
| * @param address |
| * @param offset |
| * @param word_size |
| * @param count |
| * @param buffer |
| * @param rm |
| * @throws CoreException |
| */ |
| public void setMemory(IMemory tcfMemoryService, IMemoryDMContext context, final IAddress address, |
| final long offset, final int word_size, final int count, byte[] buffer, |
| long timeOutLimit) throws CoreException { |
| |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArgs(new Object[] { context, address.toHexAddressString(), offset, word_size, count })); } |
| |
| try { |
| writeBlock(tcfMemoryService, context, address, offset, word_size, count, buffer, timeOutLimit); |
| if (blockIsCached(address.add(offset), word_size * count)) { |
| MemoryByte[] update = readBlock(tcfMemoryService, context, address.add(offset), word_size, count, timeOutLimit); |
| updateMemoryCache(address.add(offset), update.length, update); |
| } |
| } catch (Exception e) { |
| throw new CoreException(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, "Fail to write memory.")); |
| } |
| |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| |
| /** |
| * This function figures out if a block of memory is already cached. |
| * |
| * @param modBlockStart |
| * @param count |
| * @return |
| */ |
| private boolean blockIsCached(IAddress modBlockStart, int count) { |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArgs(new Object[] { modBlockStart.toHexAddressString(), count })); } |
| boolean cacheFound = false; |
| |
| synchronized (memoryBlockList) |
| { |
| IAddress modBlockEnd = modBlockStart.add(count); |
| ListIterator<MemoryBlock> iter = memoryBlockList.listIterator(); |
| |
| while (iter.hasNext()) { |
| MemoryBlock cachedBlock = iter.next(); |
| IAddress cachedBlockStart = cachedBlock.fAddress; |
| IAddress cachedBlockEnd = cachedBlock.fAddress.add(cachedBlock.fLength); |
| |
| // 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) { |
| cacheFound = true; |
| } |
| |
| // 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) { |
| cacheFound = true; |
| } |
| |
| // 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) { |
| cacheFound = true; |
| } |
| } |
| } |
| |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceExit(null, EDCTrace.fixArg(cacheFound)); } |
| return cacheFound; |
| } |
| |
| /** |
| * This function writes a memory block to the target. |
| * |
| * @param tcfMemoryService |
| * @param context |
| * @param address |
| * @param offset |
| * @param word_size |
| * @param count |
| * @param buffer |
| * @param timeOutLimit |
| * @throws IOException |
| * @throws TimeoutException |
| * @throws ExecutionException |
| * @throws InterruptedException |
| */ |
| private void writeBlock(final IMemory tcfMemoryService, final IMemoryDMContext context, final IAddress address, |
| final long offset, final int word_size, final int count, final byte[] buffer, long timeOutLimit) throws IOException, InterruptedException, ExecutionException, TimeoutException { |
| |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArgs(new Object[] { context, address.toHexAddressString(), offset, word_size, count })); } |
| |
| final TCFTask<MemoryByte[]> tcfTask = new TCFTask<MemoryByte[]>(TIMEOUT) { |
| |
| public void run() { |
| final TCFTask<MemoryByte[]> task = this; |
| String memoryContextID = ((IEDCDMContext) context).getID(); |
| tcfMemoryService.getContext(memoryContextID, new DoneGetContext() { |
| |
| public void doneGetContext(IToken token, Exception error, MemoryContext context) { |
| if (error == null) { |
| Number tcfAddress = address.add(offset).getValue(); |
| context.set(tcfAddress, word_size, buffer, 0, count * word_size, 0, new DoneMemory() { |
| |
| public void doneMemory(IToken token, MemoryError error) { |
| if (error == null) { |
| task.done(null); |
| } else { |
| task.error(error); |
| } |
| } |
| }); |
| } else { |
| task.error(error); |
| } |
| } |
| }); |
| } |
| }; |
| |
| tcfTask.get(timeOutLimit, TimeUnit.MILLISECONDS); |
| |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| |
| private class MemoryBlock { |
| public MemoryBlock(IAddress fAddress, long fLength, MemoryByte[] fBlock) { |
| super(); |
| this.fAddress = fAddress; |
| this.fLength = fLength; |
| this.fBlock = fBlock; |
| } |
| |
| public IAddress fAddress; |
| public long fLength; |
| public MemoryByte[] fBlock; |
| } |
| |
| @SuppressWarnings("serial") |
| private class SortedMemoryBlockList extends LinkedList<MemoryBlock> { |
| public SortedMemoryBlockList() { |
| super(); |
| } |
| |
| // Insert the block in the sorted linked list and merge contiguous |
| // blocks if necessary |
| @Override |
| synchronized public boolean add(MemoryBlock block) { |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArgs(new Object[] { block.fAddress.toHexAddressString(), block.fLength })); } |
| |
| // 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); |
| |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| 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) { |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArgs(new Object[] { 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.fLength); |
| if (endOfPreviousBlock.distanceTo(newBlock.fAddress).longValue() == 0) { |
| long newLength = prevBlock.fLength + newBlock.fLength; |
| if (newLength <= Integer.MAX_VALUE) { |
| MemoryByte[] block = new MemoryByte[(int) newLength]; |
| System.arraycopy(prevBlock.fBlock, 0, block, 0, (int) prevBlock.fLength); |
| System.arraycopy(newBlock.fBlock, 0, block, (int) prevBlock.fLength, (int) newBlock.fLength); |
| newBlock = new MemoryBlock(prevBlock.fAddress, newLength, 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.fLength); |
| if (endOfNewBlock.distanceTo(nextBlock.fAddress).longValue() == 0) { |
| long newLength = newBlock.fLength + nextBlock.fLength; |
| if (newLength <= Integer.MAX_VALUE) { |
| MemoryByte[] block = new MemoryByte[(int) newLength]; |
| System.arraycopy(newBlock.fBlock, 0, block, 0, (int) newBlock.fLength); |
| System.arraycopy(nextBlock.fBlock, 0, block, (int) newBlock.fLength, (int) nextBlock.fLength); |
| newBlock = new MemoryBlock(newBlock.fAddress, newLength, block); |
| set(index, newBlock); |
| remove(index + 1); |
| } |
| } |
| } |
| |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| } |
| } |
| |
| /** |
| * Refreshes cache blocks when memory has been changed through an event. |
| * |
| * @param tcfMemoryService |
| * @param context |
| * @param address |
| * @param offset |
| * @param word_size |
| * @param count |
| * @param rm |
| * @param timeOutLimit |
| * @return true = cache has been modified |
| */ |
| public boolean refreshMemory(IMemory tcfMemoryService, IMemoryDMContext context, IAddress address, int offset, |
| int word_size, int count, RequestMonitor rm, long timeOutLimit) { |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceEntry(null, EDCTrace.fixArgs(new Object[] { context, address.toHexAddressString(), offset, count })); } |
| |
| boolean modified = false; |
| // 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) |
| LinkedList<MemoryBlock> list = getListOfMissingBlocks(address, count); |
| int sizeToRead = 0; |
| for (MemoryBlock block : list) { |
| sizeToRead += block.fLength; |
| } |
| |
| // If none of the requested memory is in cache, just get out |
| if (sizeToRead == count) { |
| rm.done(); |
| return false; |
| } |
| |
| try { |
| MemoryByte[] newBlock = readBlock(tcfMemoryService, context, address, word_size, count, timeOutLimit); |
| MemoryByte[] oldBlock = getMemoryBlockFromCache(address, count); |
| 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, count, newBlock); |
| modified = true; |
| } |
| } catch (Exception e) { |
| rm.setStatus(new Status(IStatus.ERROR, EDCDebugger.PLUGIN_ID, IDsfStatusConstants.INTERNAL_ERROR, |
| "Error Writing Memory", e)); //$NON-NLS-1$ |
| } |
| rm.done(); |
| |
| if (EDCTrace.MEMORY_TRACE_ON) { EDCTrace.getTrace().traceExit(null); } |
| return modified; |
| } |
| |
| private static final String MEMORY_CACHE = "memory_cache"; |
| private static final String MEMORY_BLOCK = "memory_block"; |
| private static final String ATTR_ADDRESS = "address"; |
| private static final String ATTR_LENGTH = "length"; |
| private static final String ATTR_VALUE = "value"; |
| |
| public void loadSnapshot(Element element) throws Exception { |
| reset(); |
| snapshot = true; |
| NodeList blockElements = element.getElementsByTagName(MEMORY_BLOCK); |
| |
| int numBlocks = blockElements.getLength(); |
| for (int i = 0; i < numBlocks; i++) { |
| Element blockElement = (Element) blockElements.item(i); |
| String blockAddress = blockElement.getAttribute(ATTR_ADDRESS); |
| String blockLength = blockElement.getAttribute(ATTR_LENGTH); |
| String blockValue = blockElement.getAttribute(ATTR_VALUE); |
| MemoryByte[] blockBytes = MemoryUtils.convertHexStringToMemoryBytes(blockValue, blockValue.length() / 2, 2); |
| MemoryBlock newBlock = new MemoryBlock(new Addr64(blockAddress), Long.parseLong(blockLength), blockBytes); |
| memoryBlockList.add(newBlock); |
| } |
| } |
| |
| public Element takeSnapshot(IAlbum album, Document document, IProgressMonitor monitor) { |
| synchronized (memoryBlockList) |
| { |
| SubMonitor progress = SubMonitor.convert(monitor, memoryBlockList.size()); |
| progress.subTask("Memory"); |
| Element memoryCacheElement = document.createElement(MEMORY_CACHE); |
| ListIterator<MemoryBlock> iter = memoryBlockList.listIterator(); |
| |
| while (iter.hasNext()) { |
| MemoryBlock block = iter.next(); |
| Element blockElement = document.createElement(MEMORY_BLOCK); |
| blockElement.setAttribute(ATTR_ADDRESS, block.fAddress.toHexAddressString()); |
| blockElement.setAttribute(ATTR_LENGTH, Long.toString(block.fLength)); |
| blockElement.setAttribute(ATTR_VALUE, MemoryUtils.convertMemoryBytesToHexString(block.fBlock)); |
| memoryCacheElement.appendChild(blockElement); |
| progress.worked(1); |
| } |
| return memoryCacheElement; |
| } |
| } |
| } |