From 342ef10c85e4e9953050971a1a83387750e64d1f Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Fri, 30 Oct 2020 14:14:32 -0400 Subject: [PATCH] GP-196 fixed issue with emulating accessing varnodes in unique space --- .../ghidra/app/util/pcode/PcodeFormatter.java | 33 ++- .../java/ghidra/pcode/emulate/Emulate.java | 18 +- .../pcode/memstate/UniqueMemoryBank.java | 207 +++++++++++++- .../pcode/memstate/UniqueMemoryBankTest.java | 253 ++++++++++++++++++ 4 files changed, 472 insertions(+), 39 deletions(-) create mode 100644 Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/pcode/memstate/UniqueMemoryBankTest.java diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/pcode/PcodeFormatter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/pcode/PcodeFormatter.java index f2470ad3fe..d5d81626c3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/pcode/PcodeFormatter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/pcode/PcodeFormatter.java @@ -202,8 +202,7 @@ public class PcodeFormatter { formatVarnodeTpl(program, opcode, -1, output, lineList); lineList.add(EQUALS); } - Color color = - (opcode == PcodeOp.UNIMPLEMENTED) ? Color.RED : Color.BLUE.darker(); + Color color = (opcode == PcodeOp.UNIMPLEMENTED) ? Color.RED : Color.BLUE.darker(); lineList.add(new AttributedString(PcodeOp.getMnemonic(opcode), color, metrics)); VarnodeTpl[] inputs = op.getInput(); for (int i = 0; i < inputs.length; i++) { @@ -262,7 +261,7 @@ public class PcodeFormatter { formatConstant(offset, size, lineList); } else if (space.isUniqueSpace()) { - formatUnique(offset, size, opIndex < 0, lineList); + formatUnique(offset, size, lineList); } else { formatAddress(program, space.getSpaceId(), offset, size, lineList); @@ -284,25 +283,21 @@ public class PcodeFormatter { private void formatRaw(AddressSpace space, ConstTpl offset, ConstTpl size, List lineList) { // same format as the Varnode.toString - String str = - "(" + space.getName() + ", 0x" + Long.toHexString(offset.getReal()) + ", " + - size.getReal() + ")"; + String str = "(" + space.getName() + ", 0x" + Long.toHexString(offset.getReal()) + ", " + + size.getReal() + ")"; lineList.add(new AttributedString(str, Color.BLUE, metrics)); } - private void formatUnique(ConstTpl offset, ConstTpl size, boolean isOutput, - List lineList) { + private void formatUnique(ConstTpl offset, ConstTpl size, List lineList) { if (offset.getType() != ConstTpl.REAL) { throw new RuntimeException("Unsupported unique offset type: " + offset.getType()); } if (size.getType() != ConstTpl.REAL) { throw new RuntimeException("Unsupported unique size type: " + size.getType()); } - lineList.add(new AttributedString("$U" + Long.toHexString(offset.getReal()), localColor, - metrics)); - if (isOutput) { - formatSize(size, lineList); - } + lineList.add( + new AttributedString("$U" + Long.toHexString(offset.getReal()), localColor, metrics)); + formatSize(size, lineList); } private void formatAddress(Program program, AddressSpace addrSpace, ConstTpl offset, @@ -314,8 +309,8 @@ public class PcodeFormatter { long offsetValue = offset.getReal(); if (addrSpace == null) { lineList.add(STAR); - lineList.add(new AttributedString("0x" + Long.toHexString(offsetValue), addressColor, - metrics)); + lineList.add( + new AttributedString("0x" + Long.toHexString(offsetValue), addressColor, metrics)); if (size.getType() != ConstTpl.J_CURSPACE_SIZE) { formatSize(size, lineList); } @@ -328,7 +323,8 @@ public class PcodeFormatter { lineList.add(new AttributedString(reg.getName(), registerColor, metrics)); if (reg.getMinimumByteSize() > sizeValue) { lineList.add(COLON); - lineList.add(new AttributedString(Integer.toString(sizeValue), this.scalarColor, metrics)); + lineList.add( + new AttributedString(Integer.toString(sizeValue), this.scalarColor, metrics)); } return; } @@ -336,7 +332,7 @@ public class PcodeFormatter { lineList.add(LEFT_BRACKET); lineList.add(new AttributedString(addrSpace.getName(), Color.BLUE, metrics)); lineList.add(RIGHT_BRACKET); - + long wordOffset = offsetValue / addrSpace.getAddressableUnitSize(); long offcut = offsetValue % addrSpace.getAddressableUnitSize(); String str = "0x" + Long.toHexString(wordOffset); @@ -396,7 +392,8 @@ public class PcodeFormatter { } private boolean formatLabelInput(VarnodeTpl input0, List lineList) { - if (input0.getSpace().isConstSpace() && input0.getOffset().getType() == ConstTpl.J_RELATIVE) { + if (input0.getSpace().isConstSpace() && + input0.getOffset().getType() == ConstTpl.J_RELATIVE) { String label = "<" + input0.getOffset().getReal() + ">"; lineList.add(new AttributedString(label, Color.BLUE, metrics)); return true; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/emulate/Emulate.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/emulate/Emulate.java index 7c62a54663..ad7462e2a9 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/emulate/Emulate.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/emulate/Emulate.java @@ -310,10 +310,12 @@ public class Emulate { else { takeBranch = memstate.getValue(condVar) != 0; } - if (takeBranch) + if (takeBranch) { executeBranch(op); - else + } + else { fallthruOp(); + } } /// Since the full instruction is cached, we can do relative branches properly @@ -324,10 +326,12 @@ public class Emulate { long id = destaddr.getOffset(); id = id + current_op; current_op = (int) id; - if (current_op == pcode.length) + if (current_op == pcode.length) { fallthruOp(); - else if ((current_op < 0) || (current_op >= pcode.length)) + } + else if ((current_op < 0) || (current_op >= pcode.length)) { throw new LowlevelError("Bad intra-instruction branch"); + } } else { setCurrentAddress(destaddr); @@ -451,18 +455,18 @@ public class Emulate { "Unsupported pcode op (opcode=" + op.getOpcode() + ", seq=" + op.getSeqnum() + ")"); } if (behave instanceof UnaryOpBehavior) { - UnaryOpBehavior uniaryBehave = (UnaryOpBehavior) behave; + UnaryOpBehavior unaryBehave = (UnaryOpBehavior) behave; Varnode in1var = op.getInput(0); Varnode outvar = op.getOutput(); if (in1var.getSize() > 8 || outvar.getSize() > 8) { BigInteger in1 = memstate.getBigInteger(op.getInput(0), false); - BigInteger out = uniaryBehave.evaluateUnary(op.getOutput().getSize(), + BigInteger out = unaryBehave.evaluateUnary(op.getOutput().getSize(), op.getInput(0).getSize(), in1); memstate.setValue(op.getOutput(), out); } else { long in1 = memstate.getValue(op.getInput(0)); - long out = uniaryBehave.evaluateUnary(op.getOutput().getSize(), + long out = unaryBehave.evaluateUnary(op.getOutput().getSize(), op.getInput(0).getSize(), in1); memstate.setValue(op.getOutput(), out); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/memstate/UniqueMemoryBank.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/memstate/UniqueMemoryBank.java index a366248e3f..625eae2527 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/memstate/UniqueMemoryBank.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/memstate/UniqueMemoryBank.java @@ -20,9 +20,28 @@ import generic.stl.MapSTL; import ghidra.pcode.error.LowlevelError; import ghidra.program.model.address.AddressSpace; +/** + * An subclass of {@link MemoryBank} intended for modeling the "unique" memory + * space. The space is byte-addressable and paging is not supported. + */ public class UniqueMemoryBank extends MemoryBank { - protected MapSTL map = new ComparableMapSTL(); + /**A map from {@link Long} offsets to byte values would require many lookups. + * As an optimization, this map is defined from {@link Long} values to + * {@link WordInfo} objects, each of which represents an eight-byte word + * of memory. Each key in this map must be 0 mod 8. + */ + protected MapSTL map = new ComparableMapSTL(); + + private static final long ALIGNMENT_MASK = 0xfffffffffffffff8L; + + //note that WordInfo use the bits in a byte to record whether + //or not a given byte has been written to, so you can't just + //change WORD_SIZE to another value and without also changing + //the implementation of WordInfo + private static final int WORD_SIZE = 8; + + private byte[] buffer = new byte[WORD_SIZE]; public UniqueMemoryBank(AddressSpace spc, boolean isBigEndian) { super(spc, isBigEndian, 0, null); @@ -45,24 +64,93 @@ public class UniqueMemoryBank extends MemoryBank { } @Override - public int getChunk(long addrOffset, int size, byte[] res, boolean ignoreFault) { - byte[] value = map.get(addrOffset); - if (value == null) { - throw new LowlevelError("Unique value read before written: 0x" + - Long.toHexString(addrOffset)); + public int getChunk(long offset, int size, byte[] dest, boolean stopOnUninitialized) { + int bytesRead = 0; + if (size == 0) { + return bytesRead; } - if (value.length != size) { - throw new LowlevelError("Unique value size mismatch: 0x" + Long.toHexString(addrOffset)); + try { + //align if necessary + int adjustment = (int) offset % WORD_SIZE; + if (adjustment != 0) { + WordInfo word = map.get(offset & ALIGNMENT_MASK); + if (word == null) { + throw new LowlevelError("Attempted to read uninitialized word in unique space"); + } + for (int i = adjustment; i < WORD_SIZE && bytesRead < size; ++i) { + dest[bytesRead++] = word.getByte(i); + offset += 1; + } + } + //copy a word at a time + while (size - bytesRead > 0) { + WordInfo word = map.get(offset & ALIGNMENT_MASK); + if (word == null) { + throw new LowlevelError("Attempted to read uninitialized word in unique space"); + } + offset += WORD_SIZE; + //whole word is initialized, copy it (or the appropriate + //initial segment) all at once + int bytesToRead = Math.min(WORD_SIZE, size - bytesRead); + if (word.isEntireWordInitialized()) { + word.getWord(buffer); + System.arraycopy(buffer, 0, dest, bytesRead, + Math.min(WORD_SIZE, size - bytesRead)); + bytesRead += bytesToRead; + continue; + } + //not entirely initialized, copy one byte at a time until + //all requested bytes read (or word.getByte throws an exception) + int base = bytesRead; + for (int i = 0; i < bytesToRead; ++i) { + dest[base + i] = word.getByte(i); + bytesRead += 1; + } + } + return bytesRead; + } + catch (LowlevelError e) { + if (stopOnUninitialized) { + return bytesRead; + } + throw e; } - System.arraycopy(value, 0, res, 0, size); - return size; } @Override - public void setChunk(long offset, int size, byte[] val) { - byte[] value = new byte[size]; - System.arraycopy(val, 0, value, 0, size); - map.put(offset, value); + public void setChunk(long offset, int size, byte[] src) { + if (size == 0 || src.length == 0) { + return; + } + int currentPosition = 0; + //align if necessary + int adjustment = (int) offset % WORD_SIZE; + if (adjustment != 0) { + WordInfo word = map.get(offset & ALIGNMENT_MASK); + if (word == null) { + word = new WordInfo(); + map.put(offset & ALIGNMENT_MASK, word); + } + for (int i = adjustment; i < WORD_SIZE; ++i) { + word.setByte(src[currentPosition], i); + offset += 1; + currentPosition += 1; + } + } + while (size > currentPosition) { + WordInfo word = map.get(offset & ALIGNMENT_MASK); + if (word == null) { + word = new WordInfo(); + map.put(offset & ALIGNMENT_MASK, word); + } + int bytesToWrite = Math.min(WORD_SIZE, size - currentPosition); + for (int i = 0; i < bytesToWrite; i++) { + word.setByte(src[currentPosition + i], i); + } + offset += bytesToWrite; + currentPosition += bytesToWrite; + } + return; } /** @@ -72,4 +160,95 @@ public class UniqueMemoryBank extends MemoryBank { map.clear(); } + /** + * A simple class representing a byte-addressable word of memory. Each + * byte can be either initialized to a byte value or uninitialized. + * It is an error to attempt to read an uninitialized byte. + */ + public static class WordInfo { + public byte initialized; + public long word; + + /** + * Constructs a {@link WordInfo} object with all bytes uninitialized. + */ + public WordInfo() { + initialized = 0; + word = 0; + } + + /** + * Initializes the byte at {@code index} and sets its value to + * {@code val} + * @param val new value + * @param index index + * @throws LowlevelError if the index is invalid + */ + public void setByte(byte val, int index) { + validateIndex(index); + word &= ~(0xffL << (WORD_SIZE * index)); + long shifted = ((long) val) << (WORD_SIZE * index); + word |= shifted; + initialized |= (1 << index); + } + + /** + * Returns the byte at the given index + * @param index index + * @return corresponding byte value + * @throws LowlevelError if the index is invalid or the requested byte + * is not initialized. + */ + public byte getByte(int index) { + validateIndex(index); + checkInitialized(index); + long selected = word & (0xffL << (WORD_SIZE * index)); + long adjusted = selected >> (WORD_SIZE * index); + return (byte) adjusted; + } + + /** + * Writes an entire word into {@code buffer} + * @param buffer buffer to write a single word to. Must have + * length 8. + * @throws LowlevelError if the entire word is not initialized + */ + public void getWord(byte[] buffer) { + if (initialized != ((byte) (0xff))) { + throw new LowlevelError("Attempted to read uninitialized word in unique space"); + } + if (buffer.length != WORD_SIZE) { + throw new IllegalArgumentException("Buffer must have length 8"); + } + for (int i = 0; i < WORD_SIZE; ++i) { + buffer[i] = (byte) ((word & (0xffL << (WORD_SIZE * i))) >> (WORD_SIZE * i)); + } + } + + /** + * Returns true precisely when the entire word is initialized. + * @return true if entire work initialized + */ + protected boolean isEntireWordInitialized() { + return initialized == (byte) 0xff; + } + + //assumes 0 <= index <= 7 + private void checkInitialized(int index) { + if ((initialized & (1 << (index))) == 0) { + throw new LowlevelError( + "Attempted to read uninitialized memory in the unique space."); + } + } + + //ensure that the provided index is valid + private void validateIndex(int index) { + if (index < 0 || index > 7) { + throw new LowlevelError("Invalid index: " + Integer.toString(index)); + } + return; + } + + } + } diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/pcode/memstate/UniqueMemoryBankTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/pcode/memstate/UniqueMemoryBankTest.java new file mode 100644 index 0000000000..4512cf6c57 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/pcode/memstate/UniqueMemoryBankTest.java @@ -0,0 +1,253 @@ +/* ### + * 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.pcode.memstate; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; + +import generic.test.AbstractGenericTest; +import ghidra.pcode.error.LowlevelError; +import ghidra.pcode.memstate.UniqueMemoryBank.WordInfo; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.address.GenericAddressSpace; + +public class UniqueMemoryBankTest extends AbstractGenericTest { + + private AddressSpace uniqueSpace; + private UniqueMemoryBank uniqueBank; + private byte[] eightTestBytes = new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 }; + private byte[] eightZeroBytes = new byte[8]; + private byte[] sixteenTestBytes = new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf }; + + @Before + public void setUp() { + uniqueSpace = new GenericAddressSpace("unique", 64, AddressSpace.TYPE_UNIQUE, 0); + uniqueBank = new UniqueMemoryBank(uniqueSpace, false); + } + + public UniqueMemoryBankTest() { + super(); + } + + @Test + public void WordInfoBasicTest() { + WordInfo info = new WordInfo(); + assertFalse(info.isEntireWordInitialized()); + info.setByte((byte) 0x0, 0); + assertFalse(info.isEntireWordInitialized()); + info.setByte((byte) 0x1, 1); + assertFalse(info.isEntireWordInitialized()); + info.setByte((byte) 0x2, 2); + assertFalse(info.isEntireWordInitialized()); + info.setByte((byte) 0x3, 3); + assertFalse(info.isEntireWordInitialized()); + info.setByte((byte) 0x4, 4); + assertFalse(info.isEntireWordInitialized()); + info.setByte((byte) 0x5, 5); + assertFalse(info.isEntireWordInitialized()); + info.setByte((byte) 0x6, 6); + assertFalse(info.isEntireWordInitialized()); + info.setByte((byte) 0x7, 7); + assertTrue(info.isEntireWordInitialized()); + for (int i = 0; i < 8; ++i) { + assertEquals((byte) i, info.getByte(i)); + } + } + + @Test(expected = LowlevelError.class) + public void testGetUnitializedByte() { + WordInfo info = new WordInfo(); + info.setByte((byte) 0, 0); + info.setByte((byte) 1, 1); + info.setByte((byte) 3, 3); + info.setByte((byte) 4, 4); + info.setByte((byte) 5, 5); + info.setByte((byte) 6, 6); + info.setByte((byte) 7, 7); + @SuppressWarnings("unused") + byte val = info.getByte(2); + } + + @Test + public void testSimpleRead() { + uniqueBank.setChunk(0x1000, 8, eightTestBytes); + byte[] dest = new byte[8]; + int numBytes = uniqueBank.getChunk(0x1000, 8, dest, true); + assertEquals(8, numBytes); + assertTrue(Arrays.equals(dest, eightTestBytes)); + } + + @Test + public void testDifferentlySizedReads() { + uniqueBank.setChunk(0x1000, 8, eightTestBytes); + byte[] dest = new byte[4]; + int numBytes = uniqueBank.getChunk(0x1000, 4, dest, true); + assertEquals(4, numBytes); + assertTrue(Arrays.equals(dest, new byte[] { 0x0, 0x1, 0x2, 0x3 })); + numBytes = uniqueBank.getChunk(0x1004, 4, dest, true); + assertEquals(4, numBytes); + assertTrue(Arrays.equals(dest, new byte[] { 0x4, 0x5, 0x6, 0x7 })); + } + + @Test + public void testLargeReadWrite() { + uniqueBank.setChunk(0x1004, 16, sixteenTestBytes); + byte[] dest = new byte[16]; + int numBytes = uniqueBank.getChunk(0x1004, 16, dest, true); + assertEquals(16, numBytes); + assertTrue(Arrays.equals(dest, sixteenTestBytes)); + + byte[] largeSrc = new byte[64]; + for (int i = 0; i < 64; ++i) { + largeSrc[i] = (byte) (i + 1); + } + uniqueBank.setChunk(0x1007, 64, largeSrc); + dest = new byte[64]; + numBytes = uniqueBank.getChunk(0x1007, 64, dest, true); + assertEquals(64, numBytes); + assertTrue(Arrays.equals(dest, largeSrc)); + } + + @Test + public void testReadAcrossUndefined() { + byte[] fourBytes = new byte[] { 0x11, 0x22, 0x33, 0x44 }; + uniqueBank.setChunk(0x1007, 4, fourBytes); + uniqueBank.setChunk(0x100c, 4, fourBytes); + byte[] dest = new byte[9]; + int numBytes = uniqueBank.getChunk(0x1007, 9, dest, true); + assertEquals(4, numBytes); + assertEquals(0x11, dest[0]); + assertEquals(0x22, dest[1]); + assertEquals(0x33, dest[2]); + assertEquals(0x44, dest[3]); + } + + @Test + public void testNonAlignedReadWrite() { + byte[] fourBytes = new byte[] { 0x11, 0x22, 0x33, 0x44 }; + uniqueBank.setChunk(0x1004, 4, fourBytes); + byte[] dest = new byte[4]; + int numBytes = uniqueBank.getChunk(0x1004, 4, dest, true); + assertEquals(4, numBytes); + assertTrue(Arrays.equals(fourBytes, dest)); + } + + @Test + public void testOverlappingReadWrite() { + uniqueBank.setChunk(0x1000, 16, sixteenTestBytes); + uniqueBank.setChunk(0x1004, 8, eightZeroBytes); + byte[] dest = new byte[16]; + int numBytes = uniqueBank.getChunk(0x1000, 16, dest, true); + assertEquals(16, numBytes); + for (int i = 0; i < 16; ++i) { + if (i > 3 && i < 12) { + assertEquals(0, dest[i]); + } + else { + assertEquals(i, dest[i]); + } + } + } + + @Test + public void testOneByteRead() { + byte[] one = new byte[] { (byte) 0x7f }; + uniqueBank.setChunk(0x1000, 1, one); + byte[] dest = new byte[16]; + int numBytes = uniqueBank.getChunk(0x1000, 1, dest, false); + assertEquals(1, numBytes); + assertEquals(dest[0], (byte) 0x7f); + + } + + @Test + public void testClear() { + uniqueBank.setChunk(0x1000, 8, eightTestBytes); + byte[] dest = new byte[8]; + uniqueBank.clear(); + int numBytes = uniqueBank.getChunk(0x1000, 8, dest, true); + assertEquals(0, numBytes); + numBytes = uniqueBank.getChunk(0x1000, 7, dest, true); + assertEquals(0, numBytes); + numBytes = uniqueBank.getChunk(0x1000, 6, dest, true); + assertEquals(0, numBytes); + numBytes = uniqueBank.getChunk(0x1000, 5, dest, true); + assertEquals(0, numBytes); + numBytes = uniqueBank.getChunk(0x1000, 4, dest, true); + assertEquals(0, numBytes); + numBytes = uniqueBank.getChunk(0x1000, 3, dest, true); + assertEquals(0, numBytes); + numBytes = uniqueBank.getChunk(0x1000, 2, dest, true); + assertEquals(0, numBytes); + numBytes = uniqueBank.getChunk(0x1000, 1, dest, true); + assertEquals(0, numBytes); + numBytes = uniqueBank.getChunk(0x1000, 0, dest, true); + assertEquals(0, numBytes); + } + + @Test + public void testSimpleOverwrite() { + uniqueBank.setChunk(0x1000, 8, eightTestBytes); + byte[] dest = new byte[8]; + int numBytes = uniqueBank.getChunk(0x1000, 8, dest, true); + assertEquals(8, numBytes); + assertTrue(Arrays.equals(dest, eightTestBytes)); + uniqueBank.setChunk(0x1000, 8, eightZeroBytes); + numBytes = uniqueBank.getChunk(0x1000, 8, dest, true); + assertEquals(8, numBytes); + assertTrue(Arrays.equals(dest, eightZeroBytes)); + } + + @Test(expected = LowlevelError.class) + public void testUnitializedReadStop() { + byte[] dest = new byte[16]; + uniqueBank.getChunk(0x1000, 0x10, dest, false); + } + + @Test + public void testUnitializedReadContinue() { + byte[] dest = new byte[16]; + int bytesRead = uniqueBank.getChunk(0x1000, 0x10, dest, true); + assertEquals(0, bytesRead); + } + + @SuppressWarnings("unused") + @Test(expected = UnsupportedOperationException.class) + public void testGetPageException() { + MemoryPage page = uniqueBank.getPage(0); + } + + @Test(expected = UnsupportedOperationException.class) + public void testSetPageException() { + uniqueBank.setPage(0, new byte[0], 0, 4096, 0); + } + + @Test(expected = UnsupportedOperationException.class) + public void testSetPageInitializedException() { + uniqueBank.setPageInitialized(0, true, 0, 4096, 0); + } + + //possibly add: + //zero-byte read/write + //try to write more bytes than the array has + //try to read more bytes into the array than it has + +}