| /******************************************************************************* |
| * Copyright (c) 2006, 2016 Wind River Systems, Inc. and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Ted R Williams (Wind River Systems, Inc.) - initial implementation |
| * Alvaro Sanchez-Leon (Ericsson AB) - [Memory] Support 16 bit addressable size (Bug 426730) |
| * Ling Wang (Silicon Laboratories) - Honor start address (Bug 414519) |
| * Teodor Madan (Freescale) - Fix scrolling for memory spaces with 64-bit address |
| *******************************************************************************/ |
| |
| package org.eclipse.cdt.debug.ui.memory.traditional; |
| |
| import java.math.BigInteger; |
| import java.text.MessageFormat; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Vector; |
| |
| import org.eclipse.cdt.debug.core.model.IMemoryBlockAddressInfoRetrieval.IMemoryBlockAddressInfoItem; |
| import org.eclipse.core.runtime.IStatus; |
| 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.IDebugEventSetListener; |
| import org.eclipse.debug.core.model.IDebugElement; |
| import org.eclipse.debug.core.model.IMemoryBlock; |
| import org.eclipse.debug.core.model.IMemoryBlockExtension; |
| import org.eclipse.debug.core.model.MemoryByte; |
| import org.eclipse.debug.internal.ui.IInternalDebugUIConstants; |
| import org.eclipse.debug.internal.ui.views.memory.MemoryViewUtil; |
| import org.eclipse.debug.internal.ui.views.memory.renderings.GoToAddressComposite; |
| import org.eclipse.jface.action.Action; |
| import org.eclipse.jface.dialogs.IDialogConstants; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.dnd.Clipboard; |
| import org.eclipse.swt.dnd.TextTransfer; |
| import org.eclipse.swt.dnd.Transfer; |
| import org.eclipse.swt.events.ControlEvent; |
| import org.eclipse.swt.events.ControlListener; |
| import org.eclipse.swt.events.KeyAdapter; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Layout; |
| import org.eclipse.swt.widgets.ScrollBar; |
| import org.eclipse.swt.widgets.Text; |
| |
| @SuppressWarnings("restriction") |
| public class Rendering extends Composite implements IDebugEventSetListener { |
| // the IMemoryRendering parent |
| private TraditionalRendering fParent; |
| |
| // controls |
| |
| protected AddressPane fAddressPane; |
| |
| protected DataPane fBinaryPane; |
| |
| protected TextPane fTextPane; |
| |
| private GoToAddressComposite fAddressBar; |
| |
| protected Control fAddressBarControl; |
| |
| private Selection fSelection = new Selection(); |
| |
| // storage |
| |
| BigInteger fViewportAddress = null; // default visibility for performance |
| |
| BigInteger fMemoryBlockStartAddress = null; |
| BigInteger fMemoryBlockEndAddress = null; |
| |
| protected BigInteger fBaseAddress = null; // remember the base address |
| |
| protected int fColumnCount = 0; // auto calculate can be disabled by user, |
| // making this user settable |
| |
| protected int fBytesPerRow = 0; // current number of bytes per row are displayed |
| |
| private int fCurrentScrollSelection = 0; // current scroll selection; |
| |
| private BigInteger fCaretAddress; |
| |
| // user settings |
| |
| private int fTextMode = 1; // ASCII default, TODO make preference? |
| |
| private int fBytesPerColumn = 4; // 4 byte cell width default |
| |
| private int fRadix = RADIX_HEX; |
| |
| private int fColumnsSetting = COLUMNS_AUTO_SIZE_TO_FIT; |
| |
| private boolean fIsTargetLittleEndian = false; |
| |
| private boolean fIsDisplayLittleEndian = false; |
| |
| // constants used to identify radix |
| public final static int RADIX_HEX = 1; |
| |
| public final static int RADIX_DECIMAL_SIGNED = 2; |
| |
| public final static int RADIX_DECIMAL_UNSIGNED = 3; |
| |
| public final static int RADIX_OCTAL = 4; |
| |
| public final static int RADIX_BINARY = 5; |
| |
| // constants used to identify panes |
| public final static int PANE_ADDRESS = 1; |
| |
| public final static int PANE_BINARY = 2; |
| |
| public final static int PANE_TEXT = 3; |
| |
| // constants used to identify text, maybe java should be queried for all available sets |
| public final static int TEXT_ISO_8859_1 = 1; |
| public final static int TEXT_USASCII = 2; |
| public final static int TEXT_UTF8 = 3; |
| protected final static int TEXT_UTF16 = 4; |
| |
| // internal constants |
| public final static int COLUMNS_AUTO_SIZE_TO_FIT = 0; |
| |
| // view internal settings |
| private int fCellPadding = 2; |
| |
| private int fPaneSpacing = 16; |
| |
| private String fPaddingString = "?"; //$NON-NLS-1$ |
| |
| // flag whether the memory cache is dirty |
| private boolean fCacheDirty = false; |
| |
| // update modes |
| public final static int UPDATE_ALWAYS = 1; |
| public final static int UPDATE_ON_BREAKPOINT = 2; |
| public final static int UPDATE_MANUAL = 3; |
| public int fUpdateMode = UPDATE_ALWAYS; |
| |
| /** |
| * Maintains the subset of items visible in the current view address range. |
| * This information is refreshed when the associated Panes are about to be redrawn |
| * Note: Only the start address of each entry is included i.e. one entry per information item |
| * @since 1.4 |
| */ |
| protected final Map<BigInteger, List<IMemoryBlockAddressInfoItem>> fMapStartAddrToInfoItems = Collections |
| .synchronizedMap(new HashMap<BigInteger, List<IMemoryBlockAddressInfoItem>>()); |
| |
| /** |
| * Maps any address within a memory information range to a corresponding list of information items |
| * sharing that address. |
| * This is useful to e.g. produce a tooltip while hovering over any memory location of a memory information range |
| * |
| * @since 1.5 |
| */ |
| protected final Map<BigInteger, List<IMemoryBlockAddressInfoItem>> fMapAddrToInfoItems = Collections |
| .synchronizedMap(new HashMap<BigInteger, List<IMemoryBlockAddressInfoItem>>()); |
| |
| public Rendering(Composite parent, TraditionalRendering renderingParent) { |
| super(parent, SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND | SWT.H_SCROLL | SWT.V_SCROLL); |
| |
| this.setFont(JFaceResources.getFont(IInternalDebugUIConstants.FONT_NAME)); // TODO internal? |
| |
| this.fParent = renderingParent; |
| |
| // initialize the viewport start |
| if (fParent.getMemoryBlock() != null) { |
| // This is base address from user. |
| // Honor it if the block has no limits or the base is within the block limits. |
| // Fix Bug 414519. |
| BigInteger base = fParent.getBigBaseAddress(); |
| |
| fViewportAddress = fParent.getMemoryBlockStartAddress(); |
| |
| // this will be null if memory may be retrieved at any address less than |
| // this memory block's base. if so use the base address. |
| if (fViewportAddress == null) |
| fViewportAddress = base; |
| else { |
| BigInteger blockEndAddr = fParent.getMemoryBlockEndAddress(); |
| if (base.compareTo(fViewportAddress) > 0) { |
| if (blockEndAddr == null || base.compareTo(blockEndAddr) < 0) |
| fViewportAddress = base; |
| } |
| } |
| |
| fBaseAddress = fViewportAddress; |
| } |
| |
| // instantiate the panes, TODO default visibility from state or |
| // plugin.xml? |
| this.fAddressPane = createAddressPane(); |
| this.fBinaryPane = createDataPane(); |
| this.fTextPane = createTextPane(); |
| |
| fAddressBar = new GoToAddressComposite(); |
| fAddressBarControl = fAddressBar.createControl(parent); |
| Button button = fAddressBar.getButton(IDialogConstants.OK_ID); |
| if (button != null) { |
| button.addSelectionListener(new SelectionAdapter() { |
| |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| doGoToAddress(); |
| } |
| }); |
| |
| button = fAddressBar.getButton(IDialogConstants.CANCEL_ID); |
| if (button != null) { |
| button.addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| setVisibleAddressBar(false); |
| } |
| }); |
| } |
| } |
| |
| fAddressBar.getExpressionWidget().addSelectionListener(new SelectionAdapter() { |
| @Override |
| public void widgetDefaultSelected(SelectionEvent e) { |
| doGoToAddress(); |
| } |
| }); |
| |
| fAddressBar.getExpressionWidget().addKeyListener(new KeyAdapter() { |
| |
| @Override |
| public void keyPressed(KeyEvent e) { |
| if (e.keyCode == SWT.ESC) |
| setVisibleAddressBar(false); |
| super.keyPressed(e); |
| } |
| }); |
| |
| this.fAddressBarControl.setVisible(false); |
| |
| getHorizontalBar().addSelectionListener(createHorizontalBarSelectionListener()); |
| |
| getVerticalBar().addSelectionListener(createVerticalBarSelectinListener()); |
| |
| this.addPaintListener(pe -> { |
| pe.gc.setBackground(Rendering.this.getTraditionalRendering().getColorBackground()); |
| pe.gc.fillRectangle(0, 0, Rendering.this.getBounds().width, Rendering.this.getBounds().height); |
| }); |
| |
| setLayout(); |
| |
| this.addControlListener(new ControlListener() { |
| @Override |
| public void controlMoved(ControlEvent ce) { |
| } |
| |
| @Override |
| public void controlResized(ControlEvent ce) { |
| packColumns(); |
| } |
| }); |
| |
| DebugPlugin.getDefault().addDebugEventListener(this); |
| } |
| |
| protected void setLayout() { |
| this.setLayout(new Layout() { |
| @Override |
| public void layout(Composite composite, boolean changed) { |
| int xOffset = 0; |
| if (Rendering.this.getHorizontalBar().isVisible()) |
| xOffset = Rendering.this.getHorizontalBar().getSelection(); |
| |
| int x = xOffset * -1; |
| int y = 0; |
| |
| if (fAddressBarControl.isVisible()) { |
| fAddressBarControl.setBounds(0, 0, Rendering.this.getBounds().width, |
| fAddressBarControl.computeSize(100, 30).y); // FIXME |
| //y = fAddressBarControl.getBounds().height; |
| } |
| |
| if (fAddressPane.isPaneVisible()) { |
| fAddressPane.setBounds(x, y, fAddressPane.computeSize(0, 0).x, |
| Rendering.this.getBounds().height - y); |
| x = fAddressPane.getBounds().x + fAddressPane.getBounds().width; |
| } |
| |
| if (fBinaryPane.isPaneVisible()) { |
| fBinaryPane.setBounds(x, y, fBinaryPane.computeSize(0, 0).x, Rendering.this.getBounds().height - y); |
| x = fBinaryPane.getBounds().x + fBinaryPane.getBounds().width; |
| } |
| |
| if (fTextPane.isPaneVisible()) { |
| fTextPane.setBounds(x, y, |
| Math.max(fTextPane.computeSize(0, 0).x, Rendering.this.getClientArea().width - x - xOffset), |
| Rendering.this.getBounds().height - y); |
| } |
| |
| if (getClientArea().width >= fTextPane.getBounds().x + fTextPane.getBounds().width + xOffset) { |
| Rendering.this.getHorizontalBar().setVisible(false); |
| } else { |
| ScrollBar horizontal = Rendering.this.getHorizontalBar(); |
| |
| horizontal.setVisible(true); |
| horizontal.setMinimum(0); |
| horizontal.setMaximum(fTextPane.getBounds().x + fTextPane.getBounds().width + xOffset); |
| horizontal.setThumb(getClientArea().width); |
| horizontal.setPageIncrement(40); // TODO ? |
| horizontal.setIncrement(20); // TODO ? |
| } |
| } |
| |
| @Override |
| protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { |
| return new Point(100, 100); // dummy data |
| } |
| }); |
| } |
| |
| protected void handleDownArrow() { |
| fViewportAddress = fViewportAddress.add(BigInteger.valueOf(getAddressableCellsPerRow())); |
| ensureViewportAddressDisplayable(); |
| redrawPanes(); |
| } |
| |
| protected void handleUpArrow() { |
| fViewportAddress = fViewportAddress.subtract(BigInteger.valueOf(getAddressableCellsPerRow())); |
| ensureViewportAddressDisplayable(); |
| redrawPanes(); |
| } |
| |
| protected void handlePageDown() { |
| fViewportAddress = fViewportAddress |
| .add(BigInteger.valueOf(getAddressableCellsPerRow() * (Rendering.this.getRowCount() - 1))); |
| ensureViewportAddressDisplayable(); |
| redrawPanes(); |
| } |
| |
| protected void handlePageUp() { |
| fViewportAddress = fViewportAddress |
| .subtract(BigInteger.valueOf(getAddressableCellsPerRow() * (Rendering.this.getRowCount() - 1))); |
| ensureViewportAddressDisplayable(); |
| redrawPanes(); |
| } |
| |
| protected SelectionListener createHorizontalBarSelectionListener() { |
| return new SelectionListener() { |
| @Override |
| public void widgetSelected(SelectionEvent se) { |
| Rendering.this.layout(); |
| } |
| |
| @Override |
| public void widgetDefaultSelected(SelectionEvent se) { |
| // do nothing |
| } |
| }; |
| } |
| |
| protected SelectionListener createVerticalBarSelectinListener() { |
| return new SelectionListener() { |
| @Override |
| public void widgetSelected(SelectionEvent se) { |
| switch (se.detail) { |
| case SWT.ARROW_DOWN: |
| handleDownArrow(); |
| break; |
| case SWT.PAGE_DOWN: |
| handlePageDown(); |
| break; |
| case SWT.ARROW_UP: |
| handleUpArrow(); |
| break; |
| case SWT.PAGE_UP: |
| handlePageUp(); |
| break; |
| case SWT.SCROLL_LINE: |
| // See: BUG 203068 selection event details broken on GTK < 2.6 |
| default: |
| // Figure out the delta, ignore events with no delta |
| int deltaScroll = getVerticalBar().getSelection() - fCurrentScrollSelection; |
| if (deltaScroll == 0) |
| break; |
| |
| fViewportAddress = fViewportAddress.add( |
| BigInteger.valueOf(getAddressableCellsPerRow()).multiply(BigInteger.valueOf(deltaScroll))); |
| |
| ensureViewportAddressDisplayable(); |
| // Update tooltip |
| // FIXME conversion from slider to scrollbar |
| // getVerticalBar().setToolTipText(Rendering.this.getAddressString(fViewportAddress)); |
| |
| // Update the addresses on the Address pane. |
| if (fAddressPane.isPaneVisible()) { |
| fAddressPane.redraw(); |
| } |
| redrawPanes(); |
| break; |
| } |
| |
| } |
| |
| @Override |
| public void widgetDefaultSelected(SelectionEvent se) { |
| // do nothing |
| } |
| }; |
| } |
| |
| protected AddressPane createAddressPane() { |
| return new AddressPane(this); |
| } |
| |
| protected DataPane createDataPane() { |
| return new DataPane(this); |
| } |
| |
| protected TextPane createTextPane() { |
| return new TextPane(this); |
| } |
| |
| public TraditionalRendering getTraditionalRendering() // TODO rename |
| { |
| return fParent; |
| } |
| |
| protected void setCaretAddress(BigInteger address) { |
| fCaretAddress = address; |
| } |
| |
| protected BigInteger getCaretAddress() { |
| // Return the caret address if it has been set, otherwise return the |
| // viewport address. When the rendering is first created, the caret is |
| // unset until the user clicks somewhere in the rendering. It also reset |
| // (unset) when the user gives us a new viewport address |
| return (fCaretAddress != null) ? fCaretAddress : fViewportAddress; |
| } |
| |
| private void doGoToAddress() { |
| try { |
| BigInteger address = fAddressBar.getGoToAddress(this.getMemoryBlockStartAddress(), this.getCaretAddress()); |
| getTraditionalRendering().gotoAddress(address); |
| setVisibleAddressBar(false); |
| } catch (NumberFormatException e1) { |
| // FIXME log? |
| } |
| } |
| |
| // Ensure that all addresses displayed are within the addressable range |
| protected void ensureViewportAddressDisplayable() { |
| if (fViewportAddress.compareTo(Rendering.this.getMemoryBlockStartAddress()) < 0) { |
| fViewportAddress = Rendering.this.getMemoryBlockStartAddress(); |
| } else if (getViewportEndAddress().compareTo(getMemoryBlockEndAddress().add(BigInteger.ONE)) > 0) { |
| fViewportAddress = getMemoryBlockEndAddress() |
| .subtract(BigInteger.valueOf(getAddressableCellsPerRow() * getRowCount() - 1)); |
| } |
| |
| setCurrentScrollSelection(); |
| } |
| |
| public IMemorySelection getSelection() { |
| return fSelection; |
| } |
| |
| protected int getHistoryDepth() { |
| return fViewportCache.getHistoryDepth(); |
| } |
| |
| protected void setHistoryDepth(int depth) { |
| fViewportCache.setHistoryDepth(depth); |
| } |
| |
| public void logError(String message, Exception e) { |
| Status status = new Status(IStatus.ERROR, fParent.getRenderingId(), DebugException.INTERNAL_ERROR, message, e); |
| |
| TraditionalRenderingPlugin.getDefault().getLog().log(status); |
| } |
| |
| public void handleFontPreferenceChange(Font font) { |
| setFont(font); |
| |
| Control controls[] = this.getRenderingPanes(); |
| for (int i = 0; i < controls.length; i++) |
| controls[i].setFont(font); |
| |
| packColumns(); |
| layout(true); |
| } |
| |
| public void setPaddingString(String padding) { |
| fPaddingString = padding; |
| |
| refresh(); |
| } |
| |
| public char getPaddingCharacter() { |
| return fPaddingString.charAt(0); // use only the first character |
| } |
| |
| static int suspendCount = 0; |
| |
| @Override |
| public void handleDebugEvents(DebugEvent[] events) { |
| if (this.isDisposed()) |
| return; |
| |
| boolean isChangeOnly = false; |
| boolean isSuspend = false; |
| boolean isBreakpointHit = false; |
| |
| for (int i = 0; i < events.length; i++) { |
| if (events[0].getSource() instanceof IDebugElement) { |
| final int kind = events[i].getKind(); |
| final int detail = events[i].getDetail(); |
| final IDebugElement source = (IDebugElement) events[i].getSource(); |
| |
| if (source.getDebugTarget() == getMemoryBlock().getDebugTarget()) { |
| /* For CDT/DSF or CDT/TCF the IDebugTarget interface is not longer used, |
| * and returns null. In such a case, we should check that we are dealing |
| * with the correct memory block instead. |
| */ |
| if (source.getDebugTarget() != null || source.equals(getMemoryBlock())) { |
| if ((detail & DebugEvent.BREAKPOINT) != 0) |
| isBreakpointHit = true; |
| if (kind == DebugEvent.SUSPEND) { |
| handleSuspendEvent(detail); |
| isSuspend = true; |
| } else if (kind == DebugEvent.CHANGE) { |
| handleChangeEvent(); |
| isChangeOnly = true; |
| } |
| } |
| } |
| } |
| } |
| |
| if (isSuspend) |
| handleSuspend(isBreakpointHit); |
| else if (isChangeOnly) |
| handleChange(); |
| } |
| |
| protected void handleSuspend(boolean isBreakpointHit) { |
| if (getUpdateMode() == UPDATE_ALWAYS || (getUpdateMode() == UPDATE_ON_BREAKPOINT && isBreakpointHit)) { |
| Display.getDefault().asyncExec(() -> { |
| archiveDeltas(); |
| refresh(); |
| }); |
| } |
| } |
| |
| protected void handleChange() { |
| if (getUpdateMode() == UPDATE_ALWAYS) { |
| Display.getDefault().asyncExec(() -> { |
| archiveDeltas(); |
| refresh(); |
| }); |
| } |
| } |
| |
| protected void handleSuspendEvent(int detail) { |
| } |
| |
| protected void handleChangeEvent() { |
| } |
| |
| // return true to enable development debug print statements |
| public boolean isDebug() { |
| return false; |
| } |
| |
| protected IMemoryBlockExtension getMemoryBlock() { |
| IMemoryBlock block = fParent.getMemoryBlock(); |
| if (block != null) |
| return block.getAdapter(IMemoryBlockExtension.class); |
| |
| return null; |
| } |
| |
| public BigInteger getBigBaseAddress() { |
| return fParent.getBigBaseAddress(); |
| } |
| |
| public int getAddressableSize() { |
| return fParent.getAddressableSize(); |
| } |
| |
| protected IViewportCache getViewportCache() { |
| return fViewportCache; |
| } |
| |
| public TraditionalMemoryByte[] getBytes(BigInteger address, int bytes) throws DebugException { |
| return getViewportCache().getBytes(address, bytes); |
| } |
| |
| // default visibility for performance |
| ViewportCache fViewportCache = new ViewportCache(); |
| |
| private BigInteger fScrollStartAddress = BigInteger.ZERO; |
| |
| private interface Request { |
| } |
| |
| class ViewportCache extends Thread implements IViewportCache { |
| class ArchiveDeltas implements Request { |
| |
| } |
| |
| class AddressPair implements Request { |
| BigInteger startAddress; |
| |
| BigInteger endAddress; |
| |
| public AddressPair(BigInteger start, BigInteger end) { |
| startAddress = start; |
| endAddress = end; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null) |
| return false; |
| if (obj instanceof AddressPair) { |
| return ((AddressPair) obj).startAddress.equals(startAddress) |
| && ((AddressPair) obj).endAddress.equals(endAddress); |
| } |
| |
| return false; |
| } |
| |
| } |
| |
| class MemoryUnit { |
| BigInteger start; |
| |
| BigInteger end; |
| |
| TraditionalMemoryByte[] bytes; |
| |
| @Override |
| public MemoryUnit clone() { |
| MemoryUnit b = new MemoryUnit(); |
| |
| b.start = this.start; |
| b.end = this.end; |
| b.bytes = new TraditionalMemoryByte[this.bytes.length]; |
| for (int i = 0; i < this.bytes.length; i++) |
| b.bytes[i] = new TraditionalMemoryByte(this.bytes[i].getValue()); |
| |
| return b; |
| } |
| |
| public boolean isValid() { |
| return this.start != null && this.end != null && this.bytes != null; |
| } |
| } |
| |
| private HashMap<BigInteger, TraditionalMemoryByte[]> fEditBuffer = new HashMap<>(); |
| |
| private boolean fDisposed = false; |
| |
| private Object fLastQueued = null; |
| |
| private Vector<Object> fQueue = new Vector<>(); |
| |
| protected MemoryUnit fCache = null; |
| |
| protected MemoryUnit fHistoryCache[] = new MemoryUnit[0]; |
| |
| protected int fHistoryDepth = 0; |
| |
| public ViewportCache() { |
| start(); |
| } |
| |
| @Override |
| public void dispose() { |
| fDisposed = true; |
| synchronized (fQueue) { |
| fQueue.notify(); |
| } |
| } |
| |
| public int getHistoryDepth() { |
| return fHistoryDepth; |
| } |
| |
| public void setHistoryDepth(int depth) { |
| fHistoryDepth = depth; |
| fHistoryCache = new MemoryUnit[fHistoryDepth]; |
| } |
| |
| @Override |
| public void refresh() { |
| assert Thread.currentThread().equals(Display.getDefault().getThread()) : TraditionalRenderingMessages |
| .getString("TraditionalRendering.CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$ |
| |
| if (fCache != null) { |
| queueRequest(fViewportAddress, getViewportEndAddress()); |
| } |
| } |
| |
| @Override |
| public void archiveDeltas() { |
| assert Thread.currentThread().equals(Display.getDefault().getThread()) : TraditionalRenderingMessages |
| .getString("TraditionalRendering.CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$ |
| |
| if (fCache != null) { |
| queueRequestArchiveDeltas(); |
| } |
| } |
| |
| private void queueRequest(BigInteger startAddress, BigInteger endAddress) { |
| AddressPair pair = new AddressPair(startAddress, endAddress); |
| queue(pair); |
| } |
| |
| private void queueRequestArchiveDeltas() { |
| ArchiveDeltas archive = new ArchiveDeltas(); |
| queue(archive); |
| } |
| |
| private void queue(Object element) { |
| synchronized (fQueue) { |
| if (!(fQueue.size() > 0 && element.equals(fLastQueued))) { |
| fQueue.addElement(element); |
| fLastQueued = element; |
| } |
| fQueue.notify(); |
| } |
| } |
| |
| @Override |
| public void run() { |
| while (!fDisposed) { |
| AddressPair pair = null; |
| boolean archiveDeltas = false; |
| synchronized (fQueue) { |
| if (fQueue.size() > 0) { |
| Request request = (Request) fQueue.elementAt(0); |
| Class<?> type = request.getClass(); |
| |
| while (fQueue.size() > 0 && type.isInstance(fQueue.elementAt(0))) { |
| request = (Request) fQueue.elementAt(0); |
| fQueue.removeElementAt(0); |
| } |
| |
| if (request instanceof ArchiveDeltas) |
| archiveDeltas = true; |
| else if (request instanceof AddressPair) |
| pair = (AddressPair) request; |
| } |
| } |
| if (archiveDeltas) { |
| for (int i = fViewportCache.getHistoryDepth() - 1; i > 0; i--) |
| fHistoryCache[i] = fHistoryCache[i - 1]; |
| |
| fHistoryCache[0] = fCache.clone(); |
| } else if (pair != null) { |
| populateCache(pair.startAddress, pair.endAddress); |
| } else { |
| synchronized (fQueue) { |
| try { |
| if (fQueue.isEmpty()) { |
| fQueue.wait(); |
| } |
| } catch (Exception e) { |
| // do nothing |
| } |
| } |
| } |
| } |
| } |
| |
| // cache memory necessary to paint viewport |
| // TODO: user setting to buffer +/- x lines |
| // TODO: reuse existing cache? probably only a minor performance gain |
| private void populateCache(final BigInteger startAddress, final BigInteger endAddress) { |
| try { |
| IMemoryBlockExtension memoryBlock = getMemoryBlock(); |
| |
| final BigInteger addressableSize = BigInteger.valueOf(getAddressableSize()); |
| BigInteger lengthInBytes = endAddress.subtract(startAddress).multiply(addressableSize); |
| |
| long units = lengthInBytes.divide(addressableSize) |
| .add(lengthInBytes.mod(addressableSize).compareTo(BigInteger.ZERO) > 0 ? BigInteger.ONE |
| : BigInteger.ZERO) |
| .longValue(); |
| |
| // CDT (and maybe other backends) will call setValue() on these MemoryBlock objects. |
| // We don't want this to happen, because it interferes with this rendering's own |
| // change history. Ideally, we should strictly use the back end change notification |
| // and history, but it is only guaranteed to work for bytes within the address range |
| // of the MemoryBlock. |
| MemoryByte readBytes[] = memoryBlock.getBytesFromAddress(startAddress, units); |
| |
| TraditionalMemoryByte cachedBytes[] = new TraditionalMemoryByte[readBytes.length]; |
| for (int i = 0; i < readBytes.length; i++) |
| cachedBytes[i] = new TraditionalMemoryByte(readBytes[i].getValue(), readBytes[i].getFlags()); |
| |
| // derive the target endian from the read MemoryBytes. |
| if (cachedBytes.length > 0) { |
| if (cachedBytes[0].isEndianessKnown()) { |
| setTargetLittleEndian(!cachedBytes[0].isBigEndian()); |
| } |
| } |
| |
| // reorder bytes within unit to be a sequential byte stream if the endian is already little |
| if (isTargetLittleEndian()) { |
| // there isn't an order when the unit size is one, so skip for performance |
| if (addressableSize.compareTo(BigInteger.ONE) != 0) { |
| int unitSize = addressableSize.intValue(); |
| TraditionalMemoryByte cachedBytesAsByteSequence[] = new TraditionalMemoryByte[cachedBytes.length]; |
| for (int unit = 0; unit < units; unit++) { |
| for (int unitbyte = 0; unitbyte < unitSize; unitbyte++) { |
| cachedBytesAsByteSequence[unit * unitSize + unitbyte] = cachedBytes[unit * unitSize |
| + unitSize - unitbyte]; |
| } |
| } |
| cachedBytes = cachedBytesAsByteSequence; |
| } |
| } |
| |
| final TraditionalMemoryByte[] cachedBytesFinal = cachedBytes; |
| |
| fCache = new MemoryUnit(); |
| fCache.start = startAddress; |
| fCache.end = endAddress; |
| fCache.bytes = cachedBytesFinal; |
| |
| Display.getDefault().asyncExec(() -> { |
| // generate deltas |
| for (int historyIndex = 0; historyIndex < getHistoryDepth(); historyIndex++) { |
| if (fHistoryCache[historyIndex] != null && fHistoryCache[historyIndex].isValid()) { |
| BigInteger maxStart = startAddress.max(fHistoryCache[historyIndex].start); |
| BigInteger minEnd = endAddress.min(fHistoryCache[historyIndex].end) |
| .subtract(BigInteger.valueOf(1)); |
| |
| BigInteger overlapLength = minEnd.subtract(maxStart).multiply(addressableSize); |
| if (overlapLength.compareTo(BigInteger.valueOf(0)) > 0) { |
| // there is overlap |
| |
| int offsetIntoOld = (maxStart.subtract(fHistoryCache[historyIndex].start) |
| .multiply(addressableSize)).intValue(); |
| int offsetIntoNew = maxStart.subtract(startAddress).multiply(addressableSize) |
| .intValue(); |
| |
| for (int i = overlapLength.intValue(); i >= 0; i--) { |
| cachedBytesFinal[offsetIntoNew + i].setChanged(historyIndex, |
| cachedBytesFinal[offsetIntoNew + i] |
| .getValue() != fHistoryCache[historyIndex].bytes[offsetIntoOld + i] |
| .getValue()); |
| } |
| } |
| } |
| } |
| |
| // If the history does not exist, populate the history with the just populated cache. This solves the |
| // use case of 1) connect to target; 2) edit memory before the first suspend debug event; 3) paint |
| // differences in changed color. |
| if (fHistoryCache[0] == null) |
| fHistoryCache[0] = fCache.clone(); |
| |
| Rendering.this.redrawPanes(); |
| }); |
| |
| } catch (Exception e) { |
| // User can scroll to any memory, whether it's valid on the |
| // target or not. Doesn't make much sense to fill up the Eclipse |
| // error log with such "failures". |
| // logError( |
| // TraditionalRenderingMessages |
| // .getString("TraditionalRendering.FAILURE_READ_MEMORY"), e); //$NON-NLS-1$ |
| } |
| } |
| |
| // bytes will be fetched from cache |
| @Override |
| public TraditionalMemoryByte[] getBytes(BigInteger address, int bytesRequested) throws DebugException { |
| assert Thread.currentThread().equals(Display.getDefault().getThread()) : TraditionalRenderingMessages |
| .getString("TraditionalRendering.CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$ |
| |
| //calculate the number of units needed for the number of requested bytes |
| int rem = (bytesRequested % getAddressableSize()) > 0 ? 1 : 0; |
| int units = bytesRequested / getAddressableSize() + rem; |
| |
| if (containsEditedCell(address)) // cell size cannot be switched during an edit |
| return getEditedMemory(address); |
| |
| boolean contains = false; |
| if (fCache != null && fCache.start != null) { |
| // see if all of the data requested is in the cache |
| BigInteger dataEnd = address.add(BigInteger.valueOf(units)); |
| |
| if (fCache.start.compareTo(address) <= 0 && fCache.end.compareTo(dataEnd) >= 0 |
| && fCache.bytes.length > 0) |
| contains = true; |
| } |
| |
| if (contains) { |
| int offset = address.subtract(fCache.start).multiply(BigInteger.valueOf(getAddressableSize())) |
| .intValue(); |
| TraditionalMemoryByte bytes[] = new TraditionalMemoryByte[bytesRequested]; |
| for (int i = 0; i < bytes.length; i++) { |
| bytes[i] = fCache.bytes[offset + i]; |
| } |
| |
| return bytes; |
| } |
| |
| TraditionalMemoryByte bytes[] = new TraditionalMemoryByte[bytesRequested]; |
| for (int i = 0; i < bytes.length; i++) { |
| bytes[i] = new TraditionalMemoryByte(); |
| bytes[i].setReadable(false); |
| } |
| |
| fViewportCache.queueRequest(fViewportAddress, getViewportEndAddress()); |
| |
| return bytes; |
| } |
| |
| @Override |
| public boolean containsEditedCell(BigInteger address) { |
| assert Thread.currentThread().equals(Display.getDefault().getThread()) : TraditionalRenderingMessages |
| .getString("TraditionalRendering.CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$ |
| |
| return fEditBuffer.containsKey(address); |
| } |
| |
| private TraditionalMemoryByte[] getEditedMemory(BigInteger address) { |
| assert Thread.currentThread().equals(Display.getDefault().getThread()) : TraditionalRenderingMessages |
| .getString("TraditionalRendering.CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$ |
| |
| return fEditBuffer.get(address); |
| } |
| |
| @Override |
| public void clearEditBuffer() { |
| assert Thread.currentThread().equals(Display.getDefault().getThread()) : TraditionalRenderingMessages |
| .getString("TraditionalRendering.CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$ |
| |
| fEditBuffer.clear(); |
| Rendering.this.redrawPanes(); |
| } |
| |
| @Override |
| public void writeEditBuffer() { |
| assert Thread.currentThread().equals(Display.getDefault().getThread()) : TraditionalRenderingMessages |
| .getString("TraditionalRendering.CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$ |
| |
| Set<BigInteger> keySet = fEditBuffer.keySet(); |
| Iterator<BigInteger> iterator = keySet.iterator(); |
| |
| while (iterator.hasNext()) { |
| BigInteger address = iterator.next(); |
| TraditionalMemoryByte[] bytes = fEditBuffer.get(address); |
| |
| byte byteValue[] = new byte[bytes.length]; |
| for (int i = 0; i < bytes.length; i++) |
| byteValue[i] = bytes[i].getValue(); |
| |
| try { |
| IMemoryBlockExtension block = getMemoryBlock(); |
| BigInteger offset = address.subtract(block.getBigBaseAddress()); |
| block.setValue(offset, byteValue); |
| } catch (Exception e) { |
| MemoryViewUtil.openError( |
| TraditionalRenderingMessages.getString("TraditionalRendering.FAILURE_WRITE_MEMORY"), "", e); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| logError(TraditionalRenderingMessages.getString("TraditionalRendering.FAILURE_WRITE_MEMORY"), e); //$NON-NLS-1$ |
| } |
| } |
| |
| clearEditBuffer(); |
| } |
| |
| @Override |
| public void setEditedValue(BigInteger address, TraditionalMemoryByte[] bytes) { |
| assert Thread.currentThread().equals(Display.getDefault().getThread()) : TraditionalRenderingMessages |
| .getString("TraditionalRendering.CALLED_ON_NON_DISPATCH_THREAD"); //$NON-NLS-1$ |
| |
| fEditBuffer.put(address, bytes); |
| Rendering.this.redrawPanes(); |
| } |
| } |
| |
| public void setVisibleAddressBar(boolean visible) { |
| fAddressBarControl.setVisible(visible); |
| if (visible) { |
| String selectedStr = "0x" + getCaretAddress().toString(16); //$NON-NLS-1$ |
| Text text = fAddressBar.getExpressionWidget(); |
| text.setText(selectedStr); |
| text.setSelection(0, text.getCharCount()); |
| fAddressBar.getExpressionWidget().setFocus(); |
| } |
| |
| layout(true); |
| layoutPanes(); |
| } |
| |
| public void setDirty(boolean needRefresh) { |
| fCacheDirty = needRefresh; |
| } |
| |
| public boolean isDirty() { |
| return fCacheDirty; |
| } |
| |
| @Override |
| public void dispose() { |
| DebugPlugin.getDefault().removeDebugEventListener(this); |
| if (fViewportCache != null) { |
| fViewportCache.dispose(); |
| fViewportCache = null; |
| } |
| |
| fMapStartAddrToInfoItems.clear(); |
| fMapAddrToInfoItems.clear(); |
| super.dispose(); |
| } |
| |
| class Selection implements IMemorySelection { |
| private BigInteger fStartHigh; |
| private BigInteger fStartLow; |
| |
| private BigInteger fEndHigh; |
| private BigInteger fEndLow; |
| |
| @Override |
| public void clear() { |
| fEndHigh = fEndLow = fStartHigh = fStartLow = null; |
| redrawPanes(); |
| } |
| |
| @Override |
| public boolean hasSelection() { |
| return fStartHigh != null && fStartLow != null && fEndHigh != null && fEndLow != null; |
| } |
| |
| @Override |
| public boolean isSelected(BigInteger address) { |
| // do we have valid start and end addresses |
| if (getEnd() == null || getStart() == null) |
| return false; |
| |
| // if end is greater than start |
| if (getEnd().compareTo(getStart()) >= 0) { |
| if (address.compareTo(getStart()) >= 0 && address.compareTo(getEnd()) < 0) |
| return true; |
| } |
| // if start is greater than end |
| else if (getStart().compareTo(getEnd()) >= 0) { |
| if (address.compareTo(getEnd()) >= 0 && address.compareTo(getStart()) < 0) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public void setStart(BigInteger high, BigInteger low) { |
| if (high == null && low == null) { |
| if (fStartHigh != null && fStartLow != null) { |
| fStartHigh = null; |
| fStartLow = null; |
| redrawPanes(); |
| } |
| |
| return; |
| } |
| |
| boolean changed = false; |
| |
| if (fStartHigh == null || !fStartHigh.equals(high)) { |
| fStartHigh = high; |
| changed = true; |
| } |
| |
| if (fStartLow == null || !low.equals(fStartLow)) { |
| fStartLow = low; |
| changed = true; |
| } |
| |
| if (changed) |
| redrawPanes(); |
| } |
| |
| @Override |
| public void setEnd(BigInteger high, BigInteger low) { |
| if (high == null && low == null) { |
| if (fEndHigh != null && fEndLow != null) { |
| fEndHigh = null; |
| fEndLow = null; |
| redrawPanes(); |
| } |
| |
| return; |
| } |
| |
| boolean changed = false; |
| |
| if (fEndHigh == null || !fEndHigh.equals(high)) { |
| fEndHigh = high; |
| changed = true; |
| } |
| |
| if (fEndLow == null || !low.equals(fEndLow)) { |
| fEndLow = low; |
| changed = true; |
| } |
| |
| if (changed) |
| redrawPanes(); |
| } |
| |
| @Override |
| public BigInteger getHigh() { |
| if (!hasSelection()) |
| return null; |
| |
| return getStart().max(getEnd()); |
| } |
| |
| @Override |
| public BigInteger getLow() { |
| if (!hasSelection()) |
| return null; |
| |
| return getStart().min(getEnd()); |
| } |
| |
| @Override |
| public BigInteger getStart() { |
| // if there is no start, return null |
| if (fStartHigh == null) |
| return null; |
| |
| // if there is no end, return the high address of the start |
| if (fEndHigh == null) |
| return fStartHigh; |
| |
| // if Start High/Low equal End High/Low, return a low start and high end |
| if (fStartHigh.equals(fEndHigh) && fStartLow.equals(fEndLow)) |
| return fStartLow; |
| |
| BigInteger differenceEndToStartHigh = fEndHigh.subtract(fStartHigh).abs(); |
| BigInteger differenceEndToStartLow = fEndHigh.subtract(fStartLow).abs(); |
| |
| // return the start high or start low based on which creates a larger selection |
| if (differenceEndToStartHigh.compareTo(differenceEndToStartLow) > 0) |
| return fStartHigh; |
| else |
| return fStartLow; |
| } |
| |
| @Override |
| public BigInteger getStartLow() { |
| return fStartLow; |
| } |
| |
| @Override |
| public BigInteger getEnd() { |
| // if there is no end, return null |
| if (fEndHigh == null) |
| return null; |
| |
| // if Start High/Low equal End High/Low, return a low start and high end |
| if (fStartHigh.equals(fEndHigh) && fStartLow.equals(fEndLow)) |
| return fStartHigh; |
| |
| BigInteger differenceStartToEndHigh = fStartHigh.subtract(fEndHigh).abs(); |
| BigInteger differenceStartToEndLow = fStartHigh.subtract(fEndLow).abs(); |
| |
| // return the start high or start low based on which creates a larger selection |
| if (differenceStartToEndHigh.compareTo(differenceStartToEndLow) >= 0) |
| return fEndHigh; |
| else |
| return fEndLow; |
| } |
| } |
| |
| public void setPaneVisible(int pane, boolean visible) { |
| switch (pane) { |
| case PANE_ADDRESS: |
| fAddressPane.setPaneVisible(visible); |
| break; |
| case PANE_BINARY: |
| fBinaryPane.setPaneVisible(visible); |
| break; |
| case PANE_TEXT: |
| fTextPane.setPaneVisible(visible); |
| break; |
| } |
| |
| fireSettingsChanged(); |
| layoutPanes(); |
| } |
| |
| public boolean getPaneVisible(int pane) { |
| switch (pane) { |
| case PANE_ADDRESS: |
| return fAddressPane.isPaneVisible(); |
| case PANE_BINARY: |
| return fBinaryPane.isPaneVisible(); |
| case PANE_TEXT: |
| return fTextPane.isPaneVisible(); |
| default: |
| return false; |
| } |
| } |
| |
| protected void packColumns() { |
| int availableWidth = Rendering.this.getSize().x; |
| |
| if (fAddressPane.isPaneVisible()) { |
| availableWidth -= fAddressPane.computeSize(0, 0).x; |
| availableWidth -= Rendering.this.getRenderSpacing() * 2; |
| } |
| |
| int combinedWidth = 0; |
| |
| if (fBinaryPane.isPaneVisible()) |
| combinedWidth += fBinaryPane.getCellWidth(); |
| |
| if (fTextPane.isPaneVisible()) |
| combinedWidth += fTextPane.getCellWidth(); |
| |
| if (getColumnsSetting() == Rendering.COLUMNS_AUTO_SIZE_TO_FIT) { |
| if (combinedWidth == 0) |
| fColumnCount = 0; |
| else { |
| fColumnCount = availableWidth / combinedWidth; |
| if (fColumnCount == 0) |
| fColumnCount = 1; // paint one column even if only part can show in view |
| } |
| } else { |
| fColumnCount = getColumnsSetting(); |
| } |
| |
| try { |
| // Update the number of bytes per row; |
| // the max and min scroll range and the current thumb nail position. |
| fBytesPerRow = getBytesPerColumn() * getColumnCount(); |
| |
| getVerticalBar().setMinimum(1); |
| // scrollbar maximum range is Integer.MAX_VALUE. |
| getVerticalBar().setMaximum(getMaxScrollLines()); |
| getVerticalBar().setIncrement(1); |
| getVerticalBar().setPageIncrement(this.getRowCount() - 1); |
| //TW FIXME conversion of slider to scrollbar |
| // fScrollBar.setToolTipText(Rendering.this.getAddressString(fViewportAddress)); |
| setCurrentScrollSelection(); |
| } catch (Exception e) { |
| // FIXME precautionary |
| } |
| |
| Rendering.this.redraw(); |
| Rendering.this.redrawPanes(); |
| } |
| |
| public AbstractPane[] getRenderingPanes() { |
| return new AbstractPane[] { fAddressPane, fBinaryPane, fTextPane }; |
| } |
| |
| public int getCellPadding() { |
| return fCellPadding; |
| } |
| |
| protected int getRenderSpacing() { |
| return fPaneSpacing; |
| } |
| |
| public void refresh() { |
| if (!this.isDisposed()) { |
| if (this.isVisible() && getViewportCache() != null) { |
| getViewportCache().refresh(); |
| } else { |
| setDirty(true); |
| fParent.updateRenderingLabels(); |
| } |
| } |
| } |
| |
| protected void archiveDeltas() { |
| this.getViewportCache().archiveDeltas(); |
| } |
| |
| public void gotoAddress(BigInteger address) { |
| // Ensure that the GoTo address is within the addressable range |
| if ((address.compareTo(this.getMemoryBlockStartAddress()) < 0) |
| || (address.compareTo(this.getMemoryBlockEndAddress()) > 0)) { |
| return; |
| } |
| |
| fViewportAddress = address; |
| |
| // reset the caret and selection state (no caret and no selection) |
| fCaretAddress = null; |
| fSelection = new Selection(); |
| |
| ensureViewportAddressDisplayable(); |
| redrawPanes(); |
| } |
| |
| public void setViewportStartAddress(BigInteger newAddress) { |
| fViewportAddress = newAddress; |
| } |
| |
| public BigInteger getViewportStartAddress() { |
| return fViewportAddress; |
| } |
| |
| public BigInteger getViewportEndAddress() { |
| return fViewportAddress.add(BigInteger.valueOf(this.getBytesPerRow() * getRowCount() / getAddressableSize())); |
| } |
| |
| private BigInteger getScrollStartAddress() { |
| return fScrollStartAddress; |
| } |
| |
| private void setScrollStartAddress(BigInteger newAddress) { |
| fScrollStartAddress = newAddress; |
| } |
| |
| private BigInteger getScrollEndAddress() { |
| int scrollLines = getMaxScrollLines(); |
| // substract scroll thumb as it is not possible to scroll beyond it |
| scrollLines -= getVerticalBar().getThumb(); |
| |
| BigInteger newAddress = getScrollStartAddress() |
| .add(BigInteger.valueOf(getAddressableCellsPerRow()).multiply(BigInteger.valueOf(scrollLines))); |
| |
| return newAddress.min(getMemoryBlockEndAddress()); |
| } |
| |
| public String getAddressString(BigInteger address) { |
| StringBuilder addressString = new StringBuilder(address.toString(16).toUpperCase()); |
| for (int chars = getAddressBytes() * 2 - addressString.length(); chars > 0; chars--) { |
| addressString.insert(0, '0'); |
| } |
| addressString.insert(0, "0x"); //$NON-NLS-1$ |
| |
| return addressString.toString(); |
| } |
| |
| protected int getAddressBytes() { |
| return fParent.getAddressSize(); |
| } |
| |
| public Control getAddressBarControl() { |
| return fAddressBarControl; |
| } |
| |
| public int getColumnCount() { |
| return fColumnCount; |
| } |
| |
| public int getColumnsSetting() { |
| return fColumnsSetting; |
| } |
| |
| protected void setBytesPerRow(int count) { |
| fBytesPerRow = count; |
| } |
| |
| protected void setColumnCount(int count) { |
| fColumnCount = count; |
| } |
| |
| public void setColumnsSetting(int columns) { |
| if (fColumnsSetting != columns) { |
| fColumnsSetting = columns; |
| fireSettingsChanged(); |
| layoutPanes(); |
| } |
| } |
| |
| protected void ensureVisible(BigInteger address) { |
| BigInteger viewportStart = this.getViewportStartAddress(); |
| BigInteger viewportEnd = this.getViewportEndAddress(); |
| |
| boolean isAddressBeforeViewportStart = address.compareTo(viewportStart) < 0; |
| boolean isAddressAfterViewportEnd = address.compareTo(viewportEnd) > 0; |
| |
| if (isAddressBeforeViewportStart || isAddressAfterViewportEnd) |
| gotoAddress(address); |
| } |
| |
| protected int getRowCount() { |
| int rowCount = 0; |
| Control panes[] = getRenderingPanes(); |
| for (int i = 0; i < panes.length; i++) { |
| if (panes[i] instanceof AbstractPane) |
| rowCount = Math.max(rowCount, ((AbstractPane) panes[i]).getRowCount()); |
| } |
| |
| // Add an extra row of information as we can present part of the information on |
| // the remaining space of the canvas |
| int extra = 1; |
| return rowCount + extra; |
| } |
| |
| public int getBytesPerColumn() { |
| return fBytesPerColumn; |
| } |
| |
| protected int getBytesPerRow() { |
| return fBytesPerRow; |
| } |
| |
| protected int getAddressableCellsPerRow() { |
| return getBytesPerRow() / getAddressableSize(); |
| } |
| |
| public int getAddressesPerColumn() { |
| return this.getBytesPerColumn() / getAddressableSize(); |
| } |
| |
| /** |
| * Update current scroll selection to match current viewport range |
| */ |
| protected void setCurrentScrollSelection() { |
| BigInteger viewportStartAddress = getViewportStartAddress(); |
| if (TraceOptions.DEBUG) { |
| TraceOptions.trace(MessageFormat.format("Update scroll for viewrange[0x{0} : 0x{1}]:\n", //$NON-NLS-1$ |
| viewportStartAddress.toString(16), getViewportEndAddress().toString(16))); |
| TraceOptions.trace(MessageFormat.format(" current ScrollRange=[0x{0} : 0x{1}]; selection = {2}\n", //$NON-NLS-1$ |
| getScrollStartAddress().toString(16), getScrollEndAddress().toString(16), fCurrentScrollSelection)); |
| } |
| BigInteger addressableRow = BigInteger.valueOf(getAddressableCellsPerRow()); |
| if ((viewportStartAddress.compareTo(getScrollStartAddress()) <= 0)) { |
| // must reposition scroll area, center position into the center of the new scroll area |
| // unless it is already at the start of first line. |
| |
| int scrollLines = getMaxScrollLines() / 2; |
| BigInteger newScrollStart = BigInteger.ZERO |
| .max(viewportStartAddress.subtract(BigInteger.valueOf(scrollLines).multiply(addressableRow))); |
| setScrollStartAddress(newScrollStart); |
| |
| if (TraceOptions.DEBUG) { |
| TraceOptions.trace(MessageFormat.format(" new ScrollRange=[0x{0} : 0x{1}]\n", //$NON-NLS-1$ |
| getScrollStartAddress().toString(16), getScrollEndAddress().toString(16))); |
| } |
| } else if (getViewportEndAddress().compareTo(getScrollEndAddress()) >= 0) { |
| // must reposition scroll area, center position into the center of the new scroll area |
| // unless when user gets closer to end of addressable size of memory, when leave |
| // scroll start address at end of block memory scroll start |
| |
| BigInteger eomScrollStart = getMemoryBlockEndAddress().add(BigInteger.ONE).subtract( |
| BigInteger.valueOf(getMaxScrollLines() - getVerticalBar().getThumb()).multiply(addressableRow)); |
| |
| int scrollLines = getMaxScrollLines() / 2; |
| BigInteger newScrollStart = eomScrollStart |
| .min(viewportStartAddress.subtract(BigInteger.valueOf(scrollLines).multiply(addressableRow))); |
| setScrollStartAddress(newScrollStart); |
| |
| if (TraceOptions.DEBUG) { |
| TraceOptions.trace(MessageFormat.format(" new ScrollRange=[0x{0} : 0x{1}]\n", //$NON-NLS-1$ |
| getScrollStartAddress().toString(16), getScrollEndAddress().toString(16))); |
| } |
| } |
| |
| // calculate selection line from the start of scroll area |
| fCurrentScrollSelection = viewportStartAddress.subtract(getScrollStartAddress()).divide(addressableRow) |
| .intValue() + 1; |
| if (TraceOptions.DEBUG) { |
| TraceOptions.trace(MessageFormat.format(" new selection={0}\n", fCurrentScrollSelection)); //$NON-NLS-1$ |
| } |
| getVerticalBar().setSelection(fCurrentScrollSelection); |
| } |
| |
| /** |
| * compute the maximum scrolling range. |
| * @return number of lines that rendering can display |
| */ |
| private int getMaxScrollLines() { |
| BigInteger difference = getMemoryBlockEndAddress().subtract(getMemoryBlockStartAddress()).add(BigInteger.ONE); |
| BigInteger maxScrollRange = difference.divide(BigInteger.valueOf(getAddressableCellsPerRow())); |
| if (maxScrollRange.multiply(BigInteger.valueOf(getAddressableCellsPerRow())).compareTo(difference) != 0) |
| maxScrollRange = maxScrollRange.add(BigInteger.ONE); // add a line to hold bytes that do not fill an entire raw |
| |
| // support targets with an addressable size greater than 1 |
| maxScrollRange = maxScrollRange.divide(BigInteger.valueOf(getAddressableSize())); |
| |
| // cap to maximum lines a SWT ScrollBar can hold. |
| return maxScrollRange.min(BigInteger.valueOf(Integer.MAX_VALUE)).intValue(); |
| } |
| |
| /** |
| * @return start address of the memory block |
| */ |
| protected BigInteger getMemoryBlockStartAddress() { |
| if (fMemoryBlockStartAddress == null) |
| fMemoryBlockStartAddress = fParent.getMemoryBlockStartAddress(); |
| if (fMemoryBlockStartAddress == null) |
| fMemoryBlockStartAddress = BigInteger.ZERO; |
| |
| return fMemoryBlockStartAddress; |
| } |
| |
| /** |
| * @return end address of the memory block |
| */ |
| protected BigInteger getMemoryBlockEndAddress() { |
| if (fMemoryBlockEndAddress == null) |
| fMemoryBlockEndAddress = fParent.getMemoryBlockEndAddress(); |
| |
| return fMemoryBlockEndAddress; |
| } |
| |
| public int getRadix() { |
| return fRadix; |
| } |
| |
| protected int getNumericRadix(int radix) { |
| switch (radix) { |
| case RADIX_BINARY: |
| return 2; |
| case RADIX_OCTAL: |
| return 8; |
| case RADIX_DECIMAL_SIGNED: |
| case RADIX_DECIMAL_UNSIGNED: |
| return 10; |
| case RADIX_HEX: |
| return 16; |
| } |
| |
| return -1; |
| } |
| |
| public void setRadix(int mode) { |
| if (fRadix == mode) |
| return; |
| |
| fRadix = mode; |
| fireSettingsChanged(); |
| layoutPanes(); |
| } |
| |
| public void setTextMode(int mode) { |
| fTextMode = mode; |
| |
| fireSettingsChanged(); |
| layoutPanes(); |
| } |
| |
| public int getTextMode() { |
| return fTextMode; |
| } |
| |
| public int getUpdateMode() { |
| return fUpdateMode; |
| } |
| |
| public void setUpdateMode(int fUpdateMode) { |
| this.fUpdateMode = fUpdateMode; |
| } |
| |
| protected String getCharacterSet(int mode) { |
| switch (mode) { |
| case Rendering.TEXT_UTF8: |
| return "UTF8"; //$NON-NLS-1$ |
| case Rendering.TEXT_UTF16: |
| return "UTF16"; //$NON-NLS-1$ |
| case Rendering.TEXT_USASCII: |
| return "US-ASCII"; //$NON-NLS-1$ |
| case Rendering.TEXT_ISO_8859_1: |
| default: |
| return "ISO-8859-1"; //$NON-NLS-1$ |
| } |
| } |
| |
| public int getBytesPerCharacter() { |
| if (fTextMode == Rendering.TEXT_UTF16) |
| return 2; |
| |
| return 1; |
| } |
| |
| public boolean isTargetLittleEndian() { |
| return fIsTargetLittleEndian; |
| } |
| |
| public void setTargetLittleEndian(boolean littleEndian) { |
| if (fIsTargetLittleEndian == littleEndian) |
| return; |
| |
| fParent.setTargetMemoryLittleEndian(littleEndian); |
| fIsTargetLittleEndian = littleEndian; |
| Display.getDefault().asyncExec(() -> { |
| fireSettingsChanged(); |
| layoutPanes(); |
| }); |
| } |
| |
| public boolean isDisplayLittleEndian() { |
| return fIsDisplayLittleEndian; |
| } |
| |
| public void setDisplayLittleEndian(boolean littleEndian) { |
| if (fIsDisplayLittleEndian == littleEndian) |
| return; |
| |
| fIsDisplayLittleEndian = littleEndian; |
| |
| fireSettingsChanged(); |
| Display.getDefault().asyncExec(() -> { |
| if (isShowCrossReferenceInfo()) { |
| resolveAddressInfoForCurrentSelection(); |
| } |
| layoutPanes(); |
| }); |
| } |
| |
| public void setBytesPerColumn(int byteCount) { |
| if (fBytesPerColumn != byteCount) { |
| fBytesPerColumn = byteCount; |
| fireSettingsChanged(); |
| layoutPanes(); |
| } |
| } |
| |
| protected void redrawPane(int paneId) { |
| if (!isDisposed() && this.isVisible()) { |
| AbstractPane pane = null; |
| if (paneId == Rendering.PANE_ADDRESS) { |
| pane = fAddressPane; |
| } else if (paneId == Rendering.PANE_BINARY) { |
| pane = fBinaryPane; |
| } |
| if (paneId == Rendering.PANE_TEXT) { |
| pane = fTextPane; |
| } |
| if (pane != null && pane.isPaneVisible()) { |
| pane.redraw(); |
| pane.setRowCount(); |
| if (pane.isFocusControl()) |
| pane.updateCaret(); |
| } |
| |
| } |
| |
| fParent.updateRenderingLabels(); |
| } |
| |
| protected void redrawPanes() { |
| if (!isDisposed() && this.isVisible()) { |
| if (fAddressPane.isPaneVisible()) { |
| fAddressPane.redraw(); |
| fAddressPane.setRowCount(); |
| if (fAddressPane.isFocusControl()) |
| fAddressPane.updateCaret(); |
| } |
| |
| if (fBinaryPane.isPaneVisible()) { |
| fBinaryPane.redraw(); |
| fBinaryPane.setRowCount(); |
| if (fBinaryPane.isFocusControl()) |
| fBinaryPane.updateCaret(); |
| } |
| |
| if (fTextPane.isPaneVisible()) { |
| fTextPane.redraw(); |
| fTextPane.setRowCount(); |
| if (fTextPane.isFocusControl()) |
| fTextPane.updateCaret(); |
| } |
| } |
| |
| fParent.updateRenderingLabels(); |
| } |
| |
| private void layoutPanes() { |
| packColumns(); |
| layout(true); |
| |
| redraw(); |
| redrawPanes(); |
| } |
| |
| private void fireSettingsChanged() { |
| fAddressPane.settingsChanged(); |
| fBinaryPane.settingsChanged(); |
| fTextPane.settingsChanged(); |
| } |
| |
| protected void copyAddressToClipboard() { |
| Clipboard clip = null; |
| try { |
| clip = new Clipboard(getDisplay()); |
| |
| String addressString = "0x" + getCaretAddress().toString(16); //$NON-NLS-1$ |
| |
| TextTransfer plainTextTransfer = TextTransfer.getInstance(); |
| clip.setContents(new Object[] { addressString }, new Transfer[] { plainTextTransfer }); |
| } finally { |
| if (clip != null) { |
| clip.dispose(); |
| } |
| } |
| } |
| |
| static final char[] hexdigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; |
| |
| public String getRadixText(MemoryByte bytes[], int radix, boolean isLittleEndian) { |
| boolean readableByte = false; |
| boolean allBytesReadable = true; |
| for (int i = 0; i < bytes.length; i++) { |
| if (!bytes[i].isReadable()) { |
| allBytesReadable = false; |
| } else { |
| readableByte = true; |
| } |
| } |
| |
| // convert byte to character if all bytes are readable or |
| // it is a mixed of readable&non-readable bytes and format is Hex or Binary. Bugzilla 342239 |
| if (allBytesReadable || readableByte && (radix == Rendering.RADIX_HEX || radix == Rendering.RADIX_BINARY)) { |
| // bytes from the cache are stored as a sequential byte sequence regardless of target endian. |
| // the endian attribute tells us the recommended endian for display. the user may change this |
| // from the default. if the endian is little, we must swap the byte order. |
| boolean needsSwap = false; |
| if (isDisplayLittleEndian()) |
| needsSwap = true; |
| |
| switch (radix) { |
| case Rendering.RADIX_HEX: |
| case Rendering.RADIX_OCTAL: |
| case Rendering.RADIX_BINARY: { |
| long value = 0; |
| if (needsSwap) { |
| for (int i = 0; i < bytes.length; i++) { |
| value = value << 8; |
| value = value | (bytes[bytes.length - 1 - i].getValue() & 0xFF); |
| } |
| } else { |
| for (int i = 0; i < bytes.length; i++) { |
| value = value << 8; |
| value = value | (bytes[i].getValue() & 0xFF); |
| } |
| } |
| |
| char buf[] = new char[getRadixCharacterCount(radix, bytes.length)]; |
| |
| switch (radix) { |
| case Rendering.RADIX_BINARY: { |
| for (int i = buf.length - 1; i >= 0; i--) { |
| int byteIndex = needsSwap ? bytes.length - 1 - i / 8 : i / 8; |
| if (bytes[byteIndex].isReadable()) { |
| buf[i] = hexdigits[(int) (value & 1)]; |
| } else { |
| buf[i] = getPaddingCharacter(); |
| } |
| value = value >>> 1; |
| } |
| break; |
| } |
| case Rendering.RADIX_OCTAL: { |
| for (int i = buf.length - 1; i >= 0; i--) { |
| buf[i] = hexdigits[(int) (value & 7)]; |
| value = value >>> 3; |
| } |
| break; |
| } |
| case Rendering.RADIX_HEX: { |
| for (int i = buf.length - 1; i >= 0; i--) { |
| int byteIndex = needsSwap ? bytes.length - 1 - i / 2 : i / 2; |
| if (bytes[byteIndex].isReadable()) { |
| buf[i] = hexdigits[(int) (value & 15)]; |
| } else { |
| buf[i] = getPaddingCharacter(); |
| } |
| value = value >>> 4; |
| } |
| break; |
| } |
| } |
| |
| return new String(buf); |
| } |
| case Rendering.RADIX_DECIMAL_UNSIGNED: |
| case Rendering.RADIX_DECIMAL_SIGNED: { |
| boolean isSignedType = radix == Rendering.RADIX_DECIMAL_SIGNED ? true : false; |
| |
| int textWidth = getRadixCharacterCount(radix, bytes.length); |
| |
| char buf[] = new char[textWidth]; |
| |
| byte[] value = new byte[bytes.length + 1]; |
| |
| if (needsSwap) { |
| for (int i = 0; i < bytes.length; i++) { |
| value[bytes.length - i] = bytes[i].getValue(); |
| } |
| } else { |
| for (int i = 0; i < bytes.length; i++) { |
| value[i + 1] = bytes[i].getValue(); |
| } |
| } |
| |
| BigInteger bigValue; |
| boolean isNegative = false; |
| if (isSignedType && (value[1] & 0x80) != 0) { |
| value[0] = -1; |
| isNegative = true; |
| bigValue = new BigInteger(value).abs(); |
| } else { |
| value[0] = 0; |
| bigValue = new BigInteger(value); |
| } |
| |
| for (int i = 0; i < textWidth; i++) { |
| BigInteger divideRemainder[] = bigValue.divideAndRemainder(BigInteger.valueOf(10)); |
| int remainder = divideRemainder[1].intValue(); |
| buf[textWidth - 1 - i] = hexdigits[remainder % 10]; |
| bigValue = divideRemainder[0]; |
| } |
| |
| if (isSignedType) { |
| buf[0] = isNegative ? '-' : ' '; |
| } |
| |
| return new String(buf); |
| } |
| } |
| } |
| |
| StringBuilder errorText = new StringBuilder(); |
| for (int i = getRadixCharacterCount(radix, bytes.length); i > 0; i--) |
| errorText.append(getPaddingCharacter()); |
| |
| return errorText.toString(); |
| } |
| |
| public int getRadixCharacterCount(int radix, int bytes) { |
| switch (radix) { |
| case Rendering.RADIX_HEX: |
| return bytes * 2; |
| case Rendering.RADIX_BINARY: |
| return bytes * 8; |
| case Rendering.RADIX_OCTAL: { |
| switch (bytes) { |
| case 1: |
| return 3; |
| case 2: |
| return 6; |
| case 4: |
| return 11; |
| case 8: |
| return 22; |
| } |
| } |
| case Rendering.RADIX_DECIMAL_UNSIGNED: { |
| switch (bytes) { |
| case 1: |
| return 3; |
| case 2: |
| return 5; |
| case 4: |
| return 10; |
| case 8: |
| return 20; |
| } |
| } |
| case Rendering.RADIX_DECIMAL_SIGNED: { |
| switch (bytes) { |
| case 1: |
| return 4; |
| case 2: |
| return 6; |
| case 4: |
| return 11; |
| case 8: |
| return 21; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| protected static boolean isValidEditCharacter(char character) { |
| return (character >= '0' && character <= '9') || (character >= 'a' && character <= 'z') |
| || (character >= 'A' && character <= 'Z') || character == '-' || character == ' '; |
| } |
| |
| public String formatText(MemoryByte[] memoryBytes, boolean isLittleEndian, int textMode) { |
| // check memory byte for unreadable bytes |
| boolean readable = true; |
| for (int i = 0; i < memoryBytes.length; i++) |
| if (!memoryBytes[i].isReadable()) |
| readable = false; |
| |
| // if any bytes are not readable, return ?'s |
| if (!readable) { |
| StringBuilder errorText = new StringBuilder(); |
| for (int i = memoryBytes.length; i > 0; i--) |
| errorText.append(getPaddingCharacter()); |
| return errorText.toString(); |
| } |
| |
| // TODO |
| // does endian mean anything for text? ah, unicode? |
| |
| // create byte array from MemoryByte array |
| byte bytes[] = new byte[memoryBytes.length]; |
| for (int i = 0; i < bytes.length; i++) { |
| bytes[i] = memoryBytes[i].getValue(); |
| } |
| |
| // replace invalid characters with '.' |
| // maybe there is a way to query the character set for |
| // valid characters? |
| |
| // replace invalid US-ASCII with '.' |
| if (textMode == Rendering.TEXT_USASCII) { |
| for (int i = 0; i < bytes.length; i++) { |
| int byteValue = bytes[i]; |
| if (byteValue < 0) |
| byteValue += 256; |
| |
| if (byteValue < 0x20 || byteValue > 0x7e) |
| bytes[i] = '.'; |
| } |
| } |
| |
| // replace invalid ISO-8859-1 with '.' |
| if (textMode == Rendering.TEXT_ISO_8859_1) { |
| for (int i = 0; i < bytes.length; i++) { |
| int byteValue = bytes[i]; |
| if (byteValue < 0) |
| byteValue += 256; |
| |
| if (byteValue < 0x20 || (byteValue >= 0x7f && byteValue < 0x9f)) |
| bytes[i] = '.'; |
| } |
| } |
| |
| try { |
| // convert bytes to string using desired character set |
| StringBuilder buf = new StringBuilder(new String(bytes, this.getCharacterSet(textMode))); |
| |
| // pad string to (byte count - string length) with spaces |
| for (int i = 0; i < memoryBytes.length - buf.length(); i++) |
| buf.append(' '); |
| return buf.toString(); |
| } catch (Exception e) { |
| // return ?s the length of byte count |
| StringBuilder buf = new StringBuilder(); |
| for (int i = 0; i < memoryBytes.length - buf.length(); i++) |
| buf.append(getPaddingCharacter()); |
| return buf.toString(); |
| } |
| } |
| |
| public AbstractPane incrPane(AbstractPane currentPane, int offset) { |
| final List<AbstractPane> panes = Arrays.asList(getRenderingPanes()); |
| return panes.get((panes.indexOf(currentPane) + offset) % panes.size()); |
| } |
| |
| /** |
| * Indicates if additional address information is available to display in the current visible range |
| */ |
| boolean hasVisibleRangeInfo() { |
| return false; |
| } |
| |
| /** |
| * @return True if the given address has additional information to display e.g. variables, registers, etc. |
| */ |
| boolean hasAddressInfo(BigInteger address) { |
| return false; |
| } |
| |
| /** |
| * @return The items that would be visible in the current viewable area if the rows were to use a single |
| * height |
| */ |
| Map<BigInteger, List<IMemoryBlockAddressInfoItem>> getVisibleValueToAddressInfoItems() { |
| return fMapStartAddrToInfoItems; |
| } |
| |
| /** |
| * @since 1.5 |
| */ |
| protected boolean isShowCrossReferenceInfo() { |
| // Check settings in preference store |
| IPreferenceStore store = TraditionalRenderingPlugin.getDefault().getPreferenceStore(); |
| boolean prefShowInfo = store.getBoolean(TraditionalRenderingPreferenceConstants.MEM_CROSS_REFERENCE_INFO); |
| |
| // Cross Reference information can not be properly highlighted for a Little Endian display |
| // see Bug 509577 - [Traditional Rendering] Local variable enclosing markings may be wrong in Little Endian presentation |
| return (!isDisplayLittleEndian() && prefShowInfo); |
| } |
| |
| /** |
| * Provides a string with the information relevant to a given address, the separator helps to format it |
| * e.g. Separated items by comma, new line, etc. |
| * |
| * @param addTypeHeaders |
| * Indicates if the string shall include a data type name before each list of items of the same |
| * type e.g. Variables, Registers, etc. |
| */ |
| String buildAddressInfoString(BigInteger address, String separator, boolean addTypeHeaders) { |
| return ""; //$NON-NLS-1$ |
| } |
| |
| /** |
| * Returns Dynamic context menu actions to this render, No actions by default |
| * @since 1.4 |
| */ |
| Action[] getDynamicActions() { |
| return new Action[0]; |
| } |
| |
| /** |
| * Trigger the resolution of additional address information - No action by default |
| */ |
| void resolveAddressInfoForCurrentSelection() { |
| } |
| |
| } |