diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/map/AbstractAddressRangeMapTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/map/AbstractAddressRangeMapTest.java new file mode 100644 index 00000000000..f6b692752f8 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/map/AbstractAddressRangeMapTest.java @@ -0,0 +1,986 @@ +/* ### + * 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.database.map; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.junit.*; + +import db.Field; +import db.LongField; +import db.util.ErrorHandler; +import ghidra.program.database.ProgramDB; +import ghidra.program.database.mem.MemoryMapDB; +import ghidra.program.database.util.AddressRangeMapDB; +import ghidra.program.model.address.*; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.test.TestEnv; +import ghidra.util.Lock; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public abstract class AbstractAddressRangeMapTest extends AbstractGhidraHeadedIntegrationTest { + + private TestEnv env; // needed to discover languages + private ProgramDB program; + private AddressMap addrMap; + private AddressSpace space; + private int txId; + + protected static Field ONE = new LongField(1); + protected static Field TWO = new LongField(2); + protected AddressRangeMapDB map; + protected Address spaceMax; + + @Before + public void setUp() throws Exception { + env = new TestEnv(); + + program = createProgram(); + MemoryMapDB memory = (MemoryMapDB) program.getMemory(); + addrMap = (AddressMap) getInstanceField("addrMap", memory); + space = program.getAddressFactory().getDefaultAddressSpace(); + spaceMax = space.getMaxAddress(); + ErrorHandler errHandler = e -> fail(); + + map = new AddressRangeMapDB(program.getDBHandle(), addrMap, + new Lock("Test"), "TEST", errHandler, LongField.INSTANCE, true); + txId = program.startTransaction("test"); + } + + protected abstract ProgramDB createProgram() throws IOException; + + @After + public void tearDown() { + program.endTransaction(txId, false); + if (program != null) { + program.release(this); + } + addrMap = null; + env.dispose(); + } + + protected Address addr(long offset) { + return space.getAddress(offset); + } + + @Test + public void testPaint() { + map.paintRange(addr(0x100), addr(0x200), ONE); + map.paintRange(addr(0x300), addr(0x400), TWO); + + List ranges = getMapRanges(); + assertEquals(2, ranges.size()); + assertEquals(range(addr(0x100), addr(0x200)), ranges.get(0)); + assertEquals(range(addr(0x300), addr(0x400)), ranges.get(1)); + + checkRange(ranges.get(0), ONE); + checkRange(ranges.get(1), TWO); + } + + @Test + public void testPaintOverlap() { + assertNull(map.getValue(addr(0x200))); + + map.paintRange(addr(0x100), addr(0x300), ONE); + map.paintRange(addr(0x200), addr(0x400), TWO); + + List ranges = getMapRanges(); + assertEquals(2, ranges.size()); + assertEquals(range(addr(0x100), addr(0x1ff)), ranges.get(0)); + assertEquals(range(addr(0x200), addr(0x400)), ranges.get(1)); + + checkRange(ranges.get(0), ONE, null, TWO); + checkRange(ranges.get(1), TWO, ONE, null); + } + + @Test + public void testPaintCoallesceWithLowerExistingRange() { + map.paintRange(addr(0x100), addr(0x200), ONE); + map.paintRange(addr(0x200), addr(0x300), ONE); + + List ranges = getMapRanges(); + assertEquals(1, ranges.size()); + assertEquals(range(addr(0x100), addr(0x300)), ranges.get(0)); + + checkRange(ranges.get(0), ONE); + } + + @Test + public void testPaintCoallesceWithHigherExistingRange() { + map.paintRange(addr(0x200), addr(0x300), ONE); + map.paintRange(addr(0x100), addr(0x200), ONE); + + List ranges = getMapRanges(); + assertEquals(1, ranges.size()); + assertEquals(range(addr(0x100), addr(0x300)), ranges.get(0)); + + checkRange(ranges.get(0), ONE); + } + + @Test + public void testPaintSplitExistingRangeWithNewValue() { + map.paintRange(addr(0x100), addr(0x400), ONE); + map.paintRange(addr(0x200), addr(0x300), TWO); + + List ranges = getMapRanges(); + assertEquals(3, ranges.size()); + assertEquals(range(addr(0x100), addr(0x1ff)), ranges.get(0)); + assertEquals(range(addr(0x200), addr(0x300)), ranges.get(1)); + assertEquals(range(addr(0x301), addr(0x400)), ranges.get(2)); + + checkRange(ranges.get(0), ONE, null, TWO); + checkRange(ranges.get(1), TWO, ONE, ONE); + checkRange(ranges.get(2), ONE, TWO, null); + + } + + @Test + public void testPaintStartOfExitingRangeWithNewValue() { + map.paintRange(addr(0x200), addr(0x400), ONE); + map.paintRange(addr(0x100), addr(0x300), TWO); + + List ranges = getMapRanges(); + assertEquals(2, ranges.size()); + assertEquals(range(addr(0x100), addr(0x300)), ranges.get(0)); + assertEquals(range(addr(0x301), addr(0x400)), ranges.get(1)); + + checkRange(ranges.get(0), TWO, null, ONE); + checkRange(ranges.get(1), ONE, TWO, null); + } + + @Test + public void testPaintSameValueInsideExistingRange() { + map.paintRange(addr(0x100), addr(0x200), ONE); + map.paintRange(addr(0x110), addr(0x150), ONE); + + List ranges = getMapRanges(); + assertEquals(1, ranges.size()); + assertEquals(range(addr(0x100), addr(0x200)), ranges.get(0)); + + checkRange(ranges.get(0), ONE); + } + + @Test + public void testGetValueRecordWithStartKeyGreaterThanEndKey() { + addrMap.setImageBase(addr(0x100)); + // address 0 will have a high key, and address 0x200 will have a low key + map.paintRange(addr(0), addr(0x200), ONE); + + checkValueNoCache(ONE, addr(0x0)); + checkValueNoCache(ONE, addr(0x1)); + checkValueNoCache(ONE, addr(0xff)); + checkValueNoCache(ONE, addr(0x100)); + checkValueNoCache(ONE, addr(0x101)); + checkValueNoCache(ONE, addr(0x1ff)); + checkValueNoCache(ONE, addr(0x200)); + checkValueNoCache(null, addr(0x201)); + checkValueNoCache(null, spaceMax); + } + + @Test + public void testGetValueWithWrappingAddressRecordAndStartKeyGreaterThanEndKey() { + addrMap.setImageBase(addr(0x200)); + // address 0 will have a high key, and address 0x200 will have a low key + map.paintRange(addr(0), addr(0x400), ONE); + addrMap.setImageBase(addr(0x100)); + map.invalidate(); + + checkValueNoCache(ONE, addr(0x0)); + checkValueNoCache(ONE, addr(0xff)); + checkValueNoCache(ONE, addr(0x100)); + checkValueNoCache(ONE, addr(0x101)); + checkValueNoCache(ONE, addr(0x1ff)); + checkValueNoCache(ONE, addr(0x200)); + checkValueNoCache(ONE, addr(0x201)); + checkValueNoCache(ONE, addr(0x2ff)); + checkValueNoCache(ONE, addr(0x300)); + checkValueNoCache(null, addr(0x301)); + checkValueNoCache(null, addr(0).subtractWrap(0x101)); + checkValueNoCache(ONE, addr(0).subtractWrap(0x100)); + checkValueNoCache(ONE, addr(0).subtractWrap(0x0ff)); + checkValueNoCache(ONE, spaceMax); + } + + protected void checkValueNoCache(Field expectedValue, Address address) { + map.invalidate(); // clears cache + assertEquals(expectedValue, map.getValue(address)); + } + + @Test + public void testGetRecordCount() { + assertEquals(0, map.getRecordCount()); + + map.paintRange(addr(0x100), addr(0x200), ONE); + assertEquals(1, map.getRecordCount()); + + map.paintRange(addr(0x300), addr(0x400), ONE); + assertEquals(2, map.getRecordCount()); + + // paint the gap should result in only 1 record + map.paintRange(addr(0x200), addr(0x300), ONE); + assertEquals(1, map.getRecordCount()); + } + + @Test + public void testMoveAddressRange() throws CancelledException { + map.paintRange(addr(0x100), addr(0x300), ONE); + + map.moveAddressRange(addr(0x200), addr(0x500), 0x200, TaskMonitor.DUMMY); + + List ranges = getMapRanges(); + assertEquals(2, ranges.size()); + assertEquals(range(addr(0x100), addr(0x1ff)), ranges.get(0)); + assertEquals(range(addr(0x500), addr(0x600)), ranges.get(1)); + + checkRange(ranges.get(0), ONE); + checkRange(ranges.get(1), ONE); + } + + @Test + public void testIsEmpty() { + assertTrue(map.isEmpty()); + + map.paintRange(addr(0x100), addr(0x900), ONE); + assertFalse(map.isEmpty()); + + map.clearRange(addr(0), addr(0x1000)); + assertTrue(map.isEmpty()); + } + + @Test + public void testPaintFullRangeWithNonZeroImageBase() { + Address imageBase = addr(0x50); + + addrMap.setImageBase(imageBase); + + AddressRange range = range(space.getMinAddress(), space.getMaxAddress()); + map.paintRange(range.getMinAddress(), range.getMaxAddress(), ONE); + + assertEquals(ONE, map.getValue(addr(0))); + assertEquals(ONE, map.getValue(addr(0x200))); + assertEquals(ONE, map.getValue(space.getMaxAddress())); + + List ranges = getMapRanges(); + assertEquals(1, ranges.size()); + assertEquals(range, ranges.get(0)); + } + + @Test + public void testAddressRangeIteratorWithNoTable() { + AddressRangeIterator it = map.getAddressRanges(); + assertFalse(it.hasNext()); + } + + @Test + public void testIteratorWithMultipleRangesIncludeOneThatSpansAddressBoundary() { + + Address imageBase = addr(0x100); + + AddressRange range1 = range(addr(0x0), addr(0x10)); + AddressRange range2 = range(addr(0x20), addr(0x30)); + AddressRange range3 = range(addr(0x100), addr(0x200)); + AddressRange range4 = range(spaceMax.subtract(0x500), spaceMax); + + addrMap.setImageBase(imageBase); + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + map.paintRange(range4.getMinAddress(), range4.getMaxAddress(), ONE); + + checkRange(range1, ONE); + checkRange(range2, ONE); + checkRange(range3, ONE); + checkRange(range4, ONE); + + List ranges = getMapRanges(); + assertEquals(4, ranges.size()); + assertEquals(range1, ranges.get(0)); + assertEquals(range2, ranges.get(1)); + assertEquals(range3, ranges.get(2)); + assertEquals(range4, ranges.get(3)); + } + + @Test + public void testAddressIteratorWithStartAddressBeforeFirstRange() { + AddressRange range2 = range(addr(0x20), addr(0x30)); + AddressRange range3 = range(addr(0x100), addr(0x200)); + AddressRange range4 = range(addr(0x500), spaceMax); + + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + map.paintRange(range4.getMinAddress(), range4.getMaxAddress(), ONE); + + // try iterating starting in first range + List ranges = getMapRanges(addr(0x5)); + assertEquals(3, ranges.size()); + assertEquals(range2, ranges.get(0)); + assertEquals(range3, ranges.get(1)); + assertEquals(range4, ranges.get(2)); + + } + + @Test + public void testAddressIteratorWithStartAddressInFirstRange() { + AddressRange range1 = range(addr(0x0), addr(0x10)); + AddressRange range2 = range(addr(0x20), addr(0x30)); + AddressRange range3 = range(addr(0x100), addr(0x200)); + AddressRange range4 = range(addr(0x500), spaceMax); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + map.paintRange(range4.getMinAddress(), range4.getMaxAddress(), ONE); + + // try iterating starting in first range + List ranges = getMapRanges(addr(0x5)); + assertEquals(4, ranges.size()); + assertEquals(range(addr(0x5), range1.getMaxAddress()), ranges.get(0)); + assertEquals(range2, ranges.get(1)); + assertEquals(range3, ranges.get(2)); + assertEquals(range4, ranges.get(3)); + } + + @Test + public void testAddressIteratorWithStartAddressBetweenFirstAndSecondRange() { + AddressRange range1 = range(addr(0x0), addr(0x10)); + AddressRange range2 = range(addr(0x20), addr(0x30)); + AddressRange range3 = range(addr(0x100), addr(0x200)); + AddressRange range4 = range(addr(0x500), spaceMax); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + map.paintRange(range4.getMinAddress(), range4.getMaxAddress(), ONE); + + // try iterating starting in first range + List ranges = getMapRanges(addr(0x15)); + assertEquals(3, ranges.size()); + assertEquals(range2, ranges.get(0)); + assertEquals(range3, ranges.get(1)); + assertEquals(range4, ranges.get(2)); + } + + @Test + public void testAddressIteratorWithStartAddressInSecondRange() { + AddressRange range1 = range(addr(0x0), addr(0x10)); + AddressRange range2 = range(addr(0x20), addr(0x30)); + AddressRange range3 = range(addr(0x100), addr(0x200)); + AddressRange range4 = range(addr(0x500), spaceMax); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + map.paintRange(range4.getMinAddress(), range4.getMaxAddress(), ONE); + + // try iterating starting in first range + List ranges = getMapRanges(addr(0x25)); + assertEquals(3, ranges.size()); + assertEquals(range(addr(0x25), range2.getMaxAddress()), ranges.get(0)); + assertEquals(range3, ranges.get(1)); + assertEquals(range4, ranges.get(2)); + } + + @Test + public void testAddressIteratorWithStartAddressInLastRange() { + AddressRange range1 = range(addr(0x0), addr(0x10)); + AddressRange range2 = range(addr(0x20), addr(0x30)); + AddressRange range3 = range(addr(0x100), addr(0x200)); + AddressRange range4 = range(addr(0x500), addr(0x1000)); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + map.paintRange(range4.getMinAddress(), range4.getMaxAddress(), ONE); + + // try iterating starting in first range + List ranges = getMapRanges(addr(0x600)); + assertEquals(1, ranges.size()); + assertEquals(range(addr(0x600), range4.getMaxAddress()), ranges.get(0)); + } + + @Test + public void testAddressIteratorWithStartAddressAfterLastRange() { + AddressRange range1 = range(addr(0x0), addr(0x10)); + AddressRange range2 = range(addr(0x20), addr(0x30)); + AddressRange range3 = range(addr(0x100), addr(0x200)); + AddressRange range4 = range(addr(0x500), addr(0x600)); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + map.paintRange(range4.getMinAddress(), range4.getMaxAddress(), ONE); + + // try iterating after last range + List ranges = getMapRanges(addr(0x1000)); + assertEquals(0, ranges.size()); + } + + @Test + public void testAddressIteratorWithImageBaseStartAddressInFirstRange() { + + Address imageBase = addr(0x100); + addrMap.setImageBase(imageBase); + + AddressRange range1 = range(addr(0x0), addr(0x10)); + AddressRange range2 = range(addr(0x20), addr(0x30)); + AddressRange range3 = range(addr(0x100), addr(0x200)); + AddressRange range4 = range(spaceMax.subtract(0x500), spaceMax); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + map.paintRange(range4.getMinAddress(), range4.getMaxAddress(), ONE); + + // try iterating starting in first range + List ranges = getMapRanges(addr(0x5)); + assertEquals(4, ranges.size()); + assertEquals(range(addr(0x5), range1.getMaxAddress()), ranges.get(0)); + assertEquals(range2, ranges.get(1)); + assertEquals(range3, ranges.get(2)); + assertEquals(range4, ranges.get(3)); + } + + @Test + public void testAddressIteratorWithImageBaseStartAddressBetweenFirstAndSecondRange() { + + Address imageBase = addr(0x100); + addrMap.setImageBase(imageBase); + + AddressRange range1 = range(addr(0x0), addr(0x10)); + AddressRange range2 = range(addr(0x20), addr(0x30)); + AddressRange range3 = range(addr(0x100), addr(0x200)); + AddressRange range4 = range(addr(0x500), spaceMax); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + map.paintRange(range4.getMinAddress(), range4.getMaxAddress(), ONE); + + // try iterating starting in first range + List ranges = getMapRanges(addr(0x15)); + assertEquals(3, ranges.size()); + assertEquals(range2, ranges.get(0)); + assertEquals(range3, ranges.get(1)); + assertEquals(range4, ranges.get(2)); + } + + @Test + public void testAddressIteratorWithImageBaseStartAddressInSecondRange() { + + Address imageBase = addr(0x100); + addrMap.setImageBase(imageBase); + + AddressRange range1 = range(addr(0x0), addr(0x10)); + AddressRange range2 = range(addr(0x20), addr(0x30)); + AddressRange range3 = range(addr(0x100), addr(0x200)); + AddressRange range4 = range(addr(0x500), spaceMax); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + map.paintRange(range4.getMinAddress(), range4.getMaxAddress(), ONE); + + // try iterating starting in first range + List ranges = getMapRanges(addr(0x25)); + assertEquals(3, ranges.size()); + assertEquals(range(addr(0x25), range2.getMaxAddress()), ranges.get(0)); + assertEquals(range3, ranges.get(1)); + assertEquals(range4, ranges.get(2)); + } + + @Test + public void testAddressIteratorWithImageBaseStartAddressInLastRange() { + + Address imageBase = addr(0x100); + addrMap.setImageBase(imageBase); + + AddressRange range1 = range(addr(0x0), addr(0x10)); + AddressRange range2 = range(addr(0x20), addr(0x30)); + AddressRange range3 = range(addr(0x100), addr(0x200)); + AddressRange range4 = range(addr(0x500), spaceMax); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + map.paintRange(range4.getMinAddress(), range4.getMaxAddress(), ONE); + + // try iterating starting in first range + List ranges = getMapRanges(addr(0x600)); + assertEquals(1, ranges.size()); + assertEquals(range(addr(0x600), range4.getMaxAddress()), ranges.get(0)); + } + + @Test + public void testGetAddressIteratorWithStartAndEndBeforeFirstRange() { + AddressRange range1 = range(addr(0x100), addr(0x200)); + AddressRange range2 = range(addr(0x300), addr(0x400)); + AddressRange range3 = range(addr(0x600), addr(0x700)); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + + // try iterating starting in first range + List ranges = getMapRanges(addr(0x20), addr(0x50)); + assertEquals(0, ranges.size()); + } + + @Test + public void testGetAddressIteratorWithStartAndEndBeforeInsideRange() { + AddressRange range1 = range(addr(0x100), addr(0x200)); + AddressRange range2 = range(addr(0x300), addr(0x400)); + AddressRange range3 = range(addr(0x600), addr(0x700)); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + + // try iterating starting in first range + List ranges = getMapRanges(addr(0x310), addr(0x390)); + assertEquals(1, ranges.size()); + assertEquals(range(addr(0x310), addr(0x390)), ranges.get(0)); + } + + @Test + public void testGetAddressIteratorWithStartInOneRangeEndInAnother() { + AddressRange range1 = range(addr(0x100), addr(0x200)); + AddressRange range2 = range(addr(0x300), addr(0x400)); + AddressRange range3 = range(addr(0x600), addr(0x700)); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + + // try iterating starting in first range + List ranges = getMapRanges(addr(0x310), addr(0x610)); + assertEquals(2, ranges.size()); + assertEquals(range(addr(0x310), addr(0x400)), ranges.get(0)); + assertEquals(range(addr(0x600), addr(0x610)), ranges.get(1)); + } + + @Test + public void testGetAddressIteratorWithStartAndEndPastLastRange() { + AddressRange range1 = range(addr(0x100), addr(0x200)); + AddressRange range2 = range(addr(0x300), addr(0x400)); + AddressRange range3 = range(addr(0x600), addr(0x700)); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + + // try iterating starting in first range + List ranges = getMapRanges(addr(0x800), addr(0x900)); + assertEquals(0, ranges.size()); + } + + @Test + public void testGetAddressIteratorWithImageBaseStartingAndEndingInSpanningRecord() { + Address imageBase = addr(0x150); + addrMap.setImageBase(imageBase); + + AddressRange range1 = range(addr(0x0), addr(0x100)); + AddressRange range2 = range(addr(0x200), addr(0x300)); + AddressRange range3 = range(addr(0x500), addr(0x600)); + AddressRange range4 = range(addr(0x800), spaceMax); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + map.paintRange(range4.getMinAddress(), range4.getMaxAddress(), ONE); + + List ranges = getMapRanges(addr(0x50), addr(0x1000)); + assertEquals(4, ranges.size()); + assertEquals(range(addr(0x50), range1.getMaxAddress()), ranges.get(0)); + assertEquals(range2, ranges.get(1)); + assertEquals(range3, ranges.get(2)); + assertEquals(range(range4.getMinAddress(), addr(0x1000)), ranges.get(3)); + } + + @Test + public void testGetAddressIteratorWithImageBaseWithStartAndEndIncludeAll() { + Address imageBase = addr(0x250); + addrMap.setImageBase(imageBase); + AddressRange range1 = range(addr(0x100), addr(0x200)); + AddressRange range2 = range(addr(0x300), addr(0x400)); + AddressRange range3 = range(addr(0x600), addr(0x700)); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + + // try iterating starting in first range + List ranges = getMapRanges(addr(0x150), addr(0x650)); + assertEquals(3, ranges.size()); + assertEquals(range(addr(0x150), range1.getMaxAddress()), ranges.get(0)); + assertEquals(range2, ranges.get(1)); + assertEquals(range(range3.getMinAddress(), addr(0x650)), ranges.get(2)); + } + + @Test + public void testGetAddressIteratorWithImageBaseStartInOneRangeEndInAnother() { + AddressRange range1 = range(addr(0x100), addr(0x200)); + AddressRange range2 = range(addr(0x300), addr(0x400)); + AddressRange range3 = range(addr(0x600), addr(0x700)); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + + // try iterating starting in first range + List ranges = getMapRanges(addr(0x310), addr(0x610)); + assertEquals(2, ranges.size()); + assertEquals(range(addr(0x310), addr(0x400)), ranges.get(0)); + assertEquals(range(addr(0x600), addr(0x610)), ranges.get(1)); + } + + @Test + public void testGetAddressSet() { + map.paintRange(addr(0x100), addr(0x200), ONE); + map.paintRange(addr(0x300), addr(0x400), TWO); + + AddressSet set = map.getAddressSet(); + assertEquals(2, set.getNumAddressRanges()); + assertEquals(range(addr(0x100), addr(0x200)), set.getFirstRange()); + assertEquals(range(addr(0x300), addr(0x400)), set.getLastRange()); + } + + @Test + public void testGetAddressSetWithImageBaseSet() { + addrMap.setImageBase(addr(0x100)); + map.paintRange(addr(0), spaceMax, ONE); + AddressSet set = map.getAddressSet(); + assertEquals(1, set.getNumAddressRanges()); + assertEquals(range(addr(0), spaceMax), set.getFirstRange()); + } + + @Test + public void testGetAddressRangeContaining() { + AddressRange range1 = range(addr(0x100), addr(0x200)); + AddressRange range2 = range(addr(0x300), addr(0x400)); + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), TWO); + + assertEquals(range1, map.getAddressRangeContaining(addr(0x100))); + assertEquals(range1, map.getAddressRangeContaining(addr(0x150))); + assertEquals(range1, map.getAddressRangeContaining(addr(0x200))); + assertEquals(range2, map.getAddressRangeContaining(addr(0x300))); + assertEquals(range2, map.getAddressRangeContaining(addr(0x350))); + assertEquals(range2, map.getAddressRangeContaining(addr(0x400))); + + assertEquals(range(addr(0), addr(0xff)), map.getAddressRangeContaining(addr(0x0))); + assertEquals(range(addr(0x201), addr(0x2ff)), map.getAddressRangeContaining(addr(0x250))); + assertEquals(range(addr(0x401), spaceMax), map.getAddressRangeContaining(addr(0x900))); + } + + @Test + public void testGetRangeContainingWithStartKeyGreaterThanEndKey() { + AddressRange range = range(addr(0), addr(0x200)); + AddressRange noValueRange = range(addr(0x201), spaceMax); + addrMap.setImageBase(addr(0x100)); + // address 0 will have a high key, and address 0x200 will have a low key + map.paintRange(range.getMinAddress(), range.getMaxAddress(), ONE); + + assertEquals(range, map.getAddressRangeContaining(addr(0))); + assertEquals(range, map.getAddressRangeContaining(addr(0x100))); + assertEquals(range, map.getAddressRangeContaining(addr(0x200))); + + assertEquals(noValueRange, map.getAddressRangeContaining(addr(0x201))); + assertEquals(noValueRange, map.getAddressRangeContaining(addr(0x500))); + assertEquals(noValueRange, map.getAddressRangeContaining(spaceMax)); + + } + + @Test + public void testGetRangeContainingWithWrappingAddressRecordAndStartKeyGreaterThanEndKey() { + addrMap.setImageBase(addr(0x200)); + // address 0 will have a high key, and address 0x200 will have a low key + map.paintRange(addr(0), addr(0x400), ONE); + addrMap.setImageBase(addr(0x100)); + map.invalidate(); + + AddressRange rangeLow = range(addr(0), addr(0x300)); + AddressRange rangeHigh = range(addr(0).subtractWrap(0x100), spaceMax); + AddressRange noValueRange = range(addr(0x301), rangeHigh.getMinAddress().subtract(1)); + + assertEquals(rangeLow, map.getAddressRangeContaining(addr(0))); + assertEquals(rangeLow, map.getAddressRangeContaining(addr(0x100))); + assertEquals(rangeLow, map.getAddressRangeContaining(addr(0x200))); + assertEquals(rangeLow, map.getAddressRangeContaining(addr(0x300))); + + assertEquals(rangeHigh, map.getAddressRangeContaining(spaceMax)); + assertEquals(rangeHigh, map.getAddressRangeContaining(rangeHigh.getMinAddress())); + + assertEquals(noValueRange, map.getAddressRangeContaining(addr(0x301))); + assertEquals(noValueRange, map.getAddressRangeContaining(addr(0x500))); + assertEquals(noValueRange, + map.getAddressRangeContaining(rangeHigh.getMinAddress().subtract(1))); + + } + + @Test + public void testGetAddressRangeContainingWithSpanningAddressBoundary() { + map.paintRange(addr(0), addr(0x200), ONE); + + // move the image base to make the above range partially wrap into upper address + addrMap.setImageBase(addr(0).subtractWrap(0x100)); + + // there is still currently only one record + assertEquals(1, map.getRecordCount()); + + List ranges = getMapRanges(); + assertEquals(range(addr(0), addr(0x100)), ranges.get(0)); + assertEquals(range(addr(0).subtractWrap(0x100), spaceMax), ranges.get(1)); + } + + @Test + public void testRangeWithImageBaseMoveToInsideRange() { + map.paintRange(addr(0x10), addr(0x20), ONE); + + assertNull(map.getValue(addr(0))); + assertEquals(ONE, map.getValue(addr(0x10))); + assertEquals(ONE, map.getValue(addr(0x15))); + assertEquals(ONE, map.getValue(addr(0x20))); + assertNull(map.getValue(addr(0x25))); + + List ranges = getMapRanges(); + assertEquals(1, ranges.size()); + assertEquals(range(addr(0x10), addr(0x20)), ranges.get(0)); + + Address imageBase = addr(0x15); + addrMap.setImageBase(imageBase); + // if the image base changes, the map has to be told so that it can clear its cache. + map.invalidate(); + + // now range should be from 0x25 to 0x35 + assertNull(map.getValue(addr(0))); + assertNull(map.getValue(addr(0x10))); + assertNull(map.getValue(addr(0x15))); + assertNull(map.getValue(addr(0x20))); + assertEquals(ONE, map.getValue(addr(0x25))); + assertEquals(ONE, map.getValue(addr(0x30))); + assertEquals(ONE, map.getValue(addr(0x35))); + assertNull(map.getValue(addr(0x36))); + + ranges = getMapRanges(); + assertEquals(1, ranges.size()); + // image base will cause range to be two pieces, but effectively same set of addresses + assertEquals(range(addr(0x25), addr(0x35)), ranges.get(0)); + + } + + @Test + public void testValueRangeSet() { + AddressRange range1 = range(addr(0x100), addr(0x200)); + AddressRange range2 = range(addr(0x300), addr(0x400)); + AddressRange range3 = range(addr(0x500), addr(0x600)); + AddressRange range4 = range(addr(0x700), addr(0x800)); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), TWO); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + map.paintRange(range4.getMinAddress(), range4.getMaxAddress(), TWO); + + AddressSet setOne = addressSet(range1, range3); + AddressSet setTwo = addressSet(range2, range4); + + assertEquals(setOne, map.getAddressSet(ONE)); + assertEquals(setTwo, map.getAddressSet(TWO)); + + } + + @Test + public void testValueRangeSetWithNoGaps() { + AddressRange range1 = range(addr(0x100), addr(0x500)); + AddressRange range2 = range(addr(0x200), addr(0x300)); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), TWO); + + AddressSet setOne = addressSet(range1).subtract(addressSet(range2)); + AddressSet setTwo = addressSet(range2); + + assertEquals(setOne, map.getAddressSet(ONE)); + assertEquals(setTwo, map.getAddressSet(TWO)); + + } + + @Test + public void testValueRangeSetWithImageBaseAndSpanningRecord() { + Address imageBase = addr(0x150); + addrMap.setImageBase(imageBase); + + AddressRange range1 = range(addr(0x0), addr(0x100)); + AddressRange range2 = range(addr(0x300), addr(0x400)); + AddressRange range3 = range(addr(0x500), spaceMax); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), TWO); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + + assertEquals(addressSet(range1, range3), map.getAddressSet(ONE)); + assertEquals(addressSet(range2), map.getAddressSet(TWO)); + } + + @Test + public void testValueRangeSetWithValueNotInMap() { + AddressRange range1 = range(addr(0x100), addr(0x500)); + AddressRange range2 = range(addr(0x200), addr(0x300)); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), ONE); + + assertEquals(addressSet(), map.getAddressSet(TWO)); + } + + @Test + public void testGetAddressSetForValue() { + AddressRange range1 = range(addr(0x100), addr(0x200)); + AddressRange range2 = range(addr(0x300), addr(0x400)); + AddressRange range3 = range(addr(0x500), addr(0x600)); + AddressRange range4 = range(addr(0x700), addr(0x800)); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), TWO); + map.paintRange(range3.getMinAddress(), range3.getMaxAddress(), ONE); + map.paintRange(range4.getMinAddress(), range4.getMaxAddress(), TWO); + + AddressSet addressSet = map.getAddressSet(ONE); + assertEquals(2, addressSet.getNumAddressRanges()); + assertEquals(range1, addressSet.getFirstRange()); + assertEquals(range3, addressSet.getLastRange()); + + addressSet = map.getAddressSet(TWO); + assertEquals(2, addressSet.getNumAddressRanges()); + assertEquals(range2, addressSet.getFirstRange()); + assertEquals(range4, addressSet.getLastRange()); + } + + @Test + public void testPaintAfterImageBaseChangeSplitsWrappingRecord() { + AddressRange range1 = range(addr(0x00), addr(0x200)); + AddressRange range2 = range(addr(0x500), addr(0x600)); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + assertEquals(1, map.getRecordCount()); + assertEquals(1, getMapRanges().size()); + assertEquals(range1, getMapRanges().get(0)); + + Address imageBase = spaceMax.subtract(0xff); + addrMap.setImageBase(imageBase); + map.invalidate(); + + assertEquals(1, map.getRecordCount()); + assertEquals(2, getMapRanges().size()); + assertEquals(range(addr(0), addr(0x100)), getMapRanges().get(0)); + assertEquals(range(imageBase, spaceMax), getMapRanges().get(1)); + + // do another paint and see that the record got split + // any paint will do, so do a paint that effectively does nothing + map.paintRange(range2.getMinAddress(), range2.getMaxAddress(), null); + assertEquals(2, map.getRecordCount()); + assertEquals(2, getMapRanges().size()); + assertEquals(range(imageBase, spaceMax), getMapRanges().get(0)); + assertEquals(range(addr(0), addr(0x100)), getMapRanges().get(1)); + + } + + @Test + public void testRangeIteratorWithRestrictionsSuchThatStartRangeIsOnlyRange() { + AddressRange range1 = range(addr(0x00), addr(0x200)); + + map.paintRange(range1.getMinAddress(), range1.getMaxAddress(), ONE); + assertEquals(1, map.getRecordCount()); + assertEquals(1, getMapRanges().size()); + assertEquals(range1, getMapRanges().get(0)); + + Address imageBase = spaceMax.subtract(0xff); + addrMap.setImageBase(imageBase); + map.invalidate(); + + List ranges = getMapRanges(addr(0), addr(0x500)); + assertEquals(1, ranges.size()); + assertEquals(range(addr(0), addr(0x100)), ranges.get(0)); + + } + + private AddressRange range(Address start, Address end) { + return new AddressRangeImpl(start, end); + } + + private List getMapRanges() { + AddressRangeIterator addressRanges = map.getAddressRanges(); + List ranges = new ArrayList(); + for (AddressRange addressRange : addressRanges) { + ranges.add(addressRange); + } + return ranges; + } + + private List getMapRanges(Address start) { + AddressRangeIterator addressRanges = map.getAddressRanges(start); + List ranges = new ArrayList(); + for (AddressRange addressRange : addressRanges) { + ranges.add(addressRange); + } + return ranges; + } + + private List getMapRanges(Address start, Address end) { + AddressRangeIterator addressRanges = map.getAddressRanges(start, end); + List ranges = new ArrayList(); + for (AddressRange addressRange : addressRanges) { + ranges.add(addressRange); + } + return ranges; + } + + private void checkRange(AddressRange range, Field value) { + checkRange(range, value, null, null); + } + + private void checkRange(AddressRange range, Field value, Field valueBeforeRange, + Field valueAfterRange) { + Address start = range.getMinAddress(); + Address end = range.getMaxAddress(); + + assertEquals("Value at range start " + range, value, map.getValue(start)); + assertEquals("Value at range end " + range, value, map.getValue(end)); + + // if not at zero, check that value doesn't exist just before range; + if (start.compareTo(addr(0)) > 0) { + assertEquals("Value before range " + range, valueBeforeRange, + map.getValue(start.subtract(1))); + } + + // if not at end, check that value doesn't exist just past range + if (end.compareTo(spaceMax) < 0) { + assertEquals("Value after range " + range, valueAfterRange, map.getValue(end.add(1))); + } + } + + private AddressSet addressSet(AddressRange... ranges) { + AddressSet set = new AddressSet(); + for (AddressRange range : ranges) { + set.add(range); + } + return set; + } +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/map/AddressRangeMap32BitTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/map/AddressRangeMap32BitTest.java new file mode 100644 index 00000000000..7b0f4dbbaf0 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/map/AddressRangeMap32BitTest.java @@ -0,0 +1,31 @@ +/* ### + * 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.database.map; + +import java.io.IOException; + +import ghidra.program.database.ProgramDB; +import ghidra.program.model.lang.*; + +public class AddressRangeMap32BitTest extends AbstractAddressRangeMapTest { + + protected ProgramDB createProgram() throws IOException { + LanguageService service = getLanguageService(); + Language language = service.getLanguage(new LanguageID("sparc:BE:32:default")); + return new ProgramDB("test", language, language.getDefaultCompilerSpec(), this); + } + +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/map/AddressRangeMap64BitTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/map/AddressRangeMap64BitTest.java new file mode 100644 index 00000000000..952a7cc70aa --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/map/AddressRangeMap64BitTest.java @@ -0,0 +1,47 @@ +/* ### + * 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.database.map; + +import java.io.IOException; + +import org.junit.Test; + +import ghidra.program.database.ProgramDB; +import ghidra.program.model.lang.*; + +public class AddressRangeMap64BitTest extends AbstractAddressRangeMapTest { + + protected ProgramDB createProgram() throws IOException { + LanguageService service = getLanguageService(); + Language language = service.getLanguage(new LanguageID("sparc:BE:64:default")); + return new ProgramDB("test", language, language.getDefaultCompilerSpec(), this); + } + + @Test + public void testGetValue64BitSpecific() { + map.paintRange(addr(0), spaceMax, ONE); + + checkValueNoCache(ONE, addr(0x0)); + checkValueNoCache(ONE, addr(0x0)); + + // now check addresses that are in different address bases that don't exist yet + // addresses that differ in the upper 32bits are in different address basesI + checkValueNoCache(ONE, addr(0xA0000000000000L)); + checkValueNoCache(ONE, addr(0xB0000000000000L)); + + } + +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/util/ProgramContextTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/util/ProgramContextTest.java index ded42540f61..9bd0ba7e42e 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/util/ProgramContextTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/util/ProgramContextTest.java @@ -30,6 +30,7 @@ import ghidra.program.model.listing.ProgramContext; import ghidra.program.model.mem.Memory; import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitorAdapter; /** @@ -78,7 +79,7 @@ public void testAll() { int id = program.startTransaction("Test"); try { - Address start = getAddress(0); + Address start = addr(0); try { mem.createInitializedBlock("first", start, 100, (byte) 0, TaskMonitorAdapter.DUMMY_MONITOR, false); @@ -91,7 +92,7 @@ public void testAll() { boolean didSomething = false; Address startAddress = start; - Address endAddress = getAddress(0x30); + Address endAddress = addr(0x30); // stick a value into each one! BigInteger value = BigInteger.valueOf(255); @@ -157,8 +158,38 @@ public void testAll() { } } - private Address getAddress(long offset) { - return space.getAddress(offset); + @Test + public void testImageBaseChange() throws Exception { + int id = program.startTransaction("Test"); + Address start = addr(0x10); + Address end = addr(0x20); + + mem.createInitializedBlock("first", addr(0), 0x100, (byte) 0, TaskMonitor.DUMMY, false); + + ProgramContext programContext = program.getProgramContext(); + + Register register = programContext.getRegisters().get(0); + BigInteger value = BigInteger.valueOf(0x11); + + programContext.setValue(register, addr(0x10), addr(0x20), value); + assertNull(programContext.getValue(register, start.subtract(1), true)); + assertEquals(value, programContext.getValue(register, start, true)); + assertEquals(value, programContext.getValue(register, end, true)); + assertNull(programContext.getValue(register, end.add(1), true)); + + long imageOffset = 0x5; + Address imageBase = addr(imageOffset); + program.setImageBase(imageBase, true); + + assertNull(programContext.getValue(register, start.add(imageOffset - 1), true)); + assertEquals(value, programContext.getValue(register, start.add(imageOffset), true)); + assertEquals(value, programContext.getValue(register, end.add(imageOffset), true)); + assertNull(programContext.getValue(register, end.add(imageOffset + 1), true)); + + program.endTransaction(id, false); } + private Address addr(long offset) { + return space.getAddress(offset); + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java index a8f1e5b2cf2..f5445da4708 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java @@ -200,7 +200,6 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM private int languageMinorVersion; private LanguageTranslator languageUpgradeTranslator; - private Address storedImageBase; // current image base maintained by addrMap private boolean imageBaseOverride = false; private boolean recordChanges; @@ -1119,7 +1118,7 @@ private void refreshName() throws IOException { private void refreshImageBase() throws IOException { long baseOffset = getStoredBaseImageOffset(); - storedImageBase = addressFactory.getDefaultAddressSpace().getAddress(baseOffset); + Address storedImageBase = addressFactory.getDefaultAddressSpace().getAddress(baseOffset); if (!imageBaseOverride) { Address currentImageBase = getImageBase(); if (!currentImageBase.equals(storedImageBase)) { @@ -1268,7 +1267,6 @@ public void setImageBase(Address base, boolean commit) if (commit) { try { dataMap.put(IMAGE_OFFSET, Long.toHexString(base.getOffset())); - storedImageBase = base; imageBaseOverride = false; setChanged(ChangeManager.DOCR_IMAGE_BASE_CHANGED, oldBase, base); @@ -1736,9 +1734,7 @@ protected void clearCache(boolean all) { refreshName(); overlaySpaceAdapter.updateOverlaySpaces(addressFactory); addrMap.invalidateCache(); - if (!imageBaseOverride) { - refreshImageBase(); - } + refreshImageBase(); for (int i = 0; i < NUM_MANAGERS; i++) { managers[i].invalidateCache(all); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/map/AddressMap.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/map/AddressMap.java index e2d1c143dfe..c8024eee4ca 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/map/AddressMap.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/map/AddressMap.java @@ -87,12 +87,19 @@ public interface AddressMap { * never be generated. The returned key ranges will correspond * to those key ranges which have previously been created within * the specified address range and may represent a much smaller subset - * of addresses within the specified range. - * @param start minimum address of range + * of addresses within the specified range. + * NOTE: if the create parameter is true, the given range must not extend in the upper 32 bits + * by more than 1 segment. For example, range(0x0000000000000000 - 0x0000000100000000) + * is acceptable, but the range (0x0000000000000000 - 0x0000000200000000) is not because the + * upper 32 bits differ by 2. + * @param start the start address of the range * @param end maximum address of range * @param create true if a new keys may be generated, otherwise returned - * key-ranges will be limited to those already defined. + * key-ranges will be limited to those already defined. And if true, the range will be limited + * to a size of 2^32 so that at most it creates two new address bases * @return "sorted" list of KeyRange objects + * @throws UnsupportedOperationException if the given range is so large that the upper 32 bit + * segments differ by more than 1. */ public List getKeyRanges(Address start, Address end, boolean create); @@ -136,7 +143,8 @@ public interface AddressMap { * key-ranges will be limited to those already defined. * @return "sorted" list of KeyRange objects */ - public List getKeyRanges(Address start, Address end, boolean absolute, boolean create); + public List getKeyRanges(Address start, Address end, boolean absolute, + boolean create); /** * Generates a properly ordered list of database key ranges for a diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/map/AddressMapDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/map/AddressMapDB.java index f16a8f58591..5bf5d31b043 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/map/AddressMapDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/map/AddressMapDB.java @@ -15,6 +15,8 @@ */ package ghidra.program.database.map; +import static generic.util.UnsignedDataUtils.*; + import java.io.IOException; import java.util.*; @@ -811,15 +813,16 @@ private void getKeyRangesForAddressSet(AddressSetView set, boolean absolute, boo /** * Create all memory base segments within the specified range. * NOTE: minAddress and maxAddress must have the same address space! - * @param minAddress - * @param maxAddress + * @param minAddress start address of the range + * @param maxAddress last address of the range + * @param absolute if the address are absolute and not relative */ - private void createBaseSegments(Address minAddress, Address maxAddress) { + private void createBaseSegments(Address minAddress, Address maxAddress, boolean absolute) { long minBase; long maxBase; - if (isInDefaultAddressSpace(minAddress)) { + if (!absolute && isInDefaultAddressSpace(minAddress)) { minBase = getNormalizedOffset(minAddress) & BASE_MASK; maxBase = getNormalizedOffset(maxAddress) & BASE_MASK; } @@ -828,7 +831,15 @@ private void createBaseSegments(Address minAddress, Address maxAddress) { maxBase = maxAddress.getOffset() & BASE_MASK; } - for (long base = minBase; base <= maxBase; base += (MAX_OFFSET + 1)) { + long numBases = (maxBase >>> 32) - (minBase >>> 32); + + if (numBases > 2) { + throw new UnsupportedOperationException("Can't create address bases for a range that" + + "extends across more than two upper 32 bit segments!"); + } + + for (long base = minBase; unsignedLessThanOrEqual(base, maxBase); base += + (MAX_OFFSET + 1)) { getBaseAddressIndex(minAddress.getNewAddress(base), false, INDEX_CREATE); } } @@ -836,17 +847,19 @@ private void createBaseSegments(Address minAddress, Address maxAddress) { /** * Add simple key ranges where the address range lies within a single base segment for a single space. * NOTE: start and end addresses must have the same address space! - * @param keyRangeList - * @param start - * @param end - * @param absolute - * @param create + * @param keyRangeList the list to store key ranges into + * @param start the start address + * @param end the end address + * @param absolute true if the address are to be encoded as absolute (not relative to the + * image base + * @param create if true, this method will add new address bases that are required to + * store addresses in that database that have that address base */ private void addKeyRanges(List keyRangeList, Address start, Address end, boolean absolute, boolean create) { if (start.isMemoryAddress()) { if (create) { - createBaseSegments(start, end); + createBaseSegments(start, end, absolute); } Address normalizedStart = absolute ? start : getShiftedAddr(start); Address normalizedEnd = absolute ? end : getShiftedAddr(end); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/register/AddressRangeObjectMap.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/register/AddressRangeObjectMap.java index 35b82e99911..908ebdacd14 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/register/AddressRangeObjectMap.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/register/AddressRangeObjectMap.java @@ -15,12 +15,12 @@ */ package ghidra.program.database.register; +import java.util.*; + import ghidra.program.model.address.*; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; -import java.util.*; - /** * Associates objects with address ranges. */ @@ -170,7 +170,8 @@ private AddressValueRange adjustPreviousRangeForOverlap(Address start, Addres T object, AddressValueRange newRange, int pos) { AddressValueRange previousRange = ranges.get(pos); - if ((start.previous() == null) || (previousRange.getEnd().compareTo(start.previous()) < 0)) { + if ((start.previous() == null) || + (previousRange.getEnd().compareTo(start.previous()) < 0)) { return newRange; // no overlap } @@ -469,6 +470,10 @@ public AddressRange getAddressRangeContaining(Address addr) { } return new AddressRangeImpl(min, max); } + + public void clearCache() { + lastRange = null; + } } /** diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/register/DatabaseRangeMapAdapter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/register/DatabaseRangeMapAdapter.java index e6bb3505710..98368aad407 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/register/DatabaseRangeMapAdapter.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/register/DatabaseRangeMapAdapter.java @@ -209,4 +209,9 @@ public void checkWritableState() { } } + @Override + public void invalidate() { + rangeMap.invalidate(); + } + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/register/InMemoryRangeMapAdapter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/register/InMemoryRangeMapAdapter.java index 45636be116e..628c4f871a0 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/register/InMemoryRangeMapAdapter.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/register/InMemoryRangeMapAdapter.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -143,4 +142,9 @@ public void setLanguage(LanguageTranslator translator, Register mapReg, TaskMoni } rangeMap = newRangeMap; } + + @Override + public void invalidate() { + rangeMap.clearCache(); + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/register/ProgramRegisterContextDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/register/ProgramRegisterContextDB.java index f0f914fceec..09dfdc277bf 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/register/ProgramRegisterContextDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/register/ProgramRegisterContextDB.java @@ -175,6 +175,7 @@ public void initializeDefaultValues(Language lang, CompilerSpec compilerSpec) { @Override public void invalidateCache(boolean all) throws IOException { this.invalidateReadCache(); + invalidateRegisterStores(); } @Override @@ -525,4 +526,12 @@ public RegisterValue getNonDefaultValue(Register register, Address address) { } } + private void invalidateRegisterStores() { + for (RegisterValueStore store : registerValueMap.values()) { + store.invalidate(); + } + for (RegisterValueStore store : defaultRegisterValueMap.values()) { + store.invalidate(); + } + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/util/AddressRangeMapDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/util/AddressRangeMapDB.java index 1ce138aebd1..f29b49c7620 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/util/AddressRangeMapDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/util/AddressRangeMapDB.java @@ -16,8 +16,8 @@ package ghidra.program.database.util; import java.io.IOException; -import java.util.ConcurrentModificationException; -import java.util.Iterator; +import java.util.ArrayList; +import java.util.List; import db.*; import db.util.ErrorHandler; @@ -26,57 +26,90 @@ import ghidra.program.model.address.*; import ghidra.util.Lock; import ghidra.util.Msg; -import ghidra.util.exception.CancelledException; -import ghidra.util.exception.DuplicateNameException; +import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; /** - * RangeMapDB provides a generic value range map backed by a database table. - * A given range may be occupied by at most a single value which is painted over - * that range. + * AddressRangeMapDB provides a generic value range map backed by a database table. + * Values can be stored for ranges of addresses. When a value is stored for a range, it replaces + * any previous values for that range. It is kind of like painting. If you first paint a region + * red, but then later paint a region in the middle of the red region green, you end up with + * three regions - a green region surrounded by two red regions. + *

+ * This is implemented by storing records for each contiguous range with the same value. + *

    + *
  • The key is the encoded start address of the range. + *
  • The TO_COL column of the record stores the encoded end address of the range. + *
  • The VALUE_COL column of the record stores the value for the range. + *
+ *

+ * This implementation is complicated by several issues. + *

    + *
  1. Addresses stored in Ghidra database records are encoded as long keys (see + * {@link AddressMap}). + * Encoded addresses do not necessarily encode to keys that have the same ordering. + * Therefore, all comparisons must be done in address space and not in the encoded space. + * Also, record iterators must use the {@link AddressKeyRecordIterator} which will return + * records in address order versus encoded key order. + *
  2. The default space's image base can be changed after records have been created. This can + * cause the address ranges represented by a record to wrap around. For example, suppose + * the image base is 0 and you paint a range from address 0 to 0x20, which say maps to + * keys 0 and 20, respectively. Now suppose the image base changes to 0xfffffffe, which + * means key 0 maps to address 0xfffffffe and key 0x20 maps to address 0x1e,(the addresses + * have been effectively shifted down by 2). So now the stored record has a start key of + * 0 and an end key of 0x20 which now maps to start address of 0xfffffffe and an end + * address of 0x1e. For our purposes, it is important that we don't just flip the start + * and end address which be a very large range instead of a small range. Instead, we need + * to interpret that as 2 ranges (0xfffffffe - 0xffffffff) and (0 - 0x1e). So all methods + * in this class have be coded to handle this special case. To simplify the painting + * logic, any wrapping record will first be split into two records before painting. However + * we can only do this during a write operation (when we can make changes). Since the getter + * methods and iterators cannot modify the database, they have to deal with wrapping + * records on the fly. */ public class AddressRangeMapDB implements DBListener { + public static final String RANGE_MAP_TABLE_PREFIX = "Range Map - "; + static final int TO_COL = 0; + static final int VALUE_COL = 1; + private static final String[] COLUMN_NAMES = new String[] { "To", "Value" }; + private static final int[] INDEXED_COLUMNS = new int[] { VALUE_COL }; + + private final DBHandle dbHandle; + private final AddressMap addressMap; + private final ErrorHandler errHandler; + private final Field valueField; + private final boolean indexed; + private final Lock lock; private String tableName; - private DBHandle dbHandle; - private AddressMap addrMap; - private ErrorHandler errHandler; - private Field valueField; - private boolean indexed; - private Table rangeMapTable; private Schema rangeMapSchema; + private Table rangeMapTable; - private Address lastStart; - private Address lastEnd; + // caching private Field lastValue; private AddressRange lastRange; private int modCount; - public static final String RANGE_MAP_TABLE_PREFIX = "Range Map - "; - - // Column for Range Map table (key is the From value) - private static final int TO_COL = 0; - private static final int VALUE_COL = 1; - - private static final String[] COLUMN_NAMES = new String[] { "To", "Value" }; - private static final int[] INDEXED_COLUMNS = new int[] { VALUE_COL }; - private final Lock lock; + // we only need to check for wrapping record first time we paint or if the image base changes + private boolean alreadyCheckedForWrappingRecord = false; /** - * Construct a generic range map. - * @param dbHandle database handle. - * @param name map name used in naming the underlying database table. - * This name must be unique across all range maps. - * @param errHandler database error handler. - * @param valueField Field to be used for stored values. + * Construct a generic range map + * @param dbHandle database handle + * @param addressMap the address map + * @param lock the program lock + * @param name map name used in naming the underlying database table + * This name must be unique across all range maps + * @param errHandler database error handler + * @param valueField specifies the type for the values stored in this map * @param indexed if true, values will be indexed allowing use of the - * getValueRangeIterator method. + * getValueRangeIterator method */ - public AddressRangeMapDB(DBHandle dbHandle, AddressMap addrMap, Lock lock, String name, + public AddressRangeMapDB(DBHandle dbHandle, AddressMap addressMap, Lock lock, String name, ErrorHandler errHandler, Field valueField, boolean indexed) { this.dbHandle = dbHandle; - this.addrMap = addrMap; + this.addressMap = addressMap; this.lock = lock; this.errHandler = errHandler; this.valueField = valueField; @@ -87,10 +120,20 @@ public AddressRangeMapDB(DBHandle dbHandle, AddressMap addrMap, Lock lock, Strin } /** - * Set the name associated with this range map. - * @param newName + * Tests if an AddressRangeMap table exists with the given name + * @param dbHandle the database handle + * @param name the name to test for + * @return true if the a table exists for the given name + */ + public static boolean exists(DBHandle dbHandle, String name) { + return dbHandle.getTable(RANGE_MAP_TABLE_PREFIX + name) != null; + } + + /** + * Set the name associated with this range map + * @param newName the new name for this range map * @return true if successful, else false - * @throws DuplicateNameException + * @throws DuplicateNameException if there is already range map with that name */ public boolean setName(String newName) throws DuplicateNameException { String newTableName = RANGE_MAP_TABLE_PREFIX + newName; @@ -101,12 +144,9 @@ public boolean setName(String newName) throws DuplicateNameException { return false; } - public static boolean exists(DBHandle dbHandle, String name) { - return dbHandle.getTable(RANGE_MAP_TABLE_PREFIX + name) != null; - } - /** * Returns true if this map is empty + * @return true if this map is empty */ public boolean isEmpty() { lock.acquire(); @@ -128,97 +168,74 @@ public int getRecordCount() { return rangeMapTable != null ? rangeMapTable.getRecordCount() : 0; } - private void findTable() { - rangeMapTable = dbHandle.getTable(tableName); - if (rangeMapTable != null) { - rangeMapSchema = rangeMapTable.getSchema(); - Field[] fields = rangeMapSchema.getFields(); - if (fields.length != 2 || !fields[VALUE_COL].isSameType(valueField)) { - errHandler.dbError( - new IOException("Existing range map table has unexpected value class")); - } - if (indexed) { - int[] indexedCols = rangeMapTable.getIndexedColumns(); - if (indexedCols.length != 1 || indexedCols[0] != VALUE_COL) { - errHandler.dbError(new IOException("Existing range map table is not indexed")); - } - } - } - } - - private void createTable() throws IOException { - rangeMapSchema = - new Schema(0, "From", new Field[] { LongField.INSTANCE, valueField }, COLUMN_NAMES); - if (indexed) { - rangeMapTable = dbHandle.createTable(tableName, rangeMapSchema, INDEXED_COLUMNS); - } - else { - rangeMapTable = dbHandle.createTable(tableName, rangeMapSchema); - } - } - /** - * Returns the value associated with the given address. - * @param addr the address of the value + * Returns the value associated with the given address + * @param address the address of the value * @return value or null no value exists */ - public Field getValue(Address addr) { + public Field getValue(Address address) { lock.acquire(); try { - if (rangeMapTable != null) { - if (lastStart != null && addr.compareTo(lastStart) >= 0 && - addr.compareTo(lastEnd) <= 0) { + if (rangeMapTable == null) { + return null; + } + // check last cached range + if (lastRange != null && lastRange.contains(address)) { + return lastValue; + } + + DBRecord record = findRecordContaining(address); + List ranges = getRangesForRecord(record); + for (AddressRange range : ranges) { + if (range.contains(address)) { + lastRange = range; + lastValue = record.getFieldValue(VALUE_COL); return lastValue; } - try { - long index = addrMap.getKey(addr, false); - DBRecord rec = rangeMapTable.getRecordAtOrBefore(index); - if (rec != null) { - Address startAddr = addrMap.decodeAddress(rec.getKey()); - Address endAddr = addrMap.decodeAddress(rec.getLongValue(TO_COL)); - if (addr.compareTo(startAddr) >= 0 && addr.compareTo(endAddr) <= 0) { - Field value = rec.getFieldValue(VALUE_COL); - lastStart = startAddr; - lastEnd = endAddr; - lastValue = value; - lastRange = new AddressRangeImpl(lastStart, lastEnd); - return value; - } - } - } - catch (IOException e) { - errHandler.dbError(e); - } } - return null; + } + catch (IOException e) { + errHandler.dbError(e); } finally { lock.release(); } + return null; } /** - * Associates the given value with every index from start to end (inclusive) - * Any previous associates are overwritten. - * @param startAddr the start address. - * @param endAddr the end address. + * Associates the given value with every address from start to end (inclusive) + * Any previous associates are overwritten. + * @param startAddress the start address. + * @param endAddress the end address. * @param value value to be painted, or null for value removal. + * @throws IllegalArgumentException if the start and end addresses are not in the same + * address space + * @throws IllegalArgumentException if the end address is greater then the start address */ - public void paintRange(Address startAddr, Address endAddr, Field value) { + public void paintRange(Address startAddress, Address endAddress, Field value) { + if (!startAddress.hasSameAddressSpace(endAddress)) { + throw new IllegalArgumentException("Addresses must be in the same space!"); + } + if (startAddress.compareTo(endAddress) > 0) { + throw new IllegalArgumentException("Start address must be <= end address!"); + } + lock.acquire(); try { - lastStart = startAddr; - lastEnd = endAddr; - lastValue = value; - lastRange = null; - if (startAddr.compareTo(endAddr) > 0) - throw new IllegalArgumentException(); - + clearCache(); ++modCount; - for (KeyRange range : addrMap.getKeyRanges(startAddr, endAddr, true)) { - paintRange(range.minKey, range.maxKey, value); - } + if (rangeMapTable == null) { + if (value == null) { + return; + } + createTable(); + } + doPaintRange(startAddress, endAddress, value); + } + catch (IOException e) { + errHandler.dbError(e); } finally { lock.release(); @@ -235,14 +252,16 @@ public void paintRange(Address startAddr, Address endAddr, Field value) { */ public void moveAddressRange(Address fromAddr, Address toAddr, long length, TaskMonitor monitor) throws CancelledException { - if (length <= 0) + if (length <= 0) { return; + } + DBHandle tmpDb = null; AddressRangeMapDB tmpMap = null; lock.acquire(); try { tmpDb = dbHandle.getScratchPad(); - tmpMap = new AddressRangeMapDB(tmpDb, addrMap, lock, "TEMP", errHandler, valueField, + tmpMap = new AddressRangeMapDB(tmpDb, addressMap, lock, "TEMP", errHandler, valueField, indexed); Address fromEndAddr = fromAddr.add(length - 1); @@ -283,145 +302,26 @@ public void moveAddressRange(Address fromAddr, Address toAddr, long length, Task } } - /** - * Paint over a range of address keys which fall within the same key base - * @param start - * @param end - * @param value - */ - private void paintRange(long start, long end, Field value) { - - try { - if (rangeMapTable == null) { - if (value == null) - return; - createTable(); - } - - // fix up the start of the range, unless the range starts at a key-range MIN - if (!addrMap.isKeyRangeMin(start)) { - DBRecord rec = rangeMapTable.getRecordBefore(start); - if (rec != null) { - long to = rec.getLongValue(TO_COL); - if (addrMap.hasSameKeyBase(start, to)) { - if (rec.fieldEquals(VALUE_COL, value)) { - if (to >= (start - 1)) { - if (to >= end) - return; // range already painted - // Combine with new range - start = rec.getKey(); - rangeMapTable.deleteRecord(start); - } - } - else if (to >= start) { - // Truncate existing range - rec.setLongValue(TO_COL, start - 1); - rangeMapTable.putRecord(rec); - - if (to > end) { - // Must split existing record - rec.setKey(end + 1); - rec.setLongValue(TO_COL, to); - rangeMapTable.putRecord(rec); - } - } - } - } - } - - // fix up the end of the range, unless the end goes to key-range MAX - if (!addrMap.isKeyRangeMax(end)) { - DBRecord rec = rangeMapTable.getRecord(end + 1); - if (rec != null && rec.fieldEquals(VALUE_COL, value)) { - end = rec.getLongValue(TO_COL); - rangeMapTable.deleteRecord(rec.getKey()); - } - } - - // fix records which overlap paint range - RecordIterator iter = rangeMapTable.iterator(start, end, start); - while (iter.hasNext()) { - DBRecord rec = iter.next(); - iter.delete(); - long to = rec.getLongValue(TO_COL); - if (to > end) { - if (rec.fieldEquals(VALUE_COL, value)) { - end = to; - } - else { - rec.setKey(end + 1); - rangeMapTable.putRecord(rec); - } - } - } - - // insert new range entry if value was specified - if (value != null) { - DBRecord rec = rangeMapSchema.createRecord(start); - rec.setLongValue(TO_COL, end); - rec.setField(VALUE_COL, value); - rangeMapTable.putRecord(rec); - } - - // Check for empty table - else { - if (rangeMapTable.getRecordCount() == 0) { - dbHandle.deleteTable(tableName); - rangeMapTable = null; - } - } - } - catch (IOException e) { - errHandler.dbError(e); - } - } - /** * Remove values from the given range. * @param startAddr the start address. * @param endAddr the end address. */ public void clearRange(Address startAddr, Address endAddr) { - lock.acquire(); - try { - lastStart = startAddr; - lastEnd = endAddr; - lastValue = null; - lastRange = null; - if (startAddr.compareTo(endAddr) > 0) - throw new IllegalArgumentException(); - - ++modCount; - for (KeyRange range : addrMap.getKeyRanges(startAddr, endAddr, false)) { - paintRange(range.minKey, range.maxKey, null); - } - } - finally { - lock.release(); - } + paintRange(startAddr, endAddr, null); } /** - * Returns a complete address set where any value has been set. - * @return address set + * Returns set of addresses where a values has been set + * @return set of addresses where a values has been set */ public AddressSet getAddressSet() { lock.acquire(); try { AddressSet set = new AddressSet(); - if (rangeMapTable != null) { - try { - RecordIterator iterator = rangeMapTable.iterator(); - while (iterator.hasNext()) { - DBRecord rec = iterator.next(); - Address startAddr = addrMap.decodeAddress(rec.getKey()); - Address endAddr = addrMap.decodeAddress(rec.getLongValue(TO_COL)); - set.addRange(startAddr, endAddr); - } - } - catch (IOException e) { - errHandler.dbError(e); - } + AddressRangeIterator addressRanges = getAddressRanges(); + for (AddressRange addressRange : addressRanges) { + set.add(addressRange); } return set; } @@ -431,533 +331,611 @@ public AddressSet getAddressSet() { } /** - * Returns a complete address set where the specified value has been set. - * @param value field value - * @return address set + * Returns set of addresses where the given value has been set + * @param value the value to search for + * @return set of addresses where the given value has been set */ public AddressSet getAddressSet(Field value) { + AddressSet set = new AddressSet(); lock.acquire(); try { - AddressSet set = new AddressSet(); - if (rangeMapTable != null) { - try { - RecordIterator iterator = - rangeMapTable.indexIterator(VALUE_COL, value, value, true); - while (iterator.hasNext()) { - DBRecord rec = iterator.next(); - Address startAddr = addrMap.decodeAddress(rec.getKey()); - Address endAddr = addrMap.decodeAddress(rec.getLongValue(TO_COL)); - set.addRange(startAddr, endAddr); - } - } - catch (IOException e) { - errHandler.dbError(e); - } + RecordIterator it = rangeMapTable.indexIterator(VALUE_COL, value, value, true); + while (it.hasNext()) { + DBRecord record = it.next(); + List rangesForRecord = getRangesForRecord(record); + rangesForRecord.forEach(r -> set.addRange(r.getMinAddress(), r.getMaxAddress())); } - return set; + } + catch (IOException e) { + dbError(e); } finally { lock.release(); } + return set; } /** - * Returns an address range iterator over all occupied ranges in the map. - * @return AddressRangeIterator that iterates over all occupied ranges in th - * map. + * Returns an address range iterator over all ranges in the map where a value has been set + * @return AddressRangeIterator that iterates over all occupied ranges in the map */ public AddressRangeIterator getAddressRanges() { - return new RangeIterator(); + if (rangeMapTable == null) { + return new EmptyAddressRangeIterator(); + } + try { + return new AddressRangeMapIterator(this); + } + catch (IOException e) { + dbError(e); + return null; + } } /** - * Returns an address range iterator over all occupied ranges in the map. - * The first range must have a FROM address at or after - * the specified startAddr. - * @param startAddr the address to start the iterator. - * @return AddressRangeIterator that iterates over all occupied ranges in th - * map. + * Returns an address range iterator over all ranges in the map where a value has been set + * starting with the given address + * @param startAddress The address at which to start iterating ranges + * @return AddressRangeIterator that iterates over all occupied ranges in the map from the + * given start address */ - public AddressRangeIterator getAddressRanges(Address startAddr) { - return new RangeIterator(startAddr); + public AddressRangeIterator getAddressRanges(Address startAddress) { + if (rangeMapTable == null) { + return new EmptyAddressRangeIterator(); + } + try { + return new AddressRangeMapIterator(this, startAddress); + } + catch (IOException e) { + dbError(e); + return null; + } } /** - * Returns an address range iterator over all occupied ranges whose - * FROM address falls within the range startAddr to endAddr. - * @param startAddr start of range - * @param endAddr end of range - * @return AddressRangeIterator + * Returns an address range iterator over all ranges in the map where a value has been set + * starting with the given address and ending with the given end address + * @param startAddress the address at which to start iterating ranges + * @param endAddr the address at which to end the iterator + * @return AddressRangeIterator that iterates over all occupied ranges in the map from the + * given start address */ - public AddressRangeIterator getAddressRanges(Address startAddr, Address endAddr) { - return new RangeIterator(startAddr, endAddr); + public AddressRangeIterator getAddressRanges(Address startAddress, Address endAddr) { + if (rangeMapTable == null) { + return new EmptyAddressRangeIterator(); + } + try { + return new AddressRangeMapIterator(this, startAddress, endAddr); + } + catch (IOException e) { + dbError(e); + return null; + } + } + + @Override + public void dbRestored(DBHandle dbh) { + lock.acquire(); + try { + clearCache(); + findTable(); + } + finally { + lock.release(); + } + } + + @Override + public void dbClosed(DBHandle dbh) { + // do nothing + } + + @Override + public void tableDeleted(DBHandle dbh, Table table) { + if (table == rangeMapTable) { + lock.acquire(); + try { + clearCache(); + rangeMapTable = null; + } + finally { + lock.release(); + } + } + } + + @Override + public void tableAdded(DBHandle dbh, Table table) { + if (tableName.equals(table.getName())) { + rangeMapTable = table; + } + } + + /** + * Deletes the database table used to store this range map. + */ + public void dispose() { + lock.acquire(); + try { + if (rangeMapTable != null) { + try { + dbHandle.deleteTable(tableName); + } + catch (IOException e) { + errHandler.dbError(e); + } + clearCache(); + rangeMapTable = null; + } + } + finally { + lock.release(); + } } /** - * Returns an address range iterator for those ranges which contain the - * specified value. This method may only be invoked for indexed maps. - * @param value - * @return AddressRangeIterator + * Notification that that something may have changed (undo/redo/image base change) and we need + * to invalidate our cache and possibly have a wrapping record again. */ - public AddressRangeIterator getValueRanges(Field value) { - return new ValueRangeIterator(value); + public void invalidate() { + lock.acquire(); + try { + clearCache(); + alreadyCheckedForWrappingRecord = false; + } + finally { + lock.release(); + } } /** - * Returns the bounding address-range containing addr and the the same value throughout. - * @param addr the contained address - * @return single value address-range containing addr + * Returns the bounding address range for the given address where all addresses in that + * range have the same value (this also works for now value. i.e finding a gap) + * @param address the address to find a range for + * @return an address range that contains the given address and has all the same value */ - public AddressRange getAddressRangeContaining(Address addr) { + public AddressRange getAddressRangeContaining(Address address) { lock.acquire(); try { - if (lastRange != null && lastRange.contains(addr)) { + // check cache + if (lastRange != null && lastRange.contains(address)) { return lastRange; } - try { - Address min = addr.getAddressSpace().getMinAddress(); - Address max = addr.getAddressSpace().getMaxAddress(); - if (rangeMapTable != null) { - AddressKeyRecordIterator addressKeyRecordIterator = - new AddressKeyRecordIterator(rangeMapTable, addrMap, min, addr, addr, true); - if (addressKeyRecordIterator.hasPrevious()) { - DBRecord rec = addressKeyRecordIterator.previous(); - Address fromAddr = addrMap.decodeAddress(rec.getKey()); - Address toAddr = addrMap.decodeAddress(rec.getLongValue(TO_COL)); - if (toAddr.compareTo(addr) >= 0) { - lastRange = new AddressRangeImpl(fromAddr, toAddr); - return lastRange; - } - min = toAddr.next(); - } - addressKeyRecordIterator = - new AddressKeyRecordIterator(rangeMapTable, addrMap, addr, max, addr, true); - if (addressKeyRecordIterator.hasNext()) { - DBRecord rec = addressKeyRecordIterator.next(); - Address fromAddr = addrMap.decodeAddress(rec.getKey()); - Address toAddr = addrMap.decodeAddress(rec.getLongValue(TO_COL)); - if (fromAddr.compareTo(addr) == 0) { - lastRange = new AddressRangeImpl(fromAddr, toAddr); - return lastRange; - } - max = fromAddr.previous(); - } - } - lastRange = new AddressRangeImpl(min, max); - } - catch (IOException e) { - errHandler.dbError(e); + + // look for a stored value range that contains that address + AddressRange range = findValueRangeContainingAddress(address); + if (range == null) { + // so the address doesn't have a value, find its containing gap range + range = findGapRange(address); } - return lastRange; + return range; + } + catch (IOException e) { + dbError(e); + return null; } finally { lock.release(); } } - /** - * An address range iterator which returns all occupied ranges. - */ - private class RangeIterator implements AddressRangeIterator { + private AddressRange findValueRangeContainingAddress(Address address) throws IOException { + DBRecord record = findRecordContaining(address); + List rangesForRecord = getRangesForRecord(record); + for (AddressRange range : rangesForRecord) { + if (range.contains(address)) { + lastRange = range; + lastValue = record.getFieldValue(VALUE_COL); + return range; + } + } + return null; + } - private RecordIterator recIter; - private DBRecord nextRec; + // note this method assumes the address has already been determined to not be in + // a range that has a value + private AddressRange findGapRange(Address address) throws IOException { + // initialize to entire address space + Address gapStart = address.getAddressSpace().getMinAddress(); + Address gapEnd = address.getAddressSpace().getMaxAddress(); - private boolean checkStart; - private long startIndex; + // adjust if there is a wrapping record + if (address.hasSameAddressSpace(addressMap.getImageBase())) { + DBRecord wrappingRecord = getAddressWrappingRecord(); + if (wrappingRecord != null) { + // if there is a wrapping address, subtract those ranges from our defaults + gapStart = getEndAddress(wrappingRecord).add(1); + gapEnd = getStartAddress(wrappingRecord).subtract(1); + } + } - private boolean checkEnd; - private long endIndex; + // if previous record exists, then gap start is really one past the end of that range + DBRecord record = getRecordAtOrBefore(address); + if (record != null) { + Address endAddress = getEndAddress(record); + if (endAddress.hasSameAddressSpace(address) && endAddress.compareTo(address) < 0) { + gapStart = endAddress.add(1); + } + } - private int expectedModCount; + // if a follow on record exists in our space, then gap end will be just before this range + record = getRecordAfter(address); + if (record != null) { + Address startAddress = getStartAddress(record); + if (startAddress.hasSameAddressSpace(address) && + startAddress.compareTo(address) > 0) { + gapEnd = startAddress.subtract(1); + } + } - /** - * Construct a new iterator which returns all occupied ranges. - */ - RangeIterator() { - expectedModCount = modCount; - if (rangeMapTable != null) { - try { - recIter = new AddressKeyRecordIterator(rangeMapTable, addrMap); - } - catch (IOException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - errHandler.dbError(e); + return new AddressRangeImpl(gapStart, gapEnd); + } - } - } + /** + * Returns a list of AddressRanges that this record represents. This record's key is the + * encoded start key and it "TO_COL" has the end address key. But since a non-zero image base + * can cause that key range to be backwards in address space which means it represents a range + * that starts in the upper addresses and then "wraps" to a lower address, resulting in two + * address ranges, one and the bottom of the address space and one at the top of the address + * space. + * @param record a record that represents a contiguous range (in key space) of same values + * @return a list of AddressRanges that this record represents. + */ + List getRangesForRecord(DBRecord record) { + List ranges = new ArrayList<>(2); + if (record == null) { + return ranges; + } + Address start = addressMap.decodeAddress(record.getKey()); + Address end = addressMap.decodeAddress(record.getLongValue(TO_COL)); + if (start.compareTo(end) <= 0) { + ranges.add(new AddressRangeImpl(start, end)); + } + else { + // the key range spans the address boundary because of non zero image base + // so this record represents two address ranges, one at the start and one at the end + AddressSpace space = start.getAddressSpace(); + ranges.add(new AddressRangeImpl(space.getMinAddress(), end)); + ranges.add(new AddressRangeImpl(start, space.getMaxAddress())); } - /** - * Construct a new iterator which returns occupied ranges whose - * FROM address falls within the specified range. If the specified - * 'startAddr' falls anywhere within a stored range, the FROM address of the - * first range returned will be changed to 'startAddr'. - * - * @param startAddr - */ - RangeIterator(Address startAddr) { - expectedModCount = modCount; - if (rangeMapTable != null) { - try { - startIndex = addrMap.getKey(startAddr, false); - if (startIndex != AddressMap.INVALID_ADDRESS_KEY) { - DBRecord rec = rangeMapTable.getRecordBefore(startIndex); - if (rec != null) { - long to = rec.getLongValue(TO_COL); - if (addrMap.hasSameKeyBase(startIndex, to) && to >= startIndex) { - nextRec = rec; - checkStart = true; - } - } - } - - recIter = new AddressKeyRecordIterator(rangeMapTable, addrMap, startAddr, true); + return ranges; + } - } - catch (IOException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - errHandler.dbError(e); + private void doPaintRange(Address paintStart, Address paintEnd, Field value) + throws IOException { + // Eliminating wrapping records makes the following logic much simpler + splitAddressWrappingRecordIfExists(); - } - } + // examine the record before start to see if it merges with or is truncated by the new range + paintStart = checkRecordBeforeRange(paintStart, paintEnd, value); + if (paintStart == null) { + // a returned null startAddr means the new range is already painted with that value + return; } - /** - * Construct a new iterator which returns occupied ranges whose - * FROM address falls within the specified range. If the specified - * 'startAddr' falls anywhere within a stored range, the FROM address of the - * first range returned will be changed to 'startAddr'. Similarly, if the - * specified 'endAddr' falls within a stored range, the TO address of the - * last range returned will be changed to 'endAddr'. - * - * @param startAddr - * @param endAddr - */ - RangeIterator(Address startAddr, Address endAddr) { - expectedModCount = modCount; - if (rangeMapTable != null) { - try { - startIndex = addrMap.getKey(startAddr, false); - if (startIndex != AddressMap.INVALID_ADDRESS_KEY) { - DBRecord rec = rangeMapTable.getRecordBefore(startIndex); - if (rec != null) { - long to = rec.getLongValue(TO_COL); - if (addrMap.hasSameKeyBase(startIndex, to) && to >= startIndex) { - nextRec = rec; - checkStart = true; - } - } - } - endIndex = addrMap.getKey(endAddr, false); - checkEnd = (endIndex != AddressMap.INVALID_ADDRESS_KEY); - - recIter = new AddressKeyRecordIterator(rangeMapTable, addrMap, startAddr, - endAddr, startAddr, true); + // eliminate ranges that are being painted over. Last range may need to be truncated + paintEnd = fixupIntersectingRecords(paintStart, paintEnd, value); - } - catch (IOException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - errHandler.dbError(e); + // see if there is a range just after this range that can be merged + paintEnd = possiblyMergeWithNextRecord(paintEnd, value); - } - } + // insert new range entry if value was specified, otherwise this was really a delete + if (value != null) { + createRecord(paintStart, paintEnd, value); } - @Override - public Iterator iterator() { - return this; + // remove table if empty + if (rangeMapTable.getRecordCount() == 0) { + dbHandle.deleteTable(tableName); + rangeMapTable = null; } + } - @Override - public void remove() { - throw new UnsupportedOperationException(); + private void splitAddressWrappingRecordIfExists() throws IOException { + if (alreadyCheckedForWrappingRecord) { + return; } + alreadyCheckedForWrappingRecord = true; - /** - * @see ghidra.util.datastruct.IndexRangeIterator#hasNext() - */ - @Override - public boolean hasNext() { - lock.acquire(); - try { - if (expectedModCount != modCount) - throw new ConcurrentModificationException(); - if (nextRec != null) { - return true; - } - if (recIter != null) { - try { - return recIter.hasNext(); - } - catch (IOException e) { - errHandler.dbError(e); - } - } - return false; - } - finally { - lock.release(); - } + DBRecord wrappingRecord = getAddressWrappingRecord(); + if (wrappingRecord == null) { + return; } - /** - * @see ghidra.util.datastruct.IndexRangeIterator#next() - */ - @Override - public AddressRange next() { - lock.acquire(); - try { - if (expectedModCount != modCount) - throw new ConcurrentModificationException(); - AddressRange range = null; - if (recIter != null) { - try { - DBRecord rec; - if (nextRec != null) { - rec = nextRec; - nextRec = null; - } - else { - rec = recIter.next(); - } - if (rec != null) { - Field value = rec.getFieldValue(VALUE_COL); - long fromIndex = rec.getKey(); - long toIndex = rec.getLongValue(TO_COL); - if (checkStart && addrMap.hasSameKeyBase(startIndex, fromIndex) && - fromIndex < startIndex) { - fromIndex = startIndex; - } - Address rangeStart = addrMap.decodeAddress(fromIndex); - Address rangeEnd; - if (checkEnd && addrMap.hasSameKeyBase(endIndex, toIndex) && - toIndex > endIndex) { - rangeEnd = addrMap.decodeAddress(endIndex); - } - else { - rangeEnd = addrMap.decodeAddress(toIndex); - - // handle key-range boundaries where a key-base transition may occur - // consume additional ranges as needed when value matches - while (recIter.hasNext()) { - nextRec = recIter.next(); - if (!value.equals(nextRec.getFieldValue(VALUE_COL))) { - break; - } - Address nextAddr = rangeEnd.addWrap(1); - Address nextFrom = addrMap.decodeAddress(rec.getKey()); - if (!nextAddr.equals(nextFrom)) { - break; - } - toIndex = nextRec.getLongValue(TO_COL); - nextRec = null; // next record consumed - if (checkEnd && addrMap.hasSameKeyBase(endIndex, toIndex) && - toIndex > endIndex) { - rangeEnd = addrMap.decodeAddress(endIndex); - break; - } - rangeEnd = addrMap.decodeAddress(toIndex); - } - } - - lastStart = rangeStart; - lastEnd = rangeEnd; - lastValue = value; - range = new AddressRangeImpl(rangeStart, rangeEnd); - } - } - catch (IOException e) { - errHandler.dbError(e); - } - } - return range; - } - finally { - lock.release(); - } + List ranges = getRangesForRecord(wrappingRecord); + + if (ranges.size() != 2) { + throw new AssertException("wrapping records should have two ranges!"); } + Field value = getValue(wrappingRecord); + + // replace wrapping record with two non wrapping records + rangeMapTable.deleteRecord(wrappingRecord.getKey()); + createRecord(ranges.get(0), value); + createRecord(ranges.get(1), value); + } /** - * An address range iterator which returns all ranges which contain - * a specific value or range of values. + * Checks the record before our paint to see how it is affected. Returns a possible new start + * address for our paint if it gets merged. Returns null if it turns out we don't need to + * paint at all. + * + * @param paintStart the start of the paint range + * @param paintEnd the end of the paint range + * @param value the value to associate with the range + * @return a new start address for the paint or null if we don't need to paint at all + * @throws IOException if a database I/O exception occurs */ - private class ValueRangeIterator implements AddressRangeIterator { + private Address checkRecordBeforeRange(Address paintStart, Address paintEnd, Field value) + throws IOException { + DBRecord record = getRecordBefore(paintStart); + if (record == null) { + return paintStart; + } - RecordIterator recIter; - DBRecord nextRec; + Address recordStart = getStartAddress(record); + Address recordEnd = getEndAddress(record); + Field recordValue = getValue(record); - private int expectedModCount; + // if the previous record is in different address space, nothing to do, start doesn't change + if (!recordEnd.hasSameAddressSpace(paintStart)) { + return paintStart; + } - /** - * Construct an index range iterator for those ranges - * which contain the specified value. A database error will - * occur if the value column is not indexed. - * @param value - */ - ValueRangeIterator(Field value) { - expectedModCount = modCount; - if (rangeMapTable != null) { - try { - recIter = rangeMapTable.indexIterator(VALUE_COL, value, value, true); - } - catch (IOException e) { - errHandler.dbError(e); - } + // if it has the same value as the new record, there are 3 cases: + // the previous range extends past new range -> no need to paint the new range at all + // the previous range ends just before or into the new range -> delete old range and make new range + // have previous range start + // the previous range ends before this range -> no effect just leave the start the same + if (recordValue.equals(value)) { + // see if existing range already completely covers new range. + if (recordEnd.compareTo(paintEnd) >= 0) { + // return null to indicate no painting is needed + return null; } + + if (paintStart.isSuccessor(recordEnd) || paintStart.compareTo(recordEnd) <= 0) { + // otherwise, merge by deleting previous record and changing start to record's start + rangeMapTable.deleteRecord(record.getKey()); + return recordStart; + } + // otherwise previous range is completely before, so has no effect + return paintStart; } - @Override - public Iterator iterator() { - return this; + // different values, 3 cases to deal with + // case 1: the previous range is before the new range -> do nothing + // case 2: the previous range ends inside the new range -> truncate previous range + // case 3: the previous range extends past the new range -> truncate previous ragne and + // create a new region past where we are now painting + + // case 1, previous range is completely before new range, so nothing to do + if (recordEnd.compareTo(paintStart) < 0) { + return paintStart; } - @Override - public void remove() { - throw new UnsupportedOperationException(); + // in case 2 or 3 we need to truncate the previous record + record.setLongValue(TO_COL, addressMap.getKey(paintStart.subtract(1), true)); + rangeMapTable.putRecord(record); + + // in case 3, we need to create a new record for the part of the previous record that + // extends past our new range + if (recordEnd.compareTo(paintEnd) > 0) { + createRecord(paintEnd.add(1), recordEnd, recordValue); } - /** - * @see ghidra.util.datastruct.IndexRangeIterator#hasNext() - */ - @Override - public boolean hasNext() { - lock.acquire(); - try { - if (expectedModCount != modCount) - throw new ConcurrentModificationException(); - if (nextRec != null) { - return true; - } - if (recIter != null) { - try { - return recIter.hasNext(); - } - catch (IOException e) { - errHandler.dbError(e); - } + return paintStart; + } + + /** + * Removes any records that are being painted over and maybe merges with or truncates the + * last record that starts in our paint range + * @param startAddr the start of the paint range + * @param endAddr the end of the paint range + * @param value the value we are painting + * @return the new end address for our paint. if we find a record with our value that + * extends past our end, we delete it and extend our end range. + * @throws IOException if a database I/O error occurs + */ + private Address fixupIntersectingRecords(Address startAddr, Address endAddr, Field value) + throws IOException { + RecordIterator it = new AddressKeyRecordIterator(rangeMapTable, addressMap, startAddr, + endAddr, startAddr, true); + + while (it.hasNext()) { + DBRecord record = it.next(); + it.delete(); // all the records that start in the paint region need to be deleted + Address endAddress = getEndAddress(record); + if (endAddress.compareTo(endAddr) >= 0) { + Field recordValue = getValue(record); + if (recordValue.equals(value)) { + // since the last record has same value, it extends our paintRange + return endAddress; } - return false; - } - finally { - lock.release(); + // the last record extended past the paint range, create a remainder record + createRecord(endAddr.add(1), endAddress, recordValue); } } + return endAddr; - /** - * @see ghidra.util.datastruct.IndexRangeIterator#next() - */ - @Override - public AddressRange next() { - lock.acquire(); - try { - if (expectedModCount != modCount) - throw new ConcurrentModificationException(); - AddressRange range = null; - if (recIter != null) { - try { - DBRecord rec; - if (nextRec != null) { - rec = nextRec; - nextRec = null; - } - else { - rec = recIter.next(); - } - - if (rec != null) { - Address rangeStart = addrMap.decodeAddress(rec.getKey()); - Address rangeEnd = addrMap.decodeAddress(rec.getLongValue(TO_COL)); - - // handle key-range boundaries where a key-base transition may occur - // consume additional ranges as needed - while (recIter.hasNext()) { - nextRec = recIter.next(); - Address nextAddr = rangeEnd.addWrap(1); - Address nextFrom = addrMap.decodeAddress(rec.getKey()); - if (!nextAddr.equals(nextFrom)) { - break; - } - rangeEnd = addrMap.decodeAddress(nextRec.getLongValue(TO_COL)); - nextRec = null; // next record consumed - } - - lastStart = rangeStart; - lastEnd = rangeEnd; - lastValue = rec.getFieldValue(VALUE_COL); - range = new AddressRangeImpl(rangeStart, rangeEnd); - } - } - catch (IOException e) { - errHandler.dbError(e); - } - } - return range; - } - finally { - lock.release(); - } + } + + /** + * checks if there is a record just past our paint region that we can merge with. If we find + * one, delete it and adjust our end address to its end address + * @param endAddr the end of our paint region + * @param value the value we are painting + * @return the new end address for our paint + */ + private Address possiblyMergeWithNextRecord(Address endAddr, Field value) throws IOException { + if (endAddr.equals(endAddr.getAddressSpace().getMaxAddress())) { + // if the end is already the max, then no need to check for follow-on record + return endAddr; + } + Address next = endAddr.add(1); + DBRecord record = rangeMapTable.getRecord(addressMap.getKey(next, false)); + if (record != null && getValue(record).equals(value)) { + rangeMapTable.deleteRecord(record.getKey()); + return getEndAddress(record); + } + return endAddr; + } + + /** + * Clears the "last range" cache + */ + private void clearCache() { + lock.acquire(); + try { + lastRange = null; + lastValue = null; + } + finally { + lock.release(); + } + } + + DBRecord getAddressWrappingRecord() throws IOException { + Address maxAddress = addressMap.getImageBase().getAddressSpace().getMaxAddress(); + DBRecord record = getRecordAtOrBefore(maxAddress); + if (record == null) { + return null; } + Address start = getStartAddress(record); + Address end = getEndAddress(record); + + // a wrapping record's end will be before its start + if (start.compareTo(end) > 0) { + return record; + } + + return null; } - @Override - public void dbRestored(DBHandle dbh) { - lastStart = null; - lastEnd = null; - lastValue = null; - lastRange = null; - findTable(); + private Address getStartAddress(DBRecord record) { + return addressMap.decodeAddress(record.getKey()); } - @Override - public void dbClosed(DBHandle dbh) { + private Field getValue(DBRecord record) { + return record.getFieldValue(VALUE_COL); } - @Override - public void tableDeleted(DBHandle dbh, Table table) { - if (table == rangeMapTable) { - lastStart = null; - lastEnd = null; - lastValue = null; - lastRange = null; - rangeMapTable = null; + private Address getEndAddress(DBRecord record) { + return addressMap.decodeAddress(record.getLongValue(TO_COL)); + } + + private void createRecord(AddressRange range, Field value) throws IOException { + createRecord(range.getMinAddress(), range.getMaxAddress(), value); + } + + private void createRecord(Address startAddr, Address endAddr, Field value) throws IOException { + long start = addressMap.getKey(startAddr, true); + long end = addressMap.getKey(endAddr, true); + DBRecord rec = rangeMapSchema.createRecord(start); + rec.setLongValue(TO_COL, end); + rec.setField(VALUE_COL, value); + rangeMapTable.putRecord(rec); + } + + private DBRecord findRecordContaining(Address address) throws IOException { + DBRecord record = getRecordAtOrBefore(address); + if (recordContainsAddress(record, address)) { + return record; + } + record = getAddressWrappingRecord(); + if (recordContainsAddress(record, address)) { + return record; } + return null; } - @Override - public void tableAdded(DBHandle dbh, Table table) { - if (tableName.equals(table.getName())) { - rangeMapTable = table; + private boolean recordContainsAddress(DBRecord record, Address address) { + List rangesForRecord = getRangesForRecord(record); + for (AddressRange addressRange : rangesForRecord) { + if (addressRange.contains(address)) { + return true; + } } + return false; } - /** - * Deletes the database table used to store this range map. - */ - public void dispose() { - lock.acquire(); - try { - if (rangeMapTable != null) { - try { - dbHandle.deleteTable(tableName); - } - catch (IOException e) { - errHandler.dbError(e); + private DBRecord getRecordAfter(Address address) throws IOException { + RecordIterator it = new AddressKeyRecordIterator(rangeMapTable, addressMap, address, true); + if (it.hasNext()) { + return it.next(); + } + return null; + } + + private DBRecord getRecordAtOrBefore(Address address) throws IOException { + RecordIterator it = new AddressKeyRecordIterator(rangeMapTable, addressMap, address, false); + if (it.hasPrevious()) { + return it.previous(); + } + return null; + } + + private DBRecord getRecordBefore(Address address) throws IOException { + RecordIterator it = new AddressKeyRecordIterator(rangeMapTable, addressMap, address, true); + if (it.hasPrevious()) { + return it.previous(); + } + return null; + } + + private void findTable() { + rangeMapTable = dbHandle.getTable(tableName); + if (rangeMapTable != null) { + rangeMapSchema = rangeMapTable.getSchema(); + Field[] fields = rangeMapSchema.getFields(); + if (fields.length != 2 || !fields[VALUE_COL].isSameType(valueField)) { + errHandler.dbError( + new IOException("Existing range map table has unexpected value class")); + } + if (indexed) { + int[] indexedCols = rangeMapTable.getIndexedColumns(); + if (indexedCols.length != 1 || indexedCols[0] != VALUE_COL) { + errHandler.dbError(new IOException("Existing range map table is not indexed")); } - lastStart = null; - lastEnd = null; - lastValue = null; - lastRange = null; - rangeMapTable = null; } } - finally { - lock.release(); + } + + private void createTable() throws IOException { + rangeMapSchema = + new Schema(0, "From", new Field[] { LongField.INSTANCE, valueField }, COLUMN_NAMES); + if (indexed) { + rangeMapTable = dbHandle.createTable(tableName, rangeMapSchema, INDEXED_COLUMNS); } + else { + rangeMapTable = dbHandle.createTable(tableName, rangeMapSchema); + } + } + + AddressMap getAddressMap() { + return addressMap; + } + + Table getTable() { + return rangeMapTable; + } + + Lock getLock() { + return lock; + } + + int getModCount() { + return modCount; + } + + void dbError(IOException e) { + Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); + errHandler.dbError(e); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/util/AddressRangeMapIterator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/util/AddressRangeMapIterator.java new file mode 100644 index 00000000000..1ae57bed66a --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/util/AddressRangeMapIterator.java @@ -0,0 +1,281 @@ +/* ### + * 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.database.util; + +import java.io.IOException; +import java.util.*; + +import db.DBRecord; +import db.RecordIterator; +import ghidra.program.database.map.AddressKeyRecordIterator; +import ghidra.program.database.map.AddressMap; +import ghidra.program.model.address.*; +import ghidra.util.Lock; + +/** + * An iterator over ranges that have a defined values in the AddressRangeMapDB + *

    + * NOTE: this iterator is complicated by the fact that there can exist a record that represents + * an address range that "wraps around" from the max address to the 0 address, where this record + * actually represents two address ranges. This is cause by changing the image base which shifts + * all records up or down. That shift can cause a record to have a wrapping range where the start + * address is larger than the end address. If such a record exists, it is found during construction + * and the lower address range is extracted from the record and is stored as a special "start range" + * that should be emitted before any other ranges in that space. The upper range of a wrapping + * record will be handled more naturally during the iteration process. When a wrapping record is + * encountered during the normal iteration, only the upper range is used and it will be in the + * correct address range ordering. + */ +class AddressRangeMapIterator implements AddressRangeIterator { + + private AddressRangeMapDB rangeMap; + private AddressMap addressMap; + private Lock lock; + private int expectedModCount; + + private DBRecord nextRecord; + private RecordIterator recordIterator; + + // this is the range from a wrapping address record that needs to be emitted before any other + // ranges in the default space. It is discovered during construction if it exists + private AddressRange startRange; + + // these will be null if iterating over all records + private Address iteratorStart; + private Address iteratorEnd; + + /** + * Constructs an AddressRangeIterator over all ranges that have a defined value + * @param rangeMap the AddressRangeMapDB to iterate over + * @throws IOException if a database I/O exception occurs + */ + AddressRangeMapIterator(AddressRangeMapDB rangeMap) throws IOException { + this.rangeMap = rangeMap; + this.lock = rangeMap.getLock(); + this.addressMap = rangeMap.getAddressMap(); + this.expectedModCount = rangeMap.getModCount(); + this.recordIterator = new AddressKeyRecordIterator(rangeMap.getTable(), addressMap); + startRange = checkForStartRangeFromWrappingAddressRecord(); + } + + /** + * Constructs an AddressRangeIterator over all ranges greater than the given start address + * that have a defined value. If start is in the middle of a defined range, the first + * range will be truncated to start with the given start address. + * @param rangeMap the AddressRangeMapDB to iterate over + * @param start the address where the iterator should start + * @throws IOException if a database I/O exception occurs + */ + AddressRangeMapIterator(AddressRangeMapDB rangeMap, Address start) throws IOException { + this(rangeMap, start, null); + } + + /** + * Constructs an AddressRangeIterator over all ranges between the given start address and the + * given end address that have a defined value. If start is in the middle of a defined range, + * the first range returned will truncated to start with the given start address. + * If the endAddress is in the middle of a defined range, the last range will be truncated to + * end with the given end address + * @param rangeMap the AddressRangeMapDB to iterate over + * @param start the address where the iterator should start + * @param end the address where the iterator should end + * @throws IOException if a database I/O exception occurs + */ + AddressRangeMapIterator(AddressRangeMapDB rangeMap, Address start, Address end) + throws IOException { + this.rangeMap = rangeMap; + this.lock = rangeMap.getLock(); + this.addressMap = rangeMap.getAddressMap(); + this.expectedModCount = rangeMap.getModCount(); + this.iteratorStart = start; + this.iteratorEnd = getIteratorEnd(end); + + // adjust start address to start of previous range if it contains this address + // so the iterator will include it + AddressRange range = rangeMap.getAddressRangeContaining(iteratorStart); + this.recordIterator = new AddressKeyRecordIterator(rangeMap.getTable(), addressMap, + range.getMinAddress(), iteratorEnd, range.getMinAddress(), true); + + startRange = trimRange(checkForStartRangeFromWrappingAddressRecord()); + } + + @Override + public Iterator iterator() { + return this; + } + + @Override + public boolean hasNext() { + lock.acquire(); + try { + if (expectedModCount != rangeMap.getModCount()) { + throw new ConcurrentModificationException(); + } + if (nextRecord != null) { + return true; + } + if (startRange != null) { + return true; + } + if (recordIterator != null) { + try { + return recordIterator.hasNext(); + } + catch (IOException e) { + rangeMap.dbError(e); + } + } + return false; + } + finally { + lock.release(); + } + } + + @Override + public AddressRange next() { + lock.acquire(); + try { + if (expectedModCount != rangeMap.getModCount()) { + throw new ConcurrentModificationException(); + } + if (nextRecord != null) { + DBRecord record = nextRecord; + nextRecord = null; + return getAddressRange(record); + } + if (recordIterator == null || !recordIterator.hasNext()) { + if (startRange != null) { + // there are no more items in the underlying iterator, so if we have a + // discovered start range, as described in the class header, then return that + AddressRange range = startRange; + startRange = null; + return range; + } + return null; + } + return getAddressRange(recordIterator.next()); + } + catch (IOException e) { + rangeMap.dbError(e); + return null; + } + finally { + lock.release(); + } + } + + /** + * Computes an address range for the given record. If a wrapping record (a record whose start + * address is greater than its end address, so it really represents two ranges) is encountered, + * it only returns the high range. The low range is specially found in the constructor and + * returned before any other ranges in that same address space are returned. + * @param record the record to convert to an address range + * @return the address range represented by the given record + */ + private AddressRange getAddressRange(DBRecord record) { + List ranges = rangeMap.getRangesForRecord(record); + if (ranges.isEmpty()) { + return null; + } + + // Normally, there is only one range for a record. If it is an address wrapping record + // caused by an image base change, then the record will produce two ranges. While iterating + // we always want the last (higher) range. + AddressRange range = ranges.get(ranges.size() - 1); + + // if there is a "start range" discovered in the constructor and this is the first + // range we see in the default space (the space with image base), then we need to return + // the start range and save this record for next time. + if (startRange != null && hasSameSpace(startRange.getMinAddress(), range.getMinAddress())) { + range = startRange; + startRange = null; + + // save current record into next record so we process it again next time + nextRecord = record; + } + + return trimRange(range); + } + + private boolean hasSameSpace(Address address1, Address address2) { + AddressSpace space1 = address1.getAddressSpace(); + AddressSpace space2 = address2.getAddressSpace(); + return space1.equals(space2); + } + + /** + * Look for a start range that needs to be issued before any other ranges in the default + * space. + *

    + * Because of a changed image base, it is possible that a single record can represent + * multiple ranges, as described in the class header. This is the code that will look for that + * case and correct the ordering. + * + * @return the start range that needs to be issued before any other ranges in this space. + * @throws IOException if a database I/O error occurs + */ + private AddressRange checkForStartRangeFromWrappingAddressRecord() + throws IOException { + + DBRecord record = rangeMap.getAddressWrappingRecord(); + if (record == null) { + return null; + } + + List ranges = rangeMap.getRangesForRecord(record); + // we want the lower range - the upper range will be handle later during iteration + return ranges.get(0); + } + + private Address getIteratorEnd(Address end) { + if (end != null) { + return end; // non-null end was supplied, use that + } + AddressFactory factory = addressMap.getAddressFactory(); + AddressSet allAddresses = factory.getAddressSet(); + return allAddresses.getMaxAddress(); + } + + /** + * Make sure the range is within the iterator's given start and end range. This really only + * matters for the first and last range returned by the iterator, but it hard to know when + * the given range is the first or last, just just trim all returned ranges. + * @param range the range to be trimmed + * @return the trimmed address range + */ + private AddressRange trimRange(AddressRange range) { + if (range == null) { + return null; + } + // if no iterator bounds set, no trimming needed + if (iteratorStart == null) { + return range; + } + + if (range.compareTo(iteratorStart) > 0 && range.compareTo(iteratorEnd) < 0) { + return range; + } + + Address start = Address.max(range.getMinAddress(), iteratorStart); + Address end = Address.min(range.getMaxAddress(), iteratorEnd); + if (start.compareTo(end) <= 0) { + return new AddressRangeImpl(start, end); + } + return null; + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/ProgramContext.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/ProgramContext.java index 3f27cd10aee..effd7d8e603 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/ProgramContext.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/ProgramContext.java @@ -243,4 +243,5 @@ public AddressRangeIterator getDefaultRegisterValueAddressRanges(Register regist * @return disassembly context register value */ public RegisterValue getDisassemblyContext(Address address); + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/RangeMapAdapter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/RangeMapAdapter.java index 5b4cefe6a35..53e4408616c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/RangeMapAdapter.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/RangeMapAdapter.java @@ -109,4 +109,9 @@ void setLanguage(LanguageTranslator translator, Register mapReg, TaskMonitor mon */ public void checkWritableState(); + /** + * Notification that something has changed that may affect internal caching + */ + public void invalidate(); + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/RegisterValueStore.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/RegisterValueStore.java index db012cb7501..a9f892555a3 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/RegisterValueStore.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/RegisterValueStore.java @@ -15,14 +15,14 @@ */ package ghidra.program.util; +import java.util.*; + import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; import ghidra.program.model.lang.RegisterValue; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; -import java.util.*; - /** * This is a generalized class for storing register values over ranges. The values include mask bits * to indicate which bits within the register are being set. The mask is stored along with the @@ -324,4 +324,11 @@ public AddressRange getValueRangeContaining(Address addr) { return rangeMap.getValueRangeContaining(addr); } + /** + * Notifies that something changed, may need to invalidate any caches + */ + public void invalidate() { + rangeMap.invalidate(); + } + }