diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/CachedBytePage.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/CachedBytePage.java new file mode 100644 index 0000000000..7f2dfb7af3 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/CachedBytePage.java @@ -0,0 +1,64 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.gui.memory; + +import java.nio.ByteBuffer; + +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.program.model.address.Address; + +public class CachedBytePage { + private boolean valid = true; + private DebuggerCoordinates coordinates; + private Address start; + private byte[] page = new byte[4096]; + private ByteBuffer buf = ByteBuffer.wrap(page); + + public byte getByte(DebuggerCoordinates coordinates, Address address) { + long offset; + if (!valid || this.coordinates == null || !this.coordinates.equals(coordinates) || + start == null || !start.hasSameAddressSpace(address)) { + offset = refresh(coordinates, address); + } + else { + offset = address.subtract(start); + if (offset < 0 || 4096 <= offset) { + offset = refresh(coordinates, address); + } + } + return page[(int) offset]; + } + + public void invalidate() { + valid = false; + } + + private long refresh(DebuggerCoordinates coordinates, Address address) { + valid = false; + buf.clear(); + Address min = address.getAddressSpace().getMinAddress(); + start = address.subtractWrap(page.length / 2); + + if (start.compareTo(min) < 0 || start.compareTo(address) > 0) { + start = min; + } + coordinates.getTrace() + .getMemoryManager() + .getViewBytes(coordinates.getViewSnap(), start, buf); + valid = true; + return address.subtract(start); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java index 67d6db2267..e109f9e02f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java @@ -47,7 +47,10 @@ import ghidra.program.model.mem.MemoryBlock; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.trace.model.Trace; +import ghidra.trace.model.Trace.TraceMemoryBytesChangeType; +import ghidra.trace.model.TraceDomainObjectListener; import ghidra.trace.model.program.TraceProgramView; +import ghidra.trace.util.TraceAddressSpace; import ghidra.util.Swing; public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvider { @@ -75,6 +78,19 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi return true; } + protected class ListenerForChanges extends TraceDomainObjectListener { + public ListenerForChanges() { + listenFor(TraceMemoryBytesChangeType.CHANGED, this::bytesChanged); + } + + private void bytesChanged(TraceAddressSpace space) { + if (space.getAddressSpace().isMemorySpace()) { + currCache.invalidate(); + prevCache.invalidate(); + } + } + } + protected class ForMemoryBytesGoToTrait extends DebuggerGoToTrait { public ForMemoryBytesGoToTrait() { super(DebuggerMemoryBytesProvider.this.tool, DebuggerMemoryBytesProvider.this.plugin, @@ -151,7 +167,13 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi protected AutoReadMemorySpec autoReadMemorySpec = defaultReadMemorySpec; // TODO: followsCurrentSnap? + private final ListenerForChanges listenerForChanges = new ListenerForChanges(); + DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; + private DebuggerCoordinates previous = DebuggerCoordinates.NOWHERE; + + private final CachedBytePage currCache = new CachedBytePage(); + private final CachedBytePage prevCache = new CachedBytePage(); protected final boolean isMainViewer; @@ -174,6 +196,27 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi setHelpLocation(DebuggerResources.HELP_PROVIDER_MEMORY_BYTES); } + @Override + protected ByteBlockChangeManager newByteBlockChangeManager(ProgramByteBlockSet blockSet, + ByteBlockChangeManager bbcm) { + return new ByteBlockChangeManager(blockSet, bbcm) { + @Override + protected boolean isChanged(ByteBlock block, BigInteger offset, int unitByteSize) { + if (super.isChanged(block, offset, unitByteSize)) { + return true; + } + if (previous.getTrace() != current.getTrace()) { + return false; + } + Address address = blockSet.getAddress(block, offset); + if (address == null) { + return false; + } + return currCache.getByte(current, address) != prevCache.getByte(previous, address); + } + }; + } + @Override protected ProgramByteBlockSet newByteBlockSet(ByteBlockChangeManager changeManager) { if (program == null) { @@ -304,6 +347,18 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi return result; } + protected void removeOldListeners() { + if (current.getTrace() != null) { + current.getTrace().removeListener(listenerForChanges); + } + } + + protected void addNewListeners() { + if (current.getTrace() != null) { + current.getTrace().addListener(listenerForChanges); + } + } + protected DebuggerCoordinates adjustCoordinates(DebuggerCoordinates coordinates) { if (followsCurrentThread) { return coordinates; @@ -318,7 +373,10 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi current = coordinates; return; } + previous = current; + removeOldListeners(); current = coordinates; + addNewListeners(); doSetProgram(current.getView()); goToTrait.goToCoordinates(coordinates); trackingTrait.goToCoordinates(coordinates); diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteBlockChangeManager.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteBlockChangeManager.java index 57c41ccc13..4bbd943d45 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteBlockChangeManager.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteBlockChangeManager.java @@ -39,20 +39,12 @@ public class ByteBlockChangeManager { private static String OLD_VALUE = "OldValue"; private static String NEW_VALUE = "NewValue"; - private int dummy = 4; - /** * Construct new change manager. */ - ByteBlockChangeManager(ProgramByteBlockSet blockSet) { + protected ByteBlockChangeManager(ProgramByteBlockSet blockSet, ByteBlockChangeManager bbcm) { this.blockSet = blockSet; - changeList = new ArrayList(3); - } - - ByteBlockChangeManager(ProgramByteBlockSet blockSet, ByteBlockChangeManager bbcm) { - - this.blockSet = blockSet; - changeList = bbcm.changeList; + changeList = bbcm == null ? new ArrayList<>() : bbcm.changeList; } /** @@ -61,7 +53,7 @@ public class ByteBlockChangeManager { * @param edit edit object that has the old value and new value * */ - void add(ByteEditInfo edit) { + protected void add(ByteEditInfo edit) { byte[] oldValue = edit.getOldValue(); byte[] newValue = edit.getNewValue(); @@ -127,7 +119,7 @@ public class ByteBlockChangeManager { * * @return boolean true if an offset in the range was found */ - boolean isChanged(ByteBlock block, BigInteger offset, int unitByteSize) { + protected boolean isChanged(ByteBlock block, BigInteger offset, int unitByteSize) { Address blockAddr = blockSet.getBlockStart(block); for (int i = 0; i < unitByteSize; i++) { diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteBlockSet.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteBlockSet.java index 057c85315a..7d98e04de0 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteBlockSet.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteBlockSet.java @@ -46,12 +46,7 @@ public class ProgramByteBlockSet implements ByteBlockSet { this.provider = provider; this.program = program; - if (bbcm == null) { - this.bbcm = new ByteBlockChangeManager(this); - } - else { - this.bbcm = new ByteBlockChangeManager(this, bbcm); - } + this.bbcm = provider.newByteBlockChangeManager(this, bbcm); newMemoryBlocks(); } @@ -204,8 +199,7 @@ public class ProgramByteBlockSet implements ByteBlockSet { /** * Get the address for the given block and offset. */ - protected Address getAddress(ByteBlock block, BigInteger offset) { - + public Address getAddress(ByteBlock block, BigInteger offset) { for (int i = 0; i < blocks.length; i++) { if (blocks[i] != block) { continue; diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteViewerComponentProvider.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteViewerComponentProvider.java index 8fa3ebbd70..df931344fd 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteViewerComponentProvider.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteViewerComponentProvider.java @@ -537,7 +537,7 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi event.containsEvent(DomainObject.DO_DOMAIN_FILE_CHANGED)) { // drop all changes - blockSet.setByteBlockChangeManager(new ByteBlockChangeManager(blockSet)); + blockSet.setByteBlockChangeManager(newByteBlockChangeManager(blockSet, null)); updateManager.update(); } } @@ -563,6 +563,11 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi } } + protected ByteBlockChangeManager newByteBlockChangeManager(ProgramByteBlockSet blockSet, + ByteBlockChangeManager bbcm) { + return new ByteBlockChangeManager(blockSet, bbcm); + } + protected ProgramByteBlockSet newByteBlockSet(ByteBlockChangeManager changeManager) { if (program == null) { return null; diff --git a/Ghidra/Features/ByteViewer/src/test.slow/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin1Test.java b/Ghidra/Features/ByteViewer/src/test.slow/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin1Test.java index 3f12c44e50..a4e98f2a38 100644 --- a/Ghidra/Features/ByteViewer/src/test.slow/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin1Test.java +++ b/Ghidra/Features/ByteViewer/src/test.slow/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin1Test.java @@ -125,7 +125,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest { @Test public void testOpenProgramNoMemory() throws Exception { - tool.removePlugins(new Plugin[] { cbPlugin }); + tool.removePlugins(List.of(cbPlugin)); runSwing(() -> { ProgramManager pm = tool.getService(ProgramManager.class);