GP-3839: Various speed improvements for Trace RMI

This commit is contained in:
Dan
2024-02-14 15:53:59 -05:00
parent bc24351495
commit b34aaa4952
67 changed files with 2698 additions and 955 deletions
@@ -367,7 +367,7 @@ def refresh_modules(node: sch.Schema('ModuleContainer')):
# node is Module so this appears in Modules panel # node is Module so this appears in Modules panel
@REGISTRY.method(display='Load all Modules and all Sections') @REGISTRY.method(display='Refresh all Modules and all Sections')
def load_all_sections(node: sch.Schema('Module')): def load_all_sections(node: sch.Schema('Module')):
""" """
Load/refresh all modules and all sections. Load/refresh all modules and all sections.
@@ -51,8 +51,7 @@ public interface LocationTracker {
* @param coordinates the trace, thread, snap, etc., of the tool * @param coordinates the trace, thread, snap, etc., of the tool
* @return the address to navigate to * @return the address to navigate to
*/ */
CompletableFuture<Address> computeTraceAddress(PluginTool tool, Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates);
DebuggerCoordinates coordinates);
/** /**
* Get the suggested input if the user activates "Go To" while this tracker is active * Get the suggested input if the user activates "Go To" while this tracker is active
@@ -484,10 +484,10 @@ public class TraceRmiHandler implements TraceRmiConnection {
RootMessage.Builder dispatch(RootMessage req, RootMessage.Builder rep) throws Exception; RootMessage.Builder dispatch(RootMessage req, RootMessage.Builder rep) throws Exception;
default RootMessage handle(RootMessage req) { default RootMessage handle(RootMessage req) {
/*String desc = toString(req); String desc = toString(req);
if (desc != null) { if (desc != null) {
TimedMsg.debug(this, "HANDLING: " + desc); TimedMsg.debug(this, "HANDLING: " + desc);
}*/ }
RootMessage.Builder rep = RootMessage.newBuilder(); RootMessage.Builder rep = RootMessage.newBuilder();
try { try {
rep = dispatch(req, rep); rep = dispatch(req, rep);
@@ -514,12 +514,12 @@ public class TraceRmiHandler implements TraceRmiConnection {
case REQUEST_START_TX -> "startTx(%d,%s)".formatted( case REQUEST_START_TX -> "startTx(%d,%s)".formatted(
req.getRequestStartTx().getTxid().getId(), req.getRequestStartTx().getTxid().getId(),
req.getRequestStartTx().getDescription()); req.getRequestStartTx().getDescription());
case REQUEST_SET_VALUE -> "setValue(%d,%s,%s,=%s)".formatted( /*case REQUEST_SET_VALUE -> "setValue(%d,%s,%s,=%s)".formatted(
req.getRequestSetValue().getValue().getParent().getId(), req.getRequestSetValue().getValue().getParent().getId(),
req.getRequestSetValue().getValue().getParent().getPath().getPath(), req.getRequestSetValue().getValue().getParent().getPath().getPath(),
req.getRequestSetValue().getValue().getKey(), req.getRequestSetValue().getValue().getKey(),
ValueDecoder.DISPLAY ValueDecoder.DISPLAY
.toValue(req.getRequestSetValue().getValue().getValue())); .toValue(req.getRequestSetValue().getValue().getValue()));*/
default -> null; default -> null;
}; };
} }
@@ -62,6 +62,7 @@ import ghidra.util.exception.CancelledException;
import resources.MultiIcon; import resources.MultiIcon;
public interface DebuggerResources { public interface DebuggerResources {
String OPTIONS_CATEGORY_DEBUGGER = "Debugger"; String OPTIONS_CATEGORY_DEBUGGER = "Debugger";
String OPTIONS_CATEGORY_WORKFLOW = "Workflow"; String OPTIONS_CATEGORY_WORKFLOW = "Workflow";
@@ -19,7 +19,6 @@ import java.awt.Color;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture;
import docking.ActionContext; import docking.ActionContext;
import docking.ComponentProvider; import docking.ComponentProvider;
@@ -34,13 +33,13 @@ import ghidra.app.plugin.core.debug.gui.colors.MultiSelectionBlendedLayoutBackgr
import ghidra.app.plugin.core.debug.gui.listing.DebuggerTrackedRegisterListingBackgroundColorModel; import ghidra.app.plugin.core.debug.gui.listing.DebuggerTrackedRegisterListingBackgroundColorModel;
import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel; import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel;
import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.async.AsyncUtils;
import ghidra.debug.api.action.*; import ghidra.debug.api.action.*;
import ghidra.debug.api.action.LocationTrackingSpec.TrackingSpecConfigFieldCodec; import ghidra.debug.api.action.LocationTrackingSpec.TrackingSpecConfigFieldCodec;
import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoConfigStateField; import ghidra.framework.plugintool.annotation.AutoConfigStateField;
import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*; import ghidra.trace.model.*;
import ghidra.trace.model.stack.TraceStack; import ghidra.trace.model.stack.TraceStack;
@@ -252,7 +251,7 @@ public class DebuggerTrackLocationTrait {
doTrack(); doTrack();
} }
protected CompletableFuture<ProgramLocation> computeTrackedLocation() { protected ProgramLocation computeTrackedLocation() {
// Change of register values (for current frame) // Change of register values (for current frame)
// Change of stack pc (for current frame) // Change of stack pc (for current frame)
// Change of current view (if not caused by goTo) // Change of current view (if not caused by goTo)
@@ -262,16 +261,18 @@ public class DebuggerTrackLocationTrait {
// Change of tracking settings // Change of tracking settings
DebuggerCoordinates cur = current; DebuggerCoordinates cur = current;
if (cur.getView() == null) { if (cur.getView() == null) {
return AsyncUtils.nil(); return null;
} }
TraceThread thread = cur.getThread(); TraceThread thread = cur.getThread();
if (thread == null || spec == null) { if (thread == null || spec == null) {
return AsyncUtils.nil(); return null;
} }
// NB: view's snap may be forked for emulation // NB: view's snap may be forked for emulation
return tracker.computeTraceAddress(tool, cur).thenApply(address -> { Address address = tracker.computeTraceAddress(tool, cur);
return address == null ? null : new ProgramLocation(cur.getView(), address); if (address == null) {
}); return null;
}
return new ProgramLocation(cur.getView(), address);
} }
public String computeLabelText() { public String computeLabelText() {
@@ -282,13 +283,13 @@ public class DebuggerTrackLocationTrait {
} }
protected void doTrack() { protected void doTrack() {
computeTrackedLocation().thenAccept(loc -> { try {
trackedLocation = loc; trackedLocation = computeTrackedLocation();
locationTracked(); locationTracked();
}).exceptionally(ex -> { }
catch (Throwable ex) {
Msg.error(this, "Error while computing location: " + ex); Msg.error(this, "Error while computing location: " + ex);
return null; }
});
} }
protected void addNewListeners() { protected void addNewListeners() {
@@ -15,12 +15,9 @@
*/ */
package ghidra.app.plugin.core.debug.gui.action; package ghidra.app.plugin.core.debug.gui.action;
import java.util.concurrent.CompletableFuture;
import javax.swing.Icon; import javax.swing.Icon;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
import ghidra.async.AsyncUtils;
import ghidra.debug.api.action.*; import ghidra.debug.api.action.*;
import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
@@ -66,9 +63,8 @@ public enum NoneLocationTrackingSpec implements LocationTrackingSpec, LocationTr
} }
@Override @Override
public CompletableFuture<Address> computeTraceAddress(PluginTool tool, public Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
DebuggerCoordinates coordinates) { return null;
return AsyncUtils.nil();
} }
@Override @Override
@@ -15,8 +15,6 @@
*/ */
package ghidra.app.plugin.core.debug.gui.action; package ghidra.app.plugin.core.debug.gui.action;
import java.util.concurrent.CompletableFuture;
import javax.swing.Icon; import javax.swing.Icon;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
@@ -67,7 +65,8 @@ public enum PCByStackLocationTrackingSpec implements LocationTrackingSpec, Locat
return this; return this;
} }
public Address doComputeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) { @Override
public Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
Trace trace = coordinates.getTrace(); Trace trace = coordinates.getTrace();
TraceThread thread = coordinates.getThread(); TraceThread thread = coordinates.getThread();
long snap = coordinates.getSnap(); long snap = coordinates.getSnap();
@@ -83,16 +82,10 @@ public enum PCByStackLocationTrackingSpec implements LocationTrackingSpec, Locat
return frame.getProgramCounter(snap); return frame.getProgramCounter(snap);
} }
@Override
public CompletableFuture<Address> computeTraceAddress(PluginTool tool,
DebuggerCoordinates coordinates) {
return CompletableFuture.supplyAsync(() -> doComputeTraceAddress(tool, coordinates));
}
@Override @Override
public GoToInput getDefaultGoToInput(PluginTool tool, DebuggerCoordinates coordinates, public GoToInput getDefaultGoToInput(PluginTool tool, DebuggerCoordinates coordinates,
ProgramLocation location) { ProgramLocation location) {
Address address = doComputeTraceAddress(tool, coordinates); Address address = computeTraceAddress(tool, coordinates);
if (address == null) { if (address == null) {
return NoneLocationTrackingSpec.INSTANCE.getDefaultGoToInput(tool, coordinates, return NoneLocationTrackingSpec.INSTANCE.getDefaultGoToInput(tool, coordinates,
location); location);
@@ -15,8 +15,6 @@
*/ */
package ghidra.app.plugin.core.debug.gui.action; package ghidra.app.plugin.core.debug.gui.action;
import java.util.concurrent.CompletableFuture;
import javax.swing.Icon; import javax.swing.Icon;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
@@ -70,17 +68,14 @@ public enum PCLocationTrackingSpec implements LocationTrackingSpec, LocationTrac
} }
@Override @Override
public CompletableFuture<Address> computeTraceAddress(PluginTool tool, public Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
DebuggerCoordinates coordinates) { if (coordinates.getTime().isSnapOnly()) {
return CompletableFuture.supplyAsync(() -> { Address pc = BY_STACK.computeTraceAddress(tool, coordinates);
if (coordinates.getTime().isSnapOnly()) { if (pc != null) {
Address pc = BY_STACK.doComputeTraceAddress(tool, coordinates); return pc;
if (pc != null) {
return pc;
}
} }
return BY_REG.doComputeTraceAddress(tool, coordinates); }
}); return BY_REG.computeTraceAddress(tool, coordinates);
} }
@Override @Override
@@ -15,8 +15,6 @@
*/ */
package ghidra.app.plugin.core.debug.gui.action; package ghidra.app.plugin.core.debug.gui.action;
import java.util.concurrent.CompletableFuture;
import ghidra.debug.api.action.*; import ghidra.debug.api.action.*;
import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
@@ -52,7 +50,8 @@ public interface RegisterLocationTrackingSpec extends LocationTrackingSpec, Loca
return this; return this;
} }
default Address doComputeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) { @Override
default Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
Trace trace = coordinates.getTrace(); Trace trace = coordinates.getTrace();
TracePlatform platform = coordinates.getPlatform(); TracePlatform platform = coordinates.getPlatform();
TraceThread thread = coordinates.getThread(); TraceThread thread = coordinates.getThread();
@@ -88,12 +87,6 @@ public interface RegisterLocationTrackingSpec extends LocationTrackingSpec, Loca
.getAddress(value.getUnsignedValue().longValue(), true)); .getAddress(value.getUnsignedValue().longValue(), true));
} }
@Override
default CompletableFuture<Address> computeTraceAddress(PluginTool tool,
DebuggerCoordinates coordinates) {
return CompletableFuture.supplyAsync(() -> doComputeTraceAddress(tool, coordinates));
}
@Override @Override
default GoToInput getDefaultGoToInput(PluginTool tool, DebuggerCoordinates coordinates, default GoToInput getDefaultGoToInput(PluginTool tool, DebuggerCoordinates coordinates,
ProgramLocation location) { ProgramLocation location) {
@@ -16,13 +16,11 @@
package ghidra.app.plugin.core.debug.gui.action; package ghidra.app.plugin.core.debug.gui.action;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import javax.swing.Icon; import javax.swing.Icon;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
import ghidra.async.AsyncUtils;
import ghidra.debug.api.action.*; import ghidra.debug.api.action.*;
import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.debug.api.watch.WatchRow; import ghidra.debug.api.watch.WatchRow;
@@ -115,28 +113,25 @@ public class WatchLocationTrackingSpec implements LocationTrackingSpec {
class WatchLocationTracker implements LocationTracker { class WatchLocationTracker implements LocationTracker {
private AddressSetView reads; private AddressSetView reads;
private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
private PcodeExecutor<WatchValue> asyncExec = null; private PcodeExecutor<WatchValue> exec = null;
private PcodeExpression compiled; private PcodeExpression compiled;
@Override @Override
public CompletableFuture<Address> computeTraceAddress(PluginTool tool, public Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
DebuggerCoordinates coordinates) { if (!Objects.equals(current, coordinates) || exec == null) {
if (!Objects.equals(current, coordinates) || asyncExec == null) {
current = coordinates; current = coordinates;
asyncExec = current.getPlatform() == null ? null exec = current.getPlatform() == null ? null
: DebuggerPcodeUtils.buildWatchExecutor(tool, coordinates); : DebuggerPcodeUtils.buildWatchExecutor(tool, coordinates);
} }
else { else {
asyncExec.getState().clear(); exec.getState().clear();
} }
if (current.getTrace() == null) { if (current.getTrace() == null) {
return AsyncUtils.nil(); return null;
} }
return CompletableFuture.supplyAsync(() -> { compiled = DebuggerPcodeUtils.compileExpression(tool, current, expression);
compiled = DebuggerPcodeUtils.compileExpression(tool, current, expression); WatchValue value = compiled.evaluate(exec);
WatchValue value = compiled.evaluate(asyncExec); return value == null ? null : value.address();
return value == null ? null : value.address();
});
} }
@Override @Override
@@ -20,7 +20,6 @@ import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.*; import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.services.DebuggerLogicalBreakpointService; import ghidra.app.services.DebuggerLogicalBreakpointService;
import ghidra.app.services.DebuggerModelService;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
@@ -259,48 +259,40 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
@Override @Override
public void processEvent(PluginEvent event) { public void processEvent(PluginEvent event) {
if (event instanceof ProgramLocationPluginEvent) { if (event instanceof ProgramLocationPluginEvent ev) {
cbProgramLocationEvents.invoke(() -> { cbProgramLocationEvents.invoke(() -> {
ProgramLocationPluginEvent ev = (ProgramLocationPluginEvent) event;
if (heedLocationEvent(ev)) { if (heedLocationEvent(ev)) {
connectedProvider.staticProgramLocationChanged(ev.getLocation()); connectedProvider.staticProgramLocationChanged(ev.getLocation());
} }
}); });
} }
if (event instanceof ProgramSelectionPluginEvent) { if (event instanceof ProgramSelectionPluginEvent ev) {
cbProgramSelectionEvents.invoke(() -> { cbProgramSelectionEvents.invoke(() -> {
ProgramSelectionPluginEvent ev = (ProgramSelectionPluginEvent) event;
if (heedSelectionEvent(ev)) { if (heedSelectionEvent(ev)) {
connectedProvider.staticProgramSelectionChanged(ev.getProgram(), connectedProvider.staticProgramSelectionChanged(ev.getProgram(),
ev.getSelection()); ev.getSelection());
} }
}); });
} }
if (event instanceof ProgramOpenedPluginEvent) { if (event instanceof ProgramOpenedPluginEvent ev) {
ProgramOpenedPluginEvent ev = (ProgramOpenedPluginEvent) event;
allProviders(p -> p.programOpened(ev.getProgram())); allProviders(p -> p.programOpened(ev.getProgram()));
} }
if (event instanceof ProgramClosedPluginEvent) { if (event instanceof ProgramClosedPluginEvent ev) {
ProgramClosedPluginEvent ev = (ProgramClosedPluginEvent) event;
allProviders(p -> p.programClosed(ev.getProgram())); allProviders(p -> p.programClosed(ev.getProgram()));
} }
if (event instanceof ProgramActivatedPluginEvent) { if (event instanceof ProgramActivatedPluginEvent ev) {
ProgramActivatedPluginEvent ev = (ProgramActivatedPluginEvent) event;
allProviders(p -> p.staticProgramActivated(ev.getActiveProgram())); allProviders(p -> p.staticProgramActivated(ev.getActiveProgram()));
} }
if (event instanceof TraceActivatedPluginEvent) { if (event instanceof TraceActivatedPluginEvent ev) {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
current = ev.getActiveCoordinates(); current = ev.getActiveCoordinates();
allProviders(p -> p.coordinatesActivated(current)); allProviders(p -> p.coordinatesActivated(current));
} }
if (event instanceof TraceClosedPluginEvent) { if (event instanceof TraceClosedPluginEvent ev) {
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
if (current.getTrace() == ev.getTrace()) { if (current.getTrace() == ev.getTrace()) {
current = DebuggerCoordinates.NOWHERE; current = DebuggerCoordinates.NOWHERE;
} }
allProviders(p -> p.traceClosed(ev.getTrace())); allProviders(p -> p.traceClosed(ev.getTrace()));
} }
// TODO: Sync selection and highlights?
} }
void fireStaticLocationEvent(ProgramLocation staticLoc) { void fireStaticLocationEvent(ProgramLocation staticLoc) {
@@ -1159,24 +1159,22 @@ public class DebuggerListingProvider extends CodeViewerProvider {
} }
protected void doGoToTracked() { protected void doGoToTracked() {
ProgramLocation loc = trackingTrait.getTrackedLocation(); Swing.runIfSwingOrRunLater(() -> {
ProgramLocation trackedStatic = doMarkTrackedLocation(); ProgramLocation loc = trackingTrait.getTrackedLocation();
if (loc == null) { ProgramLocation trackedStatic = doMarkTrackedLocation();
return; if (loc == null) {
} return;
TraceProgramView curView = current.getView(); }
if (!syncTrait.isAutoSyncCursorWithStaticListing() || trackedStatic == null) { TraceProgramView curView = current.getView();
Swing.runIfSwingOrRunLater(() -> { if (!syncTrait.isAutoSyncCursorWithStaticListing() || trackedStatic == null) {
if (curView != current.getView()) { if (curView != current.getView()) {
// Trace changed before Swing scheduled us // Trace changed before Swing scheduled us
return; return;
} }
goToAndUpdateTrackingLabel(curView, loc); goToAndUpdateTrackingLabel(curView, loc);
doCheckCurrentModuleMissing(); doCheckCurrentModuleMissing();
}); }
} else {
else {
Swing.runIfSwingOrRunLater(() -> {
if (curView != current.getView()) { if (curView != current.getView()) {
// Trace changed before Swing scheduled us // Trace changed before Swing scheduled us
return; return;
@@ -1184,8 +1182,8 @@ public class DebuggerListingProvider extends CodeViewerProvider {
goToAndUpdateTrackingLabel(curView, loc); goToAndUpdateTrackingLabel(curView, loc);
doCheckCurrentModuleMissing(); doCheckCurrentModuleMissing();
plugin.fireStaticLocationEvent(trackedStatic); plugin.fireStaticLocationEvent(trackedStatic);
}); }
} });
} }
protected void doAutoDisassemble(Address start) { protected void doAutoDisassemble(Address start) {
@@ -150,13 +150,11 @@ public class DebuggerMemoryBytesPlugin
@Override @Override
public void processEvent(PluginEvent event) { public void processEvent(PluginEvent event) {
if (event instanceof TraceActivatedPluginEvent) { if (event instanceof TraceActivatedPluginEvent ev) {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
current = ev.getActiveCoordinates(); current = ev.getActiveCoordinates();
allProviders(p -> p.coordinatesActivated(current)); allProviders(p -> p.coordinatesActivated(current));
} }
if (event instanceof TraceClosedPluginEvent) { if (event instanceof TraceClosedPluginEvent ev) {
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
if (current.getTrace() == ev.getTrace()) { if (current.getTrace() == ev.getTrace()) {
current = DebuggerCoordinates.NOWHERE; current = DebuggerCoordinates.NOWHERE;
} }
@@ -66,20 +66,16 @@ public class DebuggerRegionsPlugin extends AbstractDebuggerPlugin {
@Override @Override
public void processEvent(PluginEvent event) { public void processEvent(PluginEvent event) {
super.processEvent(event); super.processEvent(event);
if (event instanceof ProgramActivatedPluginEvent) { if (event instanceof ProgramActivatedPluginEvent ev) {
ProgramActivatedPluginEvent ev = (ProgramActivatedPluginEvent) event;
provider.setProgram(ev.getActiveProgram()); provider.setProgram(ev.getActiveProgram());
} }
else if (event instanceof ProgramLocationPluginEvent) { else if (event instanceof ProgramLocationPluginEvent ev) {
ProgramLocationPluginEvent ev = (ProgramLocationPluginEvent) event;
provider.setLocation(ev.getLocation()); provider.setLocation(ev.getLocation());
} }
else if (event instanceof ProgramClosedPluginEvent) { else if (event instanceof ProgramClosedPluginEvent ev) {
ProgramClosedPluginEvent ev = (ProgramClosedPluginEvent) event;
provider.programClosed(ev.getProgram()); provider.programClosed(ev.getProgram());
} }
else if (event instanceof TraceActivatedPluginEvent) { else if (event instanceof TraceActivatedPluginEvent ev) {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
provider.coordinatesActivated(ev.getActiveCoordinates()); provider.coordinatesActivated(ev.getActiveCoordinates());
} }
} }
@@ -26,22 +26,21 @@ import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@PluginInfo( // @PluginInfo(
shortDescription = "Displays memory vs time", // shortDescription = "Displays memory vs time",
description = "Provides visualiztion/navigation across time/address axes", // description = "Provides visualiztion/navigation across time/address axes",
category = PluginCategoryNames.DEBUGGER, // category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME, // packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED, // status = PluginStatus.RELEASED,
eventsConsumed = { // eventsConsumed = {
TraceActivatedPluginEvent.class // TraceActivatedPluginEvent.class
}, // },
servicesRequired = { // servicesRequired = {
DebuggerTraceManagerService.class // DebuggerTraceManagerService.class
}, // },
servicesProvided = { // servicesProvided = {
MemviewService.class // MemviewService.class
} // })
)
public class DebuggerMemviewPlugin extends AbstractDebuggerPlugin implements MemviewService { public class DebuggerMemviewPlugin extends AbstractDebuggerPlugin implements MemviewService {
protected MemviewProvider provider; protected MemviewProvider provider;
@@ -67,12 +66,12 @@ public class DebuggerMemviewPlugin extends AbstractDebuggerPlugin implements Mem
@Override @Override
public void processEvent(PluginEvent event) { public void processEvent(PluginEvent event) {
super.processEvent(event); super.processEvent(event);
if (event instanceof TraceActivatedPluginEvent) { if (event instanceof TraceActivatedPluginEvent ev) {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
listener.coordinatesActivated(ev.getActiveCoordinates()); listener.coordinatesActivated(ev.getActiveCoordinates());
} }
} }
@Override
public MemviewProvider getProvider() { public MemviewProvider getProvider() {
return provider; return provider;
} }
@@ -65,20 +65,16 @@ public class DebuggerModulesPlugin extends AbstractDebuggerPlugin {
@Override @Override
public void processEvent(PluginEvent event) { public void processEvent(PluginEvent event) {
super.processEvent(event); super.processEvent(event);
if (event instanceof ProgramActivatedPluginEvent) { if (event instanceof ProgramActivatedPluginEvent ev) {
ProgramActivatedPluginEvent ev = (ProgramActivatedPluginEvent) event;
provider.setProgram(ev.getActiveProgram()); provider.setProgram(ev.getActiveProgram());
} }
else if (event instanceof ProgramLocationPluginEvent) { else if (event instanceof ProgramLocationPluginEvent ev) {
ProgramLocationPluginEvent ev = (ProgramLocationPluginEvent) event;
provider.setLocation(ev.getLocation()); provider.setLocation(ev.getLocation());
} }
else if (event instanceof ProgramClosedPluginEvent) { else if (event instanceof ProgramClosedPluginEvent ev) {
ProgramClosedPluginEvent ev = (ProgramClosedPluginEvent) event;
provider.programClosed(ev.getProgram()); provider.programClosed(ev.getProgram());
} }
else if (event instanceof TraceActivatedPluginEvent) { else if (event instanceof TraceActivatedPluginEvent ev) {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
provider.coordinatesActivated(ev.getActiveCoordinates()); provider.coordinatesActivated(ev.getActiveCoordinates());
} }
} }
@@ -64,12 +64,10 @@ public class DebuggerStaticMappingPlugin extends AbstractDebuggerPlugin {
@Override @Override
public void processEvent(PluginEvent event) { public void processEvent(PluginEvent event) {
super.processEvent(event); super.processEvent(event);
if (event instanceof TraceActivatedPluginEvent) { if (event instanceof TraceActivatedPluginEvent ev) {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
provider.setTrace(ev.getActiveCoordinates().getTrace()); provider.setTrace(ev.getActiveCoordinates().getTrace());
} }
if (event instanceof ProgramActivatedPluginEvent) { if (event instanceof ProgramActivatedPluginEvent ev) {
ProgramActivatedPluginEvent ev = (ProgramActivatedPluginEvent) event;
provider.setProgram(ev.getActiveProgram()); provider.setProgram(ev.getActiveProgram());
} }
} }
@@ -112,38 +112,32 @@ public class DebuggerObjectsPlugin extends AbstractDebuggerPlugin
provider.traceOpened(ev.getTrace()); provider.traceOpened(ev.getTrace());
} }
} }
else if (event instanceof TraceActivatedPluginEvent) { else if (event instanceof TraceActivatedPluginEvent ev) {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
for (DebuggerObjectsProvider provider : providers) { for (DebuggerObjectsProvider provider : providers) {
provider.traceActivated(ev.getActiveCoordinates()); provider.traceActivated(ev.getActiveCoordinates());
} }
} }
else if (event instanceof TraceClosedPluginEvent) { else if (event instanceof TraceClosedPluginEvent ev) {
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
for (DebuggerObjectsProvider provider : providers) { for (DebuggerObjectsProvider provider : providers) {
provider.traceClosed(ev.getTrace()); provider.traceClosed(ev.getTrace());
} }
} }
else if (event instanceof ModelActivatedPluginEvent) { else if (event instanceof ModelActivatedPluginEvent ev) {
ModelActivatedPluginEvent ev = (ModelActivatedPluginEvent) event;
for (DebuggerObjectsProvider provider : providers) { for (DebuggerObjectsProvider provider : providers) {
provider.modelActivated(ev.getActiveModel()); provider.modelActivated(ev.getActiveModel());
} }
} }
else if (event instanceof ProgramActivatedPluginEvent) { else if (event instanceof ProgramActivatedPluginEvent ev) {
ProgramActivatedPluginEvent ev = (ProgramActivatedPluginEvent) event;
for (DebuggerObjectsProvider provider : providers) { for (DebuggerObjectsProvider provider : providers) {
provider.setProgram(ev.getActiveProgram()); provider.setProgram(ev.getActiveProgram());
} }
} }
else if (event instanceof ProgramOpenedPluginEvent) { else if (event instanceof ProgramOpenedPluginEvent ev) {
ProgramOpenedPluginEvent ev = (ProgramOpenedPluginEvent) event;
for (DebuggerObjectsProvider provider : providers) { for (DebuggerObjectsProvider provider : providers) {
provider.setProgram(ev.getProgram()); provider.setProgram(ev.getProgram());
} }
} }
else if (event instanceof ProgramSelectionPluginEvent) { else if (event instanceof ProgramSelectionPluginEvent ev) {
ProgramSelectionPluginEvent ev = (ProgramSelectionPluginEvent) event;
for (DebuggerObjectsProvider provider : providers) { for (DebuggerObjectsProvider provider : providers) {
provider.setProgram(ev.getProgram()); provider.setProgram(ev.getProgram());
} }
@@ -53,8 +53,7 @@ public class DebuggerPcodeStepperPlugin extends AbstractDebuggerPlugin {
@Override @Override
public void processEvent(PluginEvent event) { public void processEvent(PluginEvent event) {
super.processEvent(event); super.processEvent(event);
if (event instanceof TraceActivatedPluginEvent) { if (event instanceof TraceActivatedPluginEvent ev) {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
provider.coordinatesActivated(ev.getActiveCoordinates()); provider.coordinatesActivated(ev.getActiveCoordinates());
} }
} }
@@ -29,7 +29,8 @@ import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.*; import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOpinion;
import ghidra.app.services.DebuggerPlatformService; import ghidra.app.services.DebuggerPlatformService;
import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.debug.api.platform.DebuggerPlatformMapper; import ghidra.debug.api.platform.DebuggerPlatformMapper;
@@ -307,14 +308,14 @@ public class DebuggerPlatformPlugin extends Plugin {
@Override @Override
public void processEvent(PluginEvent event) { public void processEvent(PluginEvent event) {
super.processEvent(event); super.processEvent(event);
if (event instanceof TraceActivatedPluginEvent evt) { if (event instanceof TraceActivatedPluginEvent ev) {
coordinatesActivated(evt.getActiveCoordinates()); coordinatesActivated(ev.getActiveCoordinates());
} }
if (event instanceof TraceClosedPluginEvent evt) { if (event instanceof TraceClosedPluginEvent ev) {
traceClosed(evt.getTrace()); traceClosed(ev.getTrace());
} }
if (event instanceof DebuggerPlatformPluginEvent evt) { if (event instanceof DebuggerPlatformPluginEvent ev) {
mapperActivated(evt.getTrace(), evt.getMapper()); mapperActivated(ev.getTrace(), ev.getMapper());
} }
} }
@@ -101,12 +101,10 @@ public class DebuggerRegistersPlugin extends AbstractDebuggerPlugin {
@Override @Override
public void processEvent(PluginEvent event) { public void processEvent(PluginEvent event) {
super.processEvent(event); super.processEvent(event);
if (event instanceof TraceActivatedPluginEvent) { if (event instanceof TraceActivatedPluginEvent ev) {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
connectedProvider.coordinatesActivated(ev.getActiveCoordinates()); connectedProvider.coordinatesActivated(ev.getActiveCoordinates());
} }
if (event instanceof TraceClosedPluginEvent) { if (event instanceof TraceClosedPluginEvent ev) {
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
traceClosed(ev.getTrace()); traceClosed(ev.getTrace());
} }
} }
@@ -18,7 +18,8 @@ package ghidra.app.plugin.core.debug.gui.thread;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin; import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.*; import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent;
import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
@@ -57,8 +58,7 @@ public class DebuggerThreadsPlugin extends AbstractDebuggerPlugin {
@Override @Override
public void processEvent(PluginEvent event) { public void processEvent(PluginEvent event) {
super.processEvent(event); super.processEvent(event);
if (event instanceof TraceActivatedPluginEvent) { if (event instanceof TraceActivatedPluginEvent ev) {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
provider.coordinatesActivated(ev.getActiveCoordinates()); provider.coordinatesActivated(ev.getActiveCoordinates());
} }
} }
@@ -21,6 +21,7 @@ import java.util.Collection;
import java.util.Objects; import java.util.Objects;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors;
import javax.swing.*; import javax.swing.*;
import javax.swing.table.*; import javax.swing.table.*;
@@ -141,7 +142,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
public Component getTableCellRendererComponent(GTableCellRenderingData data) { public Component getTableCellRendererComponent(GTableCellRenderingData data) {
super.getTableCellRendererComponent(data); super.getTableCellRendererComponent(data);
SnapshotRow row = (SnapshotRow) data.getRowObject(); SnapshotRow row = (SnapshotRow) data.getRowObject();
if (row != null && row.getSnap() == currentSnap) { if (row != null && currentSnap != null && currentSnap.longValue() == row.getSnap()) {
setBold(); setBold();
} }
return this; return this;
@@ -154,7 +155,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
protected boolean hideScratch = true; protected boolean hideScratch = true;
private Trace currentTrace; private Trace currentTrace;
private Long currentSnap; private volatile Long currentSnap;
protected final SnapshotListener listener = new SnapshotListener(); protected final SnapshotListener listener = new SnapshotListener();
@@ -237,8 +238,11 @@ public class DebuggerSnapshotTablePanel extends JPanel {
Collection<? extends TraceSnapshot> snapshots = Collection<? extends TraceSnapshot> snapshots =
hideScratch ? manager.getSnapshots(0, true, Long.MAX_VALUE, true) hideScratch ? manager.getSnapshots(0, true, Long.MAX_VALUE, true)
: manager.getAllSnapshots(); : manager.getAllSnapshots();
snapshotTableModel // Use .collect instead of .toList to avoid size/sync issues
.addAll(snapshots.stream().map(s -> new SnapshotRow(currentTrace, s)).toList()); // Even though access is synchronized, size may change during iteration
snapshotTableModel.addAll(snapshots.stream()
.map(s -> new SnapshotRow(currentTrace, s))
.collect(Collectors.toList()));
} }
protected void deleteScratchSnapshots() { protected void deleteScratchSnapshots() {
@@ -250,10 +254,11 @@ public class DebuggerSnapshotTablePanel extends JPanel {
return; return;
} }
TraceTimeManager manager = currentTrace.getTimeManager(); TraceTimeManager manager = currentTrace.getTimeManager();
snapshotTableModel.addAll(manager.getSnapshots(Long.MIN_VALUE, true, 0, false) Collection<? extends TraceSnapshot> sratch =
.stream() manager.getSnapshots(Long.MIN_VALUE, true, 0, false);
snapshotTableModel.addAll(sratch.stream()
.map(s -> new SnapshotRow(currentTrace, s)) .map(s -> new SnapshotRow(currentTrace, s))
.toList()); .collect(Collectors.toList()));
} }
public ListSelectionModel getSelectionModel() { public ListSelectionModel getSelectionModel() {
@@ -265,8 +270,12 @@ public class DebuggerSnapshotTablePanel extends JPanel {
return row == null ? null : row.getSnap(); return row == null ? null : row.getSnap();
} }
public void setSelectedSnapshot(Long snap) { public void setCurrentSnapshot(Long snap) {
currentSnap = snap; currentSnap = snap;
snapshotTableModel.fireTableDataChanged();
}
public void setSelectedSnapshot(Long snap) {
if (snap == null) { if (snap == null) {
snapshotTable.clearSelection(); snapshotTable.clearSelection();
return; return;
@@ -283,6 +292,5 @@ public class DebuggerSnapshotTablePanel extends JPanel {
return; return;
} }
snapshotFilterPanel.setSelectedItem(row); snapshotFilterPanel.setSelectedItem(row);
snapshotTableModel.fireTableDataChanged();
} }
} }
@@ -122,8 +122,7 @@ public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
@Override @Override
public void processEvent(PluginEvent event) { public void processEvent(PluginEvent event) {
super.processEvent(event); super.processEvent(event);
if (event instanceof TraceActivatedPluginEvent) { if (event instanceof TraceActivatedPluginEvent ev) {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
provider.coordinatesActivated(ev.getActiveCoordinates()); provider.coordinatesActivated(ev.getActiveCoordinates());
} }
} }
@@ -209,7 +209,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
current = coordinates; current = coordinates;
mainPanel.setTrace(current.getTrace()); mainPanel.setTrace(current.getTrace());
mainPanel.setSelectedSnapshot(current.getSnap()); mainPanel.setCurrentSnapshot(current.getSnap());
} }
public void writeConfigState(SaveState saveState) { public void writeConfigState(SaveState saveState) {
@@ -1360,23 +1360,23 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
@Override @Override
public void processEvent(PluginEvent event) { public void processEvent(PluginEvent event) {
if (event instanceof ProgramOpenedPluginEvent evt) { if (event instanceof ProgramOpenedPluginEvent ev) {
programOpened(evt.getProgram()); programOpened(ev.getProgram());
} }
else if (event instanceof ProgramClosedPluginEvent evt) { else if (event instanceof ProgramClosedPluginEvent ev) {
programClosed(evt.getProgram()); programClosed(ev.getProgram());
} }
else if (event instanceof TraceOpenedPluginEvent evt) { else if (event instanceof TraceOpenedPluginEvent ev) {
traceOpened(evt.getTrace()); traceOpened(ev.getTrace());
} }
else if (event instanceof TraceActivatedPluginEvent evt) { else if (event instanceof TraceActivatedPluginEvent ev) {
traceSnapChanged(evt.getActiveCoordinates()); traceSnapChanged(ev.getActiveCoordinates());
} }
else if (event instanceof TraceInactiveCoordinatesPluginEvent evt) { else if (event instanceof TraceInactiveCoordinatesPluginEvent ev) {
traceSnapChanged(evt.getCoordinates()); traceSnapChanged(ev.getCoordinates());
} }
else if (event instanceof TraceClosedPluginEvent evt) { else if (event instanceof TraceClosedPluginEvent ev) {
traceClosed(evt.getTrace()); traceClosed(ev.getTrace());
} }
} }
} }
@@ -20,7 +20,8 @@ import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.*; import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.*; import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
@@ -342,14 +343,14 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
@Override @Override
public void processEvent(PluginEvent event) { public void processEvent(PluginEvent event) {
super.processEvent(event); super.processEvent(event);
if (event instanceof TraceOpenedPluginEvent evt) { if (event instanceof TraceOpenedPluginEvent ev) {
installAllMemoryEditors(evt.getTrace()); installAllMemoryEditors(ev.getTrace());
} }
else if (event instanceof TraceActivatedPluginEvent evt) { else if (event instanceof TraceActivatedPluginEvent ev) {
coordinatesActivated(evt.getActiveCoordinates(), evt.getCause()); coordinatesActivated(ev.getActiveCoordinates(), ev.getCause());
} }
else if (event instanceof TraceClosedPluginEvent evt) { else if (event instanceof TraceClosedPluginEvent ev) {
uninstallAllMemoryEditors(evt.getTrace()); uninstallAllMemoryEditors(ev.getTrace());
} }
} }
@@ -83,6 +83,24 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
this.mapping = mapping; this.mapping = mapping;
} }
@Override
public boolean equals(Object o) {
if (!(o instanceof MappingEntry that)) {
return false;
}
// Yes, use identity, since it should be the same trace db records
if (this.mapping != that.mapping) {
return false;
}
if (this.program != that.program) {
return false;
}
if (!Objects.equals(this.staticRange, that.staticRange)) {
return false;
}
return true;
}
public Trace getTrace() { public Trace getTrace() {
return mapping.getTrace(); return mapping.getTrace();
} }
@@ -232,11 +250,14 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
private void objectRestored() { private void objectRestored() {
synchronized (lock) { synchronized (lock) {
doAffectedByTraceClosed(trace); var old = Map.copyOf(outbound);
outbound.clear(); outbound.clear();
loadOutboundEntries(); // Also places/updates corresponding inbound entries loadOutboundEntries(); // Also places/updates corresponding inbound entries
// TODO: What about removed corresponding inbound entries? if (!old.equals(outbound)) {
doAffectedByTraceOpened(trace); // TODO: What about removed corresponding inbound entries?
doAffectedByTraceClosed(trace);
doAffectedByTraceOpened(trace);
}
} }
} }
@@ -811,13 +832,13 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
} }
protected <T> T noTraceInfo() { protected <T> T noTraceInfo() {
Msg.warn(this, "The given trace is not open in this tool " + Msg.debug(this, "The given trace is not open in this tool " +
"(or the service hasn't received and processed the open-trace event, yet)"); "(or the service hasn't received and processed the open-trace event, yet)");
return null; return null;
} }
protected <T> T noProgramInfo() { protected <T> T noProgramInfo() {
Msg.warn(this, "The given program is not open in this tool " + Msg.debug(this, "The given program is not open in this tool " +
"(or the service hasn't received and processed the open-program event, yet)"); "(or the service hasn't received and processed the open-program event, yet)");
return null; return null;
} }
@@ -269,7 +269,7 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerTes
waitForPass(() -> assertTableSize(0)); waitForPass(() -> assertTableSize(0));
} }
@Test // @Test // Not gonna with write-behind cache
public void testUndoRedo() throws Exception { public void testUndoRedo() throws Exception {
createAndOpenTrace(); createAndOpenTrace();
@@ -304,7 +304,7 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerTes
}); });
} }
@Test // @Test // Not gonna with write-behind cache
public void testAbort() throws Exception { public void testAbort() throws Exception {
createAndOpenTrace(); createAndOpenTrace();
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
@@ -1128,7 +1128,8 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerTest
} }
waitForTasks(); waitForTasks();
assertEquals(4, modelProvider.attributesTablePanel.tableModel.getModelData().size()); waitForPass(() -> assertEquals(4,
modelProvider.attributesTablePanel.tableModel.getModelData().size()));
} }
@Test @Test
@@ -1158,6 +1159,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerTest
// TODO: Should I collapse entries that are links to the same object? // TODO: Should I collapse entries that are links to the same object?
// Would use the "Life" column to display span for each included entry. // Would use the "Life" column to display span for each included entry.
// Neat, but not sure it's worth it // Neat, but not sure it's worth it
assertEquals(14, modelProvider.elementsTablePanel.tableModel.getModelData().size()); waitForPass(() -> assertEquals(14,
modelProvider.elementsTablePanel.tableModel.getModelData().size()));
} }
} }
@@ -401,7 +401,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerTes
}); });
} }
@Test // @Test // Not gonna with write-behind cache
public void testUndoRedoCausesUpdateInProvider() throws Exception { public void testUndoRedoCausesUpdateInProvider() throws Exception {
createAndOpenTrace(); createAndOpenTrace();
@@ -444,7 +444,7 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerTes
thread1.getObject().getAttribute(0, TraceObjectThread.KEY_COMMENT).getValue())); thread1.getObject().getAttribute(0, TraceObjectThread.KEY_COMMENT).getValue()));
} }
@Test // @Test // Not gonna with write-behind cache
public void testUndoRedoCausesUpdateInProvider() throws Exception { public void testUndoRedoCausesUpdateInProvider() throws Exception {
createAndOpenTrace(); createAndOpenTrace();
addThreads(); addThreads();
@@ -15,7 +15,8 @@
*/ */
package ghidra.app.plugin.core.debug.gui.time; package ghidra.app.plugin.core.debug.gui.time;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Calendar; import java.util.Calendar;
import java.util.List; import java.util.List;
@@ -286,35 +287,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
tb.trace.getTimeManager().getSnapshot(0, false).getDescription()); tb.trace.getTimeManager().getSnapshot(0, false).getDescription());
} }
@Test // TODO: Test activation bolds the row
public void testActivateSnapSelectsRow() throws Exception {
createSnaplessTrace();
traceManager.openTrace(tb.trace);
addSnapshots();
waitForDomainObject(tb.trace);
assertProviderEmpty();
traceManager.activateTrace(tb.trace);
waitForSwing();
List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
traceManager.activateSnap(0);
waitForSwing();
assertEquals(data.get(0), timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
traceManager.activateSnap(10);
waitForSwing();
assertEquals(data.get(1), timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
traceManager.activateSnap(5);
waitForSwing();
assertNull(timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
}
@Test @Test
public void testDoubleClickRowActivatesSnap() throws Exception { public void testDoubleClickRowActivatesSnap() throws Exception {
@@ -47,6 +47,7 @@ import ghidra.trace.model.target.*;
import ghidra.trace.model.thread.*; import ghidra.trace.model.thread.*;
import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager; import ghidra.trace.model.time.TraceTimeManager;
import ghidra.util.StreamUtils;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerTest { public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerTest {
@@ -88,7 +89,7 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerTe
protected void dumpObjects() { protected void dumpObjects() {
System.err.println("All objects:"); System.err.println("All objects:");
for (TraceObject object : objects.getAllObjects()) { for (TraceObject object : StreamUtils.iter(objects.getAllObjects())) {
System.err.println(" " + object); System.err.println(" " + object);
} }
} }
@@ -99,7 +100,7 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerTe
waitForPass(noExc(() -> { waitForPass(noExc(() -> {
waitOn(recorder.flushTransactions()); waitOn(recorder.flushTransactions());
assertEquals(5, objects.getAllObjects().size()); assertEquals(5, objects.getObjectCount());
})); }));
} }
@@ -32,6 +32,8 @@ import java.util.function.Consumer;
* debouncer configured with a time window that contains all the events, only the final event in the * debouncer configured with a time window that contains all the events, only the final event in the
* cluster will be processed. The cost of doing this is a waiting period, so event processing may be * cluster will be processed. The cost of doing this is a waiting period, so event processing may be
* less responsive, but will also be less frantic. * less responsive, but will also be less frantic.
*
* @param <T> the value type
*/ */
public class AsyncDebouncer<T> { public class AsyncDebouncer<T> {
protected final AsyncTimer timer; protected final AsyncTimer timer;
@@ -99,7 +101,7 @@ public class AsyncDebouncer<T> {
* This sets or resets the timer for the event window. The settled event will fire with the * This sets or resets the timer for the event window. The settled event will fire with the
* given value after this waiting period, unless another contact event occurs first. * given value after this waiting period, unless another contact event occurs first.
* *
* @param val * @param val the new value
*/ */
public synchronized void contact(T val) { public synchronized void contact(T val) {
lastContact = val; lastContact = val;
@@ -141,4 +143,16 @@ public class AsyncDebouncer<T> {
} }
return settled(); return settled();
} }
public static class Bypass<T> extends AsyncDebouncer<T> {
public Bypass() {
super(null, 0);
}
@Override
public synchronized void contact(T val) {
lastContact = val;
doSettled();
}
}
} }
@@ -15,6 +15,7 @@
*/ */
package ghidra.trace.database; package ghidra.trace.database;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -149,6 +150,8 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
protected ListenerSet<TraceProgramViewListener> viewListeners = protected ListenerSet<TraceProgramViewListener> viewListeners =
new ListenerSet<>(TraceProgramViewListener.class, true); new ListenerSet<>(TraceProgramViewListener.class, true);
private volatile boolean closing;
public DBTrace(String name, CompilerSpec baseCompilerSpec, Object consumer) public DBTrace(String name, CompilerSpec baseCompilerSpec, Object consumer)
throws IOException, LanguageNotFoundException { throws IOException, LanguageNotFoundException {
super(new DBHandle(), DBOpenMode.CREATE, TaskMonitor.DUMMY, name, DB_TIME_INTERVAL, super(new DBHandle(), DBOpenMode.CREATE, TaskMonitor.DUMMY, name, DB_TIME_INTERVAL,
@@ -608,7 +611,7 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
DBTraceProgramView view; DBTraceProgramView view;
try (LockHold hold = lockRead()) { try (LockHold hold = lockRead()) {
view = fixedProgramViews.computeIfAbsent(snap, s -> { view = fixedProgramViews.computeIfAbsent(snap, s -> {
Msg.debug(this, "Creating fixed view at snap=" + snap); Msg.trace(this, "Creating fixed view at snap=" + snap);
return new DBTraceProgramView(this, snap, baseCompilerSpec); return new DBTraceProgramView(this, snap, baseCompilerSpec);
}); });
} }
@@ -871,4 +874,29 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
public void updateViewportsSnapshotDeleted(TraceSnapshot snapshot) { public void updateViewportsSnapshotDeleted(TraceSnapshot snapshot) {
allViewports(v -> v.updateSnapshotDeleted(snapshot)); allViewports(v -> v.updateSnapshotDeleted(snapshot));
} }
@Override
public void save(String comment, TaskMonitor monitor) throws IOException, CancelledException {
objectManager.flushWbCaches();
super.save(comment, monitor);
}
@Override
public void saveToPackedFile(File outputFile, TaskMonitor monitor)
throws IOException, CancelledException {
objectManager.flushWbCaches();
super.saveToPackedFile(outputFile, monitor);
}
public boolean isClosing() {
return closing;
}
@Override
protected void close() {
closing = true;
objectManager.flushWbCaches();
super.close();
objectManager.waitWbWorkers();
}
} }
@@ -49,6 +49,21 @@ public abstract class AbstractDBTraceProgramViewMemory
protected LiveMemoryHandler memoryWriteRedirect; protected LiveMemoryHandler memoryWriteRedirect;
private static final int CACHE_PAGE_COUNT = 3;
protected final ByteCache cache = new ByteCache(CACHE_PAGE_COUNT) {
@Override
protected int doLoad(Address address, ByteBuffer buf) throws MemoryAccessException {
DBTraceMemorySpace space =
program.trace.getMemoryManager().getMemorySpace(address.getAddressSpace(), false);
if (space == null) {
int len = buf.remaining();
buf.position(buf.limit());
return len;
}
return space.getViewBytes(program.snap, address, buf);
}
};
public AbstractDBTraceProgramViewMemory(DBTraceProgramView program) { public AbstractDBTraceProgramViewMemory(DBTraceProgramView program) {
this.program = program; this.program = program;
this.memoryManager = program.trace.getMemoryManager(); this.memoryManager = program.trace.getMemoryManager();
@@ -301,24 +316,25 @@ public abstract class AbstractDBTraceProgramViewMemory
@Override @Override
public byte getByte(Address addr) throws MemoryAccessException { public byte getByte(Address addr) throws MemoryAccessException {
MemoryBlock block = getBlock(addr); try (LockHold hold = program.trace.lockRead()) {
if (block == null) { return cache.read(addr);
return 0; // Memory assumed initialized to 0
} }
return block.getByte(addr);
} }
@Override @Override
public int getBytes(Address addr, byte[] dest, int destIndex, int size) public int getBytes(Address addr, byte[] b, int off, int len) throws MemoryAccessException {
throws MemoryAccessException { try (LockHold hold = program.trace.lockRead()) {
MemoryBlock block = getBlock(addr); if (cache.canCache(addr, len)) {
if (block == null) { return cache.read(addr, ByteBuffer.wrap(b, off, len));
int avail = MathUtilities.unsignedMin(Math.max(0, size), }
addr.getAddressSpace().getMaxAddress().subtract(addr)); AddressSpace as = addr.getAddressSpace();
Arrays.fill(dest, destIndex, avail, (byte) 0); DBTraceMemorySpace space = program.trace.getMemoryManager().getMemorySpace(as, false);
return avail; if (space == null) {
throw new MemoryAccessException("Space does not exist");
}
len = MathUtilities.unsignedMin(len, as.getMaxAddress().subtract(addr) + 1);
return space.getViewBytes(program.snap, addr, ByteBuffer.wrap(b, off, len));
} }
return block.getBytes(addr, dest, destIndex, size);
} }
@Override @Override
@@ -26,7 +26,6 @@ import ghidra.program.model.address.*;
import ghidra.program.model.mem.*; import ghidra.program.model.mem.*;
import ghidra.trace.database.memory.DBTraceMemorySpace; import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.model.memory.TraceMemorySpaceInputStream; import ghidra.trace.model.memory.TraceMemorySpaceInputStream;
import ghidra.util.LockHold;
import ghidra.util.MathUtilities; import ghidra.util.MathUtilities;
public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlock { public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlock {
@@ -97,19 +96,6 @@ public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlo
private final List<MemoryBlockSourceInfo> info = private final List<MemoryBlockSourceInfo> info =
Collections.singletonList(new MyMemoryBlockSourceInfo()); Collections.singletonList(new MyMemoryBlockSourceInfo());
private static final int CACHE_PAGE_COUNT = 3;
private final ByteCache cache = new ByteCache(CACHE_PAGE_COUNT) {
@Override
protected int doLoad(Address address, ByteBuffer buf) throws MemoryAccessException {
DBTraceMemorySpace space =
program.trace.getMemoryManager().getMemorySpace(getAddressSpace(), false);
if (space == null) {
throw new MemoryAccessException("Space does not exist");
}
return space.getViewBytes(program.snap, address, buf);
}
};
protected AbstractDBTraceProgramViewMemoryBlock(DBTraceProgramView program) { protected AbstractDBTraceProgramViewMemoryBlock(DBTraceProgramView program) {
this.program = program; this.program = program;
} }
@@ -120,15 +106,6 @@ public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlo
return getStart().getAddressSpace(); return getStart().getAddressSpace();
} }
/**
* Should be called when the snap changes or when bytes change
*/
protected void invalidateBytesCache(AddressRange range) {
if (range == null || range.intersects(getAddressRange())) {
cache.invalidate(range);
}
}
protected DBTraceMemorySpace getMemorySpace() { protected DBTraceMemorySpace getMemorySpace() {
return program.trace.getMemoryManager().getMemorySpace(getAddressSpace(), false); return program.trace.getMemoryManager().getMemorySpace(getAddressSpace(), false);
} }
@@ -191,13 +168,11 @@ public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlo
@Override @Override
public byte getByte(Address addr) throws MemoryAccessException { public byte getByte(Address addr) throws MemoryAccessException {
try (LockHold hold = program.trace.lockRead()) { AddressRange range = getAddressRange();
AddressRange range = getAddressRange(); if (!range.contains(addr)) {
if (!range.contains(addr)) { throw new MemoryAccessException();
throw new MemoryAccessException();
}
return cache.read(addr);
} }
return program.memory.getByte(addr);
} }
@Override @Override
@@ -207,22 +182,12 @@ public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlo
@Override @Override
public int getBytes(Address addr, byte[] b, int off, int len) throws MemoryAccessException { public int getBytes(Address addr, byte[] b, int off, int len) throws MemoryAccessException {
try (LockHold hold = program.trace.lockRead()) { AddressRange range = getAddressRange();
AddressRange range = getAddressRange(); if (!range.contains(addr)) {
if (!range.contains(addr)) { throw new MemoryAccessException();
throw new MemoryAccessException();
}
if (cache.canCache(addr, len)) {
return cache.read(addr, ByteBuffer.wrap(b, off, len));
}
DBTraceMemorySpace space =
program.trace.getMemoryManager().getMemorySpace(range.getAddressSpace(), false);
if (space == null) {
throw new MemoryAccessException("Space does not exist");
}
len = MathUtilities.unsignedMin(len, range.getMaxAddress().subtract(addr) + 1);
return space.getViewBytes(program.snap, addr, ByteBuffer.wrap(b, off, len));
} }
len = MathUtilities.unsignedMin(len, range.getMaxAddress().subtract(addr) + 1);
return program.memory.getBytes(addr, b, off, len);
} }
@Override @Override
@@ -267,7 +232,6 @@ public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlo
@Override @Override
public boolean isOverlay() { public boolean isOverlay() {
// TODO: What effect does this have? Does it makes sense for trace "overlays"?
return getAddressSpace().isOverlaySpace(); return getAddressSpace().isOverlaySpace();
} }
@@ -20,6 +20,7 @@ import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.mem.MemoryBlock;
import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.util.LockHold; import ghidra.util.LockHold;
@@ -202,10 +203,6 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
if (regionBlocks == null) { // <init> order if (regionBlocks == null) { // <init> order
return; return;
} }
for (AbstractDBTraceProgramViewMemoryBlock block : forceFullView cache.invalidate(range);
? spaceBlocks.values()
: regionBlocks.values()) {
block.invalidateBytesCache(range);
}
} }
} }
@@ -0,0 +1,217 @@
/* ###
* 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.database.target;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import ghidra.trace.model.Lifespan;
public class CachePerDBTraceObject {
private record SnapKey(long snap, String key) implements Comparable<SnapKey> {
@Override
public int compareTo(SnapKey that) {
int c = Long.compare(this.snap, that.snap);
if (c != 0) {
return c;
}
if (this.key == that.key) {
return 0;
}
if (this.key == null) {
return 1;
}
if (that.key == null) {
return -1;
}
return this.key.compareTo(that.key);
}
public static SnapKey forValue(DBTraceObjectValue value) {
return new SnapKey(value.getMinSnap(), value.getEntryKey());
}
}
public record Cached<T>(boolean isMiss, T value) {
static final Cached<?> MISS = new Cached<>(true, null);
@SuppressWarnings("unchecked")
public static <T> Cached<T> miss() {
return (Cached<T>) MISS;
}
static <T> Cached<T> hit(T value) {
return new Cached<>(false, value);
}
}
private static final int MAX_CACHE_KEYS = 200;
private static final int MAX_VALUES_PER_KEY = 20;
private static final int MAX_VALUES_ANY_KEY = 4000;
private static final int EXPANSION = 10;
private record CachedLifespanValues<K>(Lifespan span,
NavigableMap<K, DBTraceObjectValue> values) {
}
private final Map<String, CachedLifespanValues<Long>> perKeyCache = new LinkedHashMap<>() {
protected boolean removeEldestEntry(Map.Entry<String, CachedLifespanValues<Long>> eldest) {
return size() > MAX_CACHE_KEYS;
}
};
private CachedLifespanValues<SnapKey> anyKeyCache = null;
private Stream<DBTraceObjectValue> doStreamAnyKey(NavigableMap<SnapKey, DBTraceObjectValue> map,
Lifespan lifespan) {
// TODO: Can be a HashMap, if that's faster
return map.values().stream().filter(v -> lifespan.intersects(v.getLifespan()));
}
private Stream<DBTraceObjectValue> doStreamPerKey(NavigableMap<Long, DBTraceObjectValue> map,
Lifespan lifespan, boolean forward) {
Long min = lifespan.min();
var floor = map.floorEntry(min);
if (floor != null && floor.getValue().getLifespan().contains(min)) {
min = floor.getKey();
}
NavigableMap<Long, DBTraceObjectValue> sub = map.subMap(min, true, lifespan.max(), true);
if (forward) {
return sub.values().stream();
}
return sub.descendingMap().values().stream();
}
private DBTraceObjectValue doGetValue(NavigableMap<Long, DBTraceObjectValue> map, long snap) {
Entry<Long, DBTraceObjectValue> floor = map.floorEntry(snap);
if (floor == null) {
return null;
}
DBTraceObjectValue value = floor.getValue();
if (!value.getLifespan().contains(snap)) {
return null;
}
return value;
}
public Cached<Stream<DBTraceObjectValue>> streamValues(Lifespan lifespan) {
if (anyKeyCache == null) {
return Cached.miss();
}
if (!anyKeyCache.span.encloses(lifespan)) {
return Cached.miss();
}
return Cached.hit(doStreamAnyKey(anyKeyCache.values, lifespan));
}
public Cached<Stream<DBTraceObjectValue>> streamValues(Lifespan lifespan, String key,
boolean forward) {
CachedLifespanValues<Long> cached = perKeyCache.get(key);
if (cached == null) {
return Cached.miss();
}
if (!cached.span.encloses(lifespan)) {
return Cached.miss();
}
return Cached.hit(doStreamPerKey(cached.values, lifespan, forward));
}
public Cached<DBTraceObjectValue> getValue(long snap, String key) {
CachedLifespanValues<Long> cached = perKeyCache.get(key);
if (cached == null) {
return Cached.miss();
}
if (!cached.span.contains(snap)) {
return Cached.miss();
}
return Cached.hit(doGetValue(cached.values, snap));
}
public Lifespan expandLifespan(Lifespan lifespan) {
// Expand the query to take advantage of spatial locality (in the time dimension)
long min = lifespan.lmin() - EXPANSION;
if (min > lifespan.lmin()) {
min = Lifespan.ALL.lmin();
}
long max = lifespan.lmax() + EXPANSION;
if (max < lifespan.lmax()) {
max = Lifespan.ALL.lmax();
}
return Lifespan.span(min, max);
}
private DBTraceObjectValue mergeValues(DBTraceObjectValue v1, DBTraceObjectValue v2) {
throw new IllegalStateException("Conflicting values: %s, %s".formatted(v1, v2));
}
private NavigableMap<SnapKey, DBTraceObjectValue> collectAnyKey(
Stream<DBTraceObjectValue> values) {
return values.collect(
Collectors.toMap(SnapKey::forValue, v -> v, this::mergeValues, TreeMap::new));
}
private NavigableMap<Long, DBTraceObjectValue> collectPerKey(
Stream<DBTraceObjectValue> values) {
return values.collect(
Collectors.toMap(v -> v.getLifespan().min(), v -> v, this::mergeValues, TreeMap::new));
}
public Stream<DBTraceObjectValue> offerStreamAnyKey(Lifespan expanded,
Stream<DBTraceObjectValue> values, Lifespan lifespan) {
NavigableMap<SnapKey, DBTraceObjectValue> map = collectAnyKey(values);
anyKeyCache = new CachedLifespanValues<>(expanded, map);
return doStreamAnyKey(map, lifespan);
}
public Stream<DBTraceObjectValue> offerStreamPerKey(Lifespan expanded,
Stream<DBTraceObjectValue> values, Lifespan lifespan, String key, boolean forward) {
NavigableMap<Long, DBTraceObjectValue> map = collectPerKey(values);
perKeyCache.put(key, new CachedLifespanValues<>(expanded, map));
return doStreamPerKey(map, lifespan, forward);
}
public DBTraceObjectValue offerGetValue(Lifespan expanded, Stream<DBTraceObjectValue> values,
long snap, String key) {
NavigableMap<Long, DBTraceObjectValue> map = collectPerKey(values);
perKeyCache.put(key, new CachedLifespanValues<>(expanded, map));
return doGetValue(map, snap);
}
public void notifyValueCreated(DBTraceObjectValue value) {
Objects.requireNonNull(value);
if (anyKeyCache != null && anyKeyCache.span.intersects(value.getLifespan())) {
anyKeyCache.values.put(SnapKey.forValue(value), value);
}
CachedLifespanValues<Long> cached = perKeyCache.get(value.getEntryKey());
if (cached != null && cached.span.intersects(value.getLifespan())) {
cached.values.put(value.getLifespan().min(), value);
}
}
public void notifyValueDeleted(DBTraceObjectValue value) {
Objects.requireNonNull(value);
if (anyKeyCache != null) {
anyKeyCache.values.remove(SnapKey.forValue(value));
}
CachedLifespanValues<Long> cached = perKeyCache.get(value.getEntryKey());
if (cached != null) {
cached.values.remove(value.getLifespan().min());
}
}
}
@@ -22,7 +22,7 @@ import db.LongField;
import ghidra.util.database.DBAnnotatedObject; import ghidra.util.database.DBAnnotatedObject;
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
public class DBTraceObjectDBFieldCodec<OV extends DBAnnotatedObject & InternalTraceObjectValue> public class DBTraceObjectDBFieldCodec<OV extends DBAnnotatedObject & TraceObjectValueStorage>
extends AbstractDBFieldCodec<DBTraceObject, OV, LongField> { extends AbstractDBFieldCodec<DBTraceObject, OV, LongField> {
public DBTraceObjectDBFieldCodec(Class<OV> objectType, Field field, int column) { public DBTraceObjectDBFieldCodec(Class<OV> objectType, Field field, int column) {
super(DBTraceObject.class, objectType, LongField.class, field, column); super(DBTraceObject.class, objectType, LongField.class, field, column);
@@ -32,7 +32,7 @@ public class DBTraceObjectDBFieldCodec<OV extends DBAnnotatedObject & InternalTr
return value == null ? -1 : value.getKey(); return value == null ? -1 : value.getKey();
} }
protected static DBTraceObject decode(InternalTraceObjectValue ent, long enc) { protected static DBTraceObject decode(TraceObjectValueStorage ent, long enc) {
return enc == -1 ? null : ent.getManager().getObjectById(enc); return enc == -1 ? null : ent.getManager().getObjectById(enc);
} }
@@ -31,7 +31,6 @@ import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.*; import ghidra.dbg.target.schema.*;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.util.*; import ghidra.dbg.util.*;
import ghidra.lifecycle.Internal;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Language;
import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTrace;
@@ -57,8 +56,7 @@ import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceChangeRecord; import ghidra.trace.util.TraceChangeRecord;
import ghidra.trace.util.TraceEvents; import ghidra.trace.util.TraceEvents;
import ghidra.util.LockHold; import ghidra.util.*;
import ghidra.util.Msg;
import ghidra.util.database.*; import ghidra.util.database.*;
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
import ghidra.util.database.DBCachedObjectStoreFactory.PrimitiveCodec; import ghidra.util.database.DBCachedObjectStoreFactory.PrimitiveCodec;
@@ -159,11 +157,11 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
protected final DBCachedObjectStore<DBTraceObject> objectStore; protected final DBCachedObjectStore<DBTraceObject> objectStore;
protected final DBTraceObjectValueRStarTree valueTree; protected final DBTraceObjectValueRStarTree valueTree;
protected final DBTraceObjectValueMap valueMap; protected final DBTraceObjectValueMap valueMap;
protected final DBTraceObjectValueWriteBehindCache valueWbCache;
protected final DBCachedObjectIndex<TraceObjectKeyPath, DBTraceObject> objectsByPath; protected final DBCachedObjectIndex<TraceObjectKeyPath, DBTraceObject> objectsByPath;
protected final Collection<TraceObject> objectsView; protected final Collection<TraceObject> objectsView;
protected final Collection<TraceObjectValue> valuesView;
protected TargetObjectSchema rootSchema; protected TargetObjectSchema rootSchema;
@@ -198,8 +196,9 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
objectsByPath = objectsByPath =
objectStore.getIndex(TraceObjectKeyPath.class, DBTraceObject.PATH_COLUMN); objectStore.getIndex(TraceObjectKeyPath.class, DBTraceObject.PATH_COLUMN);
valueWbCache = new DBTraceObjectValueWriteBehindCache(this);
objectsView = Collections.unmodifiableCollection(objectStore.asMap().values()); objectsView = Collections.unmodifiableCollection(objectStore.asMap().values());
valuesView = Collections.unmodifiableCollection(valueMap.values());
} }
protected void loadRootSchema() { protected void loadRootSchema() {
@@ -228,29 +227,42 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
schemasByInterface.clear(); schemasByInterface.clear();
} }
@Internal protected boolean checkMyObject(DBTraceObject object) {
if (object.manager != this) {
return false;
}
if (!objectStore.asMap().values().contains(object)) {
return false;
}
return true;
}
protected DBTraceObject assertIsMine(TraceObject object) { protected DBTraceObject assertIsMine(TraceObject object) {
if (!(object instanceof DBTraceObject)) { if (!(object instanceof DBTraceObject dbObject)) {
throw new IllegalArgumentException("Object " + object + " is not part of this trace"); throw new IllegalArgumentException("Object " + object + " is not part of this trace");
} }
DBTraceObject dbObject = (DBTraceObject) object; if (!checkMyObject(dbObject)) {
if (dbObject.manager != this) {
throw new IllegalArgumentException("Object " + object + " is not part of this trace");
}
if (!getAllObjects().contains(dbObject)) {
throw new IllegalArgumentException("Object " + object + " is not part of this trace"); throw new IllegalArgumentException("Object " + object + " is not part of this trace");
} }
return dbObject; return dbObject;
} }
protected Object validatePrimitive(Object child) { protected Object validatePrimitive(Object value) {
try { try {
PrimitiveCodec.getCodec(child.getClass()); PrimitiveCodec.getCodec(value.getClass());
} }
catch (IllegalArgumentException e) { catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Cannot encode " + child, e); throw new IllegalArgumentException("Cannot encode " + value, e);
} }
return child; return value;
}
protected Object validateValue(Object value) {
if (value instanceof TraceObject | value instanceof Address |
value instanceof AddressRange) {
return value;
}
return validatePrimitive(value);
} }
@Override @Override
@@ -267,7 +279,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
rootSchema = schema; rootSchema = schema;
} }
protected void emitValueCreated(DBTraceObject parent, InternalTraceObjectValue entry) { protected void emitValueCreated(DBTraceObject parent, DBTraceObjectValue entry) {
if (parent == null) { if (parent == null) {
// Don't need event for root value created // Don't need event for root value created
return; return;
@@ -275,24 +287,30 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
parent.emitEvents(new TraceChangeRecord<>(TraceEvents.VALUE_CREATED, null, entry)); parent.emitEvents(new TraceChangeRecord<>(TraceEvents.VALUE_CREATED, null, entry));
} }
protected InternalTraceObjectValue doCreateValue(Lifespan lifespan, protected DBTraceObjectValueData doCreateValueData(Lifespan lifespan, DBTraceObject parent,
String key, Object value) {
DBTraceObjectValueData entry =
valueMap.put(new ImmutableValueShape(parent, value, key, lifespan), null);
if (!(value instanceof DBTraceObject)) {
entry.doSetPrimitive(value);
}
return entry;
}
protected DBTraceObjectValue doCreateValue(Lifespan lifespan,
DBTraceObject parent, String key, Object value) { DBTraceObject parent, String key, Object value) {
InternalTraceObjectValue entry = valueTree.asSpatialMap() // Root is never in write-behind cache
.put(new ImmutableValueShape(parent, value, key, lifespan), null); DBTraceObjectValue entry = parent == null
? doCreateValueData(lifespan, parent, key, value).getWrapper()
: valueWbCache.doCreateValue(lifespan, parent, key, value).getWrapper();
if (parent != null) {
parent.notifyValueCreated(entry);
}
if (value instanceof DBTraceObject child) { if (value instanceof DBTraceObject child) {
child.notifyParentValueCreated(entry); child.notifyParentValueCreated(entry);
} }
else {
entry.doSetPrimitive(value);
}
if (parent != null) { // Root
parent.notifyValueCreated(entry);
}
// TODO: Perhaps a little drastic // TODO: Perhaps a little drastic
invalidateObjectsContainingCache(); invalidateObjectsContainingCache();
emitValueCreated(parent, entry); emitValueCreated(parent, entry);
return entry; return entry;
} }
@@ -326,13 +344,13 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
} }
@Override @Override
public TraceObjectValue createRootObject(TargetObjectSchema schema) { public DBTraceObjectValue createRootObject(TargetObjectSchema schema) {
try (LockHold hold = trace.lockWrite()) { try (LockHold hold = trace.lockWrite()) {
setSchema(schema); setSchema(schema);
DBTraceObject root = doCreateObject(TraceObjectKeyPath.of()); DBTraceObject root = doCreateObject(TraceObjectKeyPath.of());
assert root.getKey() == 0; assert root.getKey() == 0;
InternalTraceObjectValue val = doCreateValue(Lifespan.ALL, null, "", root); DBTraceObjectValue val = doCreateValue(Lifespan.ALL, null, "", root);
assert val.getKey() == 0; assert val.getWrapped() instanceof DBTraceObjectValueData data && data.getKey() == 0;
return val; return val;
} }
} }
@@ -344,9 +362,10 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
} }
} }
public DBTraceObjectValueData getRootValue() { public DBTraceObjectValue getRootValue() {
try (LockHold hold = trace.lockRead()) { try (LockHold hold = trace.lockRead()) {
return valueTree.getDataStore().getObjectAt(0); DBTraceObjectValueData data = valueTree.getDataStore().getObjectAt(0);
return data == null ? null : data.getWrapper();
} }
} }
@@ -381,7 +400,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
public Stream<? extends TraceObjectValPath> getValuePaths(Lifespan span, public Stream<? extends TraceObjectValPath> getValuePaths(Lifespan span,
PathPredicates predicates) { PathPredicates predicates) {
try (LockHold hold = trace.lockRead()) { try (LockHold hold = trace.lockRead()) {
DBTraceObjectValueData rootVal = getRootValue(); DBTraceObjectValue rootVal = getRootValue();
if (rootVal == null) { if (rootVal == null) {
return Stream.of(); return Stream.of();
} }
@@ -390,29 +409,60 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
} }
@Override @Override
public Collection<? extends TraceObject> getAllObjects() { public Stream<DBTraceObject> getAllObjects() {
return objectsView; return objectStore.asMap().values().stream();
} }
@Override @Override
public Collection<? extends TraceObjectValue> getAllValues() { public int getObjectCount() {
return valuesView; return objectStore.getRecordCount();
}
@Override
public Stream<DBTraceObjectValue> getAllValues() {
return Stream.concat(
valueMap.values().stream().map(v -> v.getWrapper()),
valueWbCache.streamAllValues().map(v -> v.getWrapper()));
}
protected Stream<DBTraceObjectValueData> streamValuesIntersectingData(Lifespan span,
AddressRange range, String entryKey) {
return valueMap.reduce(TraceObjectValueQuery.intersecting(
entryKey != null ? entryKey : EntryKeyDimension.INSTANCE.absoluteMin(),
entryKey != null ? entryKey : EntryKeyDimension.INSTANCE.absoluteMax(),
span, range)).values().stream();
}
protected Stream<DBTraceObjectValueBehind> streamValuesIntersectingBehind(Lifespan span,
AddressRange range, String entryKey) {
return valueWbCache.streamValuesIntersecting(span, range, entryKey);
} }
@Override @Override
public Collection<? extends TraceObjectValue> getValuesIntersecting(Lifespan span, public Collection<? extends TraceObjectValue> getValuesIntersecting(Lifespan span,
AddressRange range, String entryKey) { AddressRange range, String entryKey) {
return Collections return Stream.concat(
.unmodifiableCollection(valueMap.reduce(TraceObjectValueQuery.intersecting( streamValuesIntersectingData(span, range, entryKey).map(v -> v.getWrapper()),
entryKey != null ? entryKey : EntryKeyDimension.INSTANCE.absoluteMin(), streamValuesIntersectingBehind(span, range, entryKey).map(v -> v.getWrapper()))
entryKey != null ? entryKey : EntryKeyDimension.INSTANCE.absoluteMax(), .toList();
span, range)).values()); }
protected Stream<DBTraceObjectValueData> streamValuesAtData(long snap, Address address,
String entryKey) {
return valueMap.reduce(TraceObjectValueQuery.at(entryKey, snap, address)).values().stream();
}
protected Stream<DBTraceObjectValueBehind> streamValuesAtBehind(long snap, Address address,
String entryKey) {
return valueWbCache.streamValuesAt(snap, address, entryKey);
} }
public Collection<? extends TraceObjectValue> getValuesAt(long snap, Address address, public Collection<? extends TraceObjectValue> getValuesAt(long snap, Address address,
String entryKey) { String entryKey) {
return Collections.unmodifiableCollection( return Stream.concat(
valueMap.reduce(TraceObjectValueQuery.at(entryKey, snap, address)).values()); streamValuesAtData(snap, address, entryKey).map(v -> v.getWrapper()),
streamValuesAtBehind(snap, address, entryKey).map(v -> v.getWrapper()))
.toList();
} }
@Override @Override
@@ -450,6 +500,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
public void clear() { public void clear() {
try (LockHold hold = trace.lockWrite()) { try (LockHold hold = trace.lockWrite()) {
valueMap.clear(); valueMap.clear();
valueWbCache.clear();
objectStore.deleteAll(); objectStore.deleteAll();
schemaStore.deleteAll(); schemaStore.deleteAll();
rootSchema = null; rootSchema = null;
@@ -463,8 +514,8 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
object.emitEvents(new TraceChangeRecord<>(TraceEvents.OBJECT_DELETED, null, object)); object.emitEvents(new TraceChangeRecord<>(TraceEvents.OBJECT_DELETED, null, object));
} }
protected void doDeleteEdge(DBTraceObjectValueData edge) { protected void doDeleteValue(DBTraceObjectValueData value) {
valueTree.doDeleteEntry(edge); valueTree.doDeleteEntry(value);
// TODO: Perhaps a little drastic.... // TODO: Perhaps a little drastic....
/** /**
@@ -475,6 +526,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
invalidateObjectsContainingCache(); invalidateObjectsContainingCache();
} }
protected void doDeleteCachedValue(DBTraceObjectValueBehind value) {
valueWbCache.remove(value);
// Ditto NB from doDeleteValue
invalidateObjectsContainingCache();
}
public boolean hasSchema() { public boolean hasSchema() {
return rootSchema != null; return rootSchema != null;
} }
@@ -594,22 +651,28 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
} }
} }
static <I extends TraceObjectInterface> boolean acceptValue(DBTraceObjectValue value,
String key, Class<I> ifaceCls, Predicate<? super I> predicate) {
if (!value.hasEntryKey(key)) {
return false;
}
TraceObject parent = value.getParent();
I iface = parent.queryInterface(ifaceCls);
if (iface == null) {
return false;
}
if (!predicate.test(iface)) {
return false;
}
return true;
}
public <I extends TraceObjectInterface> AddressSetView getObjectsAddressSet(long snap, public <I extends TraceObjectInterface> AddressSetView getObjectsAddressSet(long snap,
String key, Class<I> ifaceCls, Predicate<? super I> predicate) { String key, Class<I> ifaceCls, Predicate<? super I> predicate) {
return valueMap.getAddressSetView(Lifespan.at(snap), v -> { return new UnionAddressSetView(
if (!v.hasEntryKey(key)) { valueMap.getAddressSetView(Lifespan.at(snap),
return false; v -> acceptValue(v.getWrapper(), key, ifaceCls, predicate)),
} valueWbCache.getObjectsAddresSet(snap, key, ifaceCls, predicate));
TraceObject parent = v.getParent();
I iface = parent.queryInterface(ifaceCls);
if (iface == null) {
return false;
}
if (!predicate.test(iface)) {
return false;
}
return true;
});
} }
public <I extends TraceObjectInterface> I getSuccessor(TraceObject seed, public <I extends TraceObjectInterface> I getSuccessor(TraceObject seed,
@@ -751,24 +814,21 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
} }
} }
public boolean checkMyObject(DBTraceObject object) {
if (object.manager != this) {
return false;
}
if (!getAllObjects().contains(object)) {
return false;
}
return true;
}
public TraceThread assertMyThread(TraceThread thread) { public TraceThread assertMyThread(TraceThread thread) {
if (!(thread instanceof DBTraceObjectThread)) { if (!(thread instanceof DBTraceObjectThread dbThread)) {
throw new AssertionError("Thread " + thread + " is not an object in this trace"); throw new AssertionError("Thread " + thread + " is not an object in this trace");
} }
DBTraceObjectThread dbThread = (DBTraceObjectThread) thread;
if (!checkMyObject(dbThread.getObject())) { if (!checkMyObject(dbThread.getObject())) {
throw new AssertionError("Thread " + thread + " is not an object in this trace"); throw new AssertionError("Thread " + thread + " is not an object in this trace");
} }
return dbThread; return dbThread;
} }
public void flushWbCaches() {
valueWbCache.flush();
}
public void waitWbWorkers() {
valueWbCache.waitWorkers();
}
} }
@@ -28,18 +28,18 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
return EMPTY; return EMPTY;
} }
public static DBTraceObjectValPath of(Collection<InternalTraceObjectValue> entryList) { public static DBTraceObjectValPath of(Collection<DBTraceObjectValue> entryList) {
return new DBTraceObjectValPath(List.copyOf(entryList)); return new DBTraceObjectValPath(List.copyOf(entryList));
} }
public static DBTraceObjectValPath of(InternalTraceObjectValue... entries) { public static DBTraceObjectValPath of(DBTraceObjectValue... entries) {
return DBTraceObjectValPath.of(Arrays.asList(entries)); return DBTraceObjectValPath.of(Arrays.asList(entries));
} }
private final List<InternalTraceObjectValue> entryList; private final List<DBTraceObjectValue> entryList;
private List<String> keyList; // lazily computed private List<String> keyList; // lazily computed
private DBTraceObjectValPath(List<InternalTraceObjectValue> entryList) { private DBTraceObjectValPath(List<DBTraceObjectValue> entryList) {
this.entryList = entryList; this.entryList = entryList;
} }
@@ -49,7 +49,7 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
} }
@Override @Override
public List<? extends InternalTraceObjectValue> getEntryList() { public List<DBTraceObjectValue> getEntryList() {
return entryList; return entryList;
} }
@@ -77,10 +77,10 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
if (!entryList.isEmpty() && entry.getTrace() != entryList.get(0).getTrace()) { if (!entryList.isEmpty() && entry.getTrace() != entryList.get(0).getTrace()) {
throw new IllegalArgumentException("All values in path must be from the same trace"); throw new IllegalArgumentException("All values in path must be from the same trace");
} }
if (!(entry instanceof InternalTraceObjectValue val)) { if (!(entry instanceof DBTraceObjectValue val)) {
throw new IllegalArgumentException("Value must be in the database"); throw new IllegalArgumentException("Value must be in the database");
} }
InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()]; DBTraceObjectValue[] arr = new DBTraceObjectValue[1 + entryList.size()];
arr[0] = val; arr[0] = val;
for (int i = 1; i < arr.length; i++) { for (int i = 1; i < arr.length; i++) {
arr[i] = entryList.get(i - 1); arr[i] = entryList.get(i - 1);
@@ -93,10 +93,10 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
if (!entryList.isEmpty() && entry.getTrace() != entryList.get(0).getTrace()) { if (!entryList.isEmpty() && entry.getTrace() != entryList.get(0).getTrace()) {
throw new IllegalArgumentException("All values in path must be from the same trace"); throw new IllegalArgumentException("All values in path must be from the same trace");
} }
if (!(entry instanceof InternalTraceObjectValue val)) { if (!(entry instanceof DBTraceObjectValue val)) {
throw new IllegalArgumentException("Value must be in the database"); throw new IllegalArgumentException("Value must be in the database");
} }
InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()]; DBTraceObjectValue[] arr = new DBTraceObjectValue[1 + entryList.size()];
for (int i = 0; i < arr.length - 1; i++) { for (int i = 0; i < arr.length - 1; i++) {
arr[i] = entryList.get(i); arr[i] = entryList.get(i);
} }
@@ -105,7 +105,7 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
} }
@Override @Override
public InternalTraceObjectValue getFirstEntry() { public DBTraceObjectValue getFirstEntry() {
if (entryList.isEmpty()) { if (entryList.isEmpty()) {
return null; return null;
} }
@@ -114,12 +114,12 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
@Override @Override
public TraceObject getSource(TraceObject ifEmpty) { public TraceObject getSource(TraceObject ifEmpty) {
InternalTraceObjectValue first = getFirstEntry(); DBTraceObjectValue first = getFirstEntry();
return first == null ? ifEmpty : first.getParent(); return first == null ? ifEmpty : first.getParent();
} }
@Override @Override
public InternalTraceObjectValue getLastEntry() { public DBTraceObjectValue getLastEntry() {
if (entryList.isEmpty()) { if (entryList.isEmpty()) {
return null; return null;
} }
@@ -128,13 +128,13 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
@Override @Override
public Object getDestinationValue(Object ifEmpty) { public Object getDestinationValue(Object ifEmpty) {
InternalTraceObjectValue last = getLastEntry(); DBTraceObjectValue last = getLastEntry();
return last == null ? ifEmpty : last.getValue(); return last == null ? ifEmpty : last.getValue();
} }
@Override @Override
public TraceObject getDestination(TraceObject ifEmpty) { public TraceObject getDestination(TraceObject ifEmpty) {
InternalTraceObjectValue last = getLastEntry(); DBTraceObjectValue last = getLastEntry();
return last == null ? ifEmpty : last.getChild(); return last == null ? ifEmpty : last.getChild();
} }
} }
@@ -0,0 +1,400 @@
/* ###
* 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.database.target;
import java.util.*;
import java.util.stream.Stream;
import ghidra.trace.database.DBTraceUtils.LifespanMapSetter;
import ghidra.trace.database.target.visitors.TreeTraversal;
import ghidra.trace.database.target.visitors.TreeTraversal.Visitor;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.*;
import ghidra.trace.model.target.TraceObject.ConflictResolution;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.trace.util.TraceEvents;
import ghidra.util.LockHold;
import ghidra.util.StreamUtils;
public class DBTraceObjectValue implements TraceObjectValue {
static abstract class ValueLifespanSetter
extends LifespanMapSetter<DBTraceObjectValue, Object> {
protected final Lifespan range;
protected final Object value;
protected DBTraceObjectValue keep = null;
protected Collection<DBTraceObjectValue> kept = new ArrayList<>(2);
public ValueLifespanSetter(Lifespan range, Object value) {
this.range = range;
this.value = value;
}
public ValueLifespanSetter(Lifespan range, Object value,
DBTraceObjectValue keep) {
this(range, value);
this.keep = keep;
}
@Override
protected Lifespan getRange(DBTraceObjectValue entry) {
return entry.getLifespan();
}
@Override
protected Object getValue(DBTraceObjectValue entry) {
return entry.getValue();
}
@Override
protected boolean valuesEqual(Object v1, Object v2) {
if (Objects.equals(v1, v2)) {
return true;
}
if (v1 == null || !v1.getClass().isArray()) {
return false;
}
if (v1 instanceof boolean[] a1 && v2 instanceof boolean[] a2) {
return Arrays.equals(a1, a2);
}
if (v1 instanceof byte[] a1 && v2 instanceof byte[] a2) {
return Arrays.equals(a1, a2);
}
if (v1 instanceof char[] a1 && v2 instanceof char[] a2) {
return Arrays.equals(a1, a2);
}
if (v1 instanceof double[] a1 && v2 instanceof double[] a2) {
return Arrays.equals(a1, a2);
}
if (v1 instanceof float[] a1 && v2 instanceof float[] a2) {
return Arrays.equals(a1, a2);
}
if (v1 instanceof int[] a1 && v2 instanceof int[] a2) {
return Arrays.equals(a1, a2);
}
if (v1 instanceof long[] a1 && v2 instanceof long[] a2) {
return Arrays.equals(a1, a2);
}
if (v1 instanceof short[] a1 && v2 instanceof short[] a2) {
return Arrays.equals(a1, a2);
}
return false;
}
@Override
protected void remove(DBTraceObjectValue entry) {
if (valuesEqual(entry.getValue(), value)) {
if (keep == null) {
keep = entry;
}
else {
entry.doDeleteAndEmit();
}
}
else {
DBTraceObjectValue created = entry.doTruncateOrDelete(range);
if (!entry.isDeleted()) {
kept.add(entry);
}
if (created != null) {
kept.add(created);
}
}
}
@Override
protected DBTraceObjectValue put(Lifespan range, Object value) {
if (value == null) {
return null;
}
if (keep != null && valuesEqual(this.value, value)) {
keep.doSetLifespanAndEmit(range);
return keep;
}
for (DBTraceObjectValue k : kept) {
if (valuesEqual(value, k.getValue()) && Objects.equals(range, k.getLifespan())) {
kept.remove(k);
return k;
}
}
return create(range, value);
}
protected abstract DBTraceObjectValue create(Lifespan range, Object value);
}
private final DBTraceObjectManager manager;
private volatile TraceObjectValueStorage wrapped;
public DBTraceObjectValue(DBTraceObjectManager manager,
TraceObjectValueStorage wrapped) {
this.manager = manager;
this.wrapped = wrapped;
}
void setWrapped(TraceObjectValueStorage wrapped) {
this.wrapped = wrapped;
if (wrapped instanceof DBTraceObjectValueData data) {
data.setWrapper(this);
}
}
void doSetLifespanAndEmit(Lifespan lifespan) {
Lifespan oldLifespan = getLifespan();
doSetLifespan(lifespan);
getParent().emitEvents(new TraceChangeRecord<>(TraceEvents.VALUE_LIFESPAN_CHANGED,
null, this, oldLifespan, lifespan));
}
@Override
public Trace getTrace() {
return manager.trace;
}
@Override
public String getEntryKey() {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
return wrapped.getEntryKey();
}
}
protected TraceObjectKeyPath doGetCanonicalPath() {
DBTraceObject parent = wrapped.getParent();
if (parent == null) {
return TraceObjectKeyPath.of();
}
return parent.getCanonicalPath().extend(wrapped.getEntryKey());
}
@Override
public TraceObjectKeyPath getCanonicalPath() {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
return doGetCanonicalPath();
}
}
@Override
public Object getValue() {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
return wrapped.getValue();
}
}
@Override
public boolean isObject() {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
return wrapped.getChildOrNull() != null;
}
}
protected boolean doIsCanonical() {
DBTraceObject child = wrapped.getChildOrNull();
if (child == null) {
return false;
}
if (wrapped.getParent() == null) { // We're the root
return true;
}
return doGetCanonicalPath().equals(child.getCanonicalPath());
}
@Override
public boolean isCanonical() {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
return doIsCanonical();
}
}
@Override
public Lifespan getLifespan() {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
return wrapped.getLifespan();
}
}
@Override
public void setMinSnap(long minSnap) {
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
setLifespan(Lifespan.span(minSnap, getLifespan().lmax()));
}
}
@Override
public long getMinSnap() {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
return wrapped.getLifespan().lmin();
}
}
@Override
public void setMaxSnap(long maxSnap) {
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
setLifespan(Lifespan.span(getLifespan().lmin(), maxSnap));
}
}
@Override
public long getMaxSnap() {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
return wrapped.getLifespan().lmax();
}
}
void doDelete() {
getParent().notifyValueDeleted(this);
DBTraceObject child = wrapped.getChildOrNull();
if (child != null) {
child.notifyParentValueDeleted(this);
}
wrapped.doDelete();
}
void doDeleteAndEmit() {
DBTraceObject parent = getParent();
doDelete();
parent.emitEvents(new TraceChangeRecord<>(TraceEvents.VALUE_DELETED, null, this));
}
@Override
public void delete() {
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
if (getParent() == null) {
throw new IllegalArgumentException("Cannot delete root value");
}
doDeleteAndEmit();
}
}
@Override
public boolean isDeleted() {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
return wrapped.isDeleted();
}
}
@Override
public DBTraceObjectValue truncateOrDelete(Lifespan span) {
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
if (wrapped.getParent() == null) {
throw new IllegalArgumentException("Cannot truncate or delete root value");
}
return doTruncateOrDeleteAndEmitLifeChange(span);
}
}
@Override
public DBTraceObject getChild() {
try (LockHold hold = manager.trace.lockRead()) {
return (DBTraceObject) wrapped.getValue();
}
}
@Override
public void setLifespan(Lifespan lifespan) {
setLifespan(lifespan, ConflictResolution.TRUNCATE);
}
@Override
public void setLifespan(Lifespan lifespan, ConflictResolution resolution) {
try (LockHold hold = getTrace().lockWrite()) {
if (getParent() == null) {
throw new IllegalArgumentException("Cannot set lifespan of root value");
}
if (resolution == ConflictResolution.DENY) {
getParent().doCheckConflicts(lifespan, getEntryKey(), getValue());
}
else if (resolution == ConflictResolution.ADJUST) {
lifespan = getParent().doAdjust(lifespan, getEntryKey(), getValue());
}
new ValueLifespanSetter(lifespan, getValue(), this) {
@Override
protected Iterable<DBTraceObjectValue> getIntersecting(Long lower,
Long upper) {
return StreamUtils.iter(getParent().streamValuesR(
Lifespan.span(lower, upper), getEntryKey(), true).filter(v -> v != keep));
}
@Override
protected DBTraceObjectValue create(Lifespan range, Object value) {
return getParent().doCreateValue(range, getEntryKey(), value);
}
}.set(lifespan, getValue());
if (isObject()) {
DBTraceObject child = getChild();
child.emitEvents(
new TraceChangeRecord<>(TraceEvents.OBJECT_LIFE_CHANGED, null, child));
}
}
}
void doSetLifespan(Lifespan lifespan) {
if (wrapped.getLifespan().equals(lifespan)) {
return;
}
DBTraceObject parent = wrapped.getParent();
DBTraceObject child = wrapped.getChildOrNull();
parent.notifyValueDeleted(this);
if (child != null) {
child.notifyParentValueDeleted(this);
}
wrapped.doSetLifespan(lifespan);
parent.notifyValueCreated(this);
if (child != null) {
child.notifyParentValueCreated(this);
}
}
DBTraceObjectValue doTruncateOrDeleteAndEmitLifeChange(Lifespan span) {
if (!isCanonical()) {
return doTruncateOrDelete(span);
}
DBTraceObject child = wrapped.getChildOrNull();
DBTraceObjectValue result = doTruncateOrDelete(span);
child.emitEvents(new TraceChangeRecord<>(TraceEvents.OBJECT_LIFE_CHANGED, null, child));
return result;
}
DBTraceObjectValue doTruncateOrDelete(Lifespan span) {
List<Lifespan> removed = getLifespan().subtract(span);
if (removed.isEmpty()) {
doDeleteAndEmit();
return null;
}
doSetLifespanAndEmit(removed.get(0));
if (removed.size() == 2) {
return getParent().doCreateValue(removed.get(1), getEntryKey(), getValue());
}
return this;
}
@Override
public DBTraceObject getParent() {
try (LockHold hold = manager.trace.lockRead()) {
return wrapped.getParent();
}
}
protected Stream<? extends TraceObjectValPath> doStreamVisitor(Lifespan span,
Visitor visitor) {
return TreeTraversal.INSTANCE.walkValue(visitor, this, span, null);
}
public TraceObjectValueStorage getWrapped() {
return wrapped;
}
}
@@ -0,0 +1,101 @@
/* ###
* 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.database.target;
import java.util.Objects;
import ghidra.trace.model.Lifespan;
public class DBTraceObjectValueBehind implements TraceObjectValueStorage {
private final DBTraceObjectManager manager;
private final DBTraceObject parent;
private final String entryKey;
private Lifespan lifespan;
private final Object value;
private boolean deleted = false;
private final DBTraceObjectValue wrapper;
public DBTraceObjectValueBehind(DBTraceObjectManager manager, DBTraceObject parent,
String entryKey, Lifespan lifespan, Object value) {
this.manager = manager;
this.parent = Objects.requireNonNull(parent, "Root cannot be delayed");
this.entryKey = entryKey;
this.lifespan = lifespan;
this.value = value;
this.wrapper = new DBTraceObjectValue(manager, this);
}
@Override
public String getEntryKey() {
return entryKey;
}
@Override
public Object getValue() {
return value;
}
@Override
public Lifespan getLifespan() {
return lifespan;
}
@Override
public boolean isDeleted() {
return deleted;
}
@Override
public DBTraceObjectManager getManager() {
return manager;
}
@Override
public DBTraceObject getChildOrNull() {
if (value instanceof DBTraceObject child) {
return child;
}
return null;
}
@Override
public void doSetLifespan(Lifespan lifespan) {
var values = manager.valueWbCache.doRemoveNoCleanup(this);
this.lifespan = lifespan;
manager.valueWbCache.doAddDirect(values, this);
}
@Override
public void doDelete() {
deleted = true;
manager.doDeleteCachedValue(this);
}
@Override
public DBTraceObject getParent() {
return parent;
}
@Override
public DBTraceObjectValue getWrapper() {
return wrapper;
}
}
@@ -17,17 +17,12 @@ package ghidra.trace.database.target;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Stream;
import db.DBRecord; import db.DBRecord;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange; import ghidra.program.model.address.AddressRange;
import ghidra.trace.database.target.visitors.TreeTraversal;
import ghidra.trace.database.target.visitors.TreeTraversal.Visitor;
import ghidra.trace.model.Lifespan; import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace; import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.*;
import ghidra.util.LockHold;
import ghidra.util.database.DBCachedObjectStore; import ghidra.util.database.DBCachedObjectStore;
import ghidra.util.database.DBCachedObjectStoreFactory.*; import ghidra.util.database.DBCachedObjectStoreFactory.*;
import ghidra.util.database.DBObjectColumn; import ghidra.util.database.DBObjectColumn;
@@ -36,8 +31,8 @@ import ghidra.util.database.spatial.DBTreeDataRecord;
@DBAnnotatedObjectInfo(version = 1) @DBAnnotatedObjectInfo(version = 1)
public class DBTraceObjectValueData public class DBTraceObjectValueData
extends DBTreeDataRecord<ValueShape, ValueBox, InternalTraceObjectValue> extends DBTreeDataRecord<ValueShape, ValueBox, DBTraceObjectValueData>
implements InternalTraceObjectValue, ValueShape { implements TraceObjectValueStorage, ValueShape {
static final String TABLE_NAME = "ObjectValue"; static final String TABLE_NAME = "ObjectValue";
static final String PARENT_COLUMN_NAME = "Parent"; // R*-Tree parent static final String PARENT_COLUMN_NAME = "Parent"; // R*-Tree parent
@@ -89,6 +84,8 @@ public class DBTraceObjectValueData
protected Address address; protected Address address;
protected AddressRange range; protected AddressRange range;
private DBTraceObjectValue wrapper;
public DBTraceObjectValueData(DBTraceObjectManager manager, DBTraceObjectValueRStarTree tree, public DBTraceObjectValueData(DBTraceObjectManager manager, DBTraceObjectValueRStarTree tree,
DBCachedObjectStore<?> store, DBRecord record) { DBCachedObjectStore<?> store, DBRecord record) {
super(store, record); super(store, record);
@@ -96,8 +93,7 @@ public class DBTraceObjectValueData
this.tree = tree; this.tree = tree;
} }
@Override void doSetPrimitive(Object primitive) {
public void doSetPrimitive(Object primitive) {
if (primitive instanceof TraceObject) { if (primitive instanceof TraceObject) {
throw new AssertionError(); throw new AssertionError();
} }
@@ -193,11 +189,6 @@ public class DBTraceObjectValueData
} }
} }
@Override
public Trace getTrace() {
return manager.trace;
}
@Override @Override
public DBTraceObject getParent() { public DBTraceObject getParent() {
return objParent; return objParent;
@@ -208,51 +199,18 @@ public class DBTraceObjectValueData
return entryKey; return entryKey;
} }
protected TraceObjectKeyPath doGetCanonicalPath() {
if (objParent == null) {
return TraceObjectKeyPath.of();
}
return objParent.getCanonicalPath().extend(entryKey);
}
protected boolean doIsCanonical() {
if (child == null) {
return false;
}
if (objParent == null) { // We're the root
return true;
}
return doGetCanonicalPath().equals(child.getCanonicalPath());
}
@Override
public TraceObjectKeyPath getCanonicalPath() {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
return doGetCanonicalPath();
}
}
@Override
public boolean isCanonical() {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
return doIsCanonical();
}
}
@Override @Override
public Object getValue() { public Object getValue() {
try (LockHold hold = manager.trace.lockRead()) { if (child != null) {
if (child != null) { return child;
return child;
}
if (address != null) {
return address;
}
if (range != null) {
return range;
}
return child != null ? child : primitive;
} }
if (address != null) {
return address;
}
if (range != null) {
return range;
}
return child != null ? child : primitive;
} }
@Override @Override
@@ -260,64 +218,9 @@ public class DBTraceObjectValueData
return (DBTraceObject) getValue(); return (DBTraceObject) getValue();
} }
@Override
public boolean isObject() {
return child != null;
}
@Override @Override
public Lifespan getLifespan() { public Lifespan getLifespan() {
try (LockHold hold = manager.trace.lockRead()) { return lifespan;
return lifespan;
}
}
@Override
public void setMinSnap(long minSnap) {
try (LockHold hold = manager.trace.lockWrite()) {
setLifespan(Lifespan.span(minSnap, maxSnap));
}
}
@Override
public long getMinSnap() {
try (LockHold hold = manager.trace.lockRead()) {
return minSnap;
}
}
@Override
public void setMaxSnap(long maxSnap) {
try (LockHold hold = manager.trace.lockWrite()) {
setLifespan(Lifespan.span(minSnap, maxSnap));
}
}
@Override
public long getMaxSnap() {
try (LockHold hold = manager.trace.lockRead()) {
return maxSnap;
}
}
@Override
public void delete() {
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
if (objParent == null) {
throw new IllegalArgumentException("Cannot delete root value");
}
doDeleteAndEmit();
}
}
@Override
public TraceObjectValue truncateOrDelete(Lifespan span) {
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
if (objParent == null) {
throw new IllegalArgumentException("Cannot truncate or delete root value");
}
return doTruncateOrDeleteAndEmitLifeChange(span);
}
} }
@Override @Override
@@ -335,12 +238,12 @@ public class DBTraceObjectValueData
} }
@Override @Override
protected void setRecordValue(InternalTraceObjectValue value) { protected void setRecordValue(DBTraceObjectValueData value) {
// Nothing. Entry is the value // Nothing. Entry is the value
} }
@Override @Override
protected InternalTraceObjectValue getRecordValue() { protected DBTraceObjectValueData getRecordValue() {
return this; return this;
} }
@@ -396,38 +299,34 @@ public class DBTraceObjectValueData
@Override @Override
public void doSetLifespan(Lifespan lifespan) { public void doSetLifespan(Lifespan lifespan) {
if (minSnap == lifespan.lmin() && maxSnap == lifespan.lmax()) { // NB. Wrapper would not call if lifespan weren't different
return;
}
DBTraceObjectValueRStarTree tree = this.tree; DBTraceObjectValueRStarTree tree = this.tree;
tree.doUnparentEntry(this); tree.doUnparentEntry(this);
objParent.notifyValueDeleted(this);
if (child != null) {
child.notifyParentValueDeleted(this);
}
minSnap = lifespan.lmin(); minSnap = lifespan.lmin();
maxSnap = lifespan.lmax(); maxSnap = lifespan.lmax();
update(MIN_SNAP_COLUMN, MAX_SNAP_COLUMN); update(MIN_SNAP_COLUMN, MAX_SNAP_COLUMN);
this.lifespan = lifespan; this.lifespan = lifespan;
updateBounds(); updateBounds();
tree.doInsertDataEntry(this); tree.doInsertDataEntry(this);
objParent.notifyValueCreated(this);
if (child != null) {
child.notifyParentValueCreated(this);
}
} }
@Override @Override
public void doDelete() { public void doDelete() {
objParent.notifyValueDeleted(this); manager.doDeleteValue(this);
if (child != null) {
child.notifyParentValueDeleted(this);
}
manager.doDeleteEdge(this);
} }
protected Stream<? extends TraceObjectValPath> doStreamVisitor(Lifespan span, @Override
Visitor visitor) { public DBTraceObjectValue getWrapper() {
return TreeTraversal.INSTANCE.walkValue(visitor, this, span, null); if (wrapper == null) {
wrapper = new DBTraceObjectValue(manager, this);
}
return wrapper;
}
void setWrapper(DBTraceObjectValue wrapper) {
if (this.wrapper != null) {
throw new AssertionError();
}
this.wrapper = wrapper;
} }
} }
@@ -35,8 +35,8 @@ public class DBTraceObjectValueMapAddressSetView extends AbstractAddressSetView
private final AddressFactory factory; private final AddressFactory factory;
private final ReadWriteLock lock; private final ReadWriteLock lock;
private final SpatialMap<ValueShape, InternalTraceObjectValue, TraceObjectValueQuery> map; private final SpatialMap<ValueShape, DBTraceObjectValueData, TraceObjectValueQuery> map;
private final Predicate<? super InternalTraceObjectValue> predicate; private final Predicate<? super DBTraceObjectValueData> predicate;
/** /**
* An address set view that unions all addresses where an entry satisfying the given predicate * An address set view that unions all addresses where an entry satisfying the given predicate
@@ -52,8 +52,8 @@ public class DBTraceObjectValueMapAddressSetView extends AbstractAddressSetView
* @param predicate a predicate to further filter entries * @param predicate a predicate to further filter entries
*/ */
public DBTraceObjectValueMapAddressSetView(AddressFactory factory, ReadWriteLock lock, public DBTraceObjectValueMapAddressSetView(AddressFactory factory, ReadWriteLock lock,
SpatialMap<ValueShape, InternalTraceObjectValue, TraceObjectValueQuery> map, SpatialMap<ValueShape, DBTraceObjectValueData, TraceObjectValueQuery> map,
Predicate<? super InternalTraceObjectValue> predicate) { Predicate<? super DBTraceObjectValueData> predicate) {
this.factory = factory; this.factory = factory;
this.lock = lock; this.lock = lock;
this.map = map; this.map = map;
@@ -63,7 +63,7 @@ public class DBTraceObjectValueMapAddressSetView extends AbstractAddressSetView
@Override @Override
public boolean contains(Address addr) { public boolean contains(Address addr) {
try (LockHold hold = LockHold.lock(lock.readLock())) { try (LockHold hold = LockHold.lock(lock.readLock())) {
for (InternalTraceObjectValue value : map for (DBTraceObjectValueData value : map
.reduce(TraceObjectValueQuery.intersecting(Lifespan.ALL, .reduce(TraceObjectValueQuery.intersecting(Lifespan.ALL,
new AddressRangeImpl(addr, addr))) new AddressRangeImpl(addr, addr)))
.values()) { .values()) {
@@ -95,7 +95,7 @@ public class DBTraceObjectValueMapAddressSetView extends AbstractAddressSetView
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
try (LockHold hold = LockHold.lock(lock.readLock())) { try (LockHold hold = LockHold.lock(lock.readLock())) {
for (InternalTraceObjectValue value : map.values()) { for (DBTraceObjectValueData value : map.values()) {
if (predicate.test(value)) { if (predicate.test(value)) {
return false; return false;
} }
@@ -107,7 +107,7 @@ public class DBTraceObjectValueMapAddressSetView extends AbstractAddressSetView
@Override @Override
public Address getMinAddress() { public Address getMinAddress() {
try (LockHold hold = LockHold.lock(lock.readLock())) { try (LockHold hold = LockHold.lock(lock.readLock())) {
for (Entry<ValueShape, InternalTraceObjectValue> entry : map for (Entry<ValueShape, DBTraceObjectValueData> entry : map
.reduce(TraceObjectValueQuery.all().starting(AddressDimension.FORWARD)) .reduce(TraceObjectValueQuery.all().starting(AddressDimension.FORWARD))
.orderedEntries()) { .orderedEntries()) {
if (predicate.test(entry.getValue())) { if (predicate.test(entry.getValue())) {
@@ -121,7 +121,7 @@ public class DBTraceObjectValueMapAddressSetView extends AbstractAddressSetView
@Override @Override
public Address getMaxAddress() { public Address getMaxAddress() {
try (LockHold hold = LockHold.lock(lock.readLock())) { try (LockHold hold = LockHold.lock(lock.readLock())) {
for (Entry<ValueShape, InternalTraceObjectValue> entry : map for (Entry<ValueShape, DBTraceObjectValueData> entry : map
.reduce(TraceObjectValueQuery.all().starting(AddressDimension.BACKWARD)) .reduce(TraceObjectValueQuery.all().starting(AddressDimension.BACKWARD))
.orderedEntries()) { .orderedEntries()) {
if (predicate.test(entry.getValue())) { if (predicate.test(entry.getValue())) {
@@ -153,14 +153,14 @@ public class DBTraceObjectValueMapAddressSetView extends AbstractAddressSetView
protected AddressRangeIterator doGetAddressRanges(RecAddress start, RecAddress end, protected AddressRangeIterator doGetAddressRanges(RecAddress start, RecAddress end,
boolean forward) { boolean forward) {
Iterator<Entry<ValueShape, InternalTraceObjectValue>> mapIt = map Iterator<Entry<ValueShape, DBTraceObjectValueData>> mapIt = map
.reduce(TraceObjectValueQuery .reduce(TraceObjectValueQuery
.intersecting(EntryKeyDimension.INSTANCE.absoluteMin(), .intersecting(EntryKeyDimension.INSTANCE.absoluteMin(),
EntryKeyDimension.INSTANCE.absoluteMax(), Lifespan.ALL, start, end) EntryKeyDimension.INSTANCE.absoluteMax(), Lifespan.ALL, start, end)
.starting(forward ? AddressDimension.FORWARD : AddressDimension.BACKWARD)) .starting(forward ? AddressDimension.FORWARD : AddressDimension.BACKWARD))
.orderedEntries() .orderedEntries()
.iterator(); .iterator();
Iterator<Entry<ValueShape, InternalTraceObjectValue>> fltIt = Iterator<Entry<ValueShape, DBTraceObjectValueData>> fltIt =
IteratorUtils.filteredIterator(mapIt, e -> predicate.test(e.getValue())); IteratorUtils.filteredIterator(mapIt, e -> predicate.test(e.getValue()));
Iterator<AddressRange> rawIt = Iterator<AddressRange> rawIt =
IteratorUtils.transformedIterator(fltIt, e -> e.getKey().getRange(factory)); IteratorUtils.transformedIterator(fltIt, e -> e.getKey().getRange(factory));
@@ -36,16 +36,16 @@ public class DBTraceObjectValueRStarTree extends AbstractHyperRStarTree< //
ValueTriple, // ValueTriple, //
ValueShape, DBTraceObjectValueData, // ValueShape, DBTraceObjectValueData, //
ValueBox, DBTraceObjectValueNode, // ValueBox, DBTraceObjectValueNode, //
InternalTraceObjectValue, TraceObjectValueQuery> { DBTraceObjectValueData, TraceObjectValueQuery> {
public static class DBTraceObjectValueMap extends AsSpatialMap<ValueShape, // public static class DBTraceObjectValueMap extends AsSpatialMap<ValueShape, //
DBTraceObjectValueData, ValueBox, InternalTraceObjectValue, TraceObjectValueQuery> { DBTraceObjectValueData, ValueBox, DBTraceObjectValueData, TraceObjectValueQuery> {
private final AddressFactory factory; private final AddressFactory factory;
private final ReadWriteLock lock; private final ReadWriteLock lock;
public DBTraceObjectValueMap(AbstractConstraintsTree<ValueShape, DBTraceObjectValueData, // public DBTraceObjectValueMap(AbstractConstraintsTree<ValueShape, DBTraceObjectValueData, //
ValueBox, ?, InternalTraceObjectValue, TraceObjectValueQuery> tree, ValueBox, ?, DBTraceObjectValueData, TraceObjectValueQuery> tree,
TraceObjectValueQuery query, AddressFactory factory, ReadWriteLock lock) { TraceObjectValueQuery query, AddressFactory factory, ReadWriteLock lock) {
super(tree, query); super(tree, query);
this.factory = factory; this.factory = factory;
@@ -59,7 +59,7 @@ public class DBTraceObjectValueRStarTree extends AbstractHyperRStarTree< //
} }
public AddressSetView getAddressSetView(Lifespan at, public AddressSetView getAddressSetView(Lifespan at,
Predicate<? super InternalTraceObjectValue> predicate) { Predicate<? super DBTraceObjectValueData> predicate) {
return new DBTraceObjectValueMapAddressSetView(factory, lock, return new DBTraceObjectValueMapAddressSetView(factory, lock,
this.reduce(TraceObjectValueQuery.intersecting( this.reduce(TraceObjectValueQuery.intersecting(
EntryKeyDimension.INSTANCE.absoluteMin(), EntryKeyDimension.INSTANCE.absoluteMin(),

Some files were not shown because too many files have changed in this diff Show More