diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/MemoryMapDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/MemoryMapDB.java index 9d67bfe76c..9a35ce950f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/MemoryMapDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/MemoryMapDB.java @@ -53,7 +53,7 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { private DataConverter defaultEndian; private List blocks;// sorted list of blocks - private AddressSet allAddrSet; // continuously updated + private SynchronizedAddressSet allAddrSet = new SynchronizedAddressSet(); // continuously updated private MemoryAddressSetView addrSetView; @@ -62,7 +62,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { * for public API methods. */ private class MemoryAddressSetView { - private AddressSet all = new AddressSet(); private AddressSet initializedAndLoaded = new AddressSet(); private AddressSet initialized = new AddressSet(); private AddressSet externalBlock = new AddressSet(); @@ -146,29 +145,26 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { } addrSetView = new MemoryAddressSetView(); + // The allAddrSet instance is generally maintained with all memory + // block addresses and need only be updated if currently empty. + // Any time allAddrSet is modified addrSetView is set to null to + // signal it becoming stale. + boolean addToAll = allAddrSet.isEmpty(); + // we have to process the non-mapped blocks first because to process the mapped // blocks we need the address sets for the non-mapped blocks to be complete for (MemoryBlockDB block : blocks) { block.clearMappedBlockList(); if (!block.isMapped()) { - addBlockAddresses(block); + addBlockAddresses(block, addToAll); } } // process all mapped blocks after non-mapped-blocks above for (MemoryBlockDB block : blocks) { if (block.isMapped()) { - addBlockAddresses(block); + addBlockAddresses(block, addToAll); } } - - if (allAddrSet == null) { - // An independent address set copy is maintained which may be changed - // dynamically when adding memory blocks. This is neccessary - // since such a changing address set may not be used to hand out - // iterators. Any time allAddrSet is modified addrSetView is - // set to null to signal it becoming stale. - allAddrSet = new AddressSet(addrSetView.all); - } } finally { lock.release(); @@ -176,21 +172,18 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { } /** - * Update the addrSetView.initialized and addrSetView.initializedAndLoaded with - * relevant initialized addresses from the specified memory block. If block is not a - * mapped-block and it may be a source to existing mapped-blocks then - * scanAllMappedBlocksIfNeeded should be passed as true unless all - * mapped blocks will be processed separately. + * Update the addrSetView address sets with relevant addresses from the + * specified memory block. In addition, allAddrSet will be updated if addToAll parameter is true. * * @param block memory block - * @param blockMayBeMappedOnto if true and block is initialized and not a mapped block - * all mapped blocks will be processed for possible introduction of newly initialized - * mapped regions. + * @param addToAll if true the allAddrSet should be updated with the specified block's address range */ - private void addBlockAddresses(MemoryBlockDB block) { + private void addBlockAddresses(MemoryBlockDB block, boolean addToAll) { Address start = block.getStart(); Address end = block.getEnd(); - addrSetView.all.add(start, end); + if (addToAll) { + allAddrSet.add(start, end); + } if (block.isExternalBlock()) { addrSetView.externalBlock.add(start, end); } @@ -222,7 +215,7 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { synchronized (this) { fileBytesAdapter.refresh(); adapter.refreshMemory(); - allAddrSet = null; + allAddrSet = new SynchronizedAddressSet(); // buildAddressSets() will populate initializeBlocks(); buildAddressSets(); } @@ -314,19 +307,6 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { return getLoadedAndInitializedAddressSet(); } - private AddressSetView getIterableAddressSet() { - lock.acquire(); - try { - if (addrSetView == null) { - buildAddressSets(); - } - return new AddressSetViewAdapter(addrSetView.all); - } - finally { - lock.release(); - } - } - @Override public AddressSetView getAllInitializedAddressSet() { lock.acquire(); @@ -1786,35 +1766,17 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { @Override public boolean contains(Address start, Address end) { - lock.acquire(); - try { - return allAddrSet.contains(start, end); - } - finally { - lock.release(); - } + return allAddrSet.contains(start, end); } @Override public boolean contains(AddressSetView s) { - lock.acquire(); - try { - return allAddrSet.contains(s); - } - finally { - lock.release(); - } + return allAddrSet.contains(s); } @Override public boolean isEmpty() { - lock.acquire(); - try { - return allAddrSet.isEmpty(); - } - finally { - lock.release(); - } + return allAddrSet.isEmpty(); } @Override @@ -1833,40 +1795,22 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { @Override public Address getMinAddress() { - lock.acquire(); - try { - return allAddrSet.getMinAddress(); - } - finally { - lock.release(); - } + return allAddrSet.getMinAddress(); } @Override public Address getMaxAddress() { - lock.acquire(); - try { - return allAddrSet.getMaxAddress(); - } - finally { - lock.release(); - } + return allAddrSet.getMaxAddress(); } @Override public int getNumAddressRanges() { - lock.acquire(); - try { - return allAddrSet.getNumAddressRanges(); - } - finally { - lock.release(); - } + return allAddrSet.getNumAddressRanges(); } @Override public AddressRangeIterator getAddressRanges() { - return getIterableAddressSet().getAddressRanges(); + return allAddrSet.getAddressRanges(); } @Override @@ -1875,117 +1819,63 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { } @Override - public AddressRangeIterator getAddressRanges(boolean startAtFront) { - return getIterableAddressSet().getAddressRanges(startAtFront); + public AddressRangeIterator getAddressRanges(boolean forward) { + return allAddrSet.getAddressRanges(forward); } @Override public long getNumAddresses() { - lock.acquire(); - try { - return allAddrSet.getNumAddresses(); - } - finally { - lock.release(); - } + return allAddrSet.getNumAddresses(); } @Override public AddressIterator getAddresses(boolean forward) { - return getIterableAddressSet().getAddresses(forward); + return allAddrSet.getAddresses(forward); } @Override public AddressIterator getAddresses(Address start, boolean forward) { - return getIterableAddressSet().getAddresses(start, forward); + return allAddrSet.getAddresses(start, forward); } @Override public boolean intersects(AddressSetView set) { - lock.acquire(); - try { - return allAddrSet.intersects(set); - } - finally { - lock.release(); - } + return allAddrSet.intersects(set); } @Override public boolean intersects(Address start, Address end) { - lock.acquire(); - try { - return allAddrSet.intersects(start, end); - } - finally { - lock.release(); - } + return allAddrSet.intersects(start, end); } @Override public AddressSet intersect(AddressSetView set) { - lock.acquire(); - try { - return allAddrSet.intersect(set); - } - finally { - lock.release(); - } + return allAddrSet.intersect(set); } @Override public AddressSet intersectRange(Address start, Address end) { - lock.acquire(); - try { - return allAddrSet.intersectRange(start, end); - } - finally { - lock.release(); - } + return allAddrSet.intersectRange(start, end); } @Override public AddressSet union(AddressSetView set) { - lock.acquire(); - try { - return allAddrSet.union(set); - } - finally { - lock.release(); - } + return allAddrSet.union(set); } @Override public AddressSet subtract(AddressSetView set) { - lock.acquire(); - try { - return allAddrSet.subtract(set); - } - finally { - lock.release(); - } + return allAddrSet.subtract(set); } @Override public AddressSet xor(AddressSetView set) { - lock.acquire(); - try { - return allAddrSet.xor(set); - } - finally { - lock.release(); - } + return allAddrSet.xor(set); } @Override public boolean hasSameAddresses(AddressSetView set) { - lock.acquire(); - try { - return allAddrSet.hasSameAddresses(set); - } - finally { - lock.release(); - } + return allAddrSet.hasSameAddresses(set); } @Override @@ -2238,61 +2128,37 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener { @Override public AddressRangeIterator getAddressRanges(Address start, boolean forward) { - return getIterableAddressSet().getAddressRanges(start, forward); + return allAddrSet.getAddressRanges(start, forward); } @Override public AddressRange getFirstRange() { - lock.acquire(); - try { - return allAddrSet.getFirstRange(); - } - finally { - lock.release(); - } + return allAddrSet.getFirstRange(); } @Override public AddressRange getLastRange() { - lock.acquire(); - try { - return allAddrSet.getLastRange(); - } - finally { - lock.release(); - } + return allAddrSet.getLastRange(); } @Override public AddressRange getRangeContaining(Address address) { - lock.acquire(); - try { - return allAddrSet.getRangeContaining(address); - } - finally { - lock.release(); - } + return allAddrSet.getRangeContaining(address); } @Override public Iterator iterator(boolean forward) { - return getIterableAddressSet().iterator(forward); + return allAddrSet.getAddressRanges(forward); } @Override public Iterator iterator(Address start, boolean forward) { - return getIterableAddressSet().iterator(start, forward); + return allAddrSet.getAddressRanges(start, forward); } @Override public Address findFirstAddressInCommon(AddressSetView set) { - lock.acquire(); - try { - return allAddrSet.findFirstAddressInCommon(set); - } - finally { - lock.release(); - } + return allAddrSet.findFirstAddressInCommon(set); } @Override diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/RecoverableAddressIterator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/RecoverableAddressIterator.java new file mode 100644 index 0000000000..1bca3e3674 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/RecoverableAddressIterator.java @@ -0,0 +1,110 @@ +/* ### + * 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.mem; + +import java.util.ConcurrentModificationException; +import java.util.Iterator; + +import ghidra.program.model.address.*; + +/** + * RecoverableAddressIterator provides the ability to iterator over an {@link AddressSet} + * which is getting modified concurrent with the iteration of Addresses contained within it. Do to + * multiple levels of prefetch caching, the results returned may be stale relative to the actual + * {@link AddressSet} at any point in time. The primary intent is to return addresses in proper order + * and avoid throwing a {@link ConcurrentModificationException} which the standard iterators are + * subject to. + *

+ * NOTES: + *

    + *
  1. The iterator methods are not symchronized but could be made so if restricted to + * use in conjunction with the {@link SynchronizedAddressSet} where it would synchronize on + * the set itself.
  2. + *
  3. This class and {@link SynchronizedAddressSet} could be made public alongside {@link AddressSet} + * if so desired in the future. Its current use has been limited until proven to be thread-safe + * and useful.
  4. + *
+ */ +class RecoverableAddressIterator implements AddressIterator { + + private AddressSetView set; + private boolean forward; + private AddressIterator iterator; + private Address next; + + /** + * Construct iterator + * @param set address set + * @param start address to start iterating at in the address set or null for all addresses + * @param forward if true address are return from lowest to highest, else from highest to lowest + */ + RecoverableAddressIterator(AddressSetView set, Address start, boolean forward) { + this.set = set; + this.forward = forward; + initIterator(start); + this.next = iterator.next(); + } + + private void initIterator(Address start) { + if (start == null) { + iterator = set.getAddresses(forward); + } + else { + iterator = set.getAddresses(start, forward); + } + } + + @Override + public Iterator
iterator() { + return this; + } + + @Override + public Address next() { + Address addr = next; + if (addr != null) { + try { + next = iterator.next(); + } + catch (ConcurrentModificationException e) { + next = recoverNext(addr); + } + } + return addr; + } + + private Address recoverNext(Address lastAddr) { + while (true) { + try { + initIterator(lastAddr); + Address a = iterator.next(); + if (a != null && a.equals(lastAddr)) { + a = iterator.next(); + } + return a; + } + catch (ConcurrentModificationException e) { + // set must have changed - try re-initializing again + } + } + } + + @Override + public boolean hasNext() { + return next != null; + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/RecoverableAddressRangeIterator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/RecoverableAddressRangeIterator.java new file mode 100644 index 0000000000..fd0a30ba7f --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/RecoverableAddressRangeIterator.java @@ -0,0 +1,130 @@ +/* ### + * 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.mem; + +import java.util.*; + +import ghidra.program.model.address.*; + +/** + * RecoverableAddressRangeIterator provides the ability to iterator over an {@link AddressSet} + * which is getting modified concurrent with the iteration of {@link AddressRange}es contained within it. Do to + * multiple levels of prefetch caching, the results returned may be stale relative to the actual + * {@link AddressSet} at any point in time. The primary intent is to return address ranges in proper order + * and avoid throwing a {@link ConcurrentModificationException} which the standard iterators are + * subject to. + *

+ * NOTES: + *

    + *
  1. The iterator methods are not symchronized but could be made so if restricted to + * use in conjunction with the {@link SynchronizedAddressSet} where it would synchronize on + * the set itself.
  2. + *
  3. This class and {@link SynchronizedAddressSet} could be made public alongside {@link AddressSet} + * if so desired in the future. Its current use has been limited until proven to be thread-safe + * and useful.
  4. + *
+ */ +class RecoverableAddressRangeIterator implements AddressRangeIterator { + + private AddressSetView set; + private boolean forward; + private AddressRangeIterator iterator; + private AddressRange next; + + /** + * Construct iterator + * @param set address set + * @param start the address the the first range should contain. + * @param forward true iterators forward, false backwards + */ + RecoverableAddressRangeIterator(AddressSetView set, Address start, boolean forward) { + this.set = set; + this.forward = forward; + initIterator(start); + try { + this.next = iterator.next(); + } + catch (NoSuchElementException e) { + this.next = null; + } + } + + private void initIterator(Address start) { + if (start == null) { + iterator = set.getAddressRanges(forward); + } + else { + iterator = set.getAddressRanges(start, forward); + } + } + + @Override + public Iterator iterator() { + return this; + } + + @Override + public AddressRange next() throws NoSuchElementException { + AddressRange range = next; + if (range == null) { + throw new NoSuchElementException(); + } + try { + next = iterator.next(); + } + catch (ConcurrentModificationException e) { + next = recoverNext(range); + } + catch (NoSuchElementException e) { + next = null; + } + return range; + } + + private AddressRange recoverNext(AddressRange lastRange) { + while (true) { + try { + Address lastAddr = forward ? lastRange.getMaxAddress() : lastRange.getMinAddress(); + initIterator(lastAddr); + AddressRange r = iterator.next(); + if (!r.intersects(lastRange)) { + return r; + } + if (forward) { + if (r.getMaxAddress().compareTo(lastAddr) > 0) { + return new AddressRangeImpl(lastAddr.next(), r.getMaxAddress()); + } + } + else if (r.getMinAddress().compareTo(lastAddr) < 0) { // reverse + return new AddressRangeImpl(r.getMinAddress(), lastAddr.previous()); + } + return iterator.next(); // skip range and return next + } + catch (ConcurrentModificationException e) { + // set must have changed - try re-initializing again + } + catch (NoSuchElementException e) { + return null; + } + } + } + + @Override + public boolean hasNext() { + return next != null; + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/SynchronizedAddressSet.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/SynchronizedAddressSet.java new file mode 100644 index 0000000000..102a0eaf85 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/mem/SynchronizedAddressSet.java @@ -0,0 +1,220 @@ +/* ### + * 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.mem; + +import java.util.Iterator; + +import ghidra.program.model.address.*; + +/** + * SynchronizedAddressSet provides a synchronized address set which + * implements the {@link AddressSetView} interface. Iterators returned by this + * implementation will recover from concurrent modification of this address set. + * See {@link RecoverableAddressRangeIterator} and {@link RecoverableAddressIterator}. + */ +class SynchronizedAddressSet implements AddressSetView { + + private AddressSet set; + + SynchronizedAddressSet() { + set = new AddressSet(); + } + + /** + * Add all addresses of the given AddressSet to this set. + * @param addrSet set of addresses to add. + * @see AddressSet#add(AddressSetView) + */ + synchronized void add(AddressSet addrSet) { + set.add(addrSet); + } + + /** + * Adds the range to this set + * @param start the start address of the range to add + * @param end the end address of the range to add + * @see AddressSet#add(Address, Address) + */ + synchronized void add(Address start, Address end) { + set.add(start, end); + } + + /** + * Deletes a range of addresses from this set + * @param start the starting address of the range to be removed + * @param end the ending address of the range to be removed (inclusive) + * @see AddressSet#delete(Address, Address) + */ + synchronized void delete(Address start, Address end) { + set.delete(start, end); + } + + @Override + public synchronized boolean contains(Address addr) { + return set.contains(addr); + } + + @Override + public synchronized boolean contains(Address start, Address end) { + return set.contains(start, end); + } + + @Override + public synchronized boolean contains(AddressSetView addrSet) { + return set.contains(addrSet); + } + + @Override + public synchronized boolean isEmpty() { + return set.isEmpty(); + } + + @Override + public synchronized Address getMinAddress() { + return set.getMinAddress(); + } + + @Override + public synchronized Address getMaxAddress() { + return set.getMaxAddress(); + } + + @Override + public synchronized int getNumAddressRanges() { + return set.getNumAddressRanges(); + } + + @Override + public synchronized AddressRangeIterator getAddressRanges() { + return set.getAddressRanges(); + } + + @Override + public synchronized AddressRangeIterator getAddressRanges(boolean forward) { + return set.getAddressRanges(forward); + } + + @Override + public synchronized AddressRangeIterator getAddressRanges(Address start, boolean forward) { + return new RecoverableAddressRangeIterator(set, start, forward); + } + + @Override + public synchronized Iterator iterator() { + return set.getAddressRanges(); + } + + @Override + public synchronized Iterator iterator(boolean forward) { + return set.getAddressRanges(forward); + } + + @Override + public synchronized Iterator iterator(Address start, boolean forward) { + return set.getAddressRanges(start, forward); + } + + @Override + public synchronized long getNumAddresses() { + return set.getNumAddresses(); + } + + @Override + public synchronized AddressIterator getAddresses(boolean forward) { + return new RecoverableAddressIterator(set, null, forward); + } + + @Override + public synchronized AddressIterator getAddresses(Address start, boolean forward) { + return new RecoverableAddressIterator(set, start, forward); + } + + @Override + public synchronized boolean intersects(AddressSetView addrSet) { + return set.intersects(addrSet); + } + + @Override + public synchronized boolean intersects(Address start, Address end) { + return set.intersects(start, end); + } + + @Override + public synchronized AddressSet intersect(AddressSetView addrSet) { + return set.intersect(addrSet); + } + + @Override + public synchronized AddressSet intersectRange(Address start, Address end) { + return set.intersectRange(start, end); + } + + @Override + public synchronized AddressSet union(AddressSetView addrSet) { + return set.union(addrSet); + } + + @Override + public synchronized AddressSet subtract(AddressSetView addrSet) { + return set.subtract(addrSet); + } + + @Override + public synchronized AddressSet xor(AddressSetView addrSet) { + return set.xor(addrSet); + } + + @Override + public synchronized boolean hasSameAddresses(AddressSetView addrSet) { + return set.hasSameAddresses(addrSet); + } + + @Override + public synchronized AddressRange getFirstRange() { + return set.getFirstRange(); + } + + @Override + public synchronized AddressRange getLastRange() { + return set.getLastRange(); + } + + @Override + public synchronized AddressRange getRangeContaining(Address address) { + return set.getRangeContaining(address); + } + + @Override + public synchronized Address findFirstAddressInCommon(AddressSetView addrSet) { + return set.findFirstAddressInCommon(addrSet); + } + + @Override + public synchronized int hashCode() { + return set.hashCode(); + } + + @Override + public synchronized boolean equals(Object obj) { + return set.equals(obj); + } + + @Override + public synchronized String toString() { + return set.toString(); + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressSet.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressSet.java index 7b76a492fc..1672192935 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressSet.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/address/AddressSet.java @@ -1249,6 +1249,9 @@ public class AddressSet implements AddressSetView { @Override public AddressRange next() { RedBlackEntry next = iterator.next(); + if (next == null) { + throw new NoSuchElementException(); + } return new AddressRangeImpl(next.getKey(), next.getValue()); } diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/mem/RecoverableAddressIteratorTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/mem/RecoverableAddressIteratorTest.java new file mode 100644 index 0000000000..fa27869b67 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/mem/RecoverableAddressIteratorTest.java @@ -0,0 +1,174 @@ +/* ### + * 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.mem; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; + +import generic.test.AbstractGenericTest; +import ghidra.program.model.address.*; + +public class RecoverableAddressIteratorTest extends AbstractGenericTest { + + private AddressSpace space; + private AddressSet set; + + @Before + public void setUp() throws Exception { + + space = new GenericAddressSpace("xx", 32, AddressSpace.TYPE_RAM, 0); + + set = new AddressSet(); + set.add(range(0x100, 0x200)); + set.add(range(0x250, 0x250)); + set.add(range(0x300, 0x400)); + } + + private Address addr(long offset) { + return space.getAddress(offset); + } + + private AddressRange range(long start, long end) { + return new AddressRangeImpl(addr(start), addr(end)); + } + + private void assertHasAddressRange(AddressRange range, boolean forward, AddressIterator it) { + Address nextAddr, endAddr; + if (forward) { + nextAddr = range.getMinAddress(); + endAddr = range.getMaxAddress(); + } + else { + nextAddr = range.getMaxAddress(); + endAddr = range.getMinAddress(); + } + while (true) { + assertEquals(nextAddr, it.next()); + if (nextAddr.equals(endAddr)) { + break; + } + nextAddr = forward ? nextAddr.next() : nextAddr.previous(); + } + } + + @Test + public void test1Forward() { + + AddressIterator it = new RecoverableAddressIterator(set, addr(0x150), true); + + assertTrue(it.hasNext()); + + set.add(range(0x350, 0x500)); + + assertHasAddressRange(range(0x150, 0x200), true, it); + + set.add(addr(0x220)); // will get skipped due to underlying iterator prefetch + + assertHasAddressRange(range(0x250, 0x250), true, it); + + assertHasAddressRange(range(0x300, 0x400), true, it); + + assertHasAddressRange(range(0x401, 0x500), true, it); + + assertFalse(it.hasNext()); + assertNull(it.next()); + } + + @Test + public void test2Forward() { + + AddressIterator it = new RecoverableAddressIterator(set, addr(0x150), true); + + assertTrue(it.hasNext()); + + set.add(range(0x210, 0x215)); + + assertHasAddressRange(range(0x150, 0x200), true, it); + + set.add(range(0x220, 0x500)); + + assertHasAddressRange(range(0x210, 0x215), true, it); + + assertHasAddressRange(range(0x220, 0x500), true, it); + + assertFalse(it.hasNext()); + } + + @Test + public void test3Forward() { + + AddressIterator it = new RecoverableAddressIterator(set, addr(0x150), true); + + assertTrue(it.hasNext()); + + set.add(range(0x210, 0x215)); + + assertHasAddressRange(range(0x150, 0x200), true, it); + + set.delete(range(0x220, 0x500)); + + assertHasAddressRange(range(0x210, 0x215), true, it); + + assertFalse(it.hasNext()); + } + + @Test + public void test1Reverse() { + + AddressIterator it = new RecoverableAddressIterator(set, addr(0x350), false); + + assertTrue(it.hasNext()); + + set.add(addr(0x240)); + + assertHasAddressRange(range(0x300, 0x350), false, it); + + set.add(range(0x50, 0x150)); + + assertHasAddressRange(range(0x250, 0x250), false, it); + + set.add(range(0x50, 0x150)); + + assertHasAddressRange(range(0x240, 0x240), false, it); + + assertHasAddressRange(range(0x50, 0x200), false, it); + + assertFalse(it.hasNext()); + } + + @Test + public void test2Reverse() { + + AddressIterator it = new RecoverableAddressIterator(set, addr(0x350), false); + + assertTrue(it.hasNext()); + + set.add(addr(0x240)); + + assertHasAddressRange(range(0x300, 0x350), false, it); + + set.delete(range(0x100, 0x200)); + + assertHasAddressRange(range(0x250, 0x250), false, it); + + assertHasAddressRange(range(0x240, 0x240), false, it); + + assertFalse(it.hasNext()); + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/mem/RecoverableAddressRangeIteratorTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/mem/RecoverableAddressRangeIteratorTest.java new file mode 100644 index 0000000000..1b55ce14c6 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/database/mem/RecoverableAddressRangeIteratorTest.java @@ -0,0 +1,164 @@ +/* ### + * 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.mem; + +import static org.junit.Assert.*; + +import java.util.NoSuchElementException; + +import org.junit.Before; +import org.junit.Test; + +import generic.test.AbstractGenericTest; +import ghidra.program.model.address.*; + +public class RecoverableAddressRangeIteratorTest extends AbstractGenericTest { + + private AddressSpace space; + private AddressSet set; + + @Before + public void setUp() throws Exception { + + space = new GenericAddressSpace("xx", 32, AddressSpace.TYPE_RAM, 0); + + set = new AddressSet(); + set.add(range(0x100, 0x200)); + set.add(range(0x250, 0x250)); + set.add(range(0x300, 0x400)); + } + + private Address addr(long offset) { + return space.getAddress(offset); + } + + private AddressRange range(long start, long end) { + return new AddressRangeImpl(addr(start), addr(end)); + } + + @Test + public void test1Forward() { + + AddressRangeIterator it = new RecoverableAddressRangeIterator(set, addr(150), true); + + assertTrue(it.hasNext()); + assertEquals(range(0x100, 0x200), it.next()); // triggers prefetch of range(0x250, 0x250) + + set.add(addr(0x220)); // will get skipped due to underlying iterator prefetch + + assertEquals(range(0x250, 0x250), it.next()); // triggers prefetch of range(0x300, 0x400) + + set.add(range(0x350, 0x500)); // modifies existing RedBlackEntry node - no iterator recovery triggered + + assertEquals(range(0x300, 0x400), it.next()); // triggers prefetch of END + + assertFalse(it.hasNext()); + + try { + it.next(); + fail("Expected NoSuchElementException"); + } + catch (NoSuchElementException e) { + // expected + } + } + + @Test + public void test2Forward() { + + AddressRangeIterator it = new RecoverableAddressRangeIterator(set, addr(150), true); + + assertTrue(it.hasNext()); + assertEquals(range(0x100, 0x200), it.next()); // triggers prefetch of range(0x250, 0x250) + + set.add(range(0x220, 0x400)); + + assertEquals(range(0x250, 0x250), it.next()); // triggers recovery prefetch of partial range(0x251, 0x400) + + assertEquals(range(0x251, 0x400), it.next()); // triggers prefetch of END + + assertFalse(it.hasNext()); + } + + @Test + public void test3Forward() { + + AddressRangeIterator it = new RecoverableAddressRangeIterator(set, addr(150), true); + + assertTrue(it.hasNext()); + assertEquals(range(0x100, 0x200), it.next()); // triggers prefetch of range(0x250, 0x250) + + set.delete(range(0x220, 0x400)); + + assertEquals(range(0x250, 0x250), it.next()); // triggers recovery prefetch of END + + assertFalse(it.hasNext()); + } + + @Test + public void test1Reverse() { + + AddressRangeIterator it = new RecoverableAddressRangeIterator(set, addr(0x350), false); + + assertTrue(it.hasNext()); + assertEquals(range(0x300, 0x400), it.next()); // triggers prefetch of range(0x250, 0x250) + + set.add(addr(0x220)); + + assertEquals(range(0x250, 0x250), it.next()); // triggers recovery prefetch of range(0x220, 0x220) + + assertEquals(range(0x220, 0x220), it.next()); // triggers prefetch of range(0x100, 0x200) + + assertEquals(range(0x100, 0x200), it.next()); // triggers prefetch of END + + assertFalse(it.hasNext()); + } + + @Test + public void test2Reverse() { + + AddressRangeIterator it = new RecoverableAddressRangeIterator(set, addr(0x350), false); + + assertTrue(it.hasNext()); + assertEquals(range(0x300, 0x400), it.next()); // triggers prefetch of range(0x250, 0x250) + + set.add(range(0x220, 0x380)); + + assertEquals(range(0x250, 0x250), it.next()); // triggers recovery prefetch of partial range(0x220, 0x24f) + + assertEquals(range(0x220, 0x24f), it.next()); // triggers prefetch of range(0x100, 0x200) + + assertEquals(range(0x100, 0x200), it.next()); // triggers prefetch of END + + assertFalse(it.hasNext()); + } + + @Test + public void test3Reverse() { + + AddressRangeIterator it = new RecoverableAddressRangeIterator(set, addr(0x350), false); + + assertTrue(it.hasNext()); + assertEquals(range(0x300, 0x400), it.next()); // triggers prefetch of range(0x250, 0x250) + + set.delete(range(0x100, 0x220)); + + assertEquals(range(0x250, 0x250), it.next()); // triggers recovery prefetch of END + + assertFalse(it.hasNext()); + } + +}