mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-08-28 05:20:21 +00:00
GP-2593: Cache bytes in DBTraceProgramViewMemory
This commit is contained in:
parent
f31e6bd52e
commit
e1a186a5d0
|
@ -812,4 +812,8 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
|||
public void updateViewsRefreshBlocks() {
|
||||
allViews(v -> v.updateMemoryRefreshBlocks());
|
||||
}
|
||||
|
||||
public void updateViewsBytesChanged(AddressRange range) {
|
||||
allViews(v -> v.updateBytesChanged(range));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -676,16 +676,18 @@ public class DBTraceMemorySpace
|
|||
|
||||
// Read back the written bytes and fire event
|
||||
byte[] bytes = Arrays.copyOfRange(buf.array(), arrOff, arrOff + result);
|
||||
ImmutableTraceAddressSnapRange tasr = new ImmutableTraceAddressSnapRange(start,
|
||||
start.add(result - 1), snap, lastSnap.snap);
|
||||
trace.setChanged(new TraceChangeRecord<>(TraceMemoryBytesChangeType.CHANGED,
|
||||
this, new ImmutableTraceAddressSnapRange(start, start.add(result - 1),
|
||||
snap, lastSnap.snap),
|
||||
oldBytes.array(), bytes));
|
||||
this, tasr, oldBytes.array(), bytes));
|
||||
|
||||
// Fixup affected code units
|
||||
DBTraceCodeSpace codeSpace = trace.getCodeManager().get(this, false);
|
||||
if (codeSpace != null) {
|
||||
codeSpace.bytesChanged(changed, snap, start, oldBytes.array(), bytes);
|
||||
}
|
||||
// Clear program view caches
|
||||
trace.updateViewsBytesChanged(tasr.getRange());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -1047,6 +1049,7 @@ public class DBTraceMemorySpace
|
|||
regionMapSpace.invalidateCache();
|
||||
regionCache.clear();
|
||||
trace.updateViewsRefreshBlocks();
|
||||
trace.updateViewsBytesChanged(null);
|
||||
stateMapSpace.invalidateCache();
|
||||
bufferStore.invalidateCache();
|
||||
blockStore.invalidateCache();
|
||||
|
|
|
@ -26,6 +26,7 @@ import ghidra.program.model.address.*;
|
|||
import ghidra.program.model.mem.*;
|
||||
import ghidra.trace.database.memory.DBTraceMemorySpace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpaceInputStream;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.MathUtilities;
|
||||
|
||||
public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlock {
|
||||
|
@ -96,6 +97,19 @@ public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlo
|
|||
private final List<MemoryBlockSourceInfo> info =
|
||||
Collections.singletonList(new MyMemoryBlockSourceInfo());
|
||||
|
||||
private static final int CACHE_PAGE_COUNT = 3;
|
||||
private final ByteCache cache = new ByteCache(CACHE_PAGE_COUNT) {
|
||||
@Override
|
||||
protected int doLoad(Address address, ByteBuffer buf) throws MemoryAccessException {
|
||||
DBTraceMemorySpace space =
|
||||
program.trace.getMemoryManager().getMemorySpace(getAddressSpace(), false);
|
||||
if (space == null) {
|
||||
throw new MemoryAccessException("Space does not exist");
|
||||
}
|
||||
return space.getViewBytes(program.snap, address, buf);
|
||||
}
|
||||
};
|
||||
|
||||
protected AbstractDBTraceProgramViewMemoryBlock(DBTraceProgramView program) {
|
||||
this.program = program;
|
||||
}
|
||||
|
@ -106,6 +120,15 @@ public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlo
|
|||
return getStart().getAddressSpace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when the snap changes or when bytes change
|
||||
*/
|
||||
protected void invalidateBytesCache(AddressRange range) {
|
||||
if (range == null || range.intersects(getAddressRange())) {
|
||||
cache.invalidate(range);
|
||||
}
|
||||
}
|
||||
|
||||
protected DBTraceMemorySpace getMemorySpace() {
|
||||
return program.trace.getMemoryManager().getMemorySpace(getAddressSpace(), false);
|
||||
}
|
||||
|
@ -167,20 +190,13 @@ public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlo
|
|||
|
||||
@Override
|
||||
public byte getByte(Address addr) throws MemoryAccessException {
|
||||
AddressRange range = getAddressRange();
|
||||
if (!range.contains(addr)) {
|
||||
throw new MemoryAccessException();
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
AddressRange range = getAddressRange();
|
||||
if (!range.contains(addr)) {
|
||||
throw new MemoryAccessException();
|
||||
}
|
||||
return cache.read(addr);
|
||||
}
|
||||
DBTraceMemorySpace space =
|
||||
program.trace.getMemoryManager().getMemorySpace(range.getAddressSpace(), false);
|
||||
if (space == null) {
|
||||
throw new MemoryAccessException("Space does not exist");
|
||||
}
|
||||
ByteBuffer buf = ByteBuffer.allocate(1);
|
||||
if (space.getViewBytes(program.snap, addr, buf) != 1) {
|
||||
throw new MemoryAccessException();
|
||||
}
|
||||
return buf.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -190,17 +206,22 @@ public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlo
|
|||
|
||||
@Override
|
||||
public int getBytes(Address addr, byte[] b, int off, int len) throws MemoryAccessException {
|
||||
AddressRange range = getAddressRange();
|
||||
if (!range.contains(addr)) {
|
||||
throw new MemoryAccessException();
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
AddressRange range = getAddressRange();
|
||||
if (!range.contains(addr)) {
|
||||
throw new MemoryAccessException();
|
||||
}
|
||||
if (cache.canCache(addr, len)) {
|
||||
return cache.read(addr, ByteBuffer.wrap(b, off, len));
|
||||
}
|
||||
DBTraceMemorySpace space =
|
||||
program.trace.getMemoryManager().getMemorySpace(range.getAddressSpace(), false);
|
||||
if (space == null) {
|
||||
throw new MemoryAccessException("Space does not exist");
|
||||
}
|
||||
len = MathUtilities.unsignedMin(len, range.getMaxAddress().subtract(addr) + 1);
|
||||
return space.getViewBytes(program.snap, addr, ByteBuffer.wrap(b, off, len));
|
||||
}
|
||||
DBTraceMemorySpace space =
|
||||
program.trace.getMemoryManager().getMemorySpace(range.getAddressSpace(), false);
|
||||
if (space == null) {
|
||||
throw new MemoryAccessException("Space does not exist");
|
||||
}
|
||||
len = MathUtilities.unsignedMin(len, range.getMaxAddress().subtract(addr) + 1);
|
||||
return space.getViewBytes(program.snap, addr, ByteBuffer.wrap(b, off, len));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -217,6 +238,7 @@ public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlo
|
|||
|
||||
@Override
|
||||
public int putBytes(Address addr, byte[] b, int off, int len) throws MemoryAccessException {
|
||||
// NB. The trace will notify us of the write, and we invalidate then
|
||||
AddressRange range = getAddressRange();
|
||||
if (!range.contains(addr)) {
|
||||
throw new MemoryAccessException();
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
/* ###
|
||||
* 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.trace.database.program;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.util.MathUtilities;
|
||||
|
||||
public abstract class ByteCache {
|
||||
public static final int BITS = 12;
|
||||
public static final long OFFSET_MASK = -1L << BITS;
|
||||
public static final int SIZE = 1 << BITS;
|
||||
|
||||
private class Page {
|
||||
private volatile boolean valid = false;
|
||||
private Address start = null;
|
||||
private final byte[] bytes = new byte[SIZE];
|
||||
private final ByteBuffer buf = ByteBuffer.wrap(bytes);
|
||||
private int len;
|
||||
|
||||
public final boolean contains(Address address, int length) {
|
||||
if (!valid || start == null) {
|
||||
return false;
|
||||
}
|
||||
long offset = address.subtract(start);
|
||||
return Long.compareUnsigned(offset + length, len) < 0;
|
||||
}
|
||||
|
||||
public final int load(Address address, int length) throws MemoryAccessException {
|
||||
valid = false;
|
||||
start = address.getNewAddress(address.getOffset() & OFFSET_MASK);
|
||||
long offset = address.subtract(start);
|
||||
buf.clear(); // NB. Do not limit. Cache what we can.
|
||||
len = doLoad(address, buf);
|
||||
if (len < offset + length) {
|
||||
throw new MemoryAccessException();
|
||||
}
|
||||
valid = true;
|
||||
return (int) offset;
|
||||
}
|
||||
|
||||
public void invalidate(AddressRange range) {
|
||||
// TODO: Is it worth it to check for intersection?
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
private final int pageCount;
|
||||
private final Page[] pages;
|
||||
|
||||
public ByteCache(int pageCount) {
|
||||
this.pageCount = pageCount;
|
||||
pages = new Page[pageCount];
|
||||
for (int i = 0; i < pageCount; i++) {
|
||||
pages[i] = newPage();
|
||||
}
|
||||
}
|
||||
|
||||
protected Page newPage() {
|
||||
return new Page();
|
||||
}
|
||||
|
||||
public boolean canCache(Address address, int len) {
|
||||
long cacheBufOff = address.getOffset() & ~OFFSET_MASK;
|
||||
return cacheBufOff + len < pageCount * SIZE;
|
||||
}
|
||||
|
||||
public byte read(Address address) throws MemoryAccessException {
|
||||
Address pageStart = address.getNewAddress(address.getOffset() & OFFSET_MASK);
|
||||
Page page = ensurePageCached(pageStart, 1);
|
||||
int cacheBufOff = (int) address.subtract(pageStart);
|
||||
return page.bytes[cacheBufOff];
|
||||
}
|
||||
|
||||
public int read(Address address, ByteBuffer buf) throws MemoryAccessException {
|
||||
long startOff = address.getOffset();
|
||||
long startPage = startOff & OFFSET_MASK;
|
||||
int bufStart = buf.position();
|
||||
|
||||
long memOffset = startPage;
|
||||
int cacheBufOff = (int) (startOff - startPage);
|
||||
while (buf.hasRemaining()) {
|
||||
int required = MathUtilities.unsignedMin(SIZE - cacheBufOff, buf.remaining());
|
||||
Page page = ensurePageCached(address.getNewAddress(memOffset), required);
|
||||
buf.put(page.bytes, cacheBufOff, required);
|
||||
memOffset += SIZE;
|
||||
cacheBufOff = 0;
|
||||
}
|
||||
return buf.position() - bufStart;
|
||||
}
|
||||
|
||||
protected int choosePage(Address address, int len) {
|
||||
for (int i = 0; i < pageCount; i++) {
|
||||
Page page = pages[i];
|
||||
if (page.contains(address, len)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a page is cached
|
||||
*
|
||||
* @param address the start address of the page
|
||||
* @param len the minimum number of bytes required from the page
|
||||
* @return the chosen cache page
|
||||
* @throws MemoryAccessException if the required bytes cannot be read
|
||||
*/
|
||||
private Page ensurePageCached(Address address, int len) throws MemoryAccessException {
|
||||
int chosen = choosePage(address, len);
|
||||
if (chosen == -1) {
|
||||
pages[pageCount - 1].load(address, len);
|
||||
chosen = pageCount - 1;
|
||||
}
|
||||
if (chosen == 0) {
|
||||
return pages[0];
|
||||
}
|
||||
synchronized (pages) {
|
||||
Page temp = pages[chosen];
|
||||
pages[chosen] = pages[0];
|
||||
pages[0] = temp;
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract int doLoad(Address address, ByteBuffer buf) throws MemoryAccessException;
|
||||
|
||||
public void invalidate(AddressRange range) {
|
||||
synchronized (pages) {
|
||||
for (Page p : pages) {
|
||||
p.invalidate(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1637,6 +1637,10 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
memory.updateRefreshBlocks();
|
||||
}
|
||||
|
||||
public void updateBytesChanged(AddressRange range) {
|
||||
memory.updateBytesChanged(range);
|
||||
}
|
||||
|
||||
protected DomainObjectEventQueues getEventQueues(TraceAddressSpace space) {
|
||||
// TODO: Should there be views on other frames?
|
||||
// IIRC, this was an abandoned experiment for "register listings"
|
||||
|
|
|
@ -67,6 +67,12 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void setSnap(long snap) {
|
||||
super.setSnap(snap);
|
||||
updateBytesChanged(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void recomputeAddressSet() {
|
||||
AddressSet temp = new AddressSet();
|
||||
|
@ -155,4 +161,15 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
|||
spaceBlocks.clear();
|
||||
recomputeAddressSet();
|
||||
}
|
||||
|
||||
public void updateBytesChanged(AddressRange range) {
|
||||
if (regionBlocks == null) { // <init> order
|
||||
return;
|
||||
}
|
||||
for (AbstractDBTraceProgramViewMemoryBlock block : forceFullView
|
||||
? spaceBlocks.values()
|
||||
: regionBlocks.values()) {
|
||||
block.invalidateBytesCache(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue