Merge remote-tracking branch 'origin/GP-2595_Dan_simplifyTraceViewport'

Conflicts:
  DBTrace.java
This commit is contained in:
Ryan Kurtz 2022-09-24 02:00:19 -04:00
commit 7ea1bbc360
21 changed files with 389 additions and 186 deletions

View file

@ -42,8 +42,6 @@ import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.trace.util.DefaultTraceTimeViewport;
import ghidra.trace.util.TraceTimeViewport;
import ghidra.util.Msg;
import ghidra.util.NotOwnerException;
@ -97,7 +95,6 @@ public class DebuggerCoordinates {
private final int hash;
private Long viewSnap;
private DefaultTraceTimeViewport viewport;
private TraceObject registerContainer;
DebuggerCoordinates(Trace trace, TracePlatform platform, TraceRecorder recorder,
@ -580,18 +577,6 @@ public class DebuggerCoordinates {
return viewSnap = snapshots.iterator().next().getKey();
}
public synchronized TraceTimeViewport getViewport() {
if (viewport != null) {
return viewport;
}
if (trace == null) {
return null;
}
viewport = new DefaultTraceTimeViewport(trace);
viewport.setSnap(getViewSnap());
return viewport;
}
public void writeDataState(PluginTool tool, SaveState saveState, String key) {
if (this == NOWHERE) {
return;

View file

@ -32,8 +32,8 @@ import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceTimeViewport;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.util.TraceTimeViewport;
import ghidra.util.MathUtilities;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;

View file

@ -25,9 +25,9 @@ import ghidra.pcode.exec.trace.data.DefaultPcodeTraceRegistersAccess;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.trace.model.TraceTimeViewport;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceTimeViewport;
import ghidra.util.Msg;
/**

View file

@ -38,10 +38,9 @@ import ghidra.program.model.address.*;
import ghidra.program.model.data.PointerDataType;
import ghidra.program.model.lang.Register;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.trace.model.Trace;
import ghidra.trace.model.*;
import ghidra.trace.model.Trace.TraceMemoryBytesChangeType;
import ghidra.trace.model.Trace.TraceStackChangeType;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.program.TraceProgramView;

View file

@ -23,10 +23,9 @@ import org.apache.commons.lang3.tuple.Pair;
import ghidra.pcode.emu.PcodeThread;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceTimeViewport;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.DefaultTraceTimeViewport;
import ghidra.trace.util.TraceTimeViewport;
/**
* An abstract implementation of {@link PcodeTraceAccess}
@ -58,7 +57,7 @@ public abstract class AbstractPcodeTraceAccess<S extends PcodeTraceMemoryAccess,
this.snap = snap;
this.threadsSnap = threadsSnap;
DefaultTraceTimeViewport viewport = new DefaultTraceTimeViewport(getTrace());
TraceTimeViewport viewport = getTrace().createTimeViewport();
viewport.setSnap(snap);
this.viewport = viewport;
}

View file

@ -21,9 +21,9 @@ import com.google.common.collect.Range;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.TraceTimeViewport;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.*;
import ghidra.trace.util.TraceTimeViewport;
/**
* An abstract data-access shim, for either memory or registers

View file

@ -16,10 +16,10 @@
package ghidra.pcode.exec.trace.data;
import ghidra.program.model.address.*;
import ghidra.trace.model.TraceTimeViewport;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemoryOperations;
import ghidra.trace.model.property.TracePropertyMapOperations;
import ghidra.trace.util.TraceTimeViewport;
/**
* The default data-access shim for trace memory

View file

@ -19,6 +19,7 @@ import ghidra.pcode.emu.PcodeThread;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceTimeViewport;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
@ -26,7 +27,6 @@ import ghidra.trace.model.property.TracePropertyMap;
import ghidra.trace.model.property.TracePropertyMapSpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceRegisterUtils;
import ghidra.trace.util.TraceTimeViewport;
/**
* The default data-access shim for trace registers

View file

@ -19,6 +19,8 @@ import java.io.IOException;
import java.util.*;
import java.util.function.Consumer;
import org.apache.commons.collections4.collection.CompositeCollection;
import com.google.common.cache.RemovalNotification;
import com.google.common.collect.Range;
@ -57,12 +59,14 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.property.TraceAddressPropertyManager;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.util.CopyOnWrite.WeakHashCowSet;
import ghidra.trace.util.CopyOnWrite.WeakValueHashCowMap;
import ghidra.trace.util.TraceChangeManager;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.*;
import ghidra.util.database.*;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.datastruct.WeakValueHashMap;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
@ -136,9 +140,12 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
protected DBTraceChangeSet traceChangeSet;
protected boolean recordChanges = false;
protected Set<DBTraceTimeViewport> viewports = new WeakHashCowSet<>();
protected DBTraceVariableSnapProgramView programView;
protected Map<DBTraceVariableSnapProgramView, Void> programViews = new WeakHashMap<>();
protected Map<Long, DBTraceProgramView> fixedProgramViews = new WeakValueHashMap<>();
protected Set<DBTraceVariableSnapProgramView> programViews = new WeakHashCowSet<>();
protected Set<TraceProgramView> programViewsView = Collections.unmodifiableSet(programViews);
protected Map<Long, DBTraceProgramView> fixedProgramViews = new WeakValueHashCowMap<>();
// NOTE: Can't pre-construct unmodifiableMap(fixedProgramViews), because values()' id changes
protected ListenerSet<TraceProgramViewListener> viewListeners =
new ListenerSet<>(TraceProgramViewListener.class);
@ -578,14 +585,10 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
// NOTE: The new viewport will need to read from the time manager during init
DBTraceProgramView view;
try (LockHold hold = lockRead()) {
synchronized (fixedProgramViews) {
view = fixedProgramViews.get(snap);
if (view != null) {
return view;
}
view = fixedProgramViews.computeIfAbsent(snap, s -> {
Msg.debug(this, "Creating fixed view at snap=" + snap);
view = new DBTraceProgramView(this, snap, baseCompilerSpec);
}
return new DBTraceProgramView(this, snap, baseCompilerSpec);
});
}
viewListeners.fire.viewCreated(view);
return view;
@ -597,10 +600,8 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
// NOTE: The new viewport will need to read from the time manager during init
DBTraceVariableSnapProgramView view;
try (LockHold hold = lockRead()) {
synchronized (programViews) {
view = new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec);
programViews.put(view, null);
}
view = new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec);
programViews.add(view);
}
viewListeners.fire.viewCreated(view);
return view;
@ -611,6 +612,15 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
return programView;
}
@Override
public synchronized DBTraceTimeViewport createTimeViewport() {
try (LockHold hold = lockRead()) {
DBTraceTimeViewport view = new DBTraceTimeViewport(this);
viewports.add(view);
return view;
}
}
@Override
public LockHold lockRead() {
return LockHold.lock(rwLock.readLock());
@ -742,25 +752,26 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
@Override
public Collection<TraceProgramView> getAllProgramViews() {
Collection<TraceProgramView> all = new ArrayList<>();
synchronized (programViews) {
all.addAll(programViews.keySet());
/**
* Cannot pre-construct fixedProgramViewsView, because the UnmodifiableMap will cache
* values() on the first call, and the CowMap will change that with every mutation. Thus,
* the view would not see changes to the underlying map.
*/
return new CompositeCollection<>(programViewsView,
Collections.unmodifiableCollection(fixedProgramViews.values()));
}
protected void allViewports(Consumer<DBTraceTimeViewport> action) {
for (DBTraceTimeViewport viewport : viewports) {
action.accept(viewport);
}
synchronized (fixedProgramViews) {
all.addAll(fixedProgramViews.values());
}
return all;
}
protected void allViews(Consumer<DBTraceProgramView> action) {
Collection<DBTraceProgramView> all = new ArrayList<>();
synchronized (programViews) {
all.addAll(programViews.keySet());
for (DBTraceProgramView view : programViews) {
action.accept(view);
}
synchronized (fixedProgramViews) {
all.addAll(fixedProgramViews.values());
}
for (DBTraceProgramView view : all) {
for (DBTraceProgramView view : fixedProgramViews.values()) {
action.accept(view);
}
}
@ -816,4 +827,16 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
public void updateViewsBytesChanged(AddressRange range) {
allViews(v -> v.updateBytesChanged(range));
}
public void updateViewportsSnapshotAdded(TraceSnapshot snapshot) {
allViewports(v -> v.updateSnapshotAdded(snapshot));
}
public void updateViewportsSnapshotChanged(TraceSnapshot snapshot) {
allViewports(v -> v.updateSnapshotChanged(snapshot));
}
public void updateViewportsSnapshotDeleted(TraceSnapshot snapshot) {
allViewports(v -> v.updateSnapshotDeleted(snapshot));
}
}

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.trace.util;
package ghidra.trace.database;
import java.util.*;
import java.util.function.Function;
@ -21,19 +21,15 @@ import java.util.stream.Collectors;
import com.google.common.collect.*;
import ghidra.framework.model.DomainObjectClosedListener;
import ghidra.framework.model.DomainObjectException;
import ghidra.program.model.address.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.TraceTimeViewport;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.*;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.exception.ClosedException;
/**
* Computes and tracks the "viewport" resulting from forking patterns encoded in snapshot schedules
@ -50,55 +46,7 @@ import ghidra.util.exception.ClosedException;
* viewport. If complex, deep forking structures prove to be desirable, then this is an area for
* optimization.
*/
public class DefaultTraceTimeViewport implements TraceTimeViewport {
protected class ForSnapshotsListener extends TraceDomainObjectListener
implements DomainObjectClosedListener {
{
listenFor(TraceSnapshotChangeType.ADDED, ignoringClosed(this::snapshotAdded));
listenFor(TraceSnapshotChangeType.CHANGED, ignoringClosed(this::snapshotChanged));
listenFor(TraceSnapshotChangeType.DELETED, ignoringClosed(this::snapshotDeleted));
}
private AffectedObjectOnlyHandler<? super TraceSnapshot> ignoringClosed(
AffectedObjectOnlyHandler<? super TraceSnapshot> handler) {
return snapshot -> {
try {
handler.handle(snapshot);
}
catch (DomainObjectException e) {
if (e.getCause() instanceof ClosedException) {
Msg.warn(this, "Ignoring ClosedException in trace viewport update");
}
else {
throw e;
}
}
};
}
private void snapshotAdded(TraceSnapshot snapshot) {
if (checkSnapshotAddedNeedsRefresh(snapshot)) {
refreshSnapRanges();
}
}
private void snapshotChanged(TraceSnapshot snapshot) {
if (checkSnapshotChangedNeedsRefresh(snapshot)) {
refreshSnapRanges();
}
}
private void snapshotDeleted(TraceSnapshot snapshot) {
if (checkSnapshotDeletedNeedsRefresh(snapshot)) {
refreshSnapRanges();
}
}
@Override
public void domainObjectClosed() {
trace.removeListener(this);
}
}
public class DBTraceTimeViewport implements TraceTimeViewport {
protected final Trace trace;
/**
@ -108,19 +56,16 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
*/
protected final List<Range<Long>> ordered = new ArrayList<>();
protected final RangeSet<Long> spanSet = TreeRangeSet.create();
protected final ForSnapshotsListener listener = new ForSnapshotsListener();
protected final ListenerSet<Runnable> changeListeners = new ListenerSet<>(Runnable.class);
protected long snap = 0;
public DefaultTraceTimeViewport(Trace trace) {
protected DBTraceTimeViewport(Trace trace) {
Range<Long> zero = Range.singleton(0L);
spanSet.add(zero);
ordered.add(zero);
this.trace = trace;
trace.addCloseListener(listener);
trace.addListener(listener);
}
@Override
@ -194,7 +139,7 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
}
protected boolean isLower(long lower) {
try (LockHold hold = trace.lockRead()) { // May not be necessary
try (LockHold hold = trace.lockRead()) {
synchronized (ordered) {
Range<Long> range = spanSet.rangeContaining(lower);
if (range == null) {
@ -283,6 +228,7 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
changeListeners.fire.run();
}
@Override
public void setSnap(long snap) {
if (this.snap == snap) {
return;
@ -291,48 +237,54 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
refreshSnapRanges();
}
protected boolean checkSnapshotAddedNeedsRefresh(TraceSnapshot snapshot) {
try (LockHold hold = trace.lockRead()) {
synchronized (ordered) {
if (snapshot.getSchedule() == null) {
return false;
}
if (spanSet.contains(snapshot.getKey())) {
return true;
}
return false;
}
protected void updateSnapshotAdded(TraceSnapshot snapshot) {
if (checkSnapshotAddedNeedsRefresh(snapshot)) {
refreshSnapRanges();
}
}
protected void updateSnapshotChanged(TraceSnapshot snapshot) {
if (checkSnapshotChangedNeedsRefresh(snapshot)) {
refreshSnapRanges();
}
}
protected void updateSnapshotDeleted(TraceSnapshot snapshot) {
if (checkSnapshotDeletedNeedsRefresh(snapshot)) {
refreshSnapRanges();
}
}
protected boolean checkSnapshotAddedNeedsRefresh(TraceSnapshot snapshot) {
if (snapshot.getSchedule() == null) {
return false;
}
if (spanSet.contains(snapshot.getKey())) {
return true;
}
return false;
}
protected boolean checkSnapshotChangedNeedsRefresh(TraceSnapshot snapshot) {
try (LockHold hold = trace.lockRead()) {
synchronized (ordered) {
if (isLower(snapshot.getKey())) {
return true;
}
if (spanSet.contains(snapshot.getKey()) && snapshot.getSchedule() != null) {
return true;
}
return false;
}
if (isLower(snapshot.getKey())) {
return true;
}
if (spanSet.contains(snapshot.getKey()) && snapshot.getSchedule() != null) {
return true;
}
return false;
}
protected boolean checkSnapshotDeletedNeedsRefresh(TraceSnapshot snapshot) {
try (LockHold hold = trace.lockRead()) {
synchronized (ordered) {
if (isLower(snapshot.getKey())) {
return true;
}
return false;
}
if (isLower(snapshot.getKey())) {
return true;
}
return false;
}
@Override
public boolean isForked() {
try (LockHold hold = trace.lockRead()) { // May not be necessary
try (LockHold hold = trace.lockRead()) {
synchronized (ordered) {
return ordered.size() > 1;
}
@ -340,7 +292,7 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
}
public List<Range<Long>> getOrderedSpans() {
try (LockHold hold = trace.lockRead()) { // May not be necessary
try (LockHold hold = trace.lockRead()) {
synchronized (ordered) {
return List.copyOf(ordered);
}
@ -348,17 +300,15 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
}
public List<Range<Long>> getOrderedSpans(long snap) {
try (LockHold hold = trace.lockRead()) { // setSnap requires this
synchronized (ordered) {
setSnap(snap);
return getOrderedSpans();
}
try (LockHold hold = trace.lockRead()) {
setSnap(snap);
return getOrderedSpans();
}
}
@Override
public List<Long> getOrderedSnaps() {
try (LockHold hold = trace.lockRead()) { // May not be necessary
try (LockHold hold = trace.lockRead()) {
synchronized (ordered) {
return ordered
.stream()
@ -370,7 +320,7 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
@Override
public List<Long> getReversedSnaps() {
try (LockHold hold = trace.lockRead()) { // May not be necessary
try (LockHold hold = trace.lockRead()) {
synchronized (ordered) {
return Lists.reverse(ordered)
.stream()

View file

@ -30,8 +30,7 @@ import com.google.common.collect.Range;
import db.DBHandle;
import ghidra.program.model.address.*;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.*;
import ghidra.trace.database.DBTraceUtils.AddressRangeMapSetter;
import ghidra.trace.database.DBTraceUtils.OffsetSnap;
import ghidra.trace.database.listing.DBTraceCodeSpace;
@ -43,7 +42,6 @@ import ghidra.trace.model.*;
import ghidra.trace.model.Trace.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.DefaultTraceTimeViewport;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.*;
import ghidra.util.AddressIteratorAdapter;
@ -95,7 +93,7 @@ public class DBTraceMemorySpace
.build()
.asMap();
protected final DefaultTraceTimeViewport viewport;
protected final DBTraceTimeViewport viewport;
public DBTraceMemorySpace(DBTraceMemoryManager manager, DBHandle dbh, AddressSpace space,
DBTraceSpaceEntry ent, TraceThread thread) throws IOException, VersionException {
@ -135,7 +133,7 @@ public class DBTraceMemorySpace
this.blocksByOffset =
blockStore.getIndex(OffsetSnap.class, DBTraceMemoryBlockEntry.LOCATION_COLUMN);
this.viewport = new DefaultTraceTimeViewport(trace);
this.viewport = trace.createTimeViewport();
}
@Override

View file

@ -45,13 +45,14 @@ import ghidra.program.model.util.PropertyMapManager;
import ghidra.program.util.ChangeManager;
import ghidra.program.util.ProgramChangeRecord;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceTimeViewport;
import ghidra.trace.database.listing.DBTraceCodeSpace;
import ghidra.trace.database.listing.DBTraceDefinedUnitsView;
import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.database.symbol.DBTraceFunctionSymbolView;
import ghidra.trace.model.*;
import ghidra.trace.model.Trace.*;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.TraceTimeViewport.*;
import ghidra.trace.model.bookmark.TraceBookmark;
import ghidra.trace.model.bookmark.TraceBookmarkType;
import ghidra.trace.model.data.TraceBasedDataTypeManager;
@ -61,8 +62,7 @@ import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.symbol.*;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.*;
import ghidra.trace.util.TraceTimeViewport.*;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.*;
import ghidra.util.datastruct.WeakValueHashMap;
import ghidra.util.exception.CancelledException;
@ -891,7 +891,7 @@ public class DBTraceProgramView implements TraceProgramView {
protected final Map<TraceThread, DBTraceProgramViewRegisters> regViewsByThread;
protected long snap;
protected final DefaultTraceTimeViewport viewport;
protected final DBTraceTimeViewport viewport;
protected final Runnable viewportChangeListener = this::viewportChanged;
// This is a strange thing
@ -910,7 +910,7 @@ public class DBTraceProgramView implements TraceProgramView {
this.language = compilerSpec.getLanguage();
this.compilerSpec = compilerSpec;
this.viewport = new DefaultTraceTimeViewport(trace);
this.viewport = trace.createTimeViewport();
this.viewport.setSnap(snap);
this.eventQueues =

View file

@ -37,11 +37,11 @@ import ghidra.program.model.util.PropertyMapManager;
import ghidra.trace.database.listing.DBTraceCodeSpace;
import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceTimeViewport;
import ghidra.trace.model.data.TraceBasedDataTypeManager;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceProgramViewMemory;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceTimeViewport;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;

View file

@ -115,9 +115,8 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
this.realTime = millis;
update(REAL_TIME_COLUMN);
manager.notifySnapshotChanged(this);
}
manager.trace.setChanged(
new TraceChangeRecord<>(TraceSnapshotChangeType.CHANGED, null, this));
}
@Override
@ -130,9 +129,8 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
this.description = description;
update(DESCRIPTION_COLUMN);
manager.notifySnapshotChanged(this);
}
manager.trace.setChanged(
new TraceChangeRecord<>(TraceSnapshotChangeType.CHANGED, null, this));
}
@Override
@ -152,9 +150,8 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
threadKey = thread.getKey();
}
update(THREAD_COLUMN);
manager.notifySnapshotChanged(this);
}
manager.trace.setChanged(
new TraceChangeRecord<>(TraceSnapshotChangeType.CHANGED, null, this));
}
@Override
@ -173,9 +170,8 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot
this.schedule = schedule;
this.scheduleStr = schedule == null ? "" : schedule.toString();
update(SCHEDULE_COLUMN);
manager.notifySnapshotChanged(this);
}
manager.trace.setChanged(
new TraceChangeRecord<>(TraceSnapshotChangeType.CHANGED, null, this));
}
@Override

View file

@ -68,6 +68,21 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
snapshotStore.invalidateCache();
}
protected void notifySnapshotAdded(DBTraceSnapshot snapshot) {
trace.updateViewportsSnapshotAdded(snapshot);
trace.setChanged(new TraceChangeRecord<>(TraceSnapshotChangeType.ADDED, null, snapshot));
}
protected void notifySnapshotChanged(DBTraceSnapshot snapshot) {
trace.updateViewportsSnapshotChanged(snapshot);
trace.setChanged(new TraceChangeRecord<>(TraceSnapshotChangeType.CHANGED, null, snapshot));
}
protected void notifySnapshotDeleted(DBTraceSnapshot snapshot) {
trace.updateViewportsSnapshotDeleted(snapshot);
trace.setChanged(new TraceChangeRecord<>(TraceSnapshotChangeType.DELETED, null, snapshot));
}
@Override
public DBTraceSnapshot createSnapshot(String description) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
@ -77,8 +92,7 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
// Convention for first snap
snapshot.setSchedule(TraceSchedule.snap(0));
}
trace.setChanged(
new TraceChangeRecord<>(TraceSnapshotChangeType.ADDED, null, snapshot));
notifySnapshotAdded(snapshot);
return snapshot;
}
}
@ -99,8 +113,7 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
// Convention for first snap
snapshot.setSchedule(TraceSchedule.snap(0));
}
trace.setChanged(
new TraceChangeRecord<>(TraceSnapshotChangeType.ADDED, null, snapshot));
notifySnapshotAdded(snapshot);
}
return snapshot;
}
@ -142,7 +155,9 @@ public class DBTraceTimeManager implements TraceTimeManager, DBTraceManager {
}
public void deleteSnapshot(DBTraceSnapshot snapshot) {
snapshotStore.delete(snapshot);
trace.setChanged(new TraceChangeRecord<>(TraceSnapshotChangeType.DELETED, null, snapshot));
try (LockHold hold = LockHold.lock(lock.writeLock())) {
snapshotStore.delete(snapshot);
notifySnapshotDeleted(snapshot);
}
}
}

View file

@ -462,6 +462,8 @@ public interface Trace extends DataTypeManagerDomainObject {
*/
TraceVariableSnapProgramView getProgramView();
TraceTimeViewport createTimeViewport();
void addProgramViewListener(TraceProgramViewListener listener);
void removeProgramViewListener(TraceProgramViewListener listener);

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.trace.util;
package ghidra.trace.model;
import java.util.*;
import java.util.function.Function;
@ -102,8 +102,29 @@ public interface TraceTimeViewport {
AddressSetView set(T t);
}
/**
* Set the snapshot for this viewport
*
* @param snap the snap
*/
void setSnap(long snap);
/**
* Add a listener for when the forking structure of this viewport changes
*
* <p>
* This can occur when the snap changes or when any snapshot involved changes
*
* @param l the listener
*/
void addChangeListener(Runnable l);
/**
* Remove a listener for forking structure changes
*
* @see #addChangeListener(Runnable)
* @param l the listener
*/
void removeChangeListener(Runnable l);
/**

View file

@ -17,8 +17,8 @@ package ghidra.trace.model.program;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceTimeViewport;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceTimeViewport;
/**
* View of a trace at a particular time, as a program

View file

@ -0,0 +1,212 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.trace.util;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import ghidra.util.datastruct.AbstractWeakValueMap;
public interface CopyOnWrite {
abstract class AbstractCowMap<K, V> implements Map<K, V> {
private final AtomicReference<Map<K, V>> map;
public AbstractCowMap() {
map = new AtomicReference<>(copyMap(Map.of()));
}
/**
* Extension point: Create a mutable copy of the given map
*
* @param map the map to copy
* @return the mutable copy
*/
protected abstract Map<K, V> copyMap(Map<K, V> map);
@Override
public int size() {
return map.get().size();
}
@Override
public boolean isEmpty() {
return map.get().isEmpty();
}
@Override
public boolean containsKey(Object key) {
return map.get().containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return map.get().containsValue(value);
}
@Override
public V get(Object key) {
return map.get().get(key);
}
@Override
public V put(K key, V value) {
return map.getAndUpdate(m -> {
Map<K, V> withPut = copyMap(m);
withPut.put(key, value);
return withPut;
}).get(key);
}
@Override
public V remove(Object key) {
return map.getAndUpdate(m -> {
Map<K, V> withRemove = copyMap(m);
withRemove.remove(key);
return withRemove;
}).get(key);
}
@Override
public void putAll(Map<? extends K, ? extends V> from) {
map.getAndUpdate(m -> {
Map<K, V> withPutAll = copyMap(m);
withPutAll.putAll(from);
return withPutAll;
});
}
@Override
public void clear() {
map.set(copyMap(Map.of()));
}
@Override
public Set<K> keySet() {
return Collections.unmodifiableSet(map.get().keySet());
}
@Override
public Collection<V> values() {
return Collections.unmodifiableCollection(map.get().values());
}
@Override
public Set<Entry<K, V>> entrySet() {
return Collections.unmodifiableSet(map.get().entrySet());
}
@Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
return map.getAndUpdate(m -> {
if (m.containsKey(key)) {
return m;
}
Map<K, V> withComputeIfAbsent = copyMap(m);
withComputeIfAbsent.put(key, mappingFunction.apply(key));
return withComputeIfAbsent;
}).get(key);
}
}
class HashCowMap<K, V> extends AbstractCowMap<K, V> {
@Override
protected Map<K, V> copyMap(Map<K, V> map) {
return new HashMap<>(map);
}
}
abstract class WeakValueAbstractCowMap<K, V> extends AbstractWeakValueMap<K, V> {
private final AbstractCowMap<K, WeakValueRef<K, V>> refMap = newCowMap();
protected abstract AbstractCowMap<K, WeakValueRef<K, V>> newCowMap();
@Override
protected Map<K, WeakValueRef<K, V>> getRefMap() {
return refMap;
}
}
class WeakValueHashCowMap<K, V> extends WeakValueAbstractCowMap<K, V> {
@Override
protected AbstractCowMap<K, WeakValueRef<K, V>> newCowMap() {
return new HashCowMap<>();
}
}
/**
* Assumes elements use system hash equality, i.e., {@link E#equals()} is ignored
*
* @param <E> the type of element in the weak set
*/
abstract class WeakAbstractCowSet<E> extends AbstractSet<E> {
private final WeakValueAbstractCowMap<Integer, E> map = newWeakValueCowMap();
protected abstract WeakValueAbstractCowMap<Integer, E> newWeakValueCowMap();
@Override
public Iterator<E> iterator() {
return map.values().iterator();
}
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public boolean contains(Object o) {
return map.get(System.identityHashCode(o)) == o;
}
@Override
public boolean add(E e) {
return map.put(System.identityHashCode(e), e) != e;
}
@Override
public boolean remove(Object o) {
return map.remove(System.identityHashCode(o)) == o;
}
@Override
public void clear() {
map.clear();
}
@Override
public Object[] toArray() {
return map.values().toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return map.values().toArray(a);
}
}
class WeakHashCowSet<E> extends WeakAbstractCowSet<E> {
@Override
protected WeakValueAbstractCowMap<Integer, E> newWeakValueCowMap() {
return new WeakValueHashCowMap<>();
}
}
}

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.trace.util;
package ghidra.trace.database;
import static org.junit.Assert.assertEquals;
@ -24,12 +24,11 @@ import org.junit.Test;
import com.google.common.collect.*;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.time.DBTraceTimeManager;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.database.UndoableTransaction;
public class DefaultTraceTimeViewportTest extends AbstractGhidraHeadlessIntegrationTest {
public class DBTraceTimeViewportTest extends AbstractGhidraHeadlessIntegrationTest {
public static <C extends Comparable<C>> RangeSet<C> rangeSetOf(List<Range<C>> ranges) {
RangeSet<C> result = TreeRangeSet.create();
ranges.forEach(result::add);
@ -39,7 +38,7 @@ public class DefaultTraceTimeViewportTest extends AbstractGhidraHeadlessIntegrat
@Test
public void testEmptyTime() throws Exception {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
DefaultTraceTimeViewport viewport = new DefaultTraceTimeViewport(tb.trace);
DBTraceTimeViewport viewport = tb.trace.createTimeViewport();
viewport.setSnap(10);
assertEquals(rangeSetOf(List.of(Range.closed(Long.MIN_VALUE, 10L))), viewport.spanSet);
}
@ -52,7 +51,7 @@ public class DefaultTraceTimeViewportTest extends AbstractGhidraHeadlessIntegrat
tb.trace.getTimeManager().getSnapshot(0, true).setSchedule(TraceSchedule.snap(0));
}
DefaultTraceTimeViewport viewport = new DefaultTraceTimeViewport(tb.trace);
DBTraceTimeViewport viewport = tb.trace.createTimeViewport();
viewport.setSnap(10);
assertEquals(rangeSetOf(List.of(Range.closed(0L, 10L))), viewport.spanSet);
}
@ -67,7 +66,7 @@ public class DefaultTraceTimeViewportTest extends AbstractGhidraHeadlessIntegrat
tm.getSnapshot(5, true).setSchedule(TraceSchedule.parse("4:1"));
}
DefaultTraceTimeViewport viewport = new DefaultTraceTimeViewport(tb.trace);
DBTraceTimeViewport viewport = tb.trace.createTimeViewport();
viewport.setSnap(10);
assertEquals(rangeSetOf(List.of(Range.closed(0L, 10L))), viewport.spanSet);
}
@ -82,7 +81,7 @@ public class DefaultTraceTimeViewportTest extends AbstractGhidraHeadlessIntegrat
tm.getSnapshot(Long.MIN_VALUE, true).setSchedule(TraceSchedule.parse("10:4"));
}
DefaultTraceTimeViewport viewport = new DefaultTraceTimeViewport(tb.trace);
DBTraceTimeViewport viewport = tb.trace.createTimeViewport();
viewport.setSnap(Long.MIN_VALUE);
assertEquals(
rangeSetOf(List.of(Range.singleton(Long.MIN_VALUE), Range.closed(0L, 10L))),
@ -98,7 +97,7 @@ public class DefaultTraceTimeViewportTest extends AbstractGhidraHeadlessIntegrat
tm.getSnapshot(Long.MIN_VALUE, true).setSchedule(TraceSchedule.parse("10:4"));
}
DefaultTraceTimeViewport viewport = new DefaultTraceTimeViewport(tb.trace);
DBTraceTimeViewport viewport = tb.trace.createTimeViewport();
viewport.setSnap(Long.MIN_VALUE);
assertEquals(rangeSetOf(List.of(Range.singleton(Long.MIN_VALUE))), viewport.spanSet);
}

View file

@ -219,6 +219,10 @@ public class ListenerMap<K, P, V extends P> {
}
protected Map<K, V> createMap() {
/**
* TODO: This is potentially flawed: The removal modifies the map in place. It does not
* adhere to "copy on write." It does have its own concurrency considerations, though.
*/
CacheBuilder<K, V> builder = CacheBuilder.newBuilder()
.removalListener(this::notifyRemoved)
.weakValues()