Merge remote-tracking branch 'origin/GP-5864_Dan_addEmulatorUtilities--SQUASHED'

This commit is contained in:
Ryan Kurtz 2025-08-25 05:41:08 -04:00
commit 12ac4660a1
43 changed files with 986 additions and 269 deletions

View file

@ -28,6 +28,7 @@ import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils.Extrema;
import ghidra.app.services.DebuggerEmulationService;
import ghidra.framework.model.DomainFile;
import ghidra.pcode.emu.EmulatorUtilities;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program;
@ -142,7 +143,7 @@ public class ProgramEmulationUtils {
EMU_SESSION_SCHEMA = EMU_CTX.getSchema(new SchemaName("EmuSession"));
}
public static final String BLOCK_NAME_STACK = "STACK";
public static final String BLOCK_NAME_STACK = EmulatorUtilities.BLOCK_NAME_STACK;
/**
* Conventional prefix for first snapshot to identify "pure emulation" traces.
@ -428,7 +429,7 @@ public class ProgramEmulationUtils {
final AddressRange alloc;
if (cSpec.stackGrowsNegative()) {
Address max = spAddr.subtractWrap(1);
Address min = spAddr.subtractWrapSpace(size);
Address min = spAddr.subtractWrap(size);
if (min.compareTo(max) > 0) {
alloc = new AddressRangeImpl(max.getAddressSpace().getMinAddress(), max);
}

View file

@ -19,6 +19,7 @@ import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import generic.util.MergeSortingIterator;
import ghidra.program.model.address.*;
import ghidra.trace.model.*;
import ghidra.trace.model.Lifespan.DefaultLifeSet;
@ -370,7 +371,7 @@ public class DBTraceTimeViewport implements TraceTimeViewport {
List<Iterator<T>> iters = getOrderedSpans().stream()
.map(rng -> iterFunc.apply(rng.lmax()))
.collect(Collectors.toList());
return new UniqIterator<>(new MergeSortingIterator<>(iters, comparator));
return new DistinctIterator<>(new MergeSortingIterator<>(iters, comparator));
}
@Override

View file

@ -19,7 +19,7 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.concurrent.locks.Lock;
import generic.NestedIterator;
import generic.util.FlattenedIterator;
import ghidra.program.model.address.*;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.space.DBTraceDelegatingManager;
@ -361,7 +361,7 @@ public abstract class AbstractBaseDBTraceCodeUnitsMemoryView<T extends DBTraceCo
* @return an iterable of units
*/
public Iterable<? extends T> get(long snap, AddressSetView set, boolean forward) {
return () -> NestedIterator.start(set.iterator(forward),
return () -> FlattenedIterator.start(set.iterator(forward),
r -> get(snap, r, forward).iterator());
}

View file

@ -15,7 +15,7 @@
*/
package ghidra.trace.database.listing;
import generic.NestedIterator;
import generic.util.FlattenedIterator;
import ghidra.program.model.address.*;
import ghidra.trace.model.*;
import ghidra.trace.model.listing.TraceBaseCodeUnitsView;
@ -124,7 +124,7 @@ public abstract class AbstractBaseDBTraceCodeUnitsView<T extends DBTraceCodeUnit
* @see TraceBaseCodeUnitsView#get(long, AddressSetView, boolean)
*/
public Iterable<? extends T> get(long snap, AddressSetView set, boolean forward) {
return () -> NestedIterator.start(set.iterator(forward),
return () -> FlattenedIterator.start(set.iterator(forward),
r -> this.get(snap, r, forward).iterator());
}

View file

@ -19,6 +19,7 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.stream.StreamSupport;
import generic.util.MergeSortingIterator;
import ghidra.program.model.address.*;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.listing.TraceCodeUnit;

View file

@ -20,7 +20,8 @@ import java.util.*;
import org.apache.commons.collections4.IteratorUtils;
import generic.NestedIterator;
import generic.util.MergeSortingIterator;
import generic.util.FlattenedIterator;
import ghidra.program.database.ProgramDB;
import ghidra.program.database.code.InstructionDB;
import ghidra.program.database.function.OverlappingFunctionException;
@ -348,7 +349,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
}
// TODO: The property map doesn't heed forking.
return new WrappingCodeUnitIterator(
NestedIterator
FlattenedIterator
.start(map.getAddressSetView(Lifespan.at(program.snap)).iterator(forward),
rng -> getTopCodeIterator(
s -> codeOperations.codeUnits().get(s, rng, forward).iterator(),
@ -371,7 +372,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
}
// TODO: The property map doesn't heed forking.
return new WrappingCodeUnitIterator(
NestedIterator
FlattenedIterator
.start(map.getAddressSetView(Lifespan.at(program.snap)).iterator(addr, forward),
rng -> getTopCodeIterator(
s -> codeOperations.codeUnits().get(s, rng, forward).iterator(),
@ -394,7 +395,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
return new WrappingCodeUnitIterator(Collections.emptyIterator());
}
// TODO: The property map doesn't heed forking.
return new WrappingCodeUnitIterator(NestedIterator.start(
return new WrappingCodeUnitIterator(FlattenedIterator.start(
new IntersectionAddressSetView(map.getAddressSetView(Lifespan.at(program.snap)),
addrSet).iterator(forward),
rng -> getTopCodeIterator(

View file

@ -23,7 +23,7 @@ import java.util.function.Predicate;
import javax.help.UnsupportedOperationException;
import generic.NestedIterator;
import generic.util.FlattenedIterator;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Variable;
@ -242,7 +242,7 @@ public abstract class AbstractDBTraceProgramViewReferenceManager implements Refe
protected Iterator<Reference> getReferenceIteratorForSnap(long snap, Address startAddr) {
AddressIterator addresses =
refs.getReferenceSources(Lifespan.at(snap)).getAddresses(startAddr, true);
return NestedIterator.start(addresses, a -> {
return FlattenedIterator.start(addresses, a -> {
return refs.getReferencesFrom(snap, a).iterator();
});
}

View file

@ -23,7 +23,7 @@ import javax.swing.Icon;
import org.apache.commons.collections4.IteratorUtils;
import generic.NestedIterator;
import generic.util.FlattenedIterator;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Bookmark;
import ghidra.program.model.listing.BookmarkType;
@ -310,7 +310,7 @@ public class DBTraceProgramViewBookmarkManager implements TraceProgramViewBookma
@Override
public Iterator<Bookmark> getBookmarksIterator() {
// TODO: This seems terribly inefficient. We'll have to see how/when it's used.
return NestedIterator.start(bookmarkManager.getActiveSpaces().iterator(),
return FlattenedIterator.start(bookmarkManager.getActiveSpaces().iterator(),
space -> filteredIterator(space.getAllBookmarks().iterator(),
bm -> program.viewport.containsAnyUpper(bm.getLifespan())));
}
@ -327,7 +327,7 @@ public class DBTraceProgramViewBookmarkManager implements TraceProgramViewBookma
AddressSet allMemory = factory.getAddressSet();
AddressSet within = forward ? factory.getAddressSet(startAddress, allMemory.getMaxAddress())
: factory.getAddressSet(allMemory.getMinAddress(), startAddress);
return NestedIterator.start(within.iterator(forward), rng -> {
return FlattenedIterator.start(within.iterator(forward), rng -> {
DBTraceBookmarkSpace space =
bookmarkManager.getBookmarkSpace(rng.getAddressSpace(), false);
if (space == null) {

View file

@ -20,7 +20,7 @@ import java.util.*;
import java.util.Map.Entry;
import java.util.function.Function;
import generic.NestedIterator;
import generic.util.FlattenedIterator;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.ContextChangeException;
@ -110,7 +110,7 @@ public class DBTraceProgramViewProgramContext extends AbstractProgramContext {
setRegisterValue(start, end, new RegisterValue(register, value));
}
private static class NestedAddressRangeIterator<U> extends NestedIterator<U, AddressRange>
private static class NestedAddressRangeIterator<U> extends FlattenedIterator<U, AddressRange>
implements AddressRangeIterator {
protected NestedAddressRangeIterator(Iterator<U> it,
Function<U, Iterator<? extends AddressRange>> f) {

View file

@ -18,8 +18,7 @@ package ghidra.trace.database.program;
import java.util.*;
import java.util.stream.StreamSupport;
import generic.NestedIterator;
import generic.util.PeekableIterator;
import generic.util.*;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
@ -342,7 +341,7 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
@Override
public SymbolIterator getSymbols(AddressSetView set, SymbolType type, boolean forward) {
return new SymbolIteratorAdapter(NestedIterator.start(set.iterator(), range -> {
return new SymbolIteratorAdapter(FlattenedIterator.start(set.iterator(), range -> {
if (range.getAddressSpace().isMemorySpace()) {
if (type == SymbolType.LABEL) {
return symbolManager.labels()
@ -426,7 +425,7 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
@Override
public SymbolIterator getPrimarySymbolIterator(AddressSetView asv, boolean forward) {
return new PrimarySymbolIterator(NestedIterator.start(asv.iterator(forward),
return new PrimarySymbolIterator(FlattenedIterator.start(asv.iterator(forward),
range -> symbolManager.labels()
.getIntersecting(Lifespan.at(program.snap), range, true, forward)
.iterator()));

View file

@ -19,7 +19,7 @@ import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.function.*;
import generic.NestedIterator;
import generic.util.FlattenedIterator;
import ghidra.program.model.address.*;
import ghidra.util.LockHold;
@ -191,7 +191,7 @@ public interface DBTraceDelegatingManager<M> {
return new AbstractCollection<>() {
@Override
public Iterator<T> iterator() {
return NestedIterator.start(spaces.iterator(), func.andThen(Iterable::iterator));
return FlattenedIterator.start(spaces.iterator(), func.andThen(Iterable::iterator));
}
@Override

View file

@ -19,7 +19,7 @@ import java.io.IOException;
import java.util.concurrent.locks.ReadWriteLock;
import db.DBHandle;
import generic.NestedIterator;
import generic.util.FlattenedIterator;
import ghidra.framework.data.OpenMode;
import ghidra.program.model.address.AddressSetView;
import ghidra.trace.database.DBTrace;
@ -119,7 +119,7 @@ public class DBTraceStackManager implements TraceStackManager, DBTraceManager {
@Override
// TODO: Should probably include a lifespan parameter?
public Iterable<TraceStackFrame> getFramesIn(AddressSetView set) {
return () -> NestedIterator.start(set.iterator(),
return () -> FlattenedIterator.start(set.iterator(),
rng -> trace.getObjectManager()
.getObjectsIntersecting(Lifespan.ALL, rng, TraceStackFrame.KEY_PC,
TraceStackFrame.class)

View file

@ -18,9 +18,9 @@ package ghidra.trace.database.symbol;
import java.util.*;
import java.util.stream.Collectors;
import generic.util.MergeSortingIterator;
import ghidra.trace.model.symbol.*;
import ghidra.util.LazyCollection;
import ghidra.util.MergeSortingIterator;
public class DBTraceSymbolMultipleTypesView<T extends AbstractDBTraceSymbol>
implements TraceSymbolView<T> {

View file

@ -20,11 +20,11 @@ import java.util.Map.Entry;
import org.apache.commons.lang3.tuple.Pair;
import generic.util.AbstractPeekableIterator;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.listing.CodeUnit;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.util.AbstractPeekableIterator;
/**
* An iterator of overlapping objects return from two given iterators.

View file

@ -15,6 +15,7 @@
*/
package ghidra.trace.util;
import generic.util.AbstractPeekableIterator;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Lifespan.DefaultLifeSet;
import ghidra.trace.model.Lifespan.MutableLifeSet;
@ -22,7 +23,6 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.AbstractPeekableIterator;
public class TraceViewportSpanIterator extends AbstractPeekableIterator<Lifespan> {
private final TraceTimeManager timeManager;

View file

@ -20,7 +20,7 @@ import static ghidra.util.MathUtilities.cmin;
import java.util.Iterator;
import ghidra.util.AbstractPeekableIterator;
import generic.util.AbstractPeekableIterator;
import ghidra.util.AddressIteratorAdapter;
/**

View file

@ -18,23 +18,24 @@ package ghidra.util;
import java.util.Iterator;
import java.util.Objects;
import generic.util.AbstractPeekableIterator;
import generic.util.PeekableIterator;
import generic.util.PeekableIterators;
/**
* A filtering iterator which removes repeated objects
*
* <p>
* This operates in style to the uniq command on UNIX, which only removes immediate repeats. To
* obtain a truly unique iteration, the wrapped iterator must visit elements in sorted order.
* This iterator only removes immediate repeats (similar to the uniq command on UNIX). To obtain a
* truly distinct iteration, the wrapped iterator must visit elements in sorted order.
*
* @param <T> the type of elements
*/
public class UniqIterator<T> extends AbstractPeekableIterator<T> {
public class DistinctIterator<T> extends AbstractPeekableIterator<T> {
protected boolean first;
protected T last;
protected final PeekableIterator<T> wrapped;
public UniqIterator(Iterator<T> wrapped) {
public DistinctIterator(Iterator<T> wrapped) {
this.wrapped = PeekableIterators.castOrWrap(wrapped);
}

View file

@ -22,7 +22,7 @@ import java.util.function.Consumer;
import java.util.stream.Stream;
import db.DBRecord;
import generic.NestedIterator;
import generic.util.FlattenedIterator;
import generic.util.PeekableIterator;
import ghidra.util.LockHold;
import ghidra.util.database.*;
@ -287,7 +287,7 @@ public abstract class AbstractConstraintsTree< //
}
nodes.add(n);
}
return NestedIterator.start(nodes.iterator(), n -> iterator(n, query));
return FlattenedIterator.start(nodes.iterator(), n -> iterator(n, query));
}
protected Iterator<DR> orderedIterator(Q query) {

View file

@ -0,0 +1,280 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.emu;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.NoSuchElementException;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.DifferenceAddressSetView;
import ghidra.util.Msg;
/**
* Utilities for working with plain emulators (not trace- or debugger-bound) and programs in
* scripts.
*/
public enum EmulatorUtilities {
;
/**
* The conventional name of the memory block used to specify the location of the stack. This
* should only be the case for single-threaded emulation.
*/
public static final String BLOCK_NAME_STACK = "STACK";
/**
* The default block size used to copy program bytes into the emulator
*/
public static final int DEFAULT_BLOCK_SIZE = 4096;
/**
* The default max size to assume for the stack
*/
public static final int DEFAULT_STACK_SIZE = 0x4000;
/**
* These utilities will avoid choosing a stack range lower than this bound, as most platforms
* will never map this page (even in kernel space) so that 0-valued pointers are never valid.
*/
public static final long PAGE_ZERO_END = 0x1000;
/**
* Copy the bytes from the given program into the given emulator's memory.
*
* <p>
* This copies each initialized block of memory from the given program into the emulator's
* shared machine state. Because the machine can have memory of any given type, it will use the
* machine's arithmetic to create values from the program's concrete data. Data is copied in
* blocks of the given size, which can be tweaked for performance. The default value, used by
* {@link #loadProgram(PcodeMachine, Program)} is {@value #DEFAULT_BLOCK_SIZE}.
*
* @param <T> the type of values used by the emulator
* @param machine the emulator whose memory to initialize
* @param program the program whose bytes should be copied into the emulator
* @param blockSize the size of the temporary buffer used for copying
* @throws MemoryAccessException if the program's memory cannot be read
*/
public static <T> void loadProgram(PcodeMachine<T> machine, Program program, int blockSize)
throws MemoryAccessException {
byte[] buf = new byte[blockSize];
PcodeExecutorState<T> state = machine.getSharedState();
for (MemoryBlock block : program.getMemory().getBlocks()) {
if (!block.isInitialized()) {
continue;
}
for (AddressRange rng : new AddressRangeChunker(block.getAddressRange(),
buf.length)) {
int len = block.getBytes(rng.getMinAddress(), buf);
state.setConcrete(rng.getMinAddress(),
len == buf.length ? buf : Arrays.copyOf(buf, len));
}
}
}
/**
* Copy the bytes from the given program into the given emulator's memory.
*
* @see #loadProgram(PcodeMachine, Program, int)
* @param machine the emulator whose memory to initialize
* @param program the program whose bytes should be copied into the emulator
* @throws MemoryAccessException if the program's memory cannot be read
*/
public static void loadProgram(PcodeMachine<?> machine, Program program)
throws MemoryAccessException {
loadProgram(machine, program, DEFAULT_BLOCK_SIZE);
}
/**
* Choose an assumed stack range by examining the entry point for a contextual value of the
* stack pointer.
*
* @param program the program whose context to examine
* @param entry the entry point where context should be examined
* @param stackSize the assumed max size of the stack
* @return the range assumed to be reserved for the stack, or null if no stack pointer value is
* in the context
*/
public static AddressRange chooseStackRangeFromContext(Program program, Address entry,
int stackSize) {
ProgramContext ctx = program.getProgramContext();
CompilerSpec cSpec = program.getCompilerSpec();
Register sp = cSpec.getStackPointer();
RegisterValue spVal = ctx.getRegisterValue(sp, entry);
if (spVal == null || !spVal.hasValue()) {
return null;
}
Address spAddr = cSpec.getStackBaseSpace().getAddress(spVal.getUnsignedValue().longValue());
if (cSpec.stackGrowsNegative()) {
Address max = spAddr.subtractWrap(1);
Address min = spAddr.subtractWrap(stackSize);
if (min.compareTo(max) > 0) {
return new AddressRangeImpl(max.getAddressSpace().getMinAddress(), max);
}
return new AddressRangeImpl(min, max);
}
// Grows positive
Address min = spAddr;
Address max = spAddr.addWrap(stackSize - 1);
if (min.compareTo(max) > 0) {
return new AddressRangeImpl(min, min.getAddressSpace().getMaxAddress());
}
return new AddressRangeImpl(min, max);
}
/**
* Choose an assumed stack range by examining the program's memory map for a
* {@value #BLOCK_NAME_STACK} block.
*
* @param program the program to examine
* @return the range assumed to be reserved for the stack, or null if no
* {@value #BLOCK_NAME_STACK} block is found.
*/
public static AddressRange chooseStackRangeFromBlock(Program program) {
AddressSpace space = program.getCompilerSpec().getStackBaseSpace();
MemoryBlock stackBlock = program.getMemory().getBlock(BLOCK_NAME_STACK);
if (stackBlock == null) {
return null;
}
if (space != stackBlock.getStart().getAddressSpace().getPhysicalSpace()) {
Msg.showError(EmulatorUtilities.class, null, "Invalid STACK block",
"The STACK block must be in the stack's base space. Ignoring.");
return null;
}
return new AddressRangeImpl(
stackBlock.getStart().getPhysicalAddress(),
stackBlock.getEnd().getPhysicalAddress());
}
/**
* Choose an assumed stack range
*
* <p>
* This will first examine the entry point's context for a stack pointer value using
* {@link #chooseStackRangeFromContext(Program, Address, int)}. Then, it will examine the
* progam's memory map using {@link #chooseStackRangeFromBlock(Program)}. Finally, it will
* search for a slack address range of the requested size. That is, it seeks a range that does
* not intersect any existing memory block. If possible, this will avoid choosing a stack range
* that intersects [0, 4096), so that 0-valued pointers are in fact invalid.
*
* <p>
* Note that a stack is not formally "allocated." Instead, the range is used to initialize a
* thread's stack pointer. Unless instrumentation is added to detect a stack overflow, nothing
* really prevents the program from exceeding the returned range. Thus, {@code stackSize} should
* be large enough to accommodate the target. Additionally, the user or client code should be
* prepared for undefined behavior caused by an unmitigated stack overflow.
*
* @param program the program
* @param entry the entry point, in case context there defines an initial stack pointer
* @param stackSize the maximum expected size of the stack
* @return the chosen range assumed to be used for the stack
*/
public static AddressRange chooseStackRange(Program program, Address entry, int stackSize) {
AddressRange customByContext = chooseStackRangeFromContext(program, entry, stackSize);
if (customByContext != null) {
return customByContext;
}
AddressRange customByBlock = chooseStackRangeFromBlock(program);
if (customByBlock != null) {
return customByBlock;
}
// Search for a range of the given size outside any block
AddressSpace space = program.getCompilerSpec().getStackBaseSpace();
Address max = space.getMaxAddress();
AddressSet eligible;
if (max.getOffsetAsBigInteger().compareTo(BigInteger.valueOf(0x1000)) < 0) {
eligible = new AddressSet(space.getMinAddress(), max);
}
else {
eligible = new AddressSet(space.getAddress(0x1000), max);
}
AddressSetView left = new DifferenceAddressSetView(eligible, program.getMemory());
for (AddressRange candidate : left) {
if (Long.compareUnsigned(candidate.getLength(), stackSize) >= 0) {
try {
return new AddressRangeImpl(candidate.getMinAddress(), stackSize);
}
catch (AddressOverflowException e) {
throw new AssertionError(e);
}
}
}
throw new NoSuchElementException();
}
/**
* Choose an assumed stack range of size {@value #DEFAULT_STACK_SIZE}
*
* @see #chooseStackRange(Program, Address, int)
* @param program the program
* @param entry the entry point, in case context there defines an initial stack pointer
* @return the chosen range assumed to be used for the stack
*/
public static AddressRange chooseStackRange(Program program, Address entry) {
return chooseStackRange(program, entry, DEFAULT_STACK_SIZE);
}
/**
* Prepare a thread to emulate a given function
*
* @param <T> the type of values in the emulator
* @param thread the thread whose state to initialize
* @param function the function to prepare to enter
* @param stackSize the maximum expected size of the stack
*/
public static <T> void initializeForFunction(PcodeThread<T> thread, Function function,
int stackSize) {
PcodeArithmetic<T> arithmetic = thread.getArithmetic();
Program program = function.getProgram();
Address entry = function.getEntryPoint();
CompilerSpec cSpec = program.getCompilerSpec();
Register sp = cSpec.getStackPointer();
ThreadPcodeExecutorState<T> state = thread.getState();
ProgramProcessorContext ctx =
new ProgramProcessorContext(program.getProgramContext(), entry);
for (Register reg : ctx.getRegisters()) {
if (!reg.isBaseRegister()) {
continue;
}
RegisterValue rv = ctx.getRegisterValue(reg);
if (rv == null || !rv.hasAnyValue()) {
continue;
}
/**
* NOTE: In theory, there's no need to combine masked values, if this is a fresh
* emulator. If I had to guess, the client would want their values to take precedence,
* so they should overwrite the values after calling this method. Combining can be
* problematic, because the emulator could return some abstraction for the current
* value.
*/
state.setRegisterValue(rv);
}
AddressRange stack = chooseStackRange(program, entry);
long stackOffset = cSpec.stackGrowsNegative() ? stack.getMaxAddress().getOffset() + 1
: stack.getMinAddress().getOffset();
state.setVar(sp, arithmetic.fromConst(stackOffset, sp.getMinimumByteSize()));
thread.overrideCounter(entry);
}
}

View file

@ -15,14 +15,14 @@
*/
package ghidra.pcode.exec;
import java.math.BigInteger;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Stream;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.*;
import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.pcode.Varnode;
@ -374,4 +374,175 @@ public interface PcodeExecutorStatePiece<A, T> {
* dark.
*/
void clear();
/**
* Convenience to set a variable to a concrete value
*
* @param address the address in memory
* @param value the value
*/
default void setConcrete(Address address, byte[] value) {
setVar(address, value.length, false, getArithmetic().fromConst(value));
}
/**
* Convenience to inspect the concrete value of a variable
*
* @param address the address in memory
* @param size the number of bytes to inspect
* @return the value
* @throws ConcretionError if the value cannot be made concrete
*/
default byte[] inspectConcrete(Address address, int size) {
return getArithmetic().toConcrete(getVar(address, size, false, Reason.INSPECT),
Purpose.INSPECT);
}
/**
* Convenience to set a variable to a concrete value as a {@link BigInteger}
*
* @param address the address is memory
* @param size the size of the variable (in bytes)
* @param value the value
*/
default void setBigInteger(Address address, int size, BigInteger value) {
setVar(address, size, false, getArithmetic().fromConst(value, size));
}
/**
* Convenience to inspect the concrete value of a variable as a {@link BigInteger}
*
* @param address the address in memory
* @param size the number of bytes to inspect
* @return the value
* @throws ConcretionError if the value cannot be made concrete
*/
default BigInteger inspectBigInteger(Address address, int size) {
return getArithmetic().toBigInteger(getVar(address, size, false, Reason.INSPECT),
Purpose.INSPECT);
}
/**
* Convenience to set a variable to a concrete value as a {@code long}
*
* @param address the address is memory
* @param value the value
*/
default void setLong(Address address, long value) {
setVar(address, Long.BYTES, false, getArithmetic().fromConst(value, Long.BYTES));
}
/**
* Convenience to inspect the concrete value of a variable as a {@code long}
*
* @param address the address in memory
* @return the value
* @throws ConcretionError if the value cannot be made concrete
*/
default long inspectLong(Address address) {
return getArithmetic().toLong(getVar(address, Long.BYTES, false, Reason.INSPECT),
Purpose.INSPECT);
}
/**
* Convenience to set a variable to a concrete value as an {@code int}
*
* @param address the address is memory
* @param value the value
*/
default void setInt(Address address, int value) {
setVar(address, Integer.BYTES, false, getArithmetic().fromConst(value, Integer.BYTES));
}
/**
* Convenience to inspect the concrete value of a variable as an {@code int}
*
* @param address the address in memory
* @return the value
* @throws ConcretionError if the value cannot be made concrete
*/
default int inspectInt(Address address) {
return (int) getArithmetic().toLong(getVar(address, Integer.BYTES, false, Reason.INSPECT),
Purpose.INSPECT);
}
/**
* Convenience to set a variable to a concrete value as a {@code short}
*
* @param address the address is memory
* @param value the value
*/
default void setShort(Address address, short value) {
setVar(address, Short.BYTES, false, getArithmetic().fromConst(value, Short.BYTES));
}
/**
* Convenience to inspect the concrete value of a variable as a {@code short}
*
* @param address the address in memory
* @return the value
* @throws ConcretionError if the value cannot be made concrete
*/
default short inspectShort(Address address) {
return (short) getArithmetic().toLong(getVar(address, Short.BYTES, false, Reason.INSPECT),
Purpose.INSPECT);
}
/**
* Convenience to set a variable to a concrete value as a {@code byte}
*
* @param address the address is memory
* @param value the value
*/
default void setByte(Address address, byte value) {
setVar(address, Byte.BYTES, false, getArithmetic().fromConst(value, Byte.BYTES));
}
/**
* Convenience to inspect the concrete value of a variable as a {@code byte}
*
* @param address the address in memory
* @return the value
* @throws ConcretionError if the value cannot be made concrete
*/
default byte inspectByte(Address address) {
return (byte) getArithmetic().toLong(getVar(address, Byte.BYTES, false, Reason.INSPECT),
Purpose.INSPECT);
}
/**
* Convenience to set a register variable to a concrete value as a {@link RegisterValue}
*
* <p>
* <b>NOTE:</b> The register from the given value does not have to match the given register, but
* their <em>sizes</em> should at least match. This permits simpler moving of values from one
* register to another. If the sizes do not match, the behavior is undefined.
*
* @param register the register
* @param value the value
*/
default void setRegisterValue(Register register, RegisterValue value) {
setVar(register, getArithmetic().fromConst(value));
}
/**
* Convenience to set a register variable to a concrete value as a {@link RegisterValue}
*
* @param value the value
*/
default void setRegisterValue(RegisterValue value) {
setRegisterValue(value.getRegister(), value);
}
/**
* Convenience to inspect the concrete value of a register variable as a {@link RegisterValue}
*
* @param register the register
* @return the value
* @throws ConcretionError if the value cannot be made concrete
*/
default RegisterValue inspectRegisterValue(Register register) {
return getArithmetic().toRegisterValue(register, getVar(register, Reason.INSPECT),
Purpose.INSPECT);
}
}

View file

@ -13,16 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util;
package generic.util;
import java.util.NoSuchElementException;
import generic.util.PeekableIterator;
/**
* An implementation of {@link PeekableIterator} that only requires a way to seek out the next
* element. This will keep that element in hand until the next element is actually requested by the
* client. This does not invoke the search until the next element is required, either because the
* client called next or else wants to peek at it.
*
* @param <T> the type of elements
*/
public abstract class AbstractPeekableIterator<T> implements PeekableIterator<T> {
protected T next = null;
protected boolean soughtNext = false;
/**
* Find the next element in this iterator, because the client called either {@link #next} or
* {@link #peek()}.
*
* @return the next element
*/
protected abstract T seekNext();
private void checkSeekNext() {
@ -52,5 +64,4 @@ public abstract class AbstractPeekableIterator<T> implements PeekableIterator<T>
}
return next;
}
}

View file

@ -13,25 +13,36 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic;
package generic.util;
import java.util.Iterator;
import java.util.function.Function;
/**
* TODO Document me
* Given an "outer" iterator and a mapping from its elements to "inner" iterators, this is a
* flattened iterator over elements from the inner iterators.
*
* Note the innerFactory may return null to skip an outer element.
*
* TODO: Test innerFactory returning null.
*
* @param <O>
* @param <I>
* @param <O> the type of elements in the outer iterator
* @param <I> the type of elements in the inner and flattened iterators
*/
public class FlattenedIterator<O, I> implements Iterator<I> {
/**
* Create a flattened iterator
* <p>
* This iterates over each element of {@code outer} and applies the given {@code innerFactory}
* to generate an "inner" iterator. The returned iterator will produce elements from the inner
* iterators as if concatentated. This is essentially a flat-map operation on iterators. Note
* the {@code innerFactory} may return null to skip an outer element.
*
* @param <O> the type of elements in the outer iterator
* @param <I> the type of elements in the inner and flattened iterators
* @param outer the outer iterator
* @param innerFactory a mapping from outer elements to inner iterators
* @return the flattened iterator
*/
public class NestedIterator<O, I> implements Iterator<I> {
public static <O, I> Iterator<I> start(Iterator<O> outer,
Function<O, Iterator<? extends I>> innerFactory) {
return new NestedIterator<>(outer, innerFactory);
return new FlattenedIterator<>(outer, innerFactory);
}
protected final Iterator<O> outer;
@ -40,7 +51,8 @@ public class NestedIterator<O, I> implements Iterator<I> {
protected Iterator<? extends I> inner;
protected Iterator<? extends I> preppedInner;
protected NestedIterator(Iterator<O> outer, Function<O, Iterator<? extends I>> innerFactory) {
protected FlattenedIterator(Iterator<O> outer,
Function<O, Iterator<? extends I>> innerFactory) {
this.outer = outer;
this.innerFactory = innerFactory;
}

View file

@ -13,12 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util;
package generic.util;
import java.util.*;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import generic.util.PeekableIterator;
import java.util.NoSuchElementException;
import java.util.PriorityQueue;
/**
* An iterator which merges sorted iterators according to a comparator
@ -87,13 +90,16 @@ public class MergeSortingIterator<T> implements PeekableIterator<T> {
/**
* Construct a merge-sorting iterator which generates labeled values
*
* <p>
* The map of iterators is a map of entries, each giving a label and an iterator to be merged.
* Each iterator must return values as sorted by the given comparator. The entries returned by
* the combined iterator give the values in sorted order, but each has a the key indicating
* which given iterator returned that value. Note that the returned entry may be re-used by the
* underlying implementation, so users needing to keep the entry should create a copy.
* <p>
* The purpose of the iterator is to know which iterator provided a given entry in the merged
* result. While this has general utility, at the moment, it is only used in our tests to verify
* proper operation of the merge-sorting implementation.
*
* @param iterMap a map of labeled iterators
* @param comparator the comparator of values
@ -110,6 +116,12 @@ public class MergeSortingIterator<T> implements PeekableIterator<T> {
protected final Comparator<? super T> comparator;
protected final PriorityQueue<PeekableIterator<? extends T>> queue;
/**
* Construct a merge sorting iterator
*
* @param iterators a collection of iterators to merge
* @param comparator the comparator defining how the input and output iterators are sorted
*/
public MergeSortingIterator(Iterable<? extends Iterator<? extends T>> iterators,
Comparator<? super T> comparator) {
this.comparator = comparator;

View file

@ -26,10 +26,11 @@ import java.util.NoSuchElementException;
public interface PeekableIterator<T> extends Iterator<T> {
/**
* Returns the item that would be returned by calling {@link #next()}, but does not
* increment the iterator as <code>next</code> would.
* Returns the item that would be returned by calling {@link #next()}, but without incrementing
* the iterator.
*
* @return the item that would be returned by calling {@link #next()}
* @throws NoSuchElementException if there is no element to peek at
*/
public T peek() throws NoSuchElementException;
}

View file

@ -13,19 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util;
package generic.util;
import java.util.Iterator;
import generic.util.PeekableIterator;
import generic.util.WrappingPeekableIterator;
/**
* Utilities for working with a {@link PeekableIterator}
*/
public enum PeekableIterators {
;
public static <T> PeekableIterator<T> castOrWrap(Iterator<T> it) {
if (it instanceof PeekableIterator) {
return (PeekableIterator<T>) it;
/**
* Ensure that the given iterator is peekable
*
* <p>
* If it is already peekable, this casts it as such. If not, it wraps it in a peekable iterator.
*
* @param <E> the type of elements
* @param it the iterator
* @return the peekable iterator
*/
public static <E> PeekableIterator<E> castOrWrap(Iterator<E> it) {
if (it instanceof PeekableIterator<E> pi) {
return pi;
}
return new WrappingPeekableIterator<T>(it);
return new WrappingPeekableIterator<E>(it);
}
}

View file

@ -16,59 +16,37 @@
package generic.util;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* An implementation of {@link PeekableIterator} that can take a Java {@link Iterator} and
* wrap it to implement the {@link PeekableIterator} interface.
* An implementation of {@link PeekableIterator} that can take a Java {@link Iterator} and wrap it
* to implement the {@link PeekableIterator} interface.
*
* @param <T> the type of the iterator
*/
public class WrappingPeekableIterator<T> implements PeekableIterator<T> {
public class WrappingPeekableIterator<T> extends AbstractPeekableIterator<T> {
private Iterator<T> iterator;
private T peek;
private boolean peeked;
private final Iterator<T> iterator;
/**
* Wrap the given iterator
*
* @see PeekableIterators#castOrWrap(Iterator)
* @param iterator the iterator
*/
public WrappingPeekableIterator(Iterator<T> iterator) {
this.iterator = iterator;
}
@Override
public boolean hasNext() {
if (peeked) {
return true;
}
return iterator.hasNext();
}
@Override
public T next() {
if (peeked) {
peeked = false;
return peek;
}
return iterator.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public T peek() throws NoSuchElementException {
if (peeked) {
return peek;
protected T seekNext() {
if (!iterator.hasNext()) {
return null;
}
if (!hasNext()) {
throw new NoSuchElementException();
return iterator.next();
}
peek = next();
peeked = true;
return peek;
}
}

View file

@ -13,20 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic;
package generic.util;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.collections4.IteratorUtils;
import org.junit.Test;
public class NestedIteratorTest {
public class FlattenedIteratorTest {
@Test
public void testEmptyOuter() {
List<Object> result =
IteratorUtils.toList(NestedIterator.start(Collections.emptyIterator(), o -> {
IteratorUtils.toList(FlattenedIterator.start(Collections.emptyIterator(), o -> {
fail();
return null;
}));
@ -36,27 +42,27 @@ public class NestedIteratorTest {
@Test
public void testSingleOuterEmptyInner() {
List<Object> result = IteratorUtils.toList(
NestedIterator.start(List.of("Test").iterator(), s -> Collections.emptyIterator()));
FlattenedIterator.start(List.of("Test").iterator(), s -> Collections.emptyIterator()));
assertTrue(result.isEmpty());
}
@Test
public void testDoubleOuterEmptyInner() {
List<Object> result = IteratorUtils.toList(
NestedIterator.start(List.of("T1", "T2").iterator(), s -> Collections.emptyIterator()));
FlattenedIterator.start(List.of("T1", "T2").iterator(), s -> Collections.emptyIterator()));
assertTrue(result.isEmpty());
}
@Test
public void testSingleOuterSingleInner() {
List<String> result = IteratorUtils
.toList(NestedIterator.start(List.of(0).iterator(), n -> List.of("Test").iterator()));
.toList(FlattenedIterator.start(List.of(0).iterator(), n -> List.of("Test").iterator()));
assertEquals(List.of("Test"), result);
}
@Test
public void testFirstEmptySecondSingleton() {
List<String> result = IteratorUtils.toList(NestedIterator.start(List.of(0, 1).iterator(),
List<String> result = IteratorUtils.toList(FlattenedIterator.start(List.of(0, 1).iterator(),
n -> n == 0 ? Collections.emptyIterator() : List.of("Test").iterator()));
assertEquals(List.of("Test"), result);
}
@ -64,20 +70,20 @@ public class NestedIteratorTest {
@Test
public void testSingleOuterDoubleInner() {
List<String> result = IteratorUtils.toList(
NestedIterator.start(List.of(0).iterator(), n -> List.of("T1", "T2").iterator()));
FlattenedIterator.start(List.of(0).iterator(), n -> List.of("T1", "T2").iterator()));
assertEquals(List.of("T1", "T2"), result);
}
@Test
public void testDoubleOuterDoubleInner() {
List<String> result = IteratorUtils.toList(NestedIterator.start(List.of(0, 1).iterator(),
List<String> result = IteratorUtils.toList(FlattenedIterator.start(List.of(0, 1).iterator(),
n -> (n == 0 ? List.of("T1", "T2") : List.of("T3", "T4")).iterator()));
assertEquals(List.of("T1", "T2", "T3", "T4"), result);
}
@Test
public void testMultipleHasNextCalls() {
Iterator<String> it = NestedIterator.start(List.of(0, 1).iterator(),
Iterator<String> it = FlattenedIterator.start(List.of(0, 1).iterator(),
n -> (n == 0 ? List.of("T1", "T2") : List.of("T3", "T4")).iterator());
assertTrue(it.hasNext());
assertTrue(it.hasNext());
@ -100,7 +106,7 @@ public class NestedIteratorTest {
@Test
public void testNoHasNextCalls() {
Iterator<String> it = NestedIterator.start(List.of(0, 1).iterator(),
Iterator<String> it = FlattenedIterator.start(List.of(0, 1).iterator(),
n -> (n == 0 ? List.of("T1", "T2") : List.of("T3", "T4")).iterator());
assertEquals("T1", it.next());
assertEquals("T2", it.next());
@ -116,7 +122,7 @@ public class NestedIteratorTest {
List<String> b = new ArrayList<>(List.of("T3", "T4"));
List<List<String>> listList = new ArrayList<>(List.of(a, b));
Iterator<String> it = NestedIterator.start(listList.iterator(), l -> l.iterator());
Iterator<String> it = FlattenedIterator.start(listList.iterator(), l -> l.iterator());
assertEquals("T1", it.next());
assertEquals("T2", it.next());
assertTrue(it.hasNext()); // Odd to do this right before a remove, but....

View file

@ -20,8 +20,23 @@ import java.util.List;
import ghidra.program.model.address.*;
/**
* An abstract implementation of {@link AddressSetView} that provides suitable defaults for as many
* of the required methods as reasonable.
*/
public abstract class AbstractAddressSetView implements AddressSetView {
/**
* Adjust the given start address so that if it lands in the given iterator's next range, that
* range would be entirely included.
*
* @param rev the iterator. Note only the first element, if present, of this iterator is
* considered. The client must ensure it's properly positioned.
* @param start the proposed start address
* @param forward true for forward iteration, i.e., the adjustment will be to the range's
* minimum address, if applicable.
* @return the adjusted start
*/
protected static Address fixStart(AddressRangeIterator rev, Address start, boolean forward) {
if (!rev.hasNext()) {
return start;

View file

@ -18,11 +18,14 @@ package ghidra.util;
import java.util.Iterator;
import java.util.NoSuchElementException;
import generic.NestedIterator;
import generic.util.FlattenedIterator;
import generic.util.PeekableIterator;
import ghidra.program.model.address.*;
public class AddressIteratorAdapter extends NestedIterator<AddressRange, Address>
/**
* Convert an {@link AddressRange} iterator to an {@link AddressIterator}.
*/
public class AddressIteratorAdapter extends FlattenedIterator<AddressRange, Address>
implements AddressIterator {
protected static class ForwardAddressIterator implements PeekableIterator<Address> {
@ -89,15 +92,39 @@ public class AddressIteratorAdapter extends NestedIterator<AddressRange, Address
}
}
/**
* Iterate over the addresses in the given range
*
* @param range the range
* @param forward true to iterate forward, false for backward
* @return the iterable
*/
public static Iterable<Address> forRange(AddressRange range, boolean forward) {
return () -> forward ? new ForwardAddressIterator(range)
: new BackwardAddressIterator(range);
}
/**
* Construct an {@link AddressIterator} over the given address ranges
*
* @param outer an iterator of address ranges
* @param forward true for forward iteration. Otherwise backward iteration. This flag must be
* consistent with the order of the given outer iterator.
*/
public AddressIteratorAdapter(Iterator<AddressRange> outer, boolean forward) {
super(outer, forward ? ForwardAddressIterator::new : BackwardAddressIterator::new);
}
/**
* Construct an {@link AddressIterator} over the given address ranges, truncating the initial
* range to the given start
*
* @param outer the iterator of address ranges, the first of which must contain or come after
* the given start.
* @param start the starting address
* @param forward true for forward iteration. Otherwise backward iteration. This flag must be
* consistent with the order of the given outer iterator.o
*/
public AddressIteratorAdapter(Iterator<AddressRange> outer, Address start, boolean forward) {
super(outer, forward ? ar -> {
if (!ar.contains(start)) {

View file

@ -18,14 +18,29 @@ package ghidra.util;
import java.util.Comparator;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressSet;
/**
* Comparators used for sorting address ranges
*/
public enum AddressRangeComparators implements Comparator<AddressRange> {
/**
* Compare ranges by their minimum address and order them smallest first.
*/
FORWARD {
@Override
public int compare(AddressRange a, AddressRange b) {
return a.getMinAddress().compareTo(b.getMinAddress());
}
},
/**
* Compare ranges by their maximum address and order them largest first.
*
* @implNote Which address is compared might not ordinarily matter, since {@link AddressSet}
* requires a disjoint union of ranges. However, these comparators often compare
* ranges from different sets, e.g., in order to merge two or more iterators. Thus, in
* reverse, we want to ensure ranges are ordered by their <em>maximum</em> address.
*/
BACKWARD {
@Override
public int compare(AddressRange a, AddressRange b) {

View file

@ -24,6 +24,10 @@ import org.apache.commons.collections4.IteratorUtils;
import ghidra.program.model.address.*;
import ghidra.util.TwoWayBreakdownAddressRangeIterator.Which;
/**
* Utilities for manipulating iterators over {@link AddressRange}s. Notably, this allows the
* creation of lazily computed set operations on {@link AddressSetView}s.
*/
public enum AddressRangeIterators {
;
@ -50,13 +54,30 @@ public enum AddressRangeIterators {
}
}
/**
* Utility for satisfying the type checker. This just forwards the method calls so that an
* {@link Iterator} over {@link AddressRange} can be used where an {@link AddressRangeIterator}
* is required. If only Java had type aliasing....
*
* @param it the iterator
* @return the wrapper, or the same iterator if it is already an {@link AddressRangeIterator}
*/
public static AddressRangeIterator castOrWrap(Iterator<AddressRange> it) {
if (it instanceof AddressRangeIterator) {
return (AddressRangeIterator) it;
if (it instanceof AddressRangeIterator ari) {
return ari;
}
return new WrappingAddressRangeIterator(it);
}
/**
* Create an iterator over the union of address ranges in the given iterators
*
* @see UnionAddressSetView
* @param iterators the iterators to union
* @param forward true for forward iteration. The given iterators must all return ranges in the
* order indicated by this flag.
* @return the iterator over the union
*/
public static AddressRangeIterator union(Collection<Iterator<AddressRange>> iterators,
boolean forward) {
return new UnionAddressRangeIterator(iterators, forward);
@ -70,6 +91,17 @@ public enum AddressRangeIterators {
: range.getMinAddress().compareTo(start) <= 0;
}
/**
* Create an iterator over the difference between two address range iterators
*
* @see DifferenceAddressSetView
* @param a the minuend
* @param b the subtrahend
* @param start the starting address, or null
* @param forward true for forward iteration. The given iterators must all return ranges in the
* order indicated by this flag.
* @return the iterator over the difference
*/
public static AddressRangeIterator subtract(Iterator<AddressRange> a, Iterator<AddressRange> b,
Address start, boolean forward) {
return new WrappingAddressRangeIterator(IteratorUtils.transformedIterator(
@ -78,6 +110,17 @@ public enum AddressRangeIterators {
e -> e.getKey()));
}
/**
* Create an iterator over the symmetric difference between two address range iterators
*
* @see SymmetricDifferenceAddressSetView
* @param a the first iterator
* @param b the second iterator
* @param start the starting address, or null
* @param forward true for forward iteration. The given iterators must all return ranges in the
* order indicated by this flag.
* @return the iterator over the symmetric difference
*/
public static AddressRangeIterator xor(Iterator<AddressRange> a, Iterator<AddressRange> b,
Address start, boolean forward) {
Iterator<Entry<AddressRange, Which>> eit =
@ -93,6 +136,16 @@ public enum AddressRangeIterators {
return new WrappingAddressRangeIterator(result);
}
/**
* Create an iterator over the intersection between two address range iterators
*
* @see IntersectionAddressSetView
* @param a the first iterator
* @param b the second iterator
* @param forward true for forward iteration. The given iterators must all return ranges in the
* order indicated by this flag.
* @return the iterator over the symmetric difference
*/
public static AddressRangeIterator intersect(Iterator<AddressRange> a, Iterator<AddressRange> b,
boolean forward) {
return new WrappingAddressRangeIterator(IteratorUtils.transformedIterator(

View file

@ -17,12 +17,33 @@ package ghidra.util;
import java.util.Iterator;
import ghidra.program.model.address.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressRangeImpl;
import ghidra.program.model.address.AddressRangeIterator;
import ghidra.program.model.address.AddressSetView;
/**
* A lazily computed {@link AddressSetView} defined as the difference between two given
* {@link AddressSetView}s.
* <p>
* This is equivalent to using {@link AddressSetView#subtract(AddressSetView)}, but does not
* materialize the difference. The choice of one over the other depends on the number of ranges in
* the inputs and the frequency of use of the result. With few ranges, or in cases where you need to
* access the entire result, anyway, just use the normal {@link AddressRange}. In cases with many,
* many ranges and where only a small part of the result needs to be computed, use this view. It may
* also be advantageous to use this view if the inputs are themselves computed lazily.
*/
public class DifferenceAddressSetView extends AbstractAddressSetView {
private final AddressSetView a;
private final AddressSetView b;
/**
* Construct the difference between two address sets
*
* @param a the minuend
* @param b the subtrahend
*/
public DifferenceAddressSetView(AddressSetView a, AddressSetView b) {
this.a = a;
this.b = b;

View file

@ -18,12 +18,33 @@ package ghidra.util;
import static ghidra.util.MathUtilities.cmax;
import static ghidra.util.MathUtilities.cmin;
import ghidra.program.model.address.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressRangeIterator;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.address.EmptyAddressRangeIterator;
/**
* A lazily computed {@link AddressSetView} defined as the intersection of two given
* {@link AddressSetView}s.
* <p>
* This is equivalent to using {@link AddressSetView#intersect(AddressSetView)}, but does not
* materialize the intersection. The choice of one over the other depends on the number of ranges in
* the inputs and the frequency of use of the result. With few ranges, or in cases where you need to
* access the entire result, anyway, just use the normal {@link AddressRange}. In cases with many,
* many ranges and where only a small part of the result needs to be computed, use this view. It may
* also be advantageous to use this view if the inputs are themselves computed lazily.
*/
public class IntersectionAddressSetView extends AbstractAddressSetView {
private final AddressSetView a;
private final AddressSetView b;
/**
* Construct the intersection of two address sets
*
* @param a the first set
* @param b the second set
*/
public IntersectionAddressSetView(AddressSetView a, AddressSetView b) {
this.a = a;
this.b = b;

View file

@ -15,12 +15,34 @@
*/
package ghidra.util;
import ghidra.program.model.address.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressRangeIterator;
import ghidra.program.model.address.AddressSetView;
/**
* A lazily computed {@link AddressSetView} defined as the symmetric difference between two given
* {@link AddressSetView}s.
* <p>
* There is no equivalent method in {@link AddressSetView}, but it could be computed using a
* combination of {@link AddressSetView#subtract(AddressSetView)} and
* {@link AddressSetView#union(AddressSetView)}. However, this class does not materialize the
* result. The choice of one over the other depends on the number of ranges in the inputs and the
* frequency of use of the result. With few ranges, or in cases where you need to access the entire
* result, anyway, just use the normal {@link AddressRange}. In cases with many, many ranges and
* where only a small part of the result needs to be computed, use this view. It may also be
* advantageous to use this if the inputs are themselves computed lazily.
*/
public class SymmetricDifferenceAddressSetView extends AbstractAddressSetView {
private final AddressSetView a;
private final AddressSetView b;
/**
* Construct the symmetric difference between two address sets
*
* @param a the first set
* @param b the second set
*/
public SymmetricDifferenceAddressSetView(AddressSetView a, AddressSetView b) {
this.a = a;
this.b = b;

View file

@ -21,37 +21,91 @@ import static ghidra.util.MathUtilities.cmin;
import java.util.Iterator;
import java.util.Map.Entry;
import generic.util.PeekableIterator;
import generic.util.*;
import ghidra.program.model.address.*;
import ghidra.util.TwoWayBreakdownAddressRangeIterator.Which;
/**
* An iterator that takes two iterators over address ranges and "breaks down" where they do and do
* not overlap. Consider one iterator L that contains only [1,3], and another R that contains only
* [2,4]. The two could be plotted:
*
* <pre>
* 1 2 3 4
* [---L---]
* [---R---]
* </pre>
*
* <p>
* This will return an iterator over range-which pairs. "Which" indicates which iterators include
* the given range, {@link Which#LEFT}, {@link Which#RIGHT}, or {@link Which#BOTH}. There is no
* {@code NONE}, so gaps are omitted. For the example above:
*
* <pre>
* 1 2 3 4
* [L][-B--][R]
* </pre>
*
* <p>
* This supports the computation of difference, symmetric difference, and intersection. <b>NOTE:</b>
* Clients cannot save the entries returned by the iterator. The entry is only valid during
* iteration, and it is reused by the iterator for each subsequent entry.
*/
public class TwoWayBreakdownAddressRangeIterator
extends AbstractPeekableIterator<Entry<AddressRange, Which>> {
/**
* Indicates which of the input iterators contain a range
*/
public enum Which {
LEFT(true, false), RIGHT(false, true), BOTH(true, true);
/** Only the left included the range */
LEFT(true, false),
/** Only the right included the range */
RIGHT(false, true),
/** Both included the range */
BOTH(true, true);
private Which(boolean includesLeft, boolean includesRight) {
this.includesLeft = includesLeft;
this.includesRight = includesRight;
}
/** Indicates the the left iterator includes this range */
public final boolean includesLeft;
/** Indicates that the right iterator includes this range */
public final boolean includesRight;
/**
* Check if this range is included in the difference: {@code left - right}
*
* @return true if included
*/
public boolean inSubtract() {
return this == LEFT;
}
/**
* Check if this range is included in the symmetric difference: {@code left Δ right}
*
* @return true if included
*/
public boolean inXor() {
return this == LEFT || this == RIGHT;
}
/**
* Check if this range is included in the intersection: {@code left right}
*
* @return true if included
*/
public boolean inIntersect() {
return this == BOTH;
}
}
/**
* A mutable map entry
*/
public static class MyEntry implements Entry<AddressRange, Which> {
private AddressRange key;
private Which val;
@ -81,6 +135,14 @@ public class TwoWayBreakdownAddressRangeIterator
private final MyEntry entry = new MyEntry();
/**
* Create an iterator that "breaks down" the two address range iterators.
*
* @param lit the iterator of ranges on the left
* @param rit the iterator of ranges on the right
* @param forward true for forward iteration, false for reverse. The input iterators must be
* ordered according to this flag.
*/
public TwoWayBreakdownAddressRangeIterator(Iterator<AddressRange> lit,
Iterator<AddressRange> rit, boolean forward) {
this.lit = PeekableIterators.castOrWrap(lit);

View file

@ -21,17 +21,26 @@ import static ghidra.util.MathUtilities.cmin;
import java.util.Collection;
import java.util.Iterator;
import generic.util.AbstractPeekableIterator;
import generic.util.MergeSortingIterator;
import generic.util.PeekableIterator;
import ghidra.program.model.address.*;
import generic.util.PeekableIterators;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressRangeImpl;
import ghidra.program.model.address.AddressRangeIterator;
/**
* The iterator implementation backing several methods in {@link UnionAddressSetView}
*/
public class UnionAddressRangeIterator extends AbstractPeekableIterator<AddressRange>
implements AddressRangeIterator {
private final PeekableIterator<AddressRange> mit;
private final PeekableIterator<AddressRange> it;
private final boolean forward;
/**
* Coalesce (by union) ranges from a single iterator
*
* <p>
* The ranges must be returned in order: in the forward direction, by increasing min address; in
* the reverse direction, by decreasing max address.
*
@ -39,13 +48,13 @@ public class UnionAddressRangeIterator extends AbstractPeekableIterator<AddressR
* @param forward true to coalesce in the forward direction, false for reverse
*/
public UnionAddressRangeIterator(Iterator<AddressRange> it, boolean forward) {
this.mit = PeekableIterators.castOrWrap(it);
this.it = PeekableIterators.castOrWrap(it);
this.forward = forward;
}
/**
* Union into a single range iterator, several range iterators
*
* <p>
* The ranges will be coalesced so that each returned range is disconnected from any other. The
* ranges of each iterator must be returned in order by direction. While not recommended, the
* ranges of each iterator may overlap, so long as they are sorted as in
@ -56,7 +65,7 @@ public class UnionAddressRangeIterator extends AbstractPeekableIterator<AddressR
*/
public UnionAddressRangeIterator(Collection<Iterator<AddressRange>> iterators,
boolean forward) {
this.mit = new MergeSortingIterator<AddressRange>(iterators,
this.it = new MergeSortingIterator<AddressRange>(iterators,
forward ? AddressRangeComparators.FORWARD : AddressRangeComparators.BACKWARD);
this.forward = forward;
}
@ -68,18 +77,18 @@ public class UnionAddressRangeIterator extends AbstractPeekableIterator<AddressR
@Override
protected AddressRange seekNext() {
if (!mit.hasNext()) {
if (!it.hasNext()) {
return null;
}
AddressRange peek = mit.peek();
AddressRange peek = it.peek();
Address min = peek.getMinAddress();
Address max = peek.getMaxAddress();
while (true) {
mit.next();
if (!mit.hasNext()) {
it.next();
if (!it.hasNext()) {
break;
}
peek = mit.peek();
peek = it.peek();
if (peek.getAddressSpace() != min.getAddressSpace()) {
break;
}

View file

@ -21,15 +21,43 @@ import static ghidra.util.MathUtilities.cmin;
import java.util.Arrays;
import java.util.Collection;
import ghidra.program.model.address.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressRangeIterator;
import ghidra.program.model.address.AddressSetView;
/**
* A lazily computed {@link AddressSetView} defined as the union of many given
* {@link AddressSetView}s.
* <p>
* This is equivalent to using {@link AddressSetView#union(AddressSetView)}, but does not
* materialize the difference. The choice of one over the other depends on the number of ranges in
* the inputs and the frequency of use of the result. With few ranges, or in cases where you need to
* access the entire result, anyway, just use the normal {@link AddressRange}. In cases with many,
* many ranges and where only a small part of the result needs to be computed, use this view. It may
* also be advantageous to use this if the inputs are themselves computed lazily.
* <p>
* This follows the conventions expected of an {@link AddressSetView} in that the returned ranges
* are disjoint. Thus, it will combine intersecting and abutting ranges from among the inputs. For
* example, the union of [[1,2]] and [[3,4]] is [[1,4]].
*/
public class UnionAddressSetView extends AbstractAddressSetView {
private final Collection<AddressSetView> views;
/**
* Construct the union of the given address set views
*
* @param views the input sets
*/
public UnionAddressSetView(AddressSetView... views) {
this(Arrays.asList(views));
}
/**
* Construct the union of the given address set views
*
* @param views the input sets
*/
public UnionAddressSetView(Collection<AddressSetView> views) {
this.views = views;
}

View file

@ -19,19 +19,16 @@ import static org.junit.Assert.*;
import java.util.*;
import org.junit.Before;
import org.junit.Test;
import generic.test.AbstractGTest;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
public class DifferenceAddressSetViewTest extends AbstractGhidraHeadlessIntegrationTest {
protected Language toy;
public class DifferenceAddressSetViewTest extends AbstractGTest {
protected AddressSpace space = new GenericAddressSpace("ram", 64, AddressSpace.TYPE_RAM, 1);
protected Address addr(long offset) {
return toy.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
return space.getAddress(offset);
}
protected AddressRange rng(long min, long max) {
@ -54,12 +51,6 @@ public class DifferenceAddressSetViewTest extends AbstractGhidraHeadlessIntegrat
return result;
}
@Before
public void setUpIteratorTest() throws LanguageNotFoundException {
toy = DefaultLanguageService.getLanguageService().getLanguage(
new LanguageID("Toy:BE:64:default"));
}
@Test
public void testCounts() {
AddressSetView difference;

View file

@ -19,19 +19,16 @@ import static org.junit.Assert.*;
import java.util.*;
import org.junit.Before;
import org.junit.Test;
import generic.test.AbstractGTest;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
public class IntersectionAddressSetViewTest extends AbstractGhidraHeadlessIntegrationTest {
protected Language toy;
public class IntersectionAddressSetViewTest extends AbstractGTest {
protected AddressSpace space = new GenericAddressSpace("ram", 64, AddressSpace.TYPE_RAM, 1);
protected Address addr(long offset) {
return toy.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
return space.getAddress(offset);
}
protected AddressRange rng(long min, long max) {
@ -54,12 +51,6 @@ public class IntersectionAddressSetViewTest extends AbstractGhidraHeadlessIntegr
return result;
}
@Before
public void setUpIteratorTest() throws LanguageNotFoundException {
toy = DefaultLanguageService.getLanguageService().getLanguage(
new LanguageID("Toy:BE:64:default"));
}
@Test
public void testCounts() {
AddressSetView intersection;

View file

@ -22,6 +22,8 @@ import java.util.Map.Entry;
import org.junit.Test;
import generic.util.MergeSortingIterator;
public class MergeSortingIteratorTest {
@Test
public void testEmptyMap() {

View file

@ -19,19 +19,16 @@ import static org.junit.Assert.*;
import java.util.*;
import org.junit.Before;
import org.junit.Test;
import generic.test.AbstractGTest;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
public class SymmetricDifferenceAddressSetViewTest extends AbstractGhidraHeadlessIntegrationTest {
protected Language toy;
public class SymmetricDifferenceAddressSetViewTest extends AbstractGTest {
protected AddressSpace space = new GenericAddressSpace("ram", 64, AddressSpace.TYPE_RAM, 1);
protected Address addr(long offset) {
return toy.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
return space.getAddress(offset);
}
protected AddressRange rng(long min, long max) {
@ -54,12 +51,6 @@ public class SymmetricDifferenceAddressSetViewTest extends AbstractGhidraHeadles
return result;
}
@Before
public void setUpIteratorTest() throws LanguageNotFoundException {
toy = DefaultLanguageService.getLanguageService().getLanguage(
new LanguageID("Toy:BE:64:default"));
}
@Test
public void testCounts() {
AddressSetView xor;

View file

@ -22,17 +22,15 @@ import java.util.Map.Entry;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.Before;
import org.junit.Test;
import generic.test.AbstractGTest;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.util.TwoWayBreakdownAddressRangeIterator.Which;
public class TwoWayBreakdownAddressRangeIteratorTest extends AbstractGhidraHeadlessIntegrationTest {
protected Language toy;
public class TwoWayBreakdownAddressRangeIteratorTest extends AbstractGTest {
protected AddressSpace rom = new GenericAddressSpace("rom", 64, AddressSpace.TYPE_RAM, 1);
protected AddressSpace ram = new GenericAddressSpace("ram", 64, AddressSpace.TYPE_RAM, 2);
protected TwoWayBreakdownAddressRangeIterator makeIterator(AddressSet a, AddressSet b,
boolean forward) {
@ -41,11 +39,11 @@ public class TwoWayBreakdownAddressRangeIteratorTest extends AbstractGhidraHeadl
}
protected Address addr(long offset) {
return toy.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
return rom.getAddress(offset);
}
protected Address dAddr(long offset) {
return toy.getAddressFactory().getAddressSpace("data").getAddress(offset);
return ram.getAddress(offset);
}
protected AddressRange rng(long min, long max) {
@ -87,12 +85,6 @@ public class TwoWayBreakdownAddressRangeIteratorTest extends AbstractGhidraHeadl
return result;
}
@Before
public void setUpIteratorTest() throws LanguageNotFoundException {
toy = DefaultLanguageService.getLanguageService()
.getLanguage(new LanguageID("Toy:BE:64:harvard"));
}
@Test
public void testBothEmpty() {
AddressSet a = new AddressSet();

View file

@ -19,23 +19,21 @@ import static org.junit.Assert.*;
import java.util.*;
import org.junit.Before;
import org.junit.Test;
import generic.test.AbstractGTest;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.util.DefaultLanguageService;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
public class UnionAddressSetViewTest extends AbstractGhidraHeadlessIntegrationTest {
protected Language toy;
public class UnionAddressSetViewTest extends AbstractGTest {
protected AddressSpace rom = new GenericAddressSpace("rom", 64, AddressSpace.TYPE_RAM, 1);
protected AddressSpace ram = new GenericAddressSpace("ram", 64, AddressSpace.TYPE_RAM, 2);
protected Address addr(long offset) {
return toy.getDefaultSpace().getAddress(offset);
return rom.getAddress(offset);
}
protected Address daddr(long offset) {
return toy.getDefaultDataSpace().getAddress(offset);
protected Address dAddr(long offset) {
return ram.getAddress(offset);
}
protected AddressRange rng(long min, long max) {
@ -43,7 +41,7 @@ public class UnionAddressSetViewTest extends AbstractGhidraHeadlessIntegrationTe
}
protected AddressRange drng(long min, long max) {
return new AddressRangeImpl(daddr(min), daddr(max));
return new AddressRangeImpl(dAddr(min), dAddr(max));
}
protected AddressSet set(AddressRange... ranges) {
@ -62,12 +60,6 @@ public class UnionAddressSetViewTest extends AbstractGhidraHeadlessIntegrationTe
return result;
}
@Before
public void setUpIteratorTest() throws LanguageNotFoundException {
toy = DefaultLanguageService.getLanguageService()
.getLanguage(new LanguageID("Toy:BE:64:harvard"));
}
@Test
public void testCounts() {
AddressSetView union;