diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/model/mem/WrappedMemoryBufferTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/model/mem/WrappedMemoryBufferTest.java new file mode 100644 index 0000000000..587fb2410a --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/model/mem/WrappedMemoryBufferTest.java @@ -0,0 +1,282 @@ +/* ### + * 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.program.model.mem; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import org.junit.Assert; +import org.junit.Test; + +import ghidra.program.database.ProgramBuilder; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.util.task.TaskMonitorAdapter; + +public class WrappedMemoryBufferTest extends AbstractGhidraHeadedIntegrationTest { + private Program program; + private MemBuffer memBuf; + + public WrappedMemoryBufferTest() { + super(); + } + + private void loadProgram(String name) throws Exception { + ProgramBuilder builder = new ProgramBuilder(name, ProgramBuilder._TOY); + builder.createMemory("ram", "0x0", 100000); + program = builder.getProgram(); + memBuf = new MemoryBufferImpl(program.getMemory(), program.getMinAddress()); + memBuf = new WrappedMemBuffer(memBuf, 20, 0); + } + + @Test + public void testGetBytes() throws Exception { + loadProgram("notepad"); + + Address minAddr = program.getMinAddress(); + Address maxAddr = program.getMaxAddress(); + setBytes(minAddr, new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }); + setBytes(maxAddr.subtract(7), new byte[] { 11, 12, 13, 14, 15, 16, 17, 18 }); + + memBuf = new MemoryBufferImpl(program.getMemory(), minAddr); + memBuf = new WrappedMemBuffer(memBuf, 5, 0); + byte[] bytes = new byte[4]; + assertEquals(4, memBuf.getBytes(bytes, 0)); + Assert.assertArrayEquals("Unexpected bytes read from memBuf", new byte[] { 1, 2, 3, 4 }, + bytes); + assertEquals(4, memBuf.getBytes(bytes, 4)); + Assert.assertArrayEquals("Unexpected bytes read from memBuf", new byte[] { 5, 6, 7, 8 }, + bytes); + + Arrays.fill(bytes, (byte) 0); + assertEquals(2, memBuf.getBytes(bytes, 99998)); + Assert.assertArrayEquals("Unexpected bytes read from memBuf", new byte[] { 17, 18, 0, 0 }, + bytes); + assertEquals(4, memBuf.getBytes(bytes, 99996)); + Assert.assertArrayEquals("Unexpected bytes read from memBuf", new byte[] { 15, 16, 17, 18 }, + bytes); + assertEquals(4, memBuf.getBytes(bytes, 99995)); + Assert.assertArrayEquals("Unexpected bytes read from memBuf", new byte[] { 14, 15, 16, 17 }, + bytes); + Arrays.fill(bytes, (byte) 0); + assertEquals(0, memBuf.getBytes(bytes, 100000)); + Assert.assertArrayEquals("Unexpected bytes read from memBuf", new byte[] { 0, 0, 0, 0 }, + bytes); + } + + private void setBytes(Address addr, byte[] bytes) throws MemoryAccessException { + + Memory mem = program.getMemory(); + int txId = program.startTransaction("Test"); + try { + mem.setBytes(addr, bytes); + } + finally { + program.endTransaction(txId, true); + } + } + + + @Test + public void testSetPosition() throws Exception { + loadProgram("notepad"); + + Memory mem = program.getMemory(); + Address addr = program.getMinAddress(); + memBuf = new MemoryBufferImpl(mem, program.getMinAddress()); + WrappedMemBuffer wrapBuf = new WrappedMemBuffer(memBuf, 5, 0); + for (int i = 0; i < 5000; i++) { + assertEquals(mem.getByte(addr), wrapBuf.getByte(0)); + assertEquals(mem.getByte(addr.add(1)), wrapBuf.getByte(1)); + assertEquals(mem.getByte(addr.add(2)), wrapBuf.getByte(2)); + assertEquals(mem.getByte(addr.add(3)), wrapBuf.getByte(3)); + addr = addr.add(5); + int offset = (int) addr.subtract(program.getMinAddress()); + wrapBuf.setBaseOffset(offset); + } + + addr = program.getMinAddress(); + memBuf = new MemoryBufferImpl(mem, program.getMinAddress()); + wrapBuf = new WrappedMemBuffer(memBuf, 5, 0); + for (int i = 0; i < 500; i++) { + assertEquals(mem.getByte(addr), memBuf.getByte(0)); + assertEquals(mem.getByte(addr.add(1)), memBuf.getByte(1)); + assertEquals(mem.getByte(addr.add(2)), memBuf.getByte(2)); + assertEquals(mem.getByte(addr.add(3)), memBuf.getByte(3)); + addr = addr.add(50); + int offset = (int) addr.subtract(program.getMinAddress()); + wrapBuf.setBaseOffset(offset); + } + + addr = program.getMinAddress(); + memBuf = new MemoryBufferImpl(mem, program.getMinAddress()); + wrapBuf = new WrappedMemBuffer(memBuf, 5, 0); + for (int i = 0; i < 10; i++) { + assertEquals(mem.getByte(addr), wrapBuf.getByte(0)); + assertEquals(mem.getByte(addr.add(1)), wrapBuf.getByte(1)); + assertEquals(mem.getByte(addr.add(2)), wrapBuf.getByte(2)); + assertEquals(mem.getByte(addr.add(3)), wrapBuf.getByte(3)); + addr = addr.add(2000); + int offset = (int) addr.subtract(program.getMinAddress()); + wrapBuf.setBaseOffset(offset); + } + } + + @Test + public void testProgram20bit() throws Exception { + program = + createDefaultProgram(testName.getMethodName(), ProgramBuilder._X86_16_REAL_MODE, this); + + Address start = program.getAddressFactory().getAddress("0000:0000"); + + InputStream is = new InputStream() { + private int pos = 0; + + @Override + public int read() throws IOException { + if (pos < 0x10000) { + ++pos; + return 0xaa; + } + if (pos < 0x20000) { + ++pos; + return 0xbb; + } + if (pos < 0x30000) { + ++pos; + return 0xcc; + } + return 0; + } + }; + + int id = program.startTransaction("add block"); + try { + program.getMemory().createInitializedBlock(".test", start, is, 0x30000, + TaskMonitorAdapter.DUMMY_MONITOR, false); + } + finally { + program.endTransaction(id, true); + } + + MemoryBufferImpl buf = new MemoryBufferImpl(program.getMemory(), start); + + WrappedMemBuffer wrapBuf = new WrappedMemBuffer(buf, 0); + for (int i = 0; i < 0x30000; i++) { + int b = 0xff & wrapBuf.getByte(i); + if (i < 0x10000) { + assertEquals(0xaa, b); + continue; + } + if (i < 0x20000) { + assertEquals(0xbb, b); + continue; + } + if (i < 0x30000) { + assertEquals(0xcc, b); + continue; + } + } + + wrapBuf = new WrappedMemBuffer(buf, 10, 0); + for (int i = 0; i < 0x30000; i++) { + int b = 0xff & wrapBuf.getByte(i); + if (i < 0x10000) { + assertEquals(0xaa, b); + continue; + } + if (i < 0x20000) { + assertEquals(0xbb, b); + continue; + } + if (i < 0x30000) { + assertEquals(0xcc, b); + continue; + } + } + } + + @Test + public void testBoundary() throws Exception { + loadProgram("notepad"); + + Memory mem = program.getMemory(); + Address addr = program.getMaxAddress(); + memBuf = new MemoryBufferImpl(mem, program.getMaxAddress()); + WrappedMemBuffer wrapBuf = new WrappedMemBuffer(memBuf, 0); + + assertEquals(mem.getByte(addr), wrapBuf.getByte(0)); + + wrapBuf.setBaseOffset(1); + + try { + wrapBuf.getByte(0); + Assert.fail("Should not have been able to get byte"); + } + catch (MemoryAccessException e) { + // good + } + + wrapBuf = new WrappedMemBuffer(memBuf, 20, 0); + + assertEquals(mem.getByte(addr), wrapBuf.getByte(0)); + + wrapBuf.setBaseOffset(1); + + try { + wrapBuf.getByte(0); + Assert.fail("Should not have been able to get byte"); + } + catch (MemoryAccessException e) { + // good + } + } + + @Test + public void testBoundary1() throws Exception { + loadProgram("notepad"); + + Memory mem = program.getMemory(); + Address addr = program.getMaxAddress(); + memBuf = new MemoryBufferImpl(mem, program.getMaxAddress()); + WrappedMemBuffer wrapBuf = new WrappedMemBuffer(memBuf, 0); + + assertEquals(mem.getByte(addr), wrapBuf.getByte(0)); + try { + wrapBuf.getByte(1); + Assert.fail("Should not have been able to get byte"); + } + catch (MemoryAccessException e) { + // good + } + + wrapBuf = new WrappedMemBuffer(memBuf, 20, 0); + + assertEquals(mem.getByte(addr), wrapBuf.getByte(0)); + try { + wrapBuf.getByte(1); + Assert.fail("Should not have been able to get byte"); + } + catch (MemoryAccessException e) { + // good + } + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java index b911b80b4b..426f7f3691 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/disassemble/Disassembler.java @@ -920,9 +920,8 @@ public class Disassembler implements DisassemblerConflictHandler { disassemblerContext.flowToAddress(addr); - // TODO: An overall better caching of bytes for this block could be done instead - // the previous buffering done here was not doing any buffering - MemBuffer instrMemBuffer = new DumbMemBufferImpl(blockMemBuffer.getMemory(), addr); + MemBuffer instrMemBuffer = new WrappedMemBuffer(blockMemBuffer, + (int) addr.subtract(blockMemBuffer.getAddress())); adjustPreParseContext(instrMemBuffer); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/MemoryBufferImpl.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/MemoryBufferImpl.java index ff94061f98..01a6accd50 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/MemoryBufferImpl.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/MemoryBufferImpl.java @@ -15,12 +15,11 @@ */ package ghidra.program.model.mem; -import ghidra.program.model.address.*; -import ghidra.util.GhidraBigEndianDataConverter; -import ghidra.util.GhidraLittleEndianDataConverter; - import java.math.BigInteger; +import ghidra.program.model.address.*; +import ghidra.util.GhidraDataConverter; + /** * MemBufferImpl implements the MemBuffer interface. It buffers up N bytes * at time, reducing the overall number of calls to Memory, greatly reducing @@ -34,8 +33,9 @@ import java.math.BigInteger; public class MemoryBufferImpl implements MutableMemBuffer { + private final GhidraDataConverter converter; + private static final int DEFAULT_BUFSIZE = 1024; - private static final int THRESH = 8; private Memory mem; private Address startAddr; @@ -43,6 +43,7 @@ public class MemoryBufferImpl implements MutableMemBuffer { private int startAddrIndex = 0; private int minOffset = 0; private int maxOffset = -1; + private int threshold = 0; /** * Construct a new MemoryBufferImpl @@ -62,6 +63,9 @@ public class MemoryBufferImpl implements MutableMemBuffer { public MemoryBufferImpl(Memory mem, Address addr, int bufSize) { this.mem = mem; buffer = new byte[bufSize]; + threshold = bufSize / 100; // 1/100 of buffer size + + this.converter = GhidraDataConverter.getInstance(mem.isBigEndian()); setPosition(addr); } @@ -81,7 +85,7 @@ public class MemoryBufferImpl implements MutableMemBuffer { if (minOffset <= maxOffset) { if (addr.getAddressSpace().equals(startAddr.getAddressSpace())) { long diff = addr.subtract(startAddr); - if (diff >= minOffset && diff < maxOffset - THRESH) { + if (diff >= minOffset && diff < maxOffset - threshold) { startAddr = addr; minOffset -= (int) diff; maxOffset -= (int) diff; @@ -155,35 +159,22 @@ public class MemoryBufferImpl implements MutableMemBuffer { @Override public short getShort(int offset) throws MemoryAccessException { - if (mem.isBigEndian()) { - return GhidraBigEndianDataConverter.INSTANCE.getShort(this, offset); - } - return GhidraLittleEndianDataConverter.INSTANCE.getShort(this, offset); + return converter.getShort(this, offset); } @Override public int getInt(int offset) throws MemoryAccessException { - if (mem.isBigEndian()) { - return GhidraBigEndianDataConverter.INSTANCE.getInt(this, offset); - } - return GhidraLittleEndianDataConverter.INSTANCE.getInt(this, offset); + return converter.getInt(this, offset); } @Override public long getLong(int offset) throws MemoryAccessException { - if (mem.isBigEndian()) { - return GhidraBigEndianDataConverter.INSTANCE.getLong(this, offset); - } - return GhidraLittleEndianDataConverter.INSTANCE.getLong(this, offset); + return converter.getLong(this, offset); } @Override public BigInteger getBigInteger(int offset, int size, boolean signed) throws MemoryAccessException { - if (mem.isBigEndian()) { - return GhidraBigEndianDataConverter.INSTANCE.getBigInteger(this, offset, size, signed); - } - return GhidraLittleEndianDataConverter.INSTANCE.getBigInteger(this, offset, size, signed); + return converter.getBigInteger(this, offset, size, signed); } - } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/WrappedMemBuffer.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/WrappedMemBuffer.java index 79f0053194..8c21783ef2 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/WrappedMemBuffer.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/mem/WrappedMemBuffer.java @@ -19,13 +19,24 @@ import java.math.BigInteger; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressOutOfBoundsException; +import ghidra.util.GhidraDataConverter; public class WrappedMemBuffer implements MemBuffer { + private final GhidraDataConverter converter; + private final MemBuffer memBuffer; private int baseOffset; private Address address; + private static final int DEFAULT_BUFSIZE = 10; + + private byte[] buffer; + private int subBufferIndex = 0; + private int minOffset = 0; + private int maxOffset = -1; + private int threshold = 0; + /** * Construct a wrapped MemBuffer with an adjustable base offset * @param buf memory buffer @@ -33,23 +44,60 @@ public class WrappedMemBuffer implements MemBuffer { * @throws AddressOutOfBoundsException */ public WrappedMemBuffer(MemBuffer buf, int offset) throws AddressOutOfBoundsException { + this(buf, DEFAULT_BUFSIZE, offset); + } + + /** + * Construct a wrapped MemBuffer with an adjustable base offset + * @param buf memory buffer + * @buffersize size of cache buffer - specify 0 for no buffering + * @param offset base offset for this buffer relative to buf's address + * @throws AddressOutOfBoundsException + */ + public WrappedMemBuffer(MemBuffer buf, int bufferSize, int offset) + throws AddressOutOfBoundsException { this.memBuffer = buf; + this.converter = GhidraDataConverter.getInstance(buf.isBigEndian()); + + buffer = new byte[bufferSize]; + threshold = bufferSize / 100; // 1/100 of buffer size + setBaseOffset(offset); } + @Override + public Address getAddress() { + return address; + } + /** * Set new base offset relative to the associated MemBuffer's address * @param offset new base offset of this buffer * @throws AddressOutOfBoundsException */ public void setBaseOffset(int offset) throws AddressOutOfBoundsException { - this.baseOffset = offset; - this.address = memBuffer.getAddress().add(baseOffset); - } + this.address = memBuffer.getAddress().add(offset); - @Override - public Address getAddress() { - return address; + // already set, changing position + if (minOffset <= maxOffset) { + long diff = offset - baseOffset; + if (diff >= minOffset && diff < (maxOffset - threshold)) { + baseOffset = offset; + minOffset -= (int) diff; + maxOffset -= (int) diff; + subBufferIndex += diff; + return; + } + } + this.baseOffset = offset; + + if (buffer.length > 0) { + subBufferIndex = 0; + minOffset = 0; + maxOffset = -1; + + maxOffset = memBuffer.getBytes(buffer, baseOffset) - 1; + } } /** @@ -70,11 +118,40 @@ public class WrappedMemBuffer implements MemBuffer { @Override public byte getByte(int offset) throws MemoryAccessException { - return memBuffer.getByte(computeOffset(offset)); + // no buffering, just get the byte + if (buffer.length <= 0) { + return memBuffer.getByte(computeOffset(offset)); + } + + // byte found in buffer + if ((offset >= minOffset) && (offset <= maxOffset)) { + return buffer[subBufferIndex + offset]; + } + + // fill the buffer + int nRead = memBuffer.getBytes(buffer, computeOffset(offset)); + + subBufferIndex = -offset; + minOffset = offset; + maxOffset = offset + nRead - 1; + + if (nRead == 0) { + throw new MemoryAccessException(); + } + return buffer[0]; } @Override public int getBytes(byte[] b, int offset) { + if (buffer.length > 0) { + // bytes are contained in the buffer + if (offset >= minOffset && (b.length + offset) <= maxOffset) { + System.arraycopy(buffer, subBufferIndex + offset, b, 0, b.length); + return b.length; + } + } + + // grab from wrapped buffer, too many bytes, or no buffer try { return memBuffer.getBytes(b, computeOffset(offset)); } @@ -83,34 +160,34 @@ public class WrappedMemBuffer implements MemBuffer { } } - @Override - public int getInt(int offset) throws MemoryAccessException { - return memBuffer.getInt(computeOffset(offset)); - } - - @Override - public long getLong(int offset) throws MemoryAccessException { - return memBuffer.getLong(computeOffset(offset)); - } - - @Override - public BigInteger getBigInteger(int offset, int size, boolean signed) - throws MemoryAccessException { - return memBuffer.getBigInteger(computeOffset(offset), size, signed); - } - @Override public Memory getMemory() { return memBuffer.getMemory(); } - @Override - public short getShort(int offset) throws MemoryAccessException { - return memBuffer.getShort(computeOffset(offset)); - } - @Override public boolean isBigEndian() { return memBuffer.isBigEndian(); } + + @Override + public short getShort(int offset) throws MemoryAccessException { + return converter.getShort(this, computeOffset(offset)); + } + + @Override + public int getInt(int offset) throws MemoryAccessException { + return converter.getInt(this, computeOffset(offset)); + } + + @Override + public long getLong(int offset) throws MemoryAccessException { + return converter.getLong(this, computeOffset(offset)); + } + + @Override + public BigInteger getBigInteger(int offset, int size, boolean signed) + throws MemoryAccessException { + return converter.getBigInteger(this, computeOffset(offset), size, signed); + } }