/******************************************************************************* * Copyright (c) 2007, 2016 Wind River Systems and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Wind River Systems - initial API and implementation * Ericsson Communication - upgrade IF to IMemoryBlockExtension * Ericsson Communication - added support for 64 bit processors * Ericsson Communication - added support for changed bytes * Ericsson Communication - better management of exceptions * Alvaro Sanchez-Leon (Ericsson AB) - [Memory] Support 16 bit addressable size (Bug 426730) *******************************************************************************/ package org.eclipse.cdt.dsf.debug.model; import java.math.BigInteger; import java.util.ArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.RejectedExecutionException; import org.eclipse.cdt.core.IAddress; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.Query; import org.eclipse.cdt.dsf.concurrent.RequestMonitor; import org.eclipse.cdt.dsf.debug.internal.provisional.model.IMemoryBlockUpdatePolicyProvider; import org.eclipse.cdt.dsf.debug.service.IMemory; import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryChangedEvent; import org.eclipse.cdt.dsf.debug.service.IMemory.IMemoryDMContext; import org.eclipse.cdt.dsf.debug.service.IRunControl; import org.eclipse.cdt.dsf.debug.service.IRunControl.StateChangeReason; import org.eclipse.cdt.dsf.internal.DsfPlugin; import org.eclipse.cdt.dsf.service.DsfServiceEventHandler; import org.eclipse.cdt.utils.Addr64; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.PlatformObject; import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.model.IDebugTarget; import org.eclipse.debug.core.model.IMemoryBlockExtension; import org.eclipse.debug.core.model.IMemoryBlockRetrieval; import org.eclipse.debug.core.model.MemoryByte; /** * This class manages the memory block retrieved from the target as a result * of a getBytesFromAddress() call from the platform. * * It performs its read/write functions using the MemoryService. * * @since 1.0 */ public class DsfMemoryBlock extends PlatformObject implements IMemoryBlockExtension, IMemoryBlockUpdatePolicyProvider { private final static String UPDATE_POLICY_AUTOMATIC = "Automatic"; //$NON-NLS-1$ private final static String UPDATE_POLICY_MANUAL = "Manual"; //$NON-NLS-1$ private final static String UPDATE_POLICY_BREAKPOINT = "On Breakpoint"; //$NON-NLS-1$ private final IMemoryDMContext fContext; private final ILaunch fLaunch; private final IDebugTarget fDebugTarget; private final DsfMemoryBlockRetrieval fRetrieval; private final String fModelId; private final String fExpression; private final BigInteger fBaseAddress; private BigInteger fBlockAddress; private int fLength; private int fWordSize; private MemoryByte[] fBlock; private String fUpdatePolicy = UPDATE_POLICY_AUTOMATIC; private ArrayList<Object> fConnections = new ArrayList<Object>(); @SuppressWarnings("unused") private boolean isEnabled; /** * Constructor. * * @param retrieval - the MemoryBlockRetrieval (session context) * @param modelId - * @param expression - the displayed expression in the UI * @param address - the actual memory block start address * @param word_size - the number of bytes per address * @param length - the requested block length (could be 0) * @since 2.1 */ protected DsfMemoryBlock(DsfMemoryBlockRetrieval retrieval, IMemoryDMContext context, String modelId, String expression, BigInteger address, int word_size, long length) { fLaunch = retrieval.getLaunch(); fDebugTarget = retrieval.getDebugTarget(); fRetrieval = retrieval; fContext = context; fModelId = modelId; fExpression = expression; fBaseAddress = address; // Current block information fBlockAddress = address; fWordSize = word_size; fLength = (int) length; fBlock = null; try { fRetrieval.getExecutor().execute(new Runnable() { @Override public void run() { fRetrieval.getSession().addServiceEventListener(DsfMemoryBlock.this, null); } }); } catch (RejectedExecutionException e) { // Session is shut down. } } // //////////////////////////////////////////////////////////////////////// // IAdaptable // //////////////////////////////////////////////////////////////////////// /* (non-Javadoc) * @see org.eclipse.core.runtime.PlatformObject#getAdapter(java.lang.Class) */ @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public Object getAdapter(Class adapter) { if (adapter.isAssignableFrom(DsfMemoryBlockRetrieval.class)) { return fRetrieval; } if (adapter.isAssignableFrom(IMemoryDMContext.class)) { return getContext(); } return super.getAdapter(adapter); } // //////////////////////////////////////////////////////////////////////// // IDebugElement // //////////////////////////////////////////////////////////////////////// /* (non-Javadoc) * @see org.eclipse.debug.core.model.IDebugElement#getDebugTarget() */ @Override public IDebugTarget getDebugTarget() { return fDebugTarget; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IDebugElement#getModelIdentifier() */ @Override public String getModelIdentifier() { return fModelId; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IDebugElement#getLaunch() */ @Override public ILaunch getLaunch() { return fLaunch; } // //////////////////////////////////////////////////////////////////////// // IMemoryBock interface - obsoleted by IMemoryBlockExtension // //////////////////////////////////////////////////////////////////////// /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlock#getStartAddress() */ @Override public long getStartAddress() { // Not implemented (obsolete) return 0; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlock#getLength() */ @Override public long getLength() { // Not implemented (obsolete) return 0; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlock#getBytes() */ @Override public byte[] getBytes() throws DebugException { // Not implemented (obsolete) return new byte[0]; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlock#supportsValueModification() */ @Override public boolean supportsValueModification() { return fRetrieval.supportsValueModification(); } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlock#setValue(long, byte[]) */ @Override public void setValue(long offset, byte[] bytes) throws DebugException { // Not implemented (obsolete) } // //////////////////////////////////////////////////////////////////////// // IMemoryBlockExtension interface // //////////////////////////////////////////////////////////////////////// /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#getExpression() */ @Override public String getExpression() { return fExpression; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#getBigBaseAddress() */ @Override public BigInteger getBigBaseAddress() throws DebugException { return fBaseAddress; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#getMemoryBlockStartAddress() */ @Override public BigInteger getMemoryBlockStartAddress() throws DebugException { // Null indicates that memory can be retrieved at addresses lower than the block base address return null; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#getMemoryBlockEndAddress() */ @Override public BigInteger getMemoryBlockEndAddress() throws DebugException { // Null indicates that memory can be retrieved at addresses higher the block base address return null; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#getBigLength() */ @Override public BigInteger getBigLength() throws DebugException { // -1 indicates that memory block is unbounded return BigInteger.valueOf(-1); } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#getAddressSize() */ @Override public int getAddressSize() throws DebugException { return fRetrieval.getAddressSize(); } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#supportBaseAddressModification() */ @Override public boolean supportBaseAddressModification() throws DebugException { return fRetrieval.supportBaseAddressModification(); } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#supportsChangeManagement() */ @Override public boolean supportsChangeManagement() { return true; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#setBaseAddress(java.math.BigInteger) */ @Override public void setBaseAddress(BigInteger address) throws DebugException { fBlockAddress = address; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#getBytesFromOffset(java.math.BigInteger, long) */ @Override public MemoryByte[] getBytesFromOffset(BigInteger offset, long units) throws DebugException { return getBytesFromAddress(fBlockAddress.add(offset), units); } private boolean fUseCachedData = false; @Override public void clearCache() { fUseCachedData = false; } @DsfServiceEventHandler public void handleCacheSuspendEvent(IRunControl.ISuspendedDMEvent e) { if (e.getReason() == StateChangeReason.BREAKPOINT) fUseCachedData = false; } private boolean isUseCacheData() { if (fUpdatePolicy.equals(DsfMemoryBlock.UPDATE_POLICY_BREAKPOINT)) return fUseCachedData; if (fUpdatePolicy.equals(DsfMemoryBlock.UPDATE_POLICY_MANUAL)) return fUseCachedData; return false; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#getBytesFromAddress(java.math.BigInteger, long) */ @SuppressWarnings("null") @Override public MemoryByte[] getBytesFromAddress(BigInteger address, long units) throws DebugException { int addressableSize = 1; try { addressableSize = getAddressableSize(); } catch (DebugException e) {} if (isUseCacheData() && fBlockAddress.compareTo(address) == 0 && units * addressableSize <= fBlock.length) return fBlock; MemoryByte[] newBlock = fetchMemoryBlock(address, units); int newLength = (newBlock != null) ? newBlock.length : 0; // If the retrieved block overlaps with the cached block, flag the changed bytes // so they can be properly highlighted in the platform Memory view. // Note: In the standard Memory view, the values are displayed in cells of 4 bytes // (on 4-bytes address boundaries) and all 4 bytes have to be flagged so the // cell is highlighted (and the delta sigh is shown). if (fBlock != null && newLength > 0) { switch (fBlockAddress.compareTo(address)) { // case : Cached block begins before the retrieved block location // // <--- fLength --------------------------------------------------> // +--------------------------------------------------------------+ // | Cached data from previous retrieve | // | | // | <------------------ length -----------------------> | // | +-------------------------------------------------+ | // | |<----------------- newLength ------------------->| | // | | Newly retrieved data | | // |<-+->+-------------------------------------------------+ | // | | address | // | | | // | +--- bigDistance/distance | // | | // | <------------------ length ----------------------------->| // | +--------------------------------------------------------+ // | |<----------------- newLength -------------------------->| // | | Newly retrieved data | // |<-+->+--------------------------------------------------------+ // | | address | // | | | // | +--- bigDistance/distance | // | | // | <------------------ length ----------------------------->| // | +---------------------------------------------------------------+ // | |<----------------- newLength --------------------------------->| // | | Newly retrieved data | // |<-+->+---------------------------------------------------------------+ // | | address | // | | | // | +--- bigDistance/distance | // | | // +--------------------------------------------------------------+ // fBlockAddress case -1: { // Determine the distance between the cached and the requested block addresses // If the distance does not exceed the length of the cached block, then there // is some overlap between the blocks and we have to mark the changed bytes. BigInteger bigDistance = address.subtract(fBlockAddress).multiply(BigInteger.valueOf(addressableSize)); if (bigDistance.compareTo(BigInteger.valueOf(fLength)) == -1) { // Calculate the length of the data we are going to examine/update int distance = bigDistance.intValue(); int length = fLength - distance; if ( length > newLength ) { length = newLength; } // Process each cell, updating the status and history/change for (int i = 0; i < length; i++) { if ( i < newLength ) { newBlock[i].setFlags(fBlock[distance + i].getFlags()); newBlock[i].setHistoryKnown(true); if (newBlock[i].getValue() != fBlock[distance + i].getValue()) { newBlock[i].setChanged(true); } } } } break; } // The cached block starts at the same address as the retrieved data // or it starts after the retrieved block and the retrieved block runs // in to it. // // Case 0: // <--- fLength --------------------------------------------------> // +--------------------------------------------------------------+ // | Cached data from previous retrieve | // | | // |<-- length ----------------> | // +---------------------------+ | // |<-- newLength ------------>| | // | Newly retrieve data | | // +---------------------------+ | // |address | // | | // |<--------------------- length ------------------------------->| // +--------------------------------------------------------------+ // |<--------------------- newLength ---------------------------->| // | Newly retrieve data | // +--------------------------------------------------------------+ // |address | // | | // +--------------------------------------------------------------+ // fBlockAddress // bigDistance/distance = 0 // // Case 1: // <--- fLength --------------------------------------------------> // +--------------------------------------------------------------+ // | Cached data from previous retrieve | // | | // |<-- length -----> | // +-----------------------+ | // |<----- newLength ----->| | // | Newly retrieved data | | // +-----------------------+ | // address| | // | | // |<----------------------- length ----------------------------->| // +---------------------------------------------------------------------+ // |<------------------------------ newLength -------------------------->| // | Newly retrieved data | // +---------------------------------------------------------------------+ // address| | // | | // |<----------------------- length ----------------------------->| // +---------------------------------------------------------------------------+ // |<------------------------------ newLength -------------------------------->| // | Newly retrieved data | // +---------------------------------------------------------------------------+ // address| | // <--+-->| | // | +--------------------------------------------------------------+ // | fBlockAddress // +---bigDistance/distance case 0: case 1: { // Determine the distance between the requested block and the cache block // If the distance does not exceed the length of the new block, then there // is some overlap between the blocks and we have to update the blanks and // possibly note they are changed. BigInteger bigDistance = fBlockAddress.subtract(address).multiply(BigInteger.valueOf(addressableSize)); if (bigDistance.compareTo(BigInteger.valueOf(newLength)) == -1) { // Calculate the length of the data we are going to examine/update int distance = bigDistance.intValue(); int length = newLength - distance; if ( length > fBlock.length ) { length = fBlock.length; } // Process each cell, updating the status and history/change for (int i = 0; i < length; i++) { if ( (distance + i) < newLength ) { newBlock[distance + i].setFlags(fBlock[i].getFlags()); newBlock[distance + i].setHistoryKnown(true); if (newBlock[distance + i].getValue() != fBlock[i].getValue()) { newBlock[distance + i].setChanged(true); } } } } break; } } } // Update the internal state fBlock = newBlock; fBlockAddress = address; fLength = newLength; if (fUpdatePolicy.equals(DsfMemoryBlock.UPDATE_POLICY_BREAKPOINT)) { fUseCachedData = true; } else if (fUpdatePolicy.equals(DsfMemoryBlock.UPDATE_POLICY_MANUAL)) { fUseCachedData = true; } return fBlock; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#setValue(java.math.BigInteger, byte[]) */ @Override public void setValue(BigInteger offset, byte[] bytes) throws DebugException { writeMemoryBlock(offset.longValue(), bytes); } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#connect(java.lang.Object) */ @Override public void connect(Object client) { if (!fConnections.contains(client)) fConnections.add(client); if (fConnections.size() == 1) enable(); } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#disconnect(java.lang.Object) */ @Override public void disconnect(Object client) { if (fConnections.contains(client)) fConnections.remove(client); if (fConnections.size() == 0) disable(); } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#getConnections() */ @Override public Object[] getConnections() { return fConnections.toArray(); } private void enable() { isEnabled = true; } private void disable() { isEnabled = false; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#dispose() */ @Override public void dispose() throws DebugException { try { fRetrieval.getExecutor().execute(new Runnable() { @Override public void run() { fRetrieval.getSession().removeServiceEventListener(DsfMemoryBlock.this); } }); } catch (RejectedExecutionException e) { // Session is down. } } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#getMemoryBlockRetrieval() */ @Override public IMemoryBlockRetrieval getMemoryBlockRetrieval() { return fRetrieval; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IMemoryBlockExtension#getAddressableSize() */ @Override public int getAddressableSize() throws DebugException { return fWordSize; } /////////////////////////////////////////////////////////////////////////// // Helper functions /////////////////////////////////////////////////////////////////////////// /** * The real thing. Since the original call is synchronous (from a platform * Job), we use a Query that will patiently wait for the underlying * asynchronous calls to complete before returning. * * @param bigAddress * @param count - Number of addressable units for this memory block * @return MemoryByte[] * @throws DebugException * @since 2.1 */ protected MemoryByte[] fetchMemoryBlock(BigInteger bigAddress, final long count) throws DebugException { //resolve the addressable size int aSize; try { aSize = getAddressableSize(); } catch (DebugException e) { aSize = 1; } final int addressableSize = aSize; // For the IAddress interface final Addr64 address = new Addr64(bigAddress); // Use a Query to synchronize the downstream calls Query<MemoryByte[]> query = new Query<MemoryByte[]>() { @Override protected void execute(final DataRequestMonitor<MemoryByte[]> drm) { IMemory memoryService = fRetrieval.getServiceTracker().getService(); if (memoryService != null) { // Go for it memoryService.getMemory( fContext, address, 0, addressableSize, (int) count, new DataRequestMonitor<MemoryByte[]>(fRetrieval.getExecutor(), drm) { @Override protected void handleSuccess() { drm.setData(getData()); drm.done(); } }); } else { drm.done(); } } }; fRetrieval.getExecutor().execute(query); try { return query.get(); } catch (InterruptedException e) { throw new DebugException(new Status(IStatus.ERROR, DsfPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR, "Error reading memory block (InterruptedException)", e)); //$NON-NLS-1$ } catch (ExecutionException e) { throw new DebugException(new Status(IStatus.ERROR, DsfPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR, "Error reading memory block (ExecutionException)", e)); //$NON-NLS-1$ } } /** * Writes an array of bytes to memory. * * @param offset * @param bytes * @throws DebugException * @since 2.1 */ protected void writeMemoryBlock(final long offset, final byte[] bytes) throws DebugException { //resolve the addressable size int aSize; try { aSize = getAddressableSize(); } catch (DebugException e) { aSize = 1; } final int addressableSize = aSize; final int addressableUnits = bytes.length/addressableSize; // For the IAddress interface final Addr64 address = new Addr64(fBaseAddress); // Use a Query to synchronize the downstream calls Query<MemoryByte[]> query = new Query<MemoryByte[]>() { @Override protected void execute(final DataRequestMonitor<MemoryByte[]> drm) { IMemory memoryService = fRetrieval.getServiceTracker().getService(); if (memoryService != null) { // Go for it memoryService.setMemory( fContext, address, offset, addressableSize, addressableUnits, bytes, new RequestMonitor(fRetrieval.getExecutor(), drm)); } else { drm.done(); } } }; fRetrieval.getExecutor().execute(query); try { query.get(); } catch (InterruptedException e) { throw new DebugException(new Status(IStatus.ERROR, DsfPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR, "Error writing memory block (InterruptedException)", e)); //$NON-NLS-1$ } catch (ExecutionException e) { throw new DebugException(new Status(IStatus.ERROR, DsfPlugin.PLUGIN_ID, DebugException.INTERNAL_ERROR, "Error writing memory block (ExecutionException)", e)); //$NON-NLS-1$ } } /////////////////////////////////////////////////////////////////////////// // Event Handlers /////////////////////////////////////////////////////////////////////////// @DsfServiceEventHandler public void eventDispatched(IRunControl.ISuspendedDMEvent e) { // Clear the "Changed" flags after each run/resume/step for (int i = 0; i < fLength; i++) fBlock[i].setChanged(false); // Generate the MemoryChangedEvents handleMemoryChange(BigInteger.ZERO); } @DsfServiceEventHandler public void eventDispatched(IMemoryChangedEvent e) { // Check if we are in the same address space if (e.getDMContext().equals(fContext)) { IAddress[] addresses = e.getAddresses(); for (int i = 0; i < addresses.length; i++) handleMemoryChange(addresses[i].getValue()); } } /** * @param address * @param length */ public void handleMemoryChange(BigInteger address) { int addressableSize; try { addressableSize = getAddressableSize(); } catch (DebugException e) { addressableSize = 1; } int addressesLength = fLength/addressableSize; // Check if the change affects this particular block (0 is universal) BigInteger fEndAddress = fBlockAddress.add(BigInteger.valueOf(addressesLength)); if (address.equals(BigInteger.ZERO) || ((fBlockAddress.compareTo(address) != 1) && (fEndAddress.compareTo(address) == 1))) { // Notify the event listeners DebugEvent debugEvent = new DebugEvent(this, DebugEvent.CHANGE, DebugEvent.CONTENT); DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { debugEvent }); } } @Override public String[] getUpdatePolicies() { return new String[] {UPDATE_POLICY_AUTOMATIC, UPDATE_POLICY_MANUAL, UPDATE_POLICY_BREAKPOINT}; } @Override public String getUpdatePolicy() { return fUpdatePolicy; } @Override public void setUpdatePolicy(String policy) { fUpdatePolicy = policy; } @Override public String getUpdatePolicyDescription(String id) { return id; } /** * Get the context specified at construction. * * @since 2.7 */ public IMemoryDMContext getContext() { return fContext; } }