GP-1949 Revised MemoryMapDB to utilize SynchronizedAddressSet

This commit is contained in:
ghidra1 2022-05-05 12:36:25 -04:00
parent 26f921cf6e
commit 2bb57e6d5b
7 changed files with 845 additions and 178 deletions

View File

@ -53,7 +53,7 @@ public class MemoryMapDB implements Memory, ManagerDB, LiveMemoryListener {
private DataConverter defaultEndian;
private List<MemoryBlockDB> 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 <code>addrSetView.initialized</code> and <code>addrSetView.initializedAndLoaded</code> 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
* <code>scanAllMappedBlocksIfNeeded</code> should be passed as <code>true</code> unless all
* mapped blocks will be processed separately.
* Update the <code>addrSetView</code> 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<AddressRange> iterator(boolean forward) {
return getIterableAddressSet().iterator(forward);
return allAddrSet.getAddressRanges(forward);
}
@Override
public Iterator<AddressRange> 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

View File

@ -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.*;
/**
* <code>RecoverableAddressIterator</code> 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.
* <p>
* NOTES:
* <ol>
* <li>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.</li>
* <li>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.</li>
* </ol>
*/
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<Address> 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;
}
}

View File

@ -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.*;
/**
* <code>RecoverableAddressRangeIterator</code> 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.
* <p>
* NOTES:
* <ol>
* <li>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.</li>
* <li>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.</li>
* </ol>
*/
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<AddressRange> 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;
}
}

View File

@ -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.*;
/**
* <code>SynchronizedAddressSet</code> 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<AddressRange> iterator() {
return set.getAddressRanges();
}
@Override
public synchronized Iterator<AddressRange> iterator(boolean forward) {
return set.getAddressRanges(forward);
}
@Override
public synchronized Iterator<AddressRange> 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();
}
}

View File

@ -1249,6 +1249,9 @@ public class AddressSet implements AddressSetView {
@Override
public AddressRange next() {
RedBlackEntry<Address, Address> next = iterator.next();
if (next == null) {
throw new NoSuchElementException();
}
return new AddressRangeImpl(next.getKey(), next.getValue());
}

View File

@ -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());
}
}

View File

@ -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());
}
}