GP-6630: Index TraceMemoryState by full lifespan of 'most-recent' effect.

This commit is contained in:
Dan
2026-04-20 16:49:55 +00:00
parent 7ff28d30bc
commit 0e19d2c6f5
40 changed files with 1011 additions and 516 deletions

View File

@@ -56,6 +56,7 @@ import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.memory.TraceMemoryOperations.StatePredicate;
import ghidra.trace.model.target.*;
import ghidra.trace.model.target.TraceObject.ConflictResolution;
import ghidra.trace.model.target.path.*;
@@ -926,11 +927,10 @@ public class TraceRmiHandler extends AbstractTraceRmiConnection {
TraceMemoryManager memoryManager = open.trace.getMemoryManager();
AddressSetView readOnly =
memoryManager.getRegionsAddressSetWith(snap, r -> !r.isWrite(snap));
AddressSetView everKnown = memoryManager.getAddressesWithState(Lifespan.since(snap),
s -> s == TraceMemoryState.KNOWN);
AddressSetView everKnown =
memoryManager.getAddressesWithState(Lifespan.since(snap), StatePredicate.IS_KNOWN);
AddressSetView roEverKnown = new IntersectionAddressSetView(readOnly, everKnown);
AddressSetView known =
memoryManager.getAddressesWithState(snap, s -> s == TraceMemoryState.KNOWN);
AddressSetView known = memoryManager.getAddressesWithState(snap, StatePredicate.IS_KNOWN);
AddressSetView disassemblable = new AddressSet(new UnionAddressSetView(known, roEverKnown));
Address start = open.toAddress(req.getStart(), true);

View File

@@ -39,6 +39,7 @@ import ghidra.trace.model.*;
import ghidra.trace.model.guest.TraceGuestPlatform;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.memory.TraceMemoryOperations.StatePredicate;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.util.IntersectionAddressSetView;
import ghidra.util.UnionAddressSetView;
@@ -159,11 +160,10 @@ public class DebuggerDisassemblerPlugin extends Plugin implements PopupActionPro
TraceMemoryManager memoryManager = trace.getMemoryManager();
AddressSetView readOnly =
memoryManager.getRegionsAddressSetWith(ks, r -> !r.isWrite(ks));
AddressSetView everKnown = memoryManager.getAddressesWithState(Lifespan.since(ks),
s -> s == TraceMemoryState.KNOWN);
AddressSetView everKnown =
memoryManager.getAddressesWithState(Lifespan.since(ks), StatePredicate.IS_KNOWN);
AddressSetView roEverKnown = new IntersectionAddressSetView(readOnly, everKnown);
AddressSetView known =
memoryManager.getAddressesWithState(ks, s -> s == TraceMemoryState.KNOWN);
AddressSetView known = memoryManager.getAddressesWithState(ks, StatePredicate.IS_KNOWN);
AddressSetView disassemblable = new UnionAddressSetView(known, roEverKnown);
return disassemblable;
}

View File

@@ -36,6 +36,7 @@ import ghidra.program.model.address.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.trace.model.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.memory.TraceMemoryOperations.StatePredicate;
import ghidra.util.MathUtilities;
import ghidra.util.task.TaskMonitor;
@@ -65,7 +66,7 @@ public enum BasicAutoReadMemorySpec implements AutoReadMemorySpec {
Target target = coordinates.getTarget();
TraceMemoryManager mm = coordinates.getTrace().getMemoryManager();
AddressSetView alreadyKnown = mm.getAddressesWithState(coordinates.getSnap(), visible,
s -> s == TraceMemoryState.KNOWN || s == TraceMemoryState.ERROR);
StatePredicate.IS_KNOWN_OR_ERROR);
AddressSet toRead = visible.subtract(alreadyKnown);
if (toRead.isEmpty()) {
@@ -91,8 +92,8 @@ public enum BasicAutoReadMemorySpec implements AutoReadMemorySpec {
Target target = coordinates.getTarget();
TraceMemoryManager mm = coordinates.getTrace().getMemoryManager();
long snap = coordinates.getSnap();
AddressSetView alreadyKnown = mm.getAddressesWithState(snap, visible,
s -> s == TraceMemoryState.KNOWN || s == TraceMemoryState.ERROR);
AddressSetView alreadyKnown =
mm.getAddressesWithState(snap, visible, StatePredicate.IS_KNOWN_OR_ERROR);
AddressSet toRead = visible.subtract(alreadyKnown);
if (toRead.isEmpty()) {
@@ -171,8 +172,7 @@ public enum BasicAutoReadMemorySpec implements AutoReadMemorySpec {
AddressSet toRead = new AddressSet(quantize(12, visible));
for (Lifespan span : coordinates.getView().getViewport().getOrderedSpans()) {
AddressSetView alreadyKnown =
mm.getAddressesWithState(span.lmin(), visible,
s -> s == TraceMemoryState.KNOWN);
mm.getAddressesWithState(span.lmin(), visible, StatePredicate.IS_KNOWN);
toRead.delete(alreadyKnown);
if (span.lmax() != span.lmin() || toRead.isEmpty()) {
break;

View File

@@ -31,6 +31,7 @@ import ghidra.trace.model.Lifespan;
import ghidra.trace.model.breakpoint.TraceBreakpointLocation;
import ghidra.trace.model.memory.TraceMemoryManager;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.memory.TraceMemoryOperations.StatePredicate;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
@@ -91,9 +92,9 @@ public class DebuggerCopyPlan {
AddressSet rngAsSet = new AddressSet(fromRange);
TraceMemoryManager mm = from.getTrace().getMemoryManager();
AddressSetView knownSet = mm.getAddressesWithState(from.getSnap(), rngAsSet,
s -> s == TraceMemoryState.KNOWN);
StatePredicate.IS_KNOWN);
AddressSetView errorSet = mm.getAddressesWithState(from.getSnap(), rngAsSet,
s -> s == TraceMemoryState.ERROR);
StatePredicate.IS_ERROR);
AddressSetView staleSet = rngAsSet.subtract(knownSet).subtract(errorSet);
setShifted(map, fromRange.getMinAddress(), intoAddress, errorSet,
DebuggerResources.COLOR_BACKGROUND_ERROR.getRGB());

View File

@@ -55,6 +55,7 @@ import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryManager;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.memory.TraceMemoryOperations.StatePredicate;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.Msg;
@@ -539,8 +540,8 @@ public class DebuggerTraceViewDiffPlugin extends AbstractDebuggerPlugin {
TraceMemoryManager mm = trace.getMemoryManager();
AddressSetView known1 = mm.getAddressesWithState(snap1, s -> s == TraceMemoryState.KNOWN);
AddressSetView known2 = mm.getAddressesWithState(snap2, s -> s == TraceMemoryState.KNOWN);
AddressSetView known1 = mm.getAddressesWithState(snap1, StatePredicate.IS_KNOWN);
AddressSetView known2 = mm.getAddressesWithState(snap2, StatePredicate.IS_KNOWN);
//AddressSet knownEither = known1.union(known2);
AddressSet knownBoth = known1.intersect(known2); // Will need byte-by-byte examination

View File

@@ -69,6 +69,7 @@ import ghidra.trace.model.*;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.memory.TraceMemoryOperations.StatePredicate;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceEvents;
@@ -895,7 +896,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
}
BigInteger getRegisterValue(Register register) {
TraceMemorySpace regs = getRegisterMemorySpace(register.getAddressSpace(), false);
Address hostReg = current.getPlatform().mapGuestToHost(register.getAddress());
TraceMemorySpace regs = getRegisterMemorySpace(hostReg.getAddressSpace(), false);
if (regs == null) {
return BigInteger.ZERO;
}
@@ -971,7 +973,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
}
TraceData getRegisterData(Register register) {
TraceCodeSpace space = getRegisterCodeSpace(register.getAddressSpace(), false);
Address hostReg = current.getPlatform().mapGuestToHost(register.getAddress());
TraceCodeSpace space = getRegisterCodeSpace(hostReg.getAddressSpace(), false);
if (space == null) {
return null;
}
@@ -1058,8 +1061,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
AddressSetView guestRegs = platform.getLanguage().getRegisterAddresses();
AddressSetView hostRegs = platform.mapGuestToHost(guestRegs);
AddressSetView viewKnownMem = view.getViewport()
.unionedAddresses(snap -> mem.getAddressesWithState(snap, hostRegs,
state -> state == TraceMemoryState.KNOWN));
.unionedAddresses(
snap -> mem.getAddressesWithState(snap, hostRegs, StatePredicate.IS_KNOWN));
AddressSpace regSpace = platform.getAddressFactory().getRegisterSpace();
if (regSpace == null) {
viewKnown = new AddressSet(viewKnownMem);
@@ -1073,8 +1076,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
AddressSetView overlayRegs =
TraceRegisterUtils.getOverlaySet(regs.getAddressSpace(), hostRegs);
AddressSetView viewKnownRegs = view.getViewport()
.unionedAddresses(snap -> regs.getAddressesWithState(snap, overlayRegs,
state -> state == TraceMemoryState.KNOWN));
.unionedAddresses(
snap -> regs.getAddressesWithState(snap, overlayRegs, StatePredicate.IS_KNOWN));
viewKnown = viewKnownRegs.union(viewKnownMem);
}
@@ -1082,8 +1085,9 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
if (viewKnown == null) {
return false;
}
TraceMemorySpace regs = getRegisterMemorySpace(current, register.getAddressSpace(), false);
if (regs == null && register.getAddressSpace().isRegisterSpace()) {
Address hostReg = current.getPlatform().mapGuestToHost(register.getAddress());
TraceMemorySpace regs = getRegisterMemorySpace(current, hostReg.getAddressSpace(), false);
if (regs == null && hostReg.getAddressSpace().isRegisterSpace()) {
return false;
}
AddressRange range = current.getPlatform()
@@ -1102,10 +1106,11 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
if (!isRegisterKnown(register)) {
return false;
}
Address hostReg = current.getPlatform().mapGuestToHost(register.getAddress());
TraceMemorySpace curSpace =
getRegisterMemorySpace(current, register.getAddressSpace(), false);
getRegisterMemorySpace(current, hostReg.getAddressSpace(), false);
TraceMemorySpace prevSpace =
getRegisterMemorySpace(previous, register.getAddressSpace(), false);
getRegisterMemorySpace(previous, hostReg.getAddressSpace(), false);
if (prevSpace == null) {
return false;
}

View File

@@ -385,10 +385,13 @@ public enum TraceEmulationIntegration {
knownButUninit.getMinAddress(),
knownButUninit.getMaxAddress());
ByteBuffer buf = ByteBuffer.allocate((int) knownBound.getLength());
acc.getBytes(knownBound.getMinAddress(), buf);
Address knownMin = knownBound.getMinAddress();
acc.getBytes(knownMin, buf);
for (AddressRange range : knownButUninit) {
piece.setVarInternal(range.getAddressSpace(), range.getMinAddress().getOffset(),
(int) range.getLength(), buf.array());
byte[] sub = new byte[(int) range.getLength()];
Address rngMin = range.getMinAddress();
buf.get((int) rngMin.subtract(knownMin), sub);
piece.setVarInternal(range.getAddressSpace(), rngMin.getOffset(), sub.length, sub);
remains.delete(range);
}
return remains;

View File

@@ -23,6 +23,7 @@ import ghidra.trace.model.Lifespan;
import ghidra.trace.model.TraceTimeViewport;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.memory.TraceMemoryOperations.StatePredicate;
import ghidra.trace.util.TraceRegisterUtils;
/**
@@ -130,8 +131,7 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace
AddressSet hostSet = new AddressSet(toOverlay(hostRange));
for (long snap : viewport.getOrderedSnaps()) {
hostSet.delete(
ops.getAddressesWithState(snap, hostSet, s -> s == TraceMemoryState.KNOWN));
hostSet.delete(ops.getAddressesWithState(snap, hostSet, StatePredicate.IS_KNOWN));
}
return hostSet.isEmpty() ? TraceMemoryState.KNOWN : TraceMemoryState.UNKNOWN;
}
@@ -147,14 +147,14 @@ public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTrace
AddressSet hostKnown = new AddressSet();
if (useFullSpans) {
for (Lifespan span : viewport.getOrderedSpans()) {
hostKnown.add(ops.getAddressesWithState(span, hostView,
st -> st != null && st != TraceMemoryState.UNKNOWN));
hostKnown.add(
ops.getAddressesWithState(span, hostView, StatePredicate.IS_KNOWN_OR_ERROR));
}
}
else {
for (long snap : viewport.getOrderedSnaps()) {
hostKnown.add(ops.getAddressesWithState(snap, hostView,
st -> st != null && st != TraceMemoryState.UNKNOWN));
hostKnown.add(
ops.getAddressesWithState(snap, hostView, StatePredicate.IS_KNOWN_OR_ERROR));
}
}
AddressSetView hostResult =

View File

@@ -74,6 +74,9 @@ import ghidra.util.task.TaskMonitor;
// Applies to creation, and to setting end snap
// Also to deleting a thread altogether.
public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, TraceChangeManager {
public final static int CHUNK_SIZE = 4096;
protected static final String TRACE_INFO = "Trace Information";
protected static final String NAME = "Name";
protected static final String DATE_CREATED = "Date Created";

View File

@@ -84,8 +84,10 @@ public class DBTraceRegisterContextSpace implements TraceRegisterContextSpace, D
protected final AddressRange all;
protected final DBCachedObjectStore<DBTraceRegisterEntry> registerStore;
protected final Map<Pair<Language, Register>, DBTraceAddressSnapRangePropertyMapSpace<byte[], DBTraceRegisterContextEntry>> registerValueMaps =
new HashMap<>();
protected final Map<Pair<Language, Register>,
DBTraceAddressSnapRangePropertyMapSpace<byte[],
DBTraceRegisterContextEntry>> registerValueMaps =
new HashMap<>();
public DBTraceRegisterContextSpace(DBTraceRegisterContextManager manager, DBHandle dbh,
AddressSpace space, DBTraceSpaceEntry ent) throws VersionException, IOException {
@@ -133,8 +135,9 @@ public class DBTraceRegisterContextSpace implements TraceRegisterContextSpace, D
language.getLanguageID().getIdAsString() + "_" + register.getName();
}
protected DBTraceAddressSnapRangePropertyMapSpace<byte[], DBTraceRegisterContextEntry> createRegisterValueMap(
Pair<Language, Register> lr) throws VersionException {
protected DBTraceAddressSnapRangePropertyMapSpace<byte[], DBTraceRegisterContextEntry>
createRegisterValueMap(
Pair<Language, Register> lr) throws VersionException {
String name = tableName(lr.getLeft(), lr.getRight());
try {
return new DBTraceAddressSnapRangePropertyMapSpace<>(name, trace,
@@ -147,8 +150,9 @@ public class DBTraceRegisterContextSpace implements TraceRegisterContextSpace, D
}
}
protected DBTraceAddressSnapRangePropertyMapSpace<byte[], DBTraceRegisterContextEntry> getRegisterValueMap(
Language language, Register register, boolean createIfAbsent) {
protected DBTraceAddressSnapRangePropertyMapSpace<byte[], DBTraceRegisterContextEntry>
getRegisterValueMap(
Language language, Register register, boolean createIfAbsent) {
ImmutablePair<Language, Register> pair = new ImmutablePair<>(language, register);
DBTraceGuestLanguage guest = manager.languageManager.getLanguageByLanguage(language);
if (createIfAbsent) {
@@ -209,11 +213,15 @@ public class DBTraceRegisterContextSpace implements TraceRegisterContextSpace, D
protected void doRemove(DBTraceAddressSnapRangePropertyMapSpace<byte[], ?> valueMap,
TraceAddressSnapRange range) {
Map<TraceAddressSnapRange, byte[]> toPutBack = new HashMap<>();
List<Entry<TraceAddressSnapRange, byte[]>> toRemove = new ArrayList<>();
for (Entry<TraceAddressSnapRange, byte[]> entry : valueMap.reduce(
TraceAddressSnapRangeQuery.intersecting(range)).entries()) {
for (TraceAddressSnapRange diff : doSubtract(entry.getKey(), range)) {
toPutBack.put(diff, entry.getValue());
}
toRemove.add(entry);
}
for (Entry<TraceAddressSnapRange, byte[]> entry : toRemove) {
valueMap.remove(entry);
}
for (Entry<TraceAddressSnapRange, byte[]> entry : toPutBack.entrySet()) {
@@ -430,8 +438,9 @@ public class DBTraceRegisterContextSpace implements TraceRegisterContextSpace, D
@Override
public void clear(Lifespan span, AddressRange range) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
for (DBTraceAddressSnapRangePropertyMapSpace<byte[], DBTraceRegisterContextEntry> valueMap : registerValueMaps
.values()) {
for (DBTraceAddressSnapRangePropertyMapSpace<byte[],
DBTraceRegisterContextEntry> valueMap : registerValueMaps
.values()) {
for (Entry<TraceAddressSnapRange, byte[]> entry : valueMap.reduce(
TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) {
DBTraceRegisterContextEntry record =
@@ -448,8 +457,9 @@ public class DBTraceRegisterContextSpace implements TraceRegisterContextSpace, D
try (LockHold hold = LockHold.lock(lock.writeLock())) {
registerStore.invalidateCache();
loadRegisterValueMaps();
for (DBTraceAddressSnapRangePropertyMapSpace<byte[], DBTraceRegisterContextEntry> map : registerValueMaps
.values()) {
for (DBTraceAddressSnapRangePropertyMapSpace<byte[],
DBTraceRegisterContextEntry> map : registerValueMaps
.values()) {
map.invalidateCache();
}
}

View File

@@ -4,9 +4,9 @@
* 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.
@@ -62,8 +62,9 @@ public interface DBTraceDataSettingsOperations
}
default void doMakeWay(Lifespan span, Address address, String name) {
for (DBTraceSettingsEntry entry : reduce(TraceAddressSnapRangeQuery.intersecting(
new AddressRangeImpl(address, address), span)).values()) {
for (DBTraceSettingsEntry entry : List.copyOf(reduce(
TraceAddressSnapRangeQuery.intersecting(new AddressRangeImpl(address, address), span))
.values())) {
if (name == null || name.equals(entry.name)) {
makeWay(entry, span);
}

View File

@@ -20,9 +20,8 @@ import java.util.Map.Entry;
import ghidra.program.model.address.*;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.trace.database.DBTraceCacheForContainingQueries;
import ghidra.trace.database.*;
import ghidra.trace.database.DBTraceCacheForContainingQueries.GetKey;
import ghidra.trace.database.DBTraceCacheForSequenceQueries;
import ghidra.trace.database.context.DBTraceRegisterContextSpace;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapAddressSetView;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapSpace;
@@ -50,6 +49,7 @@ import ghidra.util.task.TaskMonitor;
public abstract class AbstractBaseDBTraceDefinedUnitsView<T extends AbstractDBTraceCodeUnit<T>>
extends AbstractSingleDBTraceCodeUnitsView<T> {
protected final static int CHUNK_SIZE = DBTrace.CHUNK_SIZE;
protected final static int CACHE_MAX_REGIONS = 1000;
protected final static int CACHE_ADDRESS_BREADTH = 10000;
protected final static int CACHE_MAX_POINTS = 10000;
@@ -306,27 +306,35 @@ public abstract class AbstractBaseDBTraceDefinedUnitsView<T extends AbstractDBTr
/**
* @see TraceBaseDefinedUnitsView#clear(Lifespan, AddressRange, boolean, TaskMonitor)
*/
public void clear(Lifespan span, AddressRange range, boolean clearContext,
TaskMonitor monitor) throws CancelledException {
public void clear(Lifespan span, AddressRange range, boolean clearContext, TaskMonitor monitor)
throws CancelledException {
long startSnap = span.lmin();
try (LockHold hold = LockHold.lock(space.lock.writeLock())) {
cacheForContaining.invalidate();
cacheForSequence.invalidate();
for (T unit : mapSpace.reduce(
TraceAddressSnapRangeQuery.intersecting(range, span)).values()) {
monitor.checkCancelled();
if (unit.getStartSnap() < startSnap) {
Lifespan oldSpan = unit.getLifespan();
if (clearContext) {
clearContext(Lifespan.span(span.lmin(), oldSpan.lmax()), unit.getRange());
var submap = mapSpace.reduce(TraceAddressSnapRangeQuery.intersecting(range, span));
while (true) {
List<T> chunk = submap.values().stream().limit(CHUNK_SIZE).toList();
for (T unit : chunk) {
monitor.checkCancelled();
if (unit.getStartSnap() < startSnap) {
Lifespan oldSpan = unit.getLifespan();
if (clearContext) {
clearContext(Lifespan.span(span.lmin(), oldSpan.lmax()),
unit.getRange());
}
unit.setEndSnap(startSnap - 1);
}
else {
if (clearContext) {
clearContext(unit.getLifespan(), unit.getRange());
}
unit.delete();
}
unit.setEndSnap(startSnap - 1);
}
else {
if (clearContext) {
clearContext(unit.getLifespan(), unit.getRange());
}
unit.delete();
if (chunk.size() < CHUNK_SIZE) {
break;
}
}
}

View File

@@ -16,8 +16,7 @@
package ghidra.trace.database.listing;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import db.DBHandle;
@@ -55,6 +54,8 @@ import ghidra.util.task.TaskMonitor;
* {@link TraceCodeManager#getCodeRegisterSpace(TraceThread, int, boolean)}.
*/
public class DBTraceCodeSpace implements TraceCodeSpace, DBTraceSpaceBased {
protected final static int CHUNK_SIZE = DBTrace.CHUNK_SIZE;
protected final DBTraceCodeManager manager;
protected final DBHandle dbh;
protected final AddressSpace space;
@@ -65,7 +66,8 @@ public class DBTraceCodeSpace implements TraceCodeSpace, DBTraceSpaceBased {
protected final DBTraceReferenceManager referenceManager;
protected final AddressRange all;
protected final DBTraceAddressSnapRangePropertyMapSpace<DBTraceInstruction, DBTraceInstruction> instructionMapSpace;
protected final DBTraceAddressSnapRangePropertyMapSpace<DBTraceInstruction,
DBTraceInstruction> instructionMapSpace;
protected final DBTraceAddressSnapRangePropertyMapSpace<DBTraceData, DBTraceData> dataMapSpace;
// NOTE: All combinations except () and (INSTRUCTIONS,UNDEFINED)
@@ -178,28 +180,42 @@ public class DBTraceCodeSpace implements TraceCodeSpace, DBTraceSpaceBased {
definedData.invalidateCache();
undefinedData.invalidateCache();
for (DBTraceInstruction instruction : instructionMapSpace.reduce(
TraceAddressSnapRangeQuery.intersecting(range, span)).values()) {
monitor.checkCancelled();
monitor.incrementProgress(1);
if (instruction.platform != guest) {
continue;
TraceAddressSnapRangeQuery query = TraceAddressSnapRangeQuery.intersecting(range, span);
var instructionSubmap = instructionMapSpace.reduce(query);
while (true) {
List<DBTraceInstruction> chunk =
instructionSubmap.values().stream().limit(CHUNK_SIZE).toList();
for (DBTraceInstruction instruction : chunk) {
monitor.checkCancelled();
monitor.incrementProgress(1);
if (instruction.platform != guest) {
continue;
}
instructionMapSpace.deleteData(instruction);
instructions.unitRemoved(instruction);
}
if (chunk.size() < CHUNK_SIZE) {
break;
}
instructionMapSpace.deleteData(instruction);
instructions.unitRemoved(instruction);
}
monitor.setMessage("Clearing data");
monitor.setMaximum(dataMapSpace.size()); // This is OK
for (DBTraceData dataUnit : dataMapSpace.reduce(
TraceAddressSnapRangeQuery.intersecting(range, span)).values()) {
monitor.checkCancelled();
monitor.incrementProgress(1);
if (dataUnit.platform != guest) {
continue;
var dataSubmap = dataMapSpace.reduce(query);
while (true) {
List<DBTraceData> chunk = dataSubmap.values().stream().limit(CHUNK_SIZE).toList();
for (DBTraceData dataUnit : chunk) {
monitor.checkCancelled();
monitor.incrementProgress(1);
if (dataUnit.platform != guest) {
continue;
}
dataMapSpace.deleteData(dataUnit);
definedData.unitRemoved(dataUnit);
}
if (chunk.size() < CHUNK_SIZE) {
break;
}
// TODO: I don't yet have guest-language data units.
dataMapSpace.deleteData(dataUnit);
definedData.unitRemoved(dataUnit);
}
}

View File

@@ -16,6 +16,7 @@
package ghidra.trace.database.listing;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import org.apache.commons.lang3.StringUtils;
@@ -138,8 +139,8 @@ public class DBTraceCommentAdapter
}
String oldValue = null;
try (LockHold hold = LockHold.lock(lock.writeLock())) {
for (DBTraceCommentEntry entry : reduce(TraceAddressSnapRangeQuery
.intersecting(new AddressRangeImpl(address, address), lifespan)).values()) {
for (DBTraceCommentEntry entry : List.copyOf(reduce(TraceAddressSnapRangeQuery
.intersecting(new AddressRangeImpl(address, address), lifespan)).values())) {
if (entry.type == commentType.ordinal()) {
if (entry.getLifespan().contains(lifespan.lmin())) {
oldValue = entry.comment;
@@ -207,8 +208,8 @@ public class DBTraceCommentAdapter
*/
public void clearComments(Lifespan span, AddressRange range, CommentType commentType) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
for (DBTraceCommentEntry entry : reduce(
TraceAddressSnapRangeQuery.intersecting(range, span)).values()) {
for (DBTraceCommentEntry entry : List.copyOf(reduce(
TraceAddressSnapRangeQuery.intersecting(range, span)).values())) {
if (commentType == null || entry.type == commentType.ordinal()) {
makeWay(entry, span);
}

View File

@@ -21,6 +21,7 @@ import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.function.BiConsumer;
import db.*;
import ghidra.framework.data.OpenMode;
@@ -39,13 +40,17 @@ import ghidra.util.*;
import ghidra.util.database.*;
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
import ghidra.util.database.annot.*;
import ghidra.util.database.spatial.SpatialMap;
import ghidra.util.exception.NotYetImplementedException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
public abstract class AbstractDBTracePropertyMap<T, DR extends AbstractDBTraceAddressSnapRangePropertyMapData<T>>
public abstract class AbstractDBTracePropertyMap<T,
DR extends AbstractDBTraceAddressSnapRangePropertyMapData<T>>
extends DBTraceAddressSnapRangePropertyMap<T, DR> implements TracePropertyMap<T> {
protected static final int CHUNK_SIZE = DBTrace.CHUNK_SIZE;
public AbstractDBTracePropertyMap(String name, DBHandle dbh, OpenMode openMode,
ReadWriteLock lock, TaskMonitor monitor, Language baseLanguage, DBTrace trace,
DBTraceThreadManager threadManager, Class<DR> dataType,
@@ -96,26 +101,36 @@ public abstract class AbstractDBTracePropertyMap<T, DR extends AbstractDBTraceAd
return reduce(TraceAddressSnapRangeQuery.intersecting(range, lifespan)).entries();
}
protected static <T, DR extends AbstractDBTraceAddressSnapRangePropertyMapData<T>> boolean
doClear(Lifespan span, SpatialMap<TraceAddressSnapRange, T, ?> sub,
BiConsumer<Entry<TraceAddressSnapRange, T>, Lifespan> makeWay) {
boolean result = false;
while (true) {
var chunk = sub.entries().stream().limit(CHUNK_SIZE).toList();
for (Entry<TraceAddressSnapRange, T> entry : chunk) {
makeWay.accept(entry, span);
result = true;
}
if (chunk.size() < CHUNK_SIZE) {
break;
}
}
return result;
}
@Override
public boolean clear(Lifespan span, AddressRange range) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
boolean result = false;
for (Entry<TraceAddressSnapRange, T> entry : reduce(
TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) {
makeWay(entry, span);
result = true;
}
return result;
return doClear(span, reduce(TraceAddressSnapRangeQuery.intersecting(range, span)),
this::makeWay);
}
}
@Override
public T put(TraceAddressSnapRange shape, T value) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
for (Entry<TraceAddressSnapRange, T> entry : reduce(
TraceAddressSnapRangeQuery.intersecting(shape)).entries()) {
makeWay(entry, shape.getLifespan());
}
doClear(shape.getLifespan(), reduce(TraceAddressSnapRangeQuery.intersecting(shape)),
this::makeWay);
return super.put(shape, value);
}
}
@@ -206,23 +221,16 @@ public abstract class AbstractDBTracePropertyMap<T, DR extends AbstractDBTraceAd
@Override
public boolean clear(Lifespan span, AddressRange range) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
boolean result = false;
for (Entry<TraceAddressSnapRange, T> entry : reduce(
TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) {
makeWay(entry, span);
result = true;
}
return result;
return doClear(span, reduce(TraceAddressSnapRangeQuery.intersecting(range, span)),
this::makeWay);
}
}
@Override
public T put(TraceAddressSnapRange shape, T value) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
for (Entry<TraceAddressSnapRange, T> entry : reduce(
TraceAddressSnapRangeQuery.intersecting(shape)).entries()) {
makeWay(entry, shape.getLifespan());
}
doClear(shape.getLifespan(), reduce(TraceAddressSnapRangeQuery.intersecting(shape)),
this::makeWay);
return super.put(shape, value);
}
}
@@ -324,8 +332,9 @@ public abstract class AbstractDBTracePropertyMap<T, DR extends AbstractDBTraceAd
protected final Class<T> valueClass;
@SuppressWarnings({ "rawtypes", "unchecked" })
protected static <T extends Saveable> Class<DBTraceSaveablePropertyMapEntry<T>> getEntryClass(
Class<T> valueClass) {
protected static <T extends Saveable> Class<DBTraceSaveablePropertyMapEntry<T>>
getEntryClass(
Class<T> valueClass) {
return (Class) DBTraceSaveablePropertyMapEntry.class;
}

View File

@@ -496,9 +496,28 @@ public class DBTraceAddressSnapRangePropertyMapTree<T,
TraceAddressSnapRangeQuery::new);
}
public static TraceAddressSnapRangeQuery atSnap(long snap, AddressSpace space) {
return intersecting(new ImmutableTraceAddressSnapRange(space.getMinAddress(),
space.getMaxAddress(), snap, snap), null, TraceAddressSnapRangeQuery::new);
public static TraceAddressSnapRangeQuery minAt(Address address, long snap) {
return intersectingMinWithin(new AddressRangeImpl(address, address), Lifespan.at(snap));
}
public static TraceAddressSnapRangeQuery minAtSnap(long snap, AddressSpace space) {
return minWithin(Lifespan.at(snap), space);
}
public static TraceAddressSnapRangeQuery minWithin(Lifespan span, AddressSpace space) {
return intersectingMinWithin(
new AddressRangeImpl(space.getMinAddress(), space.getMaxAddress()), span);
}
public static TraceAddressSnapRangeQuery intersectingMinWithin(AddressRange range,
Lifespan span) {
AddressSpace space = range.getAddressSpace();
return new TraceAddressSnapRangeQuery(
new ImmutableTraceAddressSnapRange(space.getMinAddress(), range.getMaxAddress(),
span),
new ImmutableTraceAddressSnapRange(range.getMinAddress(), space.getMaxAddress(),
Lifespan.nowOnMaybeScratch(span.lmax())),
null);
}
public static TraceAddressSnapRangeQuery intersecting(Lifespan lifespan,
@@ -507,6 +526,12 @@ public class DBTraceAddressSnapRangePropertyMapTree<T,
space.getMaxAddress(), lifespan), null, TraceAddressSnapRangeQuery::new);
}
public static TraceAddressSnapRangeQuery intersectingEnclosed(AddressRange range,
Lifespan span) {
return intersectingEnclosed(new ImmutableTraceAddressSnapRange(range, span), null,
TraceAddressSnapRangeQuery::new);
}
/**
* Find entries which do not exist at the from snap, but do exist at the to snap
*
@@ -550,21 +575,23 @@ public class DBTraceAddressSnapRangePropertyMapTree<T,
}
public static TraceAddressSnapRangeQuery mostRecent(Address address, long snap) {
return intersecting(
new ImmutableTraceAddressSnapRange(address, address, Long.MIN_VALUE, snap),
Rectangle2DDirection.TOPMOST, TraceAddressSnapRangeQuery::new);
}
public static TraceAddressSnapRangeQuery mostRecent(Address address, Lifespan span) {
return intersecting(
new ImmutableTraceAddressSnapRange(address, span),
Rectangle2DDirection.TOPMOST, TraceAddressSnapRangeQuery::new);
return mostRecent(new AddressRangeImpl(address, address), Lifespan.since(snap));
}
public static TraceAddressSnapRangeQuery mostRecent(AddressRange range, Lifespan span) {
return intersecting(
new ImmutableTraceAddressSnapRange(range, span),
Rectangle2DDirection.TOPMOST, TraceAddressSnapRangeQuery::new);
AddressSpace space = range.getAddressSpace();
TraceAddressSnapRange r1 = new ImmutableTraceAddressSnapRange(space.getMinAddress(),
range.getMaxAddress(), span);
Lifespan maxOn = Lifespan.nowOnMaybeScratch(span.lmax());
TraceAddressSnapRange r2 = new ImmutableTraceAddressSnapRange(range.getMinAddress(),
space.getMaxAddress(), maxOn);
return new TraceAddressSnapRangeQuery(r1, r2, Rectangle2DDirection.TOPMOST);
}
public static TraceAddressSnapRangeQuery leastRecent(AddressRange range, Lifespan span) {
return mostRecent(range, span).starting(Rectangle2DDirection.BOTTOMMOST);
}
public static TraceAddressSnapRangeQuery equalTo(TraceAddressSnapRange shape) {

View File

@@ -347,11 +347,9 @@ public class DBTraceMemoryManager extends AbstractDBTraceSpaceBasedManager<DBTra
Lifespan between = from < to ? Lifespan.span(from + 1, to) : Lifespan.span(to + 1, from);
Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> result = new ArrayList<>();
for (DBTraceMemorySpace space : spaces.values()) {
AddressRange rng =
new AddressRangeImpl(space.space.getMinAddress(), space.space.getMaxAddress());
result.addAll(
space.stateMapSpace.reduce(TraceAddressSnapRangeQuery.enclosed(rng, between))
.entries());
result.addAll(space.stateMapSpace
.reduce(TraceAddressSnapRangeQuery.minWithin(between, space.space))
.entries());
}
return result;
}

View File

@@ -29,10 +29,10 @@ import ghidra.program.model.address.*;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceTimeViewport;
import ghidra.trace.database.DBTraceUtils.AddressRangeMapSetter;
import ghidra.trace.database.DBTraceUtils.OffsetSnap;
import ghidra.trace.database.listing.DBTraceCodeSpace;
import ghidra.trace.database.map.*;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapAddressSetView;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapSpace;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.Painter;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery;
import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager.DBTraceSpaceEntry;
@@ -61,9 +61,21 @@ public class DBTraceMemorySpace
public static final int BLOCK_SIZE = 1 << BLOCK_SHIFT;
public static final int BLOCK_MASK = -1 << BLOCK_SHIFT;
public static final int DEPENDENT_COMPRESSED_SIZE_TOLERANCE = BLOCK_SIZE >>> 2;
public static final int BLOCK_CACHE_SIZE = 10;
public static final int BLOCKS_PER_BUFFER = 256; // Must be a power of 2 and >= 8;
public static final int STATE_BLOCK_SHIFT = 8;
public static final int STATE_BLOCK_SIZE = 1 << STATE_BLOCK_SHIFT;
public static final int STATE_BLOCK_MASK = -1 << STATE_BLOCK_SHIFT;
public static final int STATE_CACHE_SIZE = 500;
public static final int STATE_MR_CACHE_SIZE = 100;
record AddressSetStateCacheKey(Lifespan span, long offset, StatePredicate predicate) {}
record MostRecentStateCacheKey(long snap, Address address) {}
protected final DBTraceMemoryManager manager;
protected final DBHandle dbh;
protected final AddressSpace space;
@@ -77,7 +89,12 @@ public class DBTraceMemorySpace
protected final DBCachedObjectStore<DBTraceMemoryBlockEntry> blockStore;
protected final DBCachedObjectIndex<OffsetSnap, DBTraceMemoryBlockEntry> blocksByOffset;
protected final Map<OffsetSnap, DBTraceMemoryBlockEntry> blockCacheMostRecent =
new FixedSizeHashMap<>(10);
Collections.synchronizedMap(new FixedSizeHashMap<>(BLOCK_CACHE_SIZE));
protected final Map<AddressSetStateCacheKey, AddressSetView> addressSetStateCache =
Collections.synchronizedMap(new FixedSizeHashMap<>(STATE_CACHE_SIZE));
protected final Map<MostRecentStateCacheKey,
Entry<TraceAddressSnapRange, TraceMemoryState>> mostRecentStateEntryCache =
Collections.synchronizedMap(new FixedSizeHashMap<>(STATE_MR_CACHE_SIZE));
protected final DBTraceTimeViewport viewport;
@@ -91,6 +108,7 @@ public class DBTraceMemorySpace
DBCachedObjectStoreFactory factory = trace.getStoreFactory();
// TODO: Remove the Exp thing from this and related
this.stateMapSpace = new DBTraceAddressSnapRangePropertyMapSpace<>(
DBTraceMemoryStateEntry.tableName(space), trace, factory, lock, space,
DBTraceMemoryStateEntry.class, DBTraceMemoryStateEntry::new);
@@ -133,56 +151,169 @@ public class DBTraceMemorySpace
return space;
}
protected void doSetState(long snap, Address start, Address end, TraceMemoryState state) {
if (state == null) {
throw new NullPointerException();
protected void doPutState(AddressRange range, Lifespan span, TraceMemoryState state) {
if (!range.getMinAddress().equals(range.getAddressSpace().getMinAddress())) {
Entry<TraceAddressSnapRange, TraceMemoryState> candidateLeft = stateMapSpace
.reduce(TraceAddressSnapRangeQuery.at(range.getMinAddress().previous(),
span.lmin()))
.firstEntry();
if (candidateLeft != null && candidateLeft.getValue() == state &&
candidateLeft.getKey().getLifespan().equals(span)) {
range = new AddressRangeImpl(candidateLeft.getKey().getRange().getMinAddress(),
range.getMaxAddress());
stateMapSpace.remove(candidateLeft);
}
}
var l = new Object() {
boolean changed;
};
new AddressRangeMapSetter<Entry<TraceAddressSnapRange, TraceMemoryState>,
TraceMemoryState>() {
@Override
protected AddressRange getRange(Entry<TraceAddressSnapRange, TraceMemoryState> entry) {
return entry.getKey().getRange();
if (!range.getMaxAddress().equals(range.getAddressSpace().getMaxAddress())) {
Entry<TraceAddressSnapRange, TraceMemoryState> candidateRight = stateMapSpace
.reduce(TraceAddressSnapRangeQuery.at(range.getMaxAddress().next(),
span.lmin()))
.firstEntry();
if (candidateRight != null && candidateRight.getValue() == state &&
candidateRight.getKey().getLifespan().equals(span)) {
range = new AddressRangeImpl(range.getMinAddress(),
candidateRight.getKey().getRange().getMaxAddress());
stateMapSpace.remove(candidateRight);
}
}
stateMapSpace.put(range, span, state);
}
@Override
protected TraceMemoryState getValue(
Entry<TraceAddressSnapRange, TraceMemoryState> entry) {
return entry.getValue();
protected void doTruncateAndPut(long snap, AddressSet remains, TraceMemoryState state) {
if (remains.isEmpty()) {
return;
}
Lifespan nowOn = Lifespan.nowOnMaybeScratch(snap);
// If there can't be anything ahead, just add it and be done
if (nowOn.lmin() == nowOn.lmax()) {
for (AddressRange rng : remains) {
doPutState(rng, nowOn, state);
}
@Override
protected void remove(Entry<TraceAddressSnapRange, TraceMemoryState> entry) {
stateMapSpace.remove(entry);
return;
}
// Scan ahead to see how the new entry should be split, if truncation is needed
Lifespan future = Lifespan.nowOnMaybeScratch(snap + 1);
Iterator<Entry<TraceAddressSnapRange, TraceMemoryState>> it = stateMapSpace
.reduce(TraceAddressSnapRangeQuery.intersectingEnclosed(
new AddressRangeImpl(remains.getMinAddress(), remains.getMaxAddress()), future)
.starting(Rectangle2DDirection.BOTTOMMOST))
.orderedEntries()
.iterator();
// Avoid concurrent modification, lest new entries confuse our splitting
List<TraceAddressSnapRange> toAdd = new ArrayList<>();
while (!remains.isEmpty() && it.hasNext()) {
Entry<TraceAddressSnapRange, TraceMemoryState> next = it.next();
if (next.getValue() != TraceMemoryState.KNOWN) {
continue;
}
@Override
protected Iterable<Entry<TraceAddressSnapRange, TraceMemoryState>> getIntersecting(
Address lower, Address upper) {
return stateMapSpace
.reduce(TraceAddressSnapRangeQuery.intersecting(lower, upper, snap, snap))
.entries();
}
@Override
protected Entry<TraceAddressSnapRange, TraceMemoryState> put(AddressRange range,
TraceMemoryState value) {
// This should not get called if the range is already the desired state
l.changed = true;
if (value != TraceMemoryState.UNKNOWN) {
stateMapSpace.put(new ImmutableTraceAddressSnapRange(range, snap), value);
TraceAddressSnapRange key = next.getKey();
AddressSet intersection = remains.intersectRange(key.getRange());
if (!intersection.isEmpty()) {
Lifespan portion = Lifespan.span(snap, key.getLifespan().lmin() - 1);
for (AddressRange rng : intersection) {
toAdd.add(new ImmutableTraceAddressSnapRange(rng, portion));
}
return null; // Don't need to return it
}
}.set(start, end, state);
if (l.changed) {
trace.setChanged(new TraceChangeRecord<>(TraceEvents.BYTES_STATE_CHANGED, space,
new ImmutableTraceAddressSnapRange(start, end, snap, snap), state));
remains.delete(key.getRange());
}
for (AddressRange rng : remains) { // may be empty, anyway
toAdd.add(new ImmutableTraceAddressSnapRange(rng, nowOn));
}
for (TraceAddressSnapRange box : toAdd) {
doPutState(box.getRange(), box.getLifespan(), state);
}
}
protected void doSetState(long snap, Address start, Address end, TraceMemoryState state) {
Objects.requireNonNull(state);
AddressRangeImpl range = new AddressRangeImpl(start, end);
AddressSet remains = new AddressSet(range);
AddressSet toExpand = state.truncates() ? null : new AddressSet();
List<Entry<TraceAddressSnapRange, TraceMemoryState>> truncated = new ArrayList<>();
// Where the desired state is already present, do nothing
var exist = List.copyOf(stateMapSpace
.reduce(TraceAddressSnapRangeQuery.intersecting(range, Lifespan.at(snap)))
.entries());
for (Entry<TraceAddressSnapRange, TraceMemoryState> ex : exist) {
TraceAddressSnapRange key = ex.getKey();
if (key.getLifespan().lmin() == snap) {
if (ex.getValue() == state) {
remains.delete(key.getRange());
}
else {
AddressSet diff = new AddressSet(key.getRange());
diff.delete(range);
for (AddressRange d : diff) {
truncated.add(Map.entry(new ImmutableTraceAddressSnapRange(
d, key.getLifespan()), ex.getValue()));
}
stateMapSpace.remove(ex);
if (toExpand != null /* New value does not truncate */ &&
ex.getValue().truncates() /* Old value truncates */) {
toExpand.add(key.getRange());
}
}
}
}
// Implies nothing needs to change, and toExpand ought be be empty too
if (remains.isEmpty() && (toExpand == null || toExpand.isEmpty())) {
return;
}
/**
* Truncate or delete the existing entries to make room for the new entries. Every entry is
* truncated at KNOWN. No entry is truncated by ERROR, so queries must sort that out.
*/
if (state.truncates()) {
if (!remains.isEmpty()) {
// Remove existing entries to make room. We'll add them back (truncated) later
for (Entry<TraceAddressSnapRange, TraceMemoryState> entry : exist) {
if (remains.intersects(entry.getKey().getRange())) {
stateMapSpace.remove(entry);
truncated.add(Map.entry(entry.getKey(), entry.getValue()));
}
}
}
}
else {
/**
* It's possible truncating values were removed, permitting entries from the previous
* snap to expand forward. Those entries may need to be split, depending on what
* truncating entries are in the future.
*/
if (snap != Long.MIN_VALUE && snap != 0) { // Is there a previous snap?
Lifespan prev = Lifespan.at(snap - 1);
for (AddressRange exp : toExpand) {
for (Entry<TraceAddressSnapRange, TraceMemoryState> entry : stateMapSpace
.reduce(TraceAddressSnapRangeQuery.intersecting(exp, prev))
.entries()) {
stateMapSpace.remove(entry);
doTruncateAndPut(entry.getKey().getLifespan().lmin(),
new AddressSet(entry.getKey().getRange()), entry.getValue());
}
}
}
}
if (!state.impliedByNull() && remains != null) {
doTruncateAndPut(snap, remains, state);
}
for (Entry<TraceAddressSnapRange, TraceMemoryState> entry : truncated) {
TraceAddressSnapRange key = entry.getKey();
doTruncateAndPut(key.getLifespan().lmin(), new AddressSet(key.getRange()),
entry.getValue());
}
addressSetStateCache.clear();
mostRecentStateEntryCache.clear();
// TODO: Maybe the end snap should extend as far into the future as was actually written?
trace.setChanged(new TraceChangeRecord<>(TraceEvents.BYTES_STATE_CHANGED, space,
new ImmutableTraceAddressSnapRange(start, end, snap, snap), state));
}
protected void checkState(TraceMemoryState state) {
@@ -242,9 +373,8 @@ public class DBTraceMemorySpace
@Override
public TraceMemoryState getState(long snap, Address address) {
TraceMemoryState state =
stateMapSpace.reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstValue();
return state == null ? TraceMemoryState.UNKNOWN : state;
return TraceMemoryState.orImplied(
stateMapSpace.reduce(TraceAddressSnapRangeQuery.minAt(address, snap)).firstValue());
}
@Override
@@ -252,10 +382,11 @@ public class DBTraceMemorySpace
for (Lifespan span : viewport.getOrderedSpans(snap)) {
TraceMemoryState state = getState(span.lmax(), address);
switch (state) {
case KNOWN:
case ERROR:
case KNOWN, ERROR -> {
return Map.entry(span.lmax(), state);
default: // fall through
}
default -> { // fall through
}
}
// Only the snap with the schedule specified gets the source snap's states
if (span.lmax() - span.lmin() > 0) {
@@ -268,14 +399,22 @@ public class DBTraceMemorySpace
@Override
public Entry<TraceAddressSnapRange, TraceMemoryState> getMostRecentStateEntry(long snap,
Address address) {
return stateMapSpace.reduce(
TraceAddressSnapRangeQuery.mostRecent(address, snap)).firstEntry();
return stateMapSpace.reduce(TraceAddressSnapRangeQuery.mostRecent(address, snap))
// not .firstEntry(), because TOPMOST sorts by Y2
.entries()
.stream()
.sorted(Comparator.comparing(e -> -e.getKey().getY1()))
.findFirst()
.orElse(null);
}
@Override
public Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
Address address) {
return getViewMostRecentStateEntry(snap, new AddressRangeImpl(address, address), s -> true);
// LATER: Cache here or on the delegate?
return mostRecentStateEntryCache.computeIfAbsent(new MostRecentStateCacheKey(snap, address),
k -> getViewMostRecentStateEntry(snap, new AddressRangeImpl(address, address),
StatePredicate.IS_KNOWN_OR_ERROR));
}
@Override
@@ -284,9 +423,14 @@ public class DBTraceMemorySpace
assertInSpace(range);
for (Lifespan span : viewport.getOrderedSpans(snap)) {
var entry = stateMapSpace.reduce(TraceAddressSnapRangeQuery.mostRecent(range, span))
.firstEntry();
if (entry != null && predicate.test(entry.getValue())) {
return entry;
.entries() // not ordered, since we're doing our own sort
.stream()
.filter(e -> predicate.test(e.getValue()))
// TOPMOST sorts by Y2
.sorted(Comparator.comparing(e -> -e.getKey().getY1()))
.findFirst();
if (entry.isPresent()) {
return entry.get();
}
}
return null;
@@ -295,7 +439,7 @@ public class DBTraceMemorySpace
@Override
public AddressSetView getAddressesWithState(long snap, Predicate<TraceMemoryState> predicate) {
return new DBTraceAddressSnapRangePropertyMapAddressSetView<>(space, lock,
stateMapSpace.reduce(TraceAddressSnapRangeQuery.atSnap(snap, space)),
stateMapSpace.reduce(TraceAddressSnapRangeQuery.minAtSnap(snap, space)),
predicate);
}
@@ -303,7 +447,7 @@ public class DBTraceMemorySpace
public AddressSetView getAddressesWithState(Lifespan lifespan,
Predicate<TraceMemoryState> predicate) {
return new DBTraceAddressSnapRangePropertyMapAddressSetView<>(space, lock,
stateMapSpace.reduce(TraceAddressSnapRangeQuery.intersecting(lifespan, space)),
stateMapSpace.reduce(TraceAddressSnapRangeQuery.minWithin(lifespan, space)),
predicate);
}
@@ -311,31 +455,71 @@ public class DBTraceMemorySpace
public AddressSetView getAddressesWithState(Lifespan span, AddressSetView set,
Predicate<TraceMemoryState> predicate) {
try (LockHold hold = LockHold.lock(lock.readLock())) {
if (!(predicate instanceof StatePredicate stock)) {
return doGetAddressesWithState(span, set, predicate);
}
AddressSet remains = new AddressSet(set);
AddressSet result = new AddressSet();
while (!remains.isEmpty()) {
AddressRange range = remains.getFirstRange();
remains.delete(range);
for (Entry<TraceAddressSnapRange, TraceMemoryState> entry : doGetStates(span,
range)) {
AddressRange foundRange = entry.getKey().getRange();
remains.delete(foundRange);
if (predicate.test(entry.getValue())) {
result.add(foundRange);
Address min = remains.getMinAddress();
long blockMinOffset = min.getOffset() & STATE_BLOCK_MASK;
Address blockMin = min.getNewAddress(blockMinOffset);
Address blockMax = blockMin.add(STATE_BLOCK_SIZE - 1);
remains.delete(min, blockMax);
result.add(addressSetStateCache.computeIfAbsent(
new AddressSetStateCacheKey(span, blockMinOffset, stock),
k -> doGetAddressesWithState(span, new AddressSet(blockMin, blockMax), stock)));
}
return result.intersect(set);
}
}
protected AddressSetView doGetAddressesWithState(Lifespan span, AddressSetView set,
Predicate<TraceMemoryState> predicate) {
AddressSet remains = new AddressSet(set);
AddressSet result = new AddressSet();
while (!remains.isEmpty()) {
AddressRange range = remains.getFirstRange();
AddressSet subRem = new AddressSet(range);
remains.delete(range);
for (Entry<TraceAddressSnapRange, TraceMemoryState> entry : doGetStates(span, range)) {
AddressRange foundRange = entry.getKey().getRange();
if (predicate.test(entry.getValue())) {
result.add(foundRange);
remains.delete(foundRange); // Could intersect a later range
subRem.delete(foundRange);
if (subRem.isEmpty()) {
break;
}
}
}
return result;
if (remains.isEmpty()) {
return result;
}
}
return result;
}
protected Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> doGetStates(Lifespan span,
AddressRange range) {
// LATER: A better way to handle memory-mapped registers?
if (getAddressSpace().isRegisterSpace() && !range.getAddressSpace().isRegisterSpace()) {
AddressSpace thisSpace = getAddressSpace();
if (thisSpace.isRegisterSpace() && !range.getAddressSpace().isRegisterSpace()) {
return trace.getMemoryManager().doGetStates(span, range);
}
return stateMapSpace.reduce(TraceAddressSnapRangeQuery.intersecting(range, span)).entries();
if (thisSpace != range.getAddressSpace()) {
range = new AddressRangeImpl(
thisSpace.getOverlayAddress(range.getMinAddress()),
thisSpace.getOverlayAddress(range.getMaxAddress()));
}
var subMap =
stateMapSpace.reduce(TraceAddressSnapRangeQuery.intersectingMinWithin(range, span));
return subMap.entries();
/*@SuppressWarnings({ "unchecked", "rawtypes" })
List<Entry<TraceAddressSnapRange, TraceMemoryState>> asList =
(List) Arrays.asList(subMap.entries().toArray());
return asList;*/
}
@Override
@@ -348,8 +532,10 @@ public class DBTraceMemorySpace
@Override
public Iterable<Entry<TraceAddressSnapRange, TraceMemoryState>> getMostRecentStates(
TraceAddressSnapRange within) {
return new DBTraceAddressSnapRangePropertyMapOcclusionIntoPastIterable<>(this.stateMapSpace,
within);
return stateMapSpace
.reduce(
TraceAddressSnapRangeQuery.mostRecent(within.getRange(), within.getLifespan()))
.orderedEntries();
}
protected DBTraceMemoryBlockEntry findMostRecentBlockEntry(OffsetSnap loc, boolean inclusive) {
@@ -470,7 +656,7 @@ public class DBTraceMemorySpace
break;
}
AddressSetView withState =
getAddressesWithState(next.getSnap(), remaining, state -> true);
getAddressesWithState(next.getSnap(), remaining, StatePredicate.IS_KNOWN_OR_ERROR);
remaining = remaining.subtract(withState);
long endSnap = next.getSnap() - 1;
for (AddressRange rng : withState) {
@@ -626,8 +812,7 @@ public class DBTraceMemorySpace
spans: for (Lifespan span : viewport.getOrderedSpans(snap)) {
Iterator<AddressRange> arit =
getAddressesWithState(span, remains, s -> s == TraceMemoryState.KNOWN)
.iterator(start, true);
getAddressesWithState(span, remains, StatePredicate.IS_KNOWN).iterator(start, true);
while (arit.hasNext()) {
AddressRange rng = arit.next();
if (rng.getMinAddress().compareTo(toRead.getMaxAddress()) > 0) {
@@ -710,12 +895,11 @@ public class DBTraceMemorySpace
return null;
}
// TODO: Could do better, but have to worry about viewport, too
// This will reduce the search to ranges that have been written at any snap
// We could do for this and previous snaps, but that's where the viewport comes in.
// TODO: Potentially costly to pre-compute the set concretely
// LATER: Worry about the viewport, too?
// This will reduce the search to ranges that have any once-known value at the snap.
// NOTE: Potentially costly to pre-compute the set concretely
AddressSet known = new AddressSet(
stateMapSpace.getAddressSetView(Lifespan.ALL, s -> s == TraceMemoryState.KNOWN))
stateMapSpace.getAddressSetView(Lifespan.at(snap), StatePredicate.IS_KNOWN))
.intersect(new AddressSet(range));
monitor.initialize(known.getNumAddresses());
for (AddressRange knownRange : known.getAddressRanges(forward)) {
@@ -829,9 +1013,8 @@ public class DBTraceMemorySpace
ByteBuffer buf1 = ByteBuffer.allocate(BLOCK_SIZE);
ByteBuffer buf2 = ByteBuffer.allocate(BLOCK_SIZE);
try (LockHold hold = LockHold.lock(lock.readLock())) {
for (TraceAddressSnapRange tasr : stateMapSpace.reduce(
TraceAddressSnapRangeQuery.intersecting(range, fwdOne)
.starting(Rectangle2DDirection.BOTTOMMOST))
for (TraceAddressSnapRange tasr : stateMapSpace
.reduce(TraceAddressSnapRangeQuery.leastRecent(range, fwdOne))
.orderedKeys()) {
AddressRange toExamine = range.intersect(tasr.getRange());
if (doCheckBytesChanged(tasr.getY1(), toExamine, buf1, buf2)) {
@@ -917,6 +1100,8 @@ public class DBTraceMemorySpace
bufferStore.invalidateCache();
blockStore.invalidateCache();
blockCacheMostRecent.clear();
addressSetStateCache.clear();
mostRecentStateEntryCache.clear();
}
}

View File

@@ -18,6 +18,7 @@ package ghidra.trace.database.memory;
import javax.help.UnsupportedOperationException;
import db.DBRecord;
import ghidra.lifecycle.Internal;
import ghidra.program.model.address.AddressSpace;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree;
@@ -30,7 +31,8 @@ import ghidra.util.database.annot.*;
/**
* INTERNAL: An entry to record memory observation states in the database
*/
@DBAnnotatedObjectInfo(version = 0)
@DBAnnotatedObjectInfo(version = 1)
@Internal
class DBTraceMemoryStateEntry
extends AbstractDBTraceAddressSnapRangePropertyMapData<TraceMemoryState> {

View File

@@ -4,9 +4,9 @@
* 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.
@@ -20,6 +20,7 @@ import java.util.Collection;
import java.util.Map.Entry;
import java.util.concurrent.locks.ReadWriteLock;
import ghidra.lifecycle.Internal;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Register;
@@ -31,17 +32,17 @@ import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.util.TraceRegisterUtils;
import ghidra.util.LockHold;
@Internal
public interface InternalTraceMemoryOperations extends TraceMemoryOperations {
static TraceMemoryState requireOne(
static TraceMemoryState requireOne(AddressRange range,
Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> states, Register register) {
if (states.isEmpty()) {
return TraceMemoryState.UNKNOWN;
}
if (states.size() != 1) {
TraceMemoryState state =
TraceMemoryOperations.oneState(range, states);
if (state == null) {
throw new IllegalStateException("More than one state is present in " + register);
}
return states.iterator().next().getValue();
return state;
}
/**
@@ -63,7 +64,7 @@ public interface InternalTraceMemoryOperations extends TraceMemoryOperations {
@Override
default TraceMemoryState getState(TracePlatform platform, long snap, Register register) {
AddressRange range = platform.getConventionalRegisterRange(getSpace(), register);
return requireOne(getStates(snap, range), register);
return requireOne(range, getStates(snap, range), register);
}
@Override

View File

@@ -41,8 +41,8 @@ import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.database.program.DBTraceProgramViewMemory.RegionEntry;
import ghidra.trace.model.*;
import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.TraceMemoryOperations.StatePredicate;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceProgramViewListing;
import ghidra.trace.model.property.TracePropertyMapOperations;
@@ -721,7 +721,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
throw new CodeUnitInsertionException("Code unit would extend beyond address space");
}
var mostRecent = program.memory.memoryManager.getViewMostRecentStateEntry(program.snap,
range, s -> s == TraceMemoryState.KNOWN);
range, StatePredicate.IS_KNOWN);
long snap = mostRecent == null ? program.snap : mostRecent.getKey().getY2();
return codeOperations.instructions()
.create(Lifespan.nowOn(snap), addr, program.platform, prototype, context,

View File

@@ -58,6 +58,7 @@ import ghidra.trace.model.data.TraceBasedDataTypeManager;
import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.memory.TraceMemoryOperations.StatePredicate;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.symbol.*;
import ghidra.trace.util.TraceEvents;
@@ -598,7 +599,8 @@ public class DBTraceProgramView implements TraceProgramView {
}
protected static class OverlappingAddressRangeKeyIteratorMerger<T> extends
PairingIteratorMerger<Entry<AddressRange, T>, Entry<AddressRange, T>, Entry<AddressRange, T>> {
PairingIteratorMerger<Entry<AddressRange, T>, Entry<AddressRange, T>,
Entry<AddressRange, T>> {
protected static <T> Iterable<Pair<Entry<AddressRange, T>, Entry<AddressRange, T>>> iter(
Iterable<Entry<AddressRange, T>> left, Iterable<Entry<AddressRange, T>> right) {
@@ -1452,7 +1454,7 @@ public class DBTraceProgramView implements TraceProgramView {
return RangeQueryOcclusion.super.occluded(cu, range, span);
}
AddressSetView known =
memSpace.getAddressesWithState(span, s -> s == TraceMemoryState.KNOWN);
memSpace.getAddressesWithState(span, StatePredicate.IS_KNOWN);
if (!known.intersects(range.getMinAddress(), range.getMaxAddress())) {
return RangeQueryOcclusion.super.occluded(cu, range, span);
}

View File

@@ -17,6 +17,7 @@ package ghidra.trace.database.symbol;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.stream.Stream;
@@ -50,6 +51,8 @@ import ghidra.util.database.spatial.rect.Rectangle2DDirection;
import ghidra.util.exception.VersionException;
public class DBTraceReferenceSpace implements DBTraceSpaceBased, TraceReferenceSpace {
protected final static int CHUNK_SIZE = DBTrace.CHUNK_SIZE;
protected enum TypeEnum {
MEMORY {
/**
@@ -142,9 +145,15 @@ public class DBTraceReferenceSpace implements DBTraceSpaceBased, TraceReferenceS
return DBTraceUtils.tableName(TABLE_NAME, space);
}
@DBAnnotatedField(column = TO_ADDR_MIN_COLUMN_NAME, indexed = true, codec = AddressDBFieldCodec.class)
@DBAnnotatedField(
column = TO_ADDR_MIN_COLUMN_NAME,
indexed = true,
codec = AddressDBFieldCodec.class)
protected Address toAddrMin = Address.NO_ADDRESS;
@DBAnnotatedField(column = TO_ADDR_MAX_COLUMN_NAME, indexed = true, codec = AddressDBFieldCodec.class)
@DBAnnotatedField(
column = TO_ADDR_MAX_COLUMN_NAME,
indexed = true,
codec = AddressDBFieldCodec.class)
protected Address toAddrMax = Address.NO_ADDRESS;
@DBAnnotatedField(column = SYMBOL_ID_COLUMN_NAME, indexed = true)
protected long symbolId; // TODO: Is this at the from or to address? I think TO...
@@ -343,10 +352,12 @@ public class DBTraceReferenceSpace implements DBTraceSpaceBased, TraceReferenceS
protected final AddressRangeImpl fullSpace;
protected final DBTraceAddressSnapRangePropertyMapSpace<DBTraceReferenceEntry, DBTraceReferenceEntry> referenceMapSpace;
protected final DBTraceAddressSnapRangePropertyMapSpace<DBTraceReferenceEntry,
DBTraceReferenceEntry> referenceMapSpace;
protected final DBCachedObjectIndex<Long, DBTraceReferenceEntry> refsBySymbolId;
protected final DBTraceAddressSnapRangePropertyMapSpace<DBTraceXRefEntry, DBTraceXRefEntry> xrefMapSpace;
protected final DBTraceAddressSnapRangePropertyMapSpace<DBTraceXRefEntry,
DBTraceXRefEntry> xrefMapSpace;
protected final DBCachedObjectIndex<Long, DBTraceXRefEntry> xrefsByRefKey;
public DBTraceReferenceSpace(DBTraceReferenceManager manager, DBHandle dbh, AddressSpace space,
@@ -444,10 +455,10 @@ public class DBTraceReferenceSpace implements DBTraceSpaceBased, TraceReferenceS
int operandIndex) {
// Do I consider "compatibility?" as in ReferenceDBManager?
// NOTE: Always call with the write lock
for (DBTraceReferenceEntry ent : referenceMapSpace
for (DBTraceReferenceEntry ent : List.copyOf(referenceMapSpace
.reduce(TraceAddressSnapRangeQuery
.intersecting(new AddressRangeImpl(fromAddress, fromAddress), span))
.values()) {
.values())) {
if (!ent.toRange.equals(toRange) || ent.opIndex != operandIndex) {
continue;
}
@@ -642,10 +653,18 @@ public class DBTraceReferenceSpace implements DBTraceSpaceBased, TraceReferenceS
public void clearReferencesFrom(Lifespan span, AddressRange range) {
try (LockHold hold = manager.getTrace().lockWrite()) {
long startSnap = span.lmin();
for (DBTraceReferenceEntry ref : referenceMapSpace
.reduce(TraceAddressSnapRangeQuery.intersecting(range, span))
.values()) {
truncateOrDeleteEntry(ref, startSnap);
var submap =
referenceMapSpace.reduce(TraceAddressSnapRangeQuery.intersecting(range, span));
while (true) {
List<DBTraceReferenceEntry> chunk =
submap.values().stream().limit(CHUNK_SIZE).toList();
for (DBTraceReferenceEntry ref : chunk) {
truncateOrDeleteEntry(ref, startSnap);
}
if (chunk.size() < CHUNK_SIZE) {
break;
}
}
// TODO: Coalesce events?
}
@@ -698,11 +717,17 @@ public class DBTraceReferenceSpace implements DBTraceSpaceBased, TraceReferenceS
public void clearReferencesTo(Lifespan span, AddressRange range) {
try (LockHold hold = manager.getTrace().lockWrite()) {
long startSnap = span.lmin();
for (DBTraceXRefEntry xref : xrefMapSpace
.reduce(TraceAddressSnapRangeQuery.intersecting(range, span))
.values()) {
DBTraceReferenceEntry ref = getRefEntryForXRefEntry(xref);
truncateOrDeleteEntry(ref, startSnap);
var submap = xrefMapSpace.reduce(TraceAddressSnapRangeQuery.intersecting(range, span));
while (true) {
List<DBTraceXRefEntry> chunk = submap.values().stream().limit(CHUNK_SIZE).toList();
for (DBTraceXRefEntry xref : chunk) {
DBTraceReferenceEntry ref = getRefEntryForXRefEntry(xref);
truncateOrDeleteEntry(ref, startSnap);
}
if (chunk.size() < CHUNK_SIZE) {
break;
}
}
// TODO: Coalesce events?
}

View File

@@ -57,13 +57,14 @@ public sealed interface Lifespan extends Span<Long, Lifespan>, Iterable<Long> {
* Get the lifespan from 0 to the given snap.
*
* <p>
* The lower bound is 0 to exclude scratch space.
* If the snapshot is in scratch space, then the span will have a lower endpoint of
* {@link Long#MIN_VALUE}. Otherwise, the lower endpoint is 0 to exclude scratch space.
*
* @param snap the snapshot key
* @return the lifespan
*/
static Lifespan since(long snap) {
return new Impl(0, snap);
return new Impl(isScratch(snap) ? Long.MIN_VALUE : 0, snap);
}
/**
@@ -87,10 +88,7 @@ public sealed interface Lifespan extends Span<Long, Lifespan>, Iterable<Long> {
* @return the lifespan
*/
static Lifespan nowOnMaybeScratch(long snap) {
if (isScratch(snap)) {
return new Impl(snap, -1);
}
return new Impl(snap, Long.MAX_VALUE);
return new Impl(snap, isScratch(snap) ? -1 : Long.MAX_VALUE);
}
/**

View File

@@ -18,6 +18,7 @@ package ghidra.trace.model.memory;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.function.Predicate;
@@ -32,7 +33,6 @@ import ghidra.util.task.TaskMonitor;
/**
* Operations for mutating memory regions, values, and state within a trace
*
* <p>
* This models memory over the course of an arbitrary number of snaps. The duration between snaps is
* unspecified. However, the mapping of snaps to real time ought to be strictly monotonic.
@@ -45,7 +45,11 @@ import ghidra.util.task.TaskMonitor;
* states can be manipulated directly; however, this is recommended only to record read failures,
* using the state {@link TraceMemoryState#ERROR}. A state of {@code null} is equivalent to
* {@link TraceMemoryState#UNKNOWN} and indicates no observation has been made.
*
* <p>
* To support queries for the "most recent" bytes and states, entries are extended as far into the
* future until it would collide with another entry, splitting the entry so it can be extended
* piecewise. Note that the state itself is only effective <em>at the starting snap</em> of the
* lifespan. Otherwise, the effective state is {@link TraceMemoryState#UNKNOWN}.
* <p>
* Negative snaps may have different semantics than positive, since negative snaps are used as
* "scratch space". These snaps are not presumed to have any temporal relation to their neighbors,
@@ -60,14 +64,56 @@ import ghidra.util.task.TaskMonitor;
* accidentally rely on implied temporal relationships in scratch space.
*/
public interface TraceMemoryOperations {
/**
* Check if the return value of {@link #getStates(long, AddressRange)} or similar represents a
* single entry of the given state.
*
* single state across the given range.
* <p>
* This method returns false if there is not exactly one entry of the given state whose range
* covers the given range. As a special case, an empty collection will cause this method to
* return true iff state is {@link TraceMemoryState#UNKNOWN}.
* This method returns null if any of the entries indicate a state that differs from the others,
* or if the given entries do not completely cover the given range. As a special case, an empty
* collection will result in {@link TraceMemoryState#UNKNOWN}.
* <p>
* Each of the given entries <em>must</em> intersect the given range, or else the behavior is
* undefined. If the same range is given to {@link #getStates(long, AddressRange)}, this
* requirement is satisfied.
*
* @param range the range to check, usually that passed to
* {@link #getStates(long, AddressRange)}.
* @param stateEntries the collection returned by {@link #getStates(long, AddressRange)}.
* @param states
* @return the uniform state, or null
*/
static TraceMemoryState oneState(AddressRange range,
Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> states) {
Iterator<Entry<TraceAddressSnapRange, TraceMemoryState>> it = states.iterator();
if (!it.hasNext()) {
return TraceMemoryState.IMPLIED_BY_NULL;
}
Entry<TraceAddressSnapRange, TraceMemoryState> entry = it.next();
TraceMemoryState state = entry.getValue();
AddressSet remains = new AddressSet(range);
remains.delete(entry.getKey().getRange());
while (it.hasNext()) {
if (entry.getValue() != state) {
return null;
}
remains.delete(entry.getKey().getRange());
}
return remains.isEmpty() ? state : null;
}
/**
* Check if the return value of {@link #getStates(long, AddressRange)} or similar represents a
* given state across the given range.
* <p>
* This method returns false if any of the entries indicate a state other than the given one, or
* if the given entries do not completely cover the given range. As a special case, an empty
* collection will cause this method to return true iff state is
* {@link TraceMemoryState#UNKNOWN}.
* <p>
* Each of the given entries <em>must</em> intersect the given range, or else the behavior is
* undefined. If the same range is given to {@link #getStates(long, AddressRange)}, this
* requirement is satisfied.
*
* @param range the range to check, usually that passed to
* {@link #getStates(long, AddressRange)}.
@@ -78,18 +124,7 @@ public interface TraceMemoryOperations {
static boolean isStateEntirely(AddressRange range,
Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> stateEntries,
TraceMemoryState state) {
if (stateEntries.isEmpty()) {
return state == TraceMemoryState.UNKNOWN;
}
if (stateEntries.size() != 1) {
return false;
}
Entry<TraceAddressSnapRange, TraceMemoryState> ent = stateEntries.iterator().next();
if (ent.getValue() != state) {
return false;
}
AddressRange entRange = ent.getKey().getRange();
return entRange.contains(range.getMinAddress()) && entRange.contains(range.getMaxAddress());
return oneState(range, stateEntries) == state;
}
/**
@@ -101,7 +136,6 @@ public interface TraceMemoryOperations {
/**
* Set the state of memory over a given time and address range
*
* <p>
* Setting state to {@link TraceMemoryState#KNOWN} via this method is not recommended. Setting
* bytes will automatically update the state accordingly.
@@ -131,7 +165,6 @@ public interface TraceMemoryOperations {
/**
* Get the state of memory at a given snap and address
*
* <p>
* If the location's state has not been set, the result is {@code null}, which implies
* {@link TraceMemoryState#UNKNOWN}.
@@ -153,14 +186,15 @@ public interface TraceMemoryOperations {
/**
* Get the entry recording the most recent state at the given snap and address
*
* <p>
* The entry includes the entire entry at that snap. Parts occluded by more recent snaps are not
* subtracted from the entry's address range.
* The entry may include more addresses and snaps than requested. The address range is largely
* circumstantial, but may be useful if the client is performing multiple queries in a locality.
* The lifespan is more important as it indicates the actual effective snapshot (the lower
* bound) as well as when the next change is (one after the upper bound.)
*
* @param snap the time
* @param address the location
* @return the entry including the entire recorded range
* @return the most-recent entry
*/
Entry<TraceAddressSnapRange, TraceMemoryState> getMostRecentStateEntry(long snap,
Address address);
@@ -176,6 +210,36 @@ public interface TraceMemoryOperations {
Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
Address address);
/**
* Built-in predicates for filtering/testing state entries
* <p>
* Many methods accept an unrestricted {@link Predicate} on {@link TraceMemoryState}. However,
* use of these built-ins is strongly recommended for two reasons: 1) They handle conventional
* cases, e.g., null representing {@link TraceMemoryState#UNKNOWN}. 2) Caching depends on the
* implementation being able to recognize the identity of the predicate. This generally cannot
* be done with lambda methods.
*/
enum StatePredicate implements Predicate<TraceMemoryState> {
IS_KNOWN {
@Override
public boolean test(TraceMemoryState state) {
return state == TraceMemoryState.KNOWN;
}
},
IS_ERROR {
@Override
public boolean test(TraceMemoryState state) {
return state == TraceMemoryState.ERROR;
}
},
IS_KNOWN_OR_ERROR {
@Override
public boolean test(TraceMemoryState state) {
return state != null && state != TraceMemoryState.UNKNOWN;
}
}
}
/**
* Get the entry recording the most recent state since the given snap within the given range
* that satisfies a given predicate, following schedule forks
@@ -183,13 +247,13 @@ public interface TraceMemoryOperations {
* @param snap the latest time to consider
* @param range the range of addresses
* @param predicate a predicate on the state
* @return the most-recent entry
* @return the most-recent entry or null
*/
Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
AddressRange range, Predicate<TraceMemoryState> predicate);
/**
* Get at least the subset of addresses having state satisfying the given predicate
* Get at least the intersection of addresses having state satisfying the given predicate
*
* @param snap the time
* @param set the set to examine
@@ -203,19 +267,16 @@ public interface TraceMemoryOperations {
}
/**
* Get at least the subset of addresses having state satisfying the given predicate
*
* Get at least the intersection of addresses having state satisfying the given predicate
* <p>
* The implementation may provide a larger view than requested, but within the requested set,
* The implementation may provide a larger set than requested, but within the requested set,
* only ranges satisfying the predicate may be present. Use
* {@link AddressSetView#intersect(AddressSetView)} with {@code set} if a strict subset is
* {@link AddressSetView#intersect(AddressSetView)} with {@code set} if a strict intersection is
* required.
*
* <p>
* Because {@link TraceMemoryState#UNKNOWN} is not explicitly stored in the map, to compute the
* set of {@link TraceMemoryState#UNKNOWN} addresses, use the predicate
* {@code state -> state != null && state != TraceMemoryState.UNKNOWN} and subtract the result
* from {@code set}.
* {@link StatePredicate#IS_KNOWN_OR_ERROR} and subtract the result from {@code set}.
*
* @param span the range of time
* @param set the set to examine
@@ -227,7 +288,6 @@ public interface TraceMemoryOperations {
/**
* Get the addresses having state satisfying the given predicate
*
* <p>
* The implementation may provide a view that updates with changes. Behavior is not well defined
* for predicates testing for {@link TraceMemoryState#UNKNOWN}.
@@ -241,7 +301,6 @@ public interface TraceMemoryOperations {
/**
* Get the addresses having state satisfying the given predicate at any time in the specified
* lifespan
*
* <p>
* The implementation may provide a view that updates with changes. Behavior is not well defined
* for predicates testing for {@link TraceMemoryState#UNKNOWN} .
@@ -250,12 +309,10 @@ public interface TraceMemoryOperations {
* @param predicate a predicate on state to search for
* @return the address set
*/
AddressSetView getAddressesWithState(Lifespan lifespan,
Predicate<TraceMemoryState> predicate);
AddressSetView getAddressesWithState(Lifespan lifespan, Predicate<TraceMemoryState> predicate);
/**
* Break a range of addresses into smaller ranges each mapped to its state at the given snap
*
* Get all the entries covering the given range effective at the given snap
* <p>
* Note that {@link TraceMemoryState#UNKNOWN} entries will not appear in the result. Gaps in the
* returned entries are implied to be {@link TraceMemoryState#UNKNOWN}.
@@ -276,31 +333,24 @@ public interface TraceMemoryOperations {
*/
default boolean isKnown(long snap, AddressRange range) {
Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> states = getStates(snap, range);
if (states.isEmpty()) {
return false;
}
if (states.size() != 1) {
return false;
}
AddressRange entryRange = states.iterator().next().getKey().getRange();
if (!entryRange.contains(range.getMinAddress()) ||
!entryRange.contains(range.getMaxAddress())) {
return false;
}
return true;
return isStateEntirely(range, states, TraceMemoryState.KNOWN);
}
/**
* Break a range of addresses into smaller ranges each mapped to its most recent state at the
* given time
*
* Get all the entries covering the given range, effective at or extending as "most recent" to
* the given snap.
* <p>
* Typically {@code within} is the box whose width is the address range to break down and whose
* height is from "negative infinity" to the "current" snap.
*
* Typically {@code within} is the box whose width is the address range and whose height is
* {@link Lifespan#since(long)} a desired snap. Queries that respect forking should only extend
* as far back as the most recent forked snapshot.
* <p>
* In this context, "most recent" means the latest state other than
* {@link TraceMemoryState#UNKNOWN}.
* {@link TraceMemoryState#UNKNOWN}. The entries returned <em>can</em> overlap. The rule is that
* {@link TraceMemoryState#KNOWN} entries may <em>not</em> overlap one another. They are split
* and truncated so that they extend as far as possible into the future without overlapping. A
* {@link TraceMemoryState#ERROR} entry can overlap a less-recent {@link TraceMemoryState#KNOWN}
* entry, but not a more-recent one. This is to ensure the most-recent-known values can still be
* obtained when the most recent attempt to read bytes resulted in an error.
*
* @param within a box intersecting entries to consider
* @return an iterable over the snap ranges and states
@@ -319,7 +369,6 @@ public interface TraceMemoryOperations {
/**
* Write bytes at the given snap and address
*
* <p>
* This will attempt to read {@link ByteBuffer#remaining()} bytes starting at
* {@link ByteBuffer#position()} from the source buffer {@code buf} and write them into memory
@@ -336,7 +385,6 @@ public interface TraceMemoryOperations {
/**
* Read the most recent bytes from the given snap and address
*
* <p>
* This will attempt to read {@link ByteBuffer#remaining()} of the most recent bytes from memory
* at the specified time and location and write them into the destination buffer {@code buf}
@@ -353,7 +401,6 @@ public interface TraceMemoryOperations {
/**
* Read the most recent bytes from the given snap and address, following schedule forks
*
* <p>
* This behaves similarly to {@link #getBytes(long, Address, ByteBuffer)}, except it checks for
* the {@link TraceMemoryState#KNOWN} state among each involved snap range and reads the
@@ -384,14 +431,12 @@ public interface TraceMemoryOperations {
/**
* Remove bytes from the given time and location
*
* <p>
* This deletes all observed bytes from the given address through length at the given snap. If
* there were no observations in the range at exactly the given snap, this has no effect. If
* there were, then those observations are removed. The next time those bytes are read, they
* will have a value from a previous snap, or no value at all. The affected region's state is
* also deleted, i.e., set to {@code null}, implying {@link TraceMemoryState#UNKNOWN}.
*
* also deleted, i.e., set to {@link TraceMemoryState#UNKNOWN}.
* <p>
* Note, use of this method is discouraged. The more observations within the same range that
* follow the deleted observation, the more expensive this operation typically is, since all of
@@ -405,7 +450,6 @@ public interface TraceMemoryOperations {
/**
* Get a view of a particular snap as a memory buffer
*
* <p>
* The bytes read by this buffer are the most recent bytes written before the given snap
*
@@ -430,7 +474,6 @@ public interface TraceMemoryOperations {
/**
* Find the internal storage block that most-recently defines the value at the given snap and
* address, and return the block's snap.
*
* <p>
* This method reveals portions of the internal storage so that clients can optimize difference
* computations by eliminating corresponding ranges defined by the same block. If the underlying
@@ -444,7 +487,6 @@ public interface TraceMemoryOperations {
/**
* Get the block size used by internal storage.
*
* <p>
* This method reveals portions of the internal storage so that clients can optimize searches.
* If the underlying implementation cannot answer this question, this returns 0.
@@ -455,7 +497,6 @@ public interface TraceMemoryOperations {
/**
* Optimize storage space
*
* <p>
* This gives the implementation an opportunity to clean up garbage, apply compression, etc., in
* order to best use the storage space. Because memory observations can be sparse, a trace's
@@ -466,7 +507,6 @@ public interface TraceMemoryOperations {
/**
* Set the state of a given register at a given time
*
* <p>
* Setting state to {@link TraceMemoryState#KNOWN} via this method is not recommended. Setting
* bytes will automatically update the state accordingly.
@@ -480,7 +520,6 @@ public interface TraceMemoryOperations {
/**
* Set the state of a given register at a given time
*
* <p>
* Setting state to {@link TraceMemoryState#KNOWN} via this method is not recommended. Setting
* bytes will automatically update the state accordingly.
@@ -519,8 +558,7 @@ public interface TraceMemoryOperations {
}
/**
* Break the register's range into smaller ranges each mapped to its state at the given snap
*
* Get all the entries covering the given register at the given snap
* <p>
* If the register is memory mapped, this will delegate to the appropriate space.
*
@@ -533,8 +571,7 @@ public interface TraceMemoryOperations {
long snap, Register register);
/**
* Break the register's range into smaller ranges each mapped to its state at the given snap
*
* Get all the entries covering the given register at the given snap
* <p>
* If the register is memory mapped, this will delegate to the appropriate space.
*
@@ -558,11 +595,9 @@ public interface TraceMemoryOperations {
/**
* Set the value of a register at the given snap
*
* <p>
* If the register is memory mapped, this will delegate to the appropriate space. In those
* cases, the assignment affects all threads.
*
* <p>
* <b>IMPORTANT:</b> The trace database cannot track the state ({@link TraceMemoryState#KNOWN},
* etc.) with per-bit accuracy. It only has byte precision. If the given value specifies, e.g.,
@@ -589,11 +624,9 @@ public interface TraceMemoryOperations {
/**
* Write bytes at the given snap and register address
*
* <p>
* If the register is memory mapped, this will delegate to the appropriate space. In those
* cases, the assignment affects all threads.
*
* <p>
* Note that bit-masked registers are not properly heeded. If the caller wishes to preserve
* non-masked bits, it must first retrieve the current value and combine it with the desired
@@ -611,7 +644,6 @@ public interface TraceMemoryOperations {
/**
* Get the most-recent value of a given register at the given time
*
* <p>
* If the register is memory mapped, this will delegate to the appropriate space.
*
@@ -624,7 +656,6 @@ public interface TraceMemoryOperations {
/**
* Get the most-recent value of a given register at the given time
*
* <p>
* If the register is memory mapped, this will delegate to the appropriate space.
*
@@ -637,8 +668,7 @@ public interface TraceMemoryOperations {
}
/**
* Get the most-recent value of a given register at the given time
*
* Get the most-recent value of a given register at the given time, following schedule forks
* <p>
* If the register is memory mapped, this will delegate to the appropriate space.
*
@@ -651,7 +681,6 @@ public interface TraceMemoryOperations {
/**
* Get the most-recent value of a given register at the given time, following schedule forks
*
* <p>
* If the register is memory mapped, this will delegate to the appropriate space.
*
@@ -665,7 +694,6 @@ public interface TraceMemoryOperations {
/**
* Get the most-recent bytes of a given register at the given time
*
* <p>
* If the register is memory mapped, this will delegate to the appropriate space.
*
@@ -679,7 +707,6 @@ public interface TraceMemoryOperations {
/**
* Get the most-recent bytes of a given register at the given time
*
* <p>
* If the register is memory mapped, this will delegate to the appropriate space.
*
@@ -702,10 +729,8 @@ public interface TraceMemoryOperations {
/**
* Remove a value from the given time and register
*
* <p>
* If the register is memory mapped, this will delegate to the appropriate space.
*
* <p>
* <b>IMPORANT:</b> The trace database cannot track the state ({@link TraceMemoryState#KNOWN},
* etc.) with per-bit accuracy. It only has byte precision. If the given register specifies,

View File

@@ -4,9 +4,9 @@
* 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.
@@ -15,17 +15,42 @@
*/
package ghidra.trace.model.memory;
import java.util.stream.Stream;
public enum TraceMemoryState {
/**
* The value was not observed at the snapshot
*/
UNKNOWN,
UNKNOWN(true, false),
/**
* The value was observed at the snapshot
*/
KNOWN,
KNOWN(false, true),
/**
* The value could not be observed at the snapshot
*/
ERROR,
ERROR(false, false);
public static final TraceMemoryState IMPLIED_BY_NULL =
Stream.of(values()).filter(TraceMemoryState::impliedByNull).findFirst().orElseThrow();
public static TraceMemoryState orImplied(TraceMemoryState s) {
return s == null ? IMPLIED_BY_NULL : s;
}
private final boolean impliedByNull;
private final boolean truncates;
private TraceMemoryState(boolean impliedByNull, boolean truncates) {
this.impliedByNull = impliedByNull;
this.truncates = truncates;
}
public boolean impliedByNull() {
return impliedByNull;
}
public boolean truncates() {
return truncates;
}
}

View File

@@ -4,9 +4,9 @@
* 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.
@@ -22,13 +22,13 @@ import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import org.junit.Before;
import org.junit.Test;
import org.junit.*;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
@Ignore
public class DemoFieldsTest extends AbstractGhidraHeadedIntegrationTest {
@Before
public void checkNotBatch() {

View File

@@ -33,8 +33,7 @@ import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRangeImpl;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.context.DBTraceRegisterContextManager;
@@ -1089,4 +1088,27 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest
TraceSleighUtils.evaluate("r2", tb.trace, 1, thread, 0));
}
}
@Test
public void testReadUninit() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "Toy:BE:64:default")) {
initTrace(tb, """
r0 = 0x00600000;
*:8 r0 = 0x1122334455667788;
""",
List.of());
Writer writer = createWriter(tb.host, 0);
PcodeEmulator emu = createEmulator(tb.host, writer);
AddressSpace space = emu.getLanguage().getDefaultDataSpace();
// Cause a gap in "known-but-uninitialized"
assertArrayEquals(bytes(0x33, 0x44, 0x55, 0x66),
emu.getSharedState().getVar(space, 0x00600002, 4, false, Reason.EXECUTE_READ));
// Now validate the read across that gap
assertArrayEquals(bytes(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88),
emu.getSharedState().getVar(space, 0x00600000, 8, false, Reason.EXECUTE_READ));
}
}
}

View File

@@ -467,15 +467,15 @@ public class ToyDBTraceBuilder implements AutoCloseable {
}
/**
* Create an address-span box in the trace's default space with a singleton snap
* Create an address-span box in the trace's default space with a span
*
* @param snap the snap
* @param span the span
* @param start the start address offset
* @param end the end address offset
* @return the box
*/
public TraceAddressSnapRange srange(long snap, long start, long end) {
return new ImmutableTraceAddressSnapRange(addr(start), addr(end), snap, snap);
public TraceAddressSnapRange srange(Lifespan span, long start, long end) {
return new ImmutableTraceAddressSnapRange(addr(start), addr(end), span);
}
/**

View File

@@ -23,7 +23,9 @@ import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.Map.Entry;
import org.junit.Ignore;
import org.junit.Test;
import db.DBHandle;
@@ -36,6 +38,7 @@ import ghidra.trace.database.DBTrace;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.memory.TraceMemoryOperations.StatePredicate;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.thread.TraceThreadManager;
import ghidra.util.SystemUtilities;
@@ -336,17 +339,18 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
collectAsMap(memory.getMostRecentStates(2, b.range(0x2800, 0x9000))));
expected = new HashMap<>();
expected.put(b.srange(3, 0x4000, 0x4fff), TraceMemoryState.KNOWN);
expected.put(b.srange(3, 0x5000, 0x6000), TraceMemoryState.ERROR);
expected.put(b.srange(3, 0x6001, 0x7000), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.at(3), 0x4000, 0x4800), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(3), 0x4801, 0x4fff), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(3), 0x5000, 0x6000), TraceMemoryState.ERROR);
expected.put(b.srange(Lifespan.nowOn(3), 0x6001, 0x7000), TraceMemoryState.KNOWN);
assertEquals(expected,
collectAsMap(memory.getMostRecentStates(3, b.range(0x2800, 0x9000))));
expected = new HashMap<>();
expected.put(b.srange(4, 0x3000, 0x4800), TraceMemoryState.KNOWN);
expected.put(b.srange(3, 0x4801, 0x4fff), TraceMemoryState.KNOWN);
expected.put(b.srange(3, 0x5000, 0x6000), TraceMemoryState.ERROR);
expected.put(b.srange(3, 0x6001, 0x7000), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(4), 0x3000, 0x4800), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(3), 0x4801, 0x4fff), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(3), 0x5000, 0x6000), TraceMemoryState.ERROR);
expected.put(b.srange(Lifespan.nowOn(3), 0x6001, 0x7000), TraceMemoryState.KNOWN);
assertEquals(expected,
collectAsMap(memory.getMostRecentStates(4, b.range(0x2800, 0x9000))));
assertEquals(expected,
@@ -373,14 +377,14 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
expected = new AddressSet();
expected.add(b.range(0x3000, 0x3800));
expected.add(b.range(0x3c00, 0x4800));
result = memory.getAddressesWithState(4, set, state -> state == TraceMemoryState.KNOWN);
result = memory.getAddressesWithState(4, set, StatePredicate.IS_KNOWN);
assertEquals(expected, set.intersect(result));
expected = new AddressSet();
expected.add(b.range(0x4000, 0x4800));
expected.add(b.range(0x4c00, 0x4fff));
expected.add(b.range(0x6001, 0x6100));
result = memory.getAddressesWithState(3, set, state -> state == TraceMemoryState.KNOWN);
result = memory.getAddressesWithState(3, set, StatePredicate.IS_KNOWN);
assertEquals(expected, set.intersect(result));
// Test gaps
@@ -388,7 +392,7 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
expected.add(b.range(0x2800, 0x3800));
expected.add(b.range(0x3c00, 0x3fff));
expected.add(b.range(0x8000, 0x9000));
result = memory.getAddressesWithState(3, set, state -> true);
result = memory.getAddressesWithState(3, set, StatePredicate.IS_KNOWN_OR_ERROR);
assertEquals(expected, set.subtract(result));
}
@@ -402,9 +406,9 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
Map<TraceAddressSnapRange, TraceMemoryState> expected;
expected = new HashMap<>();
expected.put(b.srange(3, 0x4000, 0x4fff), TraceMemoryState.KNOWN);
expected.put(b.srange(3, 0x5000, 0x6000), TraceMemoryState.ERROR);
expected.put(b.srange(3, 0x6001, 0x7000), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(3), 0x4000, 0x4fff), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(3), 0x5000, 0x6000), TraceMemoryState.ERROR);
expected.put(b.srange(Lifespan.nowOn(3), 0x6001, 0x7000), TraceMemoryState.KNOWN);
assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x8000))));
}
@@ -438,7 +442,7 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
// verify the corresponding change in state;
Map<TraceAddressSnapRange, TraceMemoryState> expected;
expected = new HashMap<>();
expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(3), 0x4000, 0x4003), TraceMemoryState.KNOWN);
assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000))));
ByteBuffer read = b.buf(-1, -2, -3, -4); // Verify zeros actually written
@@ -459,7 +463,7 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
// verify the corresponding change in state;
Map<TraceAddressSnapRange, TraceMemoryState> expected;
expected = new HashMap<>();
expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(3), 0x4000, 0x4003), TraceMemoryState.KNOWN);
assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000))));
ByteBuffer read = ByteBuffer.allocate(4);
@@ -534,10 +538,10 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
assertEquals(expected, collectAsMap(memory.getStates(6, b.range(0x3000, 0x5000))));
expected = new HashMap<>();
expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.span(3, 4), 0x4000, 0x4003), TraceMemoryState.KNOWN);
assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000))));
expected = new HashMap<>();
expected.put(b.srange(5, 0x4000, 0x4003), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(5), 0x4000, 0x4003), TraceMemoryState.KNOWN);
assertEquals(expected, collectAsMap(memory.getStates(5, b.range(0x3000, 0x5000))));
ByteBuffer read = b.buf(0, 0, 0, 0);
@@ -735,6 +739,16 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
b.buf(-1, -1, -1), true, TaskMonitor.DUMMY));
}
protected void dumpStates() {
System.err.println("STATES");
for (DBTraceMemorySpace space : memory.getActiveSpaces()) {
for (Entry<TraceAddressSnapRange, TraceMemoryState> entry : space.stateMapSpace
.entries()) {
System.err.println(" " + entry);
}
}
}
@Test
public void testRemoveBytes() {
try (Transaction tx = b.startTransaction()) {
@@ -783,9 +797,9 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
// Check overall effect on state
Map<TraceAddressSnapRange, TraceMemoryState> expected;
expected = new HashMap<>();
expected.put(b.srange(2, 0x47fe, 0x47fe), TraceMemoryState.KNOWN);
expected.put(b.srange(4, 0x4800, 0x4803), TraceMemoryState.KNOWN);
expected.put(b.srange(3, 0x4804, 0x4805), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(2), 0x47fe, 0x47fe), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(4), 0x4800, 0x4803), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(3), 0x4804, 0x4805), TraceMemoryState.KNOWN);
assertEquals(expected,
collectAsMap(memory.getMostRecentStates(6, b.range(0x4700, 0x4900))));
}
@@ -814,7 +828,7 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
// verify the corresponding change in state;
Map<TraceAddressSnapRange, TraceMemoryState> expected;
expected = new HashMap<>();
expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(3), 0x4000, 0x4003), TraceMemoryState.KNOWN);
assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000))));
ByteBuffer read = ByteBuffer.allocate(4);
@@ -838,7 +852,7 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
// verify the corresponding change in state;
Map<TraceAddressSnapRange, TraceMemoryState> expected;
expected = new HashMap<>();
expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(3), 0x4000, 0x4003), TraceMemoryState.KNOWN);
assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000))));
tx.abort();
@@ -866,7 +880,7 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
// verify the corresponding change in state;
Map<TraceAddressSnapRange, TraceMemoryState> expected;
expected = new HashMap<>();
expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(3), 0x4000, 0x4003), TraceMemoryState.KNOWN);
assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000))));
}
b.trace.undo();
@@ -894,7 +908,7 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
// verify the corresponding change in state;
Map<TraceAddressSnapRange, TraceMemoryState> expected;
expected = new HashMap<>();
expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(3), 0x4000, 0x4003), TraceMemoryState.KNOWN);
assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000))));
}
b.trace.undo();
@@ -917,7 +931,7 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
assertEquals(1, getBufferRecordCount());
expected = new HashMap<>();
expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN);
expected.put(b.srange(Lifespan.nowOn(3), 0x4000, 0x4003), TraceMemoryState.KNOWN);
assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000))));
read.position(0);
@@ -1003,6 +1017,7 @@ public abstract class AbstractDBTraceMemoryManagerMemoryTest
* @throws Exception because
*/
@Test
@Ignore("Developer's desk")
public void testReplicateClassCastExceptionScenario() throws Exception {
final int TICKS = 100_000;

View File

@@ -22,7 +22,6 @@ import java.util.function.Consumer;
import java.util.stream.Stream;
import db.DBRecord;
import generic.util.FlattenedIterator;
import generic.util.PeekableIterator;
import ghidra.util.LockHold;
import ghidra.util.database.*;
@@ -44,9 +43,9 @@ public abstract class AbstractConstraintsTree<
protected final DBCachedObjectStore<DR> dataStore;
protected final DBCachedObjectStore<NR> nodeStore;
protected final Map<Long, Collection<DR>> cachedDataChildren = Collections.synchronizedMap(
protected final Map<Long, List<DR>> cachedDataChildren = Collections.synchronizedMap(
new FixedSizeHashMap<>(MAX_CACHE_ENTRIES));
protected final Map<Long, Collection<NR>> cachedNodeChildren = Collections.synchronizedMap(
protected final Map<Long, List<NR>> cachedNodeChildren = Collections.synchronizedMap(
new FixedSizeHashMap<>(MAX_CACHE_ENTRIES));
protected NR root;
@@ -95,8 +94,8 @@ public abstract class AbstractConstraintsTree<
* @return a collection of the children
*/
protected Collection<NR> getNodeChildrenOf(NR parent) {
return cachedNodeChildren.computeIfAbsent(parent.getKey(),
k -> new ArrayList<>(getNodeChildrenOf(k)));
return Collections.unmodifiableList(cachedNodeChildren.computeIfAbsent(parent.getKey(),
k -> new ArrayList<>(getNodeChildrenOf(k))));
}
/**
@@ -121,9 +120,9 @@ public abstract class AbstractConstraintsTree<
* @param parent the parent node
* @return a collection of the children
*/
protected Collection<DR> getDataChildrenOf(NR parent) {
return cachedDataChildren.computeIfAbsent(parent.getKey(),
k -> new ArrayList<>(getDataChildrenOf(k)));
protected List<DR> getDataChildrenOf(NR parent) {
return Collections.unmodifiableList(cachedDataChildren.computeIfAbsent(parent.getKey(),
k -> new ArrayList<>(getDataChildrenOf(k))));
}
/**
@@ -209,51 +208,34 @@ public abstract class AbstractConstraintsTree<
}
protected abstract VisitResult visitData(NR parent, DR d, boolean included);
protected Iterator<NR> iterateNodes(Stream<NR> nodes) {
return nodes.iterator();
}
protected Iterator<DR> iterateData(Stream<DR> data) {
return data.iterator();
}
}
protected VisitResult visit(Q query, TreeRecordVisitor visitor, boolean ordered) {
return visit(null, root, query, visitor, ordered);
}
protected VisitResult visit(NR parent, NR node, Q query, TreeRecordVisitor visitor,
boolean ordered) {
QueryInclusion inclusion =
query == null ? QueryInclusion.ALL : query.testNode(node.getShape());
VisitResult r = visitor.beginNode(parent, node, inclusion);
if (r != VisitResult.DESCEND) {
return r;
}
if (node.getType().isLeaf()) {
List<DR> data = new ArrayList<>(getDataChildrenOf(node));
if (query != null && ordered) {
data.sort(Comparator.comparing(DR::getBounds, query.getBoundsComparator()));
protected VisitResult visitLeaf(NR parent, NR node, Q query, TreeRecordVisitor visitor,
boolean ordered, QueryInclusion inclusion) {
Stream<DR> data = getDataChildrenOf(node).stream();
if (query != null) {
data = data.filter(d -> query.testData(d.getShape()));
if (ordered) {
data =
data.sorted(Comparator.comparing(DR::getBounds, query.getBoundsComparator()));
}
for (DR d : data) {
if (query != null && ordered && query.terminateEarlyData(d.getShape())) {
break;
}
boolean included = query == null || query.testData(d.getShape());
r = visitor.visitData(node, d, included);
if (r == VisitResult.ASCEND) {
return visitor.endNode(parent, node, inclusion);
}
if (r == VisitResult.TERMINATE) {
visitor.endNode(parent, node, inclusion);
return r;
}
}
return visitor.endNode(parent, node, inclusion);
}
assert node.getType().isDirectory();
List<NR> nodes = new ArrayList<>(getNodeChildrenOf(node));
if (query != null && ordered) {
nodes.sort(Comparator.comparing(NR::getBounds, query.getBoundsComparator()));
}
for (NR n : nodes) {
if (query != null && ordered && query.terminateEarlyNode(n.getShape())) {
break;
}
r = visit(node, n, query, visitor, ordered);
Iterator<DR> dit = visitor.iterateData(data);
while (dit.hasNext()) {
DR d = dit.next();
VisitResult r = visitor.visitData(node, d, true);
if (r == VisitResult.ASCEND) {
return visitor.endNode(parent, node, inclusion);
}
@@ -265,29 +247,66 @@ public abstract class AbstractConstraintsTree<
return visitor.endNode(parent, node, inclusion);
}
protected VisitResult visitDirectory(NR parent, NR node, Q query, TreeRecordVisitor visitor,
boolean ordered, QueryInclusion inclusion) {
Stream<NR> nodes = getNodeChildrenOf(node).stream();
if (query != null) {
nodes = nodes.filter(n -> query.testNode(n.getShape()) != QueryInclusion.NONE);
if (ordered) {
nodes =
nodes.sorted(Comparator.comparing(NR::getBounds, query.getBoundsComparator()));
}
}
Iterator<NR> nit = visitor.iterateNodes(nodes);
while (nit.hasNext()) {
NR n = nit.next();
VisitResult r = visit(node, n, query, visitor, ordered);
if (r == VisitResult.ASCEND) {
return visitor.endNode(parent, node, inclusion);
}
if (r == VisitResult.TERMINATE) {
visitor.endNode(parent, node, inclusion);
return r;
}
}
return visitor.endNode(parent, node, inclusion);
}
protected VisitResult visit(NR parent, NR node, Q query, TreeRecordVisitor visitor,
boolean ordered) {
QueryInclusion inclusion =
query == null ? QueryInclusion.ALL : query.testNode(node.getShape());
VisitResult r = visitor.beginNode(parent, node, inclusion);
if (r != VisitResult.DESCEND) {
return r;
}
if (node.getType().isLeaf()) {
return visitLeaf(parent, node, query, visitor, ordered, inclusion);
}
assert node.getType().isDirectory();
return visitDirectory(parent, node, query, visitor, ordered, inclusion);
}
protected Iterator<DR> iterator(Q query) {
return iterator(root, query);
}
protected Iterator<DR> iterator(NR node, Q query) {
protected Stream<DR> stream(NR node, Q query) {
if (node.getType().isLeaf()) {
List<DR> data = new ArrayList<>(node.getChildCount());
for (DR d : getDataChildrenOf(node)) {
if (query != null && !query.testData(d.getShape())) {
continue;
}
data.add(d);
Collection<DR> data = getDataChildrenOf(node);
if (query == null) {
return data.stream();
}
return data.iterator();
return data.stream().filter(d -> query.testData(d.getShape()));
}
List<NR> nodes = new ArrayList<>(node.getChildCount());
for (NR n : getNodeChildrenOf(node)) {
if (query != null && query.testNode(n.getShape()) == QueryInclusion.NONE) {
continue;
}
nodes.add(n);
}
return FlattenedIterator.start(nodes.iterator(), n -> iterator(n, query));
Collection<NR> nodes = getNodeChildrenOf(node);
Stream<NR> passing = query == null ? nodes.stream()
: nodes.stream().filter(n -> query.testNode(n.getShape()) != QueryInclusion.NONE);
return passing.flatMap(n -> stream(n, query));
}
protected Iterator<DR> iterator(NR node, Q query) {
return stream(node, query).iterator();
}
protected Iterator<DR> orderedIterator(Q query) {
@@ -574,7 +593,7 @@ public abstract class AbstractConstraintsTree<
}
protected <R> void doRemoveFromCachedChildren(long parentKey, R child,
Map<Long, Collection<R>> cache) {
Map<Long, List<R>> cache) {
Collection<R> children = cache.get(parentKey);
if (children == null) {
return;
@@ -585,7 +604,7 @@ public abstract class AbstractConstraintsTree<
}
protected <R> void doAddToCachedChildren(long parentKey, R child,
Map<Long, Collection<R>> cache) {
Map<Long, List<R>> cache) {
Collection<R> children = cache.get(parentKey);
if (children == null) {
return;
@@ -596,7 +615,7 @@ public abstract class AbstractConstraintsTree<
}
protected <R extends DBTreeRecord<?, ?>> void doSetParentKey(R child, long key,
Map<Long, Collection<R>> cache) {
Map<Long, List<R>> cache) {
doRemoveFromCachedChildren(child.getParentKey(), child, cache);
child.setParentKey(key);
doAddToCachedChildren(key, child, cache);
@@ -721,6 +740,16 @@ public abstract class AbstractConstraintsTree<
}
return VisitResult.NEXT;
}
@Override
protected Iterator<NR> iterateNodes(Stream<NR> nodes) {
return nodes.toList().iterator();
}
@Override
protected Iterator<DR> iterateData(Stream<DR> data) {
return data.toList().iterator();
}
}, false);
}
@@ -876,7 +905,7 @@ public abstract class AbstractConstraintsTree<
public void checkIntegrity() {
synchronized (cachedDataChildren) {
// Before we visit, integrity check that cache. Visiting will affect cache.
for (Entry<Long, Collection<DR>> ent : cachedDataChildren.entrySet()) {
for (Entry<Long, List<DR>> ent : cachedDataChildren.entrySet()) {
Set<DR> databasedChildren = new TreeSet<>(Comparator.comparing(DR::getKey));
// NOTE: Bypass the cache by using the variant with a key parameter
databasedChildren.addAll(getDataChildrenOf(ent.getKey()));
@@ -889,7 +918,7 @@ public abstract class AbstractConstraintsTree<
}
}
synchronized (cachedNodeChildren) {
for (Entry<Long, Collection<NR>> ent : cachedNodeChildren.entrySet()) {
for (Entry<Long, List<NR>> ent : cachedNodeChildren.entrySet()) {
Set<NR> databasedChildren = new TreeSet<>(Comparator.comparing(NR::getKey));
// NOTE: Bypass the cache by using the variant with a key parameter
databasedChildren.addAll(getNodeChildrenOf(ent.getKey()));

View File

@@ -149,15 +149,21 @@ public abstract class AbstractConstraintsTreeSpatialMap<
public Object[] toArray() {
try (LockHold hold = LockHold.lock(tree.dataStore.readLock())) {
// Note, computing size requires a traversal. Bad idea, I think.
List<Entry<DS, T>> result = new ArrayList<>();
tree.visitAllData(query, new ToListConsumer<>(result) {
@Override
protected Entry<DS, T> transformed(DR t) {
/**
* One one hand, traversing twice (one to compute the size, and again to
* retrieve the elements) is not great. On the other, resizing the array list
* many times is also not great. Which is worse? IDK. LATER: Take some
* measurements with large traces.
*/
int size = AbstractConstraintsTreeSpatialMap.this.size();
Object[] a = new Object[size];
ToArrayConsumer<Object, DR, Object> consumer = new ToArrayConsumer<>(a) {
protected Object transformed(DR t) {
return t.asEntry();
}
}, false);
return result.toArray();
};
};
tree.visitAllData(query, consumer, false);
return a;
}
}

View File

@@ -4,9 +4,9 @@
* 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.
@@ -123,7 +123,7 @@ public abstract class DBTreeDataRecord<DS extends BoundedShape<NS>, NS extends B
protected abstract T getRecordValue();
protected RecordEntry<DS, NS, T> asEntry() {
return entry;
return Objects.requireNonNull(entry);
}
@Override

View File

@@ -59,6 +59,15 @@ public abstract class AbstractRectangle2DQuery<
return factory.create(r1, r2, direction);
}
protected static <X, Y, NS extends Rectangle2D<X, Y, NS>,
Q extends AbstractRectangle2DQuery<X, Y, ?, NS, Q>> Q intersectingEnclosed(
NS rect, Rectangle2DDirection direction, QueryFactory<NS, Q> factory) {
Rectangle2D<X, Y, ?> full = rect.getSpace().getFull();
NS r1 = rect.immutable(full.getX1(), rect.getX2(), rect.getY1(), full.getY2());
NS r2 = rect.immutable(rect.getX1(), full.getX2(), full.getY1(), rect.getY2());
return factory.create(r1, r2, direction);
}
protected static <X, Y, NS extends Rectangle2D<X, Y, NS>,
Q extends AbstractRectangle2DQuery<X, Y, ?, NS, Q>> Q equalTo(
NS rect, Rectangle2DDirection direction, QueryFactory<NS, Q> factory) {

View File

@@ -44,7 +44,7 @@ public class DbgMsgTracer {
}
private void doMsg(Object obj, String message) {
Msg.info(obj, "%s %s".formatted(prefixStack(), message));
Msg.info(obj, "%s %s %s".formatted(Thread.currentThread(), prefixStack(), message));
}
public record CallRec(DbgMsgTracer tracer, Object obj, String name, long start)
@@ -53,7 +53,15 @@ public class DbgMsgTracer {
public void close() {
long stop = System.currentTimeMillis();
long elapsedMs = stop - start;
tracer.doMsg(obj, "%d: (EXITED) after %f s".formatted(stop, elapsedMs / 1000.0));
String extra;
if (elapsedMs > 100) {
extra = " (LONG)";
}
else {
extra = "";
}
tracer.doMsg(obj,
"%d: (EXITED) after %f s%s".formatted(stop, elapsedMs / 1000.0, extra));
CallRec popped = tracer.stack.pop();
assert popped == this;
}

View File

@@ -19,6 +19,8 @@ import java.util.*;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import ghidra.util.MathUtilities;
/**
* A class to break a range of addresses into 'chunks' of a give size. This is useful to break-up
* processing of large swaths of addresses, such as when performing work in a background thread.
@@ -29,13 +31,14 @@ public class AddressRangeChunker implements Iterable<AddressRange> {
private Address end;
private Address nextStartAddress;
private int chunkSize;
private long chunkSizeUnsigned;
public AddressRangeChunker(AddressRange range, int chunkSize) throws IllegalArgumentException {
this(range.getMinAddress(), range.getMaxAddress(), chunkSize);
public AddressRangeChunker(AddressRange range, long chunkSizeUnsigned)
throws IllegalArgumentException {
this(range.getMinAddress(), range.getMaxAddress(), chunkSizeUnsigned);
}
public AddressRangeChunker(Address start, Address end, int chunkSize)
public AddressRangeChunker(Address start, Address end, long chunkSizeUnsigned)
throws IllegalArgumentException {
if (start == null) {
@@ -58,13 +61,13 @@ public class AddressRangeChunker implements Iterable<AddressRange> {
throw new IllegalArgumentException("Address must be in the same address space");
}
if (chunkSize < 1) {
if (chunkSizeUnsigned == 0) {
throw new IllegalArgumentException("Chunk size must be greater than 0");
}
this.end = end;
this.nextStartAddress = start;
this.chunkSize = chunkSize;
this.chunkSizeUnsigned = chunkSizeUnsigned;
}
@Override
@@ -82,15 +85,12 @@ public class AddressRangeChunker implements Iterable<AddressRange> {
return null;
}
long available = end.subtract(nextStartAddress) + 1; // +1 to be inclusive
long availableLess1 = end.subtract(nextStartAddress);
int size = chunkSize;
if (available >= 0 && available < chunkSize) {
size = (int) available;
}
long sizeLess1 = MathUtilities.unsignedMin(chunkSizeUnsigned - 1, availableLess1);
Address currentStart = nextStartAddress;
Address currentEnd = nextStartAddress.add(size - 1); // -1 since inclusive
Address currentEnd = nextStartAddress.addWrap(sizeLess1);
if (currentEnd.compareTo(end) == 0) {
nextStartAddress = null; // no more
}
@@ -111,8 +111,14 @@ public class AddressRangeChunker implements Iterable<AddressRange> {
@Override
public Spliterator<AddressRange> spliterator() {
long countAddrs = end.subtract(nextStartAddress) + 1;
long size = Long.divideUnsigned(countAddrs + chunkSize - 1, chunkSize);
long countAddrsLess1 = end.subtract(nextStartAddress);
// Can't do the (count+size-1)/size thing since count+size may overflow
long size = Long.divideUnsigned(countAddrsLess1, chunkSizeUnsigned) + 1;
if (size <= 0) {
// Known but too big to encode in (signed) long. 0 is actually 2**64.
return Spliterators.spliteratorUnknownSize(iterator(), Spliterator.DISTINCT |
Spliterator.NONNULL | Spliterator.ORDERED | Spliterator.SORTED);
}
return Spliterators.spliterator(iterator(), size,
Spliterator.DISTINCT | Spliterator.NONNULL | Spliterator.ORDERED | Spliterator.SORTED |
Spliterator.SIZED);

View File

@@ -234,7 +234,7 @@ public interface AddressSetView extends Iterable<AddressRange> {
public boolean intersects(AddressSetView addrSet);
/**
* Determine if the start and end range intersects with the specified address set.
* Determine if the start and end range intersects with this address set.
* <p>
* The specified start and end addresses must form a valid range within a single
* {@link AddressSpace}.
@@ -245,6 +245,16 @@ public interface AddressSetView extends Iterable<AddressRange> {
*/
public boolean intersects(Address start, Address end);
/**
* Determine if the range intersects with this address set.
*
* @param range the range
* @return true if the given range intersects this address set.
*/
default public boolean intersects(AddressRange range) {
return intersects(range.getMinAddress(), range.getMaxAddress());
}
/**
* Computes the intersection of this address set with the given address set.
* <p>
@@ -269,6 +279,19 @@ public interface AddressSetView extends Iterable<AddressRange> {
*/
public AddressSet intersectRange(Address start, Address end);
/**
* Computes the intersection of this address set with the given address range.
* <p>
* This method does not modify this address set.
*
* @param range the range
* @return AddressSet a new address set that contains all addresses that are contained in both
* this set and the given range.
*/
default public AddressSet intersectRange(AddressRange range) {
return intersectRange(range.getMinAddress(), range.getMaxAddress());
}
/**
* Computes the union of this address set with the given address set.
* <p>

View File

@@ -4,9 +4,9 @@
* 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.
@@ -18,6 +18,7 @@ package ghidra.program.model.address;
import static org.junit.Assert.*;
import java.util.Iterator;
import java.util.List;
import org.junit.*;
@@ -28,6 +29,7 @@ public class AddressRangeImplTest extends AbstractGenericTest {
/**
* Constructor for AddressRangeImplTest.
*
* @param name
*/
public AddressRangeImplTest() {
@@ -37,7 +39,7 @@ public class AddressRangeImplTest extends AbstractGenericTest {
@Before
public void setUp() throws Exception {
space = new GenericAddressSpace("xx", 32, AddressSpace.TYPE_RAM, 0);
space = new GenericAddressSpace("xx", 64, AddressSpace.TYPE_RAM, 0);
}
@Test
@@ -159,6 +161,37 @@ public class AddressRangeImplTest extends AbstractGenericTest {
assertFalse(it.hasNext());
}
@Test
public void testAddressRangeChunker_GiganticIn2Chunks() {
AddressRangeChunker chunker = new AddressRangeChunker(rng(0, -1), Long.MIN_VALUE);
assertEqualRanges(List.of(rng(0, Long.MAX_VALUE), rng(Long.MIN_VALUE, -1)), chunker);
}
@Test
public void testAddressRangeChunker_GiganticWithSmallChunks_DontExhaust() {
AddressRangeChunker chunker = new AddressRangeChunker(rng(0, -1), 4096);
// 1 is "64th" bit, >>> 12 into 52nd, so 0x 1 with 13 trailing 0s.
assertEquals(0x0010_0000_0000_0000L, chunker.spliterator().estimateSize());
List<AddressRange> first4 = chunker.stream().limit(4).toList();
assertEquals(
List.of(rng(0, 0x0fff), rng(0x1000, 0x1fff), rng(0x2000, 0x2fff), rng(0x3000, 0x3fff)),
first4);
}
@Test
public void testAddressRangeChunker_GiganticWithMaxChunkSize() {
AddressRangeChunker chunker = new AddressRangeChunker(rng(0, -1), -1);
assertEqualRanges(List.of(rng(0, -2), rng(-1, -1)), chunker);
}
@Test
public void testAddressRangeChunker_GiganticWithUnsignedChunkSize() {
AddressRangeChunker chunker = new AddressRangeChunker(rng(0, -1), Long.MIN_VALUE + 1);
assertEqualRanges(
List.of(rng(0, Long.MIN_VALUE), rng(Long.MIN_VALUE + 1, -1)),
chunker);
}
@Test
public void testAddressRangeChunker_NullAddresses() {
try {
@@ -180,13 +213,7 @@ public class AddressRangeImplTest extends AbstractGenericTest {
@Test
public void testAddressRangeChunker_BadChunkSize() {
try {
new AddressRangeChunker(addr(0), addr(1), -1);
Assert.fail("Did not get exception when passing bad chunk size to chunker.");
}
catch (IllegalArgumentException e) {
// good!
}
// NOTE: "Negative" chunk sizes are not possible, because chunkSize is treated unsigned
try {
new AddressRangeChunker(addr(0), addr(1), 0);
@@ -220,8 +247,8 @@ public class AddressRangeImplTest extends AbstractGenericTest {
try {
new AddressRangeChunker(a1, a2, 10);
Assert.fail("Did not get exception when passing addresses from different address "
+ "spaces to chunker.");
Assert.fail("Did not get exception when passing addresses from different address " +
"spaces to chunker.");
}
catch (IllegalArgumentException e) {
// good!
@@ -260,8 +287,8 @@ public class AddressRangeImplTest extends AbstractGenericTest {
assertTrue(
"Address Iterator does not properly enumerate address range: " +
String.format("%s (%d long) -- found %d", r1.toString(), r1.getLength(), addrCount),
addrCount == (size + 1));
String.format("%s (%d long) -- found %d", r1.toString(), r1.getLength(), addrCount),
addrCount == (size + 1));
}
@Test
@@ -281,8 +308,16 @@ public class AddressRangeImplTest extends AbstractGenericTest {
assertTrue("Address Iterator extent does not match end of range", lastAddr.equals(limit));
}
private Address addr(int a) {
private Address addr(long a) {
return new GenericAddress(space, a);
}
private AddressRange rng(long s, long e) {
return new AddressRangeImpl(addr(s), addr(e));
}
private void assertEqualRanges(List<AddressRange> expected, AddressRangeChunker actual) {
List<AddressRange> justEnough = actual.stream().limit(expected.size() + 1).toList();
assertEquals(expected, justEnough);
}
}

View File

@@ -17,7 +17,7 @@ package agent.gdb.rmi;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.junit.Assume.assumeFalse;
import java.nio.ByteBuffer;
import java.util.*;
@@ -247,13 +247,11 @@ public class GdbCommandsTest extends AbstractGdbTraceRmiTest {
quit
""".formatted(PREAMBLE, target));
String importSection = extractOutSection(out, "---Import---");
assertTrue(importSection.contains(
"""
assertTrue(importSection.contains("""
Selected Ghidra language: x86:LE:32:default
Selected Ghidra compiler: %s""".formatted(PLAT.cSpec())));
String fileSection = extractOutSection(out, "---File---");
assertTrue(fileSection.contains(
"""
assertTrue(fileSection.contains("""
Selected Ghidra language: %s
Selected Ghidra compiler: %s""".formatted(PLAT.lang(), PLAT.cSpec())));
assertEquals("""
@@ -425,7 +423,7 @@ public class GdbCommandsTest extends AbstractGdbTraceRmiTest {
Entry<TraceAddressSnapRange, TraceMemoryState> entry =
tb.trace.getMemoryManager().getMostRecentStateEntry(snap, addr);
assertEquals(Map.entry(new ImmutableTraceAddressSnapRange(
quantize(rng(addr, 10), 4096), Lifespan.at(0)), TraceMemoryState.ERROR), entry);
quantize(rng(addr, 10), 4096), Lifespan.nowOn(0)), TraceMemoryState.ERROR), entry);
}
}

View File

@@ -17,7 +17,7 @@ package agent.lldb.rmi;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.junit.Assume.assumeFalse;
import java.nio.ByteBuffer;
import java.util.*;
@@ -384,7 +384,7 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest {
Entry<TraceAddressSnapRange, TraceMemoryState> entry =
tb.trace.getMemoryManager().getMostRecentStateEntry(snap, addr);
assertEquals(Map.entry(new ImmutableTraceAddressSnapRange(
quantize(rng(addr, 10), 4096), Lifespan.at(0)), TraceMemoryState.ERROR), entry);
quantize(rng(addr, 10), 4096), Lifespan.nowOn(0)), TraceMemoryState.ERROR), entry);
}
}
@@ -900,18 +900,17 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest {
""".formatted(PREAMBLE, addr, getSpecimenPrint()));
try (ManagedDomainObject mdo = openDomainObject(projectName("expPrint"))) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
assertEquals(
"""
Parent Key Span Value Type
Test.Objects[1] vaddr [0,+inf) ram:0000dead ADDRESS
Test.Objects[1] vbool [0,+inf) True BOOL
Test.Objects[1] vbyte [0,+inf) 1 BYTE
Test.Objects[1] vchar [0,+inf) 'A' CHAR
Test.Objects[1] vint [0,+inf) 3 INT
Test.Objects[1] vlong [0,+inf) 4 LONG
Test.Objects[1] vobj [0,+inf) Test.Objects[1] OBJECT
Test.Objects[1] vshort [0,+inf) 2 SHORT\
""",
assertEquals("""
Parent Key Span Value Type
Test.Objects[1] vaddr [0,+inf) ram:0000dead ADDRESS
Test.Objects[1] vbool [0,+inf) True BOOL
Test.Objects[1] vbyte [0,+inf) 1 BYTE
Test.Objects[1] vchar [0,+inf) 'A' CHAR
Test.Objects[1] vint [0,+inf) 3 INT
Test.Objects[1] vlong [0,+inf) 4 LONG
Test.Objects[1] vobj [0,+inf) Test.Objects[1] OBJECT
Test.Objects[1] vshort [0,+inf) 2 SHORT\
""",
extractOutSection(out, "---GetValues---"));
}
}
@@ -939,11 +938,10 @@ public class LldbCommandsTest extends AbstractLldbTraceRmiTest {
""".formatted(PREAMBLE, addr, getSpecimenPrint()));
try (ManagedDomainObject mdo = openDomainObject(projectName("expPrint"))) {
tb = new ToyDBTraceBuilder((Trace) mdo.get());
assertTrue(extractOutSectionWithPrompt(out, "---GetValues---").contains(
"""
Parent Key Span Value Type
Test.Objects[1] vaddr [0,+inf) ram:0000dead ADDRESS\
"""));
assertTrue(extractOutSectionWithPrompt(out, "---GetValues---").contains("""
Parent Key Span Value Type
Test.Objects[1] vaddr [0,+inf) ram:0000dead ADDRESS\
"""));
}
}