mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-24 13:21:22 +08:00
GP-3839: Various speed improvements for Trace RMI
This commit is contained in:
@@ -367,7 +367,7 @@ def refresh_modules(node: sch.Schema('ModuleContainer')):
|
||||
|
||||
|
||||
# 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')):
|
||||
"""
|
||||
Load/refresh all modules and all sections.
|
||||
|
||||
+1
-2
@@ -51,8 +51,7 @@ public interface LocationTracker {
|
||||
* @param coordinates the trace, thread, snap, etc., of the tool
|
||||
* @return the address to navigate to
|
||||
*/
|
||||
CompletableFuture<Address> computeTraceAddress(PluginTool tool,
|
||||
DebuggerCoordinates coordinates);
|
||||
Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates);
|
||||
|
||||
/**
|
||||
* Get the suggested input if the user activates "Go To" while this tracker is active
|
||||
|
||||
+4
-4
@@ -484,10 +484,10 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||
RootMessage.Builder dispatch(RootMessage req, RootMessage.Builder rep) throws Exception;
|
||||
|
||||
default RootMessage handle(RootMessage req) {
|
||||
/*String desc = toString(req);
|
||||
String desc = toString(req);
|
||||
if (desc != null) {
|
||||
TimedMsg.debug(this, "HANDLING: " + desc);
|
||||
}*/
|
||||
}
|
||||
RootMessage.Builder rep = RootMessage.newBuilder();
|
||||
try {
|
||||
rep = dispatch(req, rep);
|
||||
@@ -514,12 +514,12 @@ public class TraceRmiHandler implements TraceRmiConnection {
|
||||
case REQUEST_START_TX -> "startTx(%d,%s)".formatted(
|
||||
req.getRequestStartTx().getTxid().getId(),
|
||||
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().getPath().getPath(),
|
||||
req.getRequestSetValue().getValue().getKey(),
|
||||
ValueDecoder.DISPLAY
|
||||
.toValue(req.getRequestSetValue().getValue().getValue()));
|
||||
.toValue(req.getRequestSetValue().getValue().getValue()));*/
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
+1
@@ -62,6 +62,7 @@ import ghidra.util.exception.CancelledException;
|
||||
import resources.MultiIcon;
|
||||
|
||||
public interface DebuggerResources {
|
||||
|
||||
String OPTIONS_CATEGORY_DEBUGGER = "Debugger";
|
||||
String OPTIONS_CATEGORY_WORKFLOW = "Workflow";
|
||||
|
||||
|
||||
+14
-13
@@ -19,7 +19,6 @@ import java.awt.Color;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import docking.ActionContext;
|
||||
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.util.viewer.listingpanel.ListingBackgroundColorModel;
|
||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.debug.api.action.*;
|
||||
import ghidra.debug.api.action.LocationTrackingSpec.TrackingSpecConfigFieldCodec;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.stack.TraceStack;
|
||||
@@ -252,7 +251,7 @@ public class DebuggerTrackLocationTrait {
|
||||
doTrack();
|
||||
}
|
||||
|
||||
protected CompletableFuture<ProgramLocation> computeTrackedLocation() {
|
||||
protected ProgramLocation computeTrackedLocation() {
|
||||
// Change of register values (for current frame)
|
||||
// Change of stack pc (for current frame)
|
||||
// Change of current view (if not caused by goTo)
|
||||
@@ -262,16 +261,18 @@ public class DebuggerTrackLocationTrait {
|
||||
// Change of tracking settings
|
||||
DebuggerCoordinates cur = current;
|
||||
if (cur.getView() == null) {
|
||||
return AsyncUtils.nil();
|
||||
return null;
|
||||
}
|
||||
TraceThread thread = cur.getThread();
|
||||
if (thread == null || spec == null) {
|
||||
return AsyncUtils.nil();
|
||||
return null;
|
||||
}
|
||||
// NB: view's snap may be forked for emulation
|
||||
return tracker.computeTraceAddress(tool, cur).thenApply(address -> {
|
||||
return address == null ? null : new ProgramLocation(cur.getView(), address);
|
||||
});
|
||||
Address address = tracker.computeTraceAddress(tool, cur);
|
||||
if (address == null) {
|
||||
return null;
|
||||
}
|
||||
return new ProgramLocation(cur.getView(), address);
|
||||
}
|
||||
|
||||
public String computeLabelText() {
|
||||
@@ -282,13 +283,13 @@ public class DebuggerTrackLocationTrait {
|
||||
}
|
||||
|
||||
protected void doTrack() {
|
||||
computeTrackedLocation().thenAccept(loc -> {
|
||||
trackedLocation = loc;
|
||||
try {
|
||||
trackedLocation = computeTrackedLocation();
|
||||
locationTracked();
|
||||
}).exceptionally(ex -> {
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
Msg.error(this, "Error while computing location: " + ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected void addNewListeners() {
|
||||
|
||||
+2
-6
@@ -15,12 +15,9 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.action;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.debug.api.action.*;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
@@ -66,9 +63,8 @@ public enum NoneLocationTrackingSpec implements LocationTrackingSpec, LocationTr
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Address> computeTraceAddress(PluginTool tool,
|
||||
DebuggerCoordinates coordinates) {
|
||||
return AsyncUtils.nil();
|
||||
public Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
-10
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.action;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
|
||||
@@ -67,7 +65,8 @@ public enum PCByStackLocationTrackingSpec implements LocationTrackingSpec, Locat
|
||||
return this;
|
||||
}
|
||||
|
||||
public Address doComputeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
|
||||
@Override
|
||||
public Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
|
||||
Trace trace = coordinates.getTrace();
|
||||
TraceThread thread = coordinates.getThread();
|
||||
long snap = coordinates.getSnap();
|
||||
@@ -83,16 +82,10 @@ public enum PCByStackLocationTrackingSpec implements LocationTrackingSpec, Locat
|
||||
return frame.getProgramCounter(snap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Address> computeTraceAddress(PluginTool tool,
|
||||
DebuggerCoordinates coordinates) {
|
||||
return CompletableFuture.supplyAsync(() -> doComputeTraceAddress(tool, coordinates));
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoToInput getDefaultGoToInput(PluginTool tool, DebuggerCoordinates coordinates,
|
||||
ProgramLocation location) {
|
||||
Address address = doComputeTraceAddress(tool, coordinates);
|
||||
Address address = computeTraceAddress(tool, coordinates);
|
||||
if (address == null) {
|
||||
return NoneLocationTrackingSpec.INSTANCE.getDefaultGoToInput(tool, coordinates,
|
||||
location);
|
||||
|
||||
+7
-12
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.action;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
|
||||
@@ -70,17 +68,14 @@ public enum PCLocationTrackingSpec implements LocationTrackingSpec, LocationTrac
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Address> computeTraceAddress(PluginTool tool,
|
||||
DebuggerCoordinates coordinates) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
if (coordinates.getTime().isSnapOnly()) {
|
||||
Address pc = BY_STACK.doComputeTraceAddress(tool, coordinates);
|
||||
if (pc != null) {
|
||||
return pc;
|
||||
}
|
||||
public Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
|
||||
if (coordinates.getTime().isSnapOnly()) {
|
||||
Address pc = BY_STACK.computeTraceAddress(tool, coordinates);
|
||||
if (pc != null) {
|
||||
return pc;
|
||||
}
|
||||
return BY_REG.doComputeTraceAddress(tool, coordinates);
|
||||
});
|
||||
}
|
||||
return BY_REG.computeTraceAddress(tool, coordinates);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-9
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.action;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.debug.api.action.*;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
@@ -52,7 +50,8 @@ public interface RegisterLocationTrackingSpec extends LocationTrackingSpec, Loca
|
||||
return this;
|
||||
}
|
||||
|
||||
default Address doComputeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
|
||||
@Override
|
||||
default Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
|
||||
Trace trace = coordinates.getTrace();
|
||||
TracePlatform platform = coordinates.getPlatform();
|
||||
TraceThread thread = coordinates.getThread();
|
||||
@@ -88,12 +87,6 @@ public interface RegisterLocationTrackingSpec extends LocationTrackingSpec, Loca
|
||||
.getAddress(value.getUnsignedValue().longValue(), true));
|
||||
}
|
||||
|
||||
@Override
|
||||
default CompletableFuture<Address> computeTraceAddress(PluginTool tool,
|
||||
DebuggerCoordinates coordinates) {
|
||||
return CompletableFuture.supplyAsync(() -> doComputeTraceAddress(tool, coordinates));
|
||||
}
|
||||
|
||||
@Override
|
||||
default GoToInput getDefaultGoToInput(PluginTool tool, DebuggerCoordinates coordinates,
|
||||
ProgramLocation location) {
|
||||
|
||||
+9
-14
@@ -16,13 +16,11 @@
|
||||
package ghidra.app.plugin.core.debug.gui.action;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.TrackLocationAction;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.debug.api.action.*;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.debug.api.watch.WatchRow;
|
||||
@@ -115,28 +113,25 @@ public class WatchLocationTrackingSpec implements LocationTrackingSpec {
|
||||
class WatchLocationTracker implements LocationTracker {
|
||||
private AddressSetView reads;
|
||||
private DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||
private PcodeExecutor<WatchValue> asyncExec = null;
|
||||
private PcodeExecutor<WatchValue> exec = null;
|
||||
private PcodeExpression compiled;
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Address> computeTraceAddress(PluginTool tool,
|
||||
DebuggerCoordinates coordinates) {
|
||||
if (!Objects.equals(current, coordinates) || asyncExec == null) {
|
||||
public Address computeTraceAddress(PluginTool tool, DebuggerCoordinates coordinates) {
|
||||
if (!Objects.equals(current, coordinates) || exec == null) {
|
||||
current = coordinates;
|
||||
asyncExec = current.getPlatform() == null ? null
|
||||
exec = current.getPlatform() == null ? null
|
||||
: DebuggerPcodeUtils.buildWatchExecutor(tool, coordinates);
|
||||
}
|
||||
else {
|
||||
asyncExec.getState().clear();
|
||||
exec.getState().clear();
|
||||
}
|
||||
if (current.getTrace() == null) {
|
||||
return AsyncUtils.nil();
|
||||
return null;
|
||||
}
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
compiled = DebuggerPcodeUtils.compileExpression(tool, current, expression);
|
||||
WatchValue value = compiled.evaluate(asyncExec);
|
||||
return value == null ? null : value.address();
|
||||
});
|
||||
compiled = DebuggerPcodeUtils.compileExpression(tool, current, expression);
|
||||
WatchValue value = compiled.evaluate(exec);
|
||||
return value == null ? null : value.address();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
-1
@@ -20,7 +20,6 @@ import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.*;
|
||||
import ghidra.app.services.DebuggerLogicalBreakpointService;
|
||||
import ghidra.app.services.DebuggerModelService;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
|
||||
|
||||
+7
-15
@@ -259,48 +259,40 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
|
||||
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
if (event instanceof ProgramLocationPluginEvent) {
|
||||
if (event instanceof ProgramLocationPluginEvent ev) {
|
||||
cbProgramLocationEvents.invoke(() -> {
|
||||
ProgramLocationPluginEvent ev = (ProgramLocationPluginEvent) event;
|
||||
if (heedLocationEvent(ev)) {
|
||||
connectedProvider.staticProgramLocationChanged(ev.getLocation());
|
||||
}
|
||||
});
|
||||
}
|
||||
if (event instanceof ProgramSelectionPluginEvent) {
|
||||
if (event instanceof ProgramSelectionPluginEvent ev) {
|
||||
cbProgramSelectionEvents.invoke(() -> {
|
||||
ProgramSelectionPluginEvent ev = (ProgramSelectionPluginEvent) event;
|
||||
if (heedSelectionEvent(ev)) {
|
||||
connectedProvider.staticProgramSelectionChanged(ev.getProgram(),
|
||||
ev.getSelection());
|
||||
}
|
||||
});
|
||||
}
|
||||
if (event instanceof ProgramOpenedPluginEvent) {
|
||||
ProgramOpenedPluginEvent ev = (ProgramOpenedPluginEvent) event;
|
||||
if (event instanceof ProgramOpenedPluginEvent ev) {
|
||||
allProviders(p -> p.programOpened(ev.getProgram()));
|
||||
}
|
||||
if (event instanceof ProgramClosedPluginEvent) {
|
||||
ProgramClosedPluginEvent ev = (ProgramClosedPluginEvent) event;
|
||||
if (event instanceof ProgramClosedPluginEvent ev) {
|
||||
allProviders(p -> p.programClosed(ev.getProgram()));
|
||||
}
|
||||
if (event instanceof ProgramActivatedPluginEvent) {
|
||||
ProgramActivatedPluginEvent ev = (ProgramActivatedPluginEvent) event;
|
||||
if (event instanceof ProgramActivatedPluginEvent ev) {
|
||||
allProviders(p -> p.staticProgramActivated(ev.getActiveProgram()));
|
||||
}
|
||||
if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
if (event instanceof TraceActivatedPluginEvent ev) {
|
||||
current = ev.getActiveCoordinates();
|
||||
allProviders(p -> p.coordinatesActivated(current));
|
||||
}
|
||||
if (event instanceof TraceClosedPluginEvent) {
|
||||
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
|
||||
if (event instanceof TraceClosedPluginEvent ev) {
|
||||
if (current.getTrace() == ev.getTrace()) {
|
||||
current = DebuggerCoordinates.NOWHERE;
|
||||
}
|
||||
allProviders(p -> p.traceClosed(ev.getTrace()));
|
||||
}
|
||||
// TODO: Sync selection and highlights?
|
||||
}
|
||||
|
||||
void fireStaticLocationEvent(ProgramLocation staticLoc) {
|
||||
|
||||
+12
-14
@@ -1159,24 +1159,22 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||
}
|
||||
|
||||
protected void doGoToTracked() {
|
||||
ProgramLocation loc = trackingTrait.getTrackedLocation();
|
||||
ProgramLocation trackedStatic = doMarkTrackedLocation();
|
||||
if (loc == null) {
|
||||
return;
|
||||
}
|
||||
TraceProgramView curView = current.getView();
|
||||
if (!syncTrait.isAutoSyncCursorWithStaticListing() || trackedStatic == null) {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
ProgramLocation loc = trackingTrait.getTrackedLocation();
|
||||
ProgramLocation trackedStatic = doMarkTrackedLocation();
|
||||
if (loc == null) {
|
||||
return;
|
||||
}
|
||||
TraceProgramView curView = current.getView();
|
||||
if (!syncTrait.isAutoSyncCursorWithStaticListing() || trackedStatic == null) {
|
||||
if (curView != current.getView()) {
|
||||
// Trace changed before Swing scheduled us
|
||||
return;
|
||||
}
|
||||
goToAndUpdateTrackingLabel(curView, loc);
|
||||
doCheckCurrentModuleMissing();
|
||||
});
|
||||
}
|
||||
else {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
}
|
||||
else {
|
||||
if (curView != current.getView()) {
|
||||
// Trace changed before Swing scheduled us
|
||||
return;
|
||||
@@ -1184,8 +1182,8 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||
goToAndUpdateTrackingLabel(curView, loc);
|
||||
doCheckCurrentModuleMissing();
|
||||
plugin.fireStaticLocationEvent(trackedStatic);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void doAutoDisassemble(Address start) {
|
||||
|
||||
+2
-4
@@ -150,13 +150,11 @@ public class DebuggerMemoryBytesPlugin
|
||||
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
if (event instanceof TraceActivatedPluginEvent ev) {
|
||||
current = ev.getActiveCoordinates();
|
||||
allProviders(p -> p.coordinatesActivated(current));
|
||||
}
|
||||
if (event instanceof TraceClosedPluginEvent) {
|
||||
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
|
||||
if (event instanceof TraceClosedPluginEvent ev) {
|
||||
if (current.getTrace() == ev.getTrace()) {
|
||||
current = DebuggerCoordinates.NOWHERE;
|
||||
}
|
||||
|
||||
+4
-8
@@ -66,20 +66,16 @@ public class DebuggerRegionsPlugin extends AbstractDebuggerPlugin {
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof ProgramActivatedPluginEvent) {
|
||||
ProgramActivatedPluginEvent ev = (ProgramActivatedPluginEvent) event;
|
||||
if (event instanceof ProgramActivatedPluginEvent ev) {
|
||||
provider.setProgram(ev.getActiveProgram());
|
||||
}
|
||||
else if (event instanceof ProgramLocationPluginEvent) {
|
||||
ProgramLocationPluginEvent ev = (ProgramLocationPluginEvent) event;
|
||||
else if (event instanceof ProgramLocationPluginEvent ev) {
|
||||
provider.setLocation(ev.getLocation());
|
||||
}
|
||||
else if (event instanceof ProgramClosedPluginEvent) {
|
||||
ProgramClosedPluginEvent ev = (ProgramClosedPluginEvent) event;
|
||||
else if (event instanceof ProgramClosedPluginEvent ev) {
|
||||
provider.programClosed(ev.getProgram());
|
||||
}
|
||||
else if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
else if (event instanceof TraceActivatedPluginEvent ev) {
|
||||
provider.coordinatesActivated(ev.getActiveCoordinates());
|
||||
}
|
||||
}
|
||||
|
||||
+17
-18
@@ -26,22 +26,21 @@ import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
@PluginInfo( //
|
||||
shortDescription = "Displays memory vs time", //
|
||||
description = "Provides visualiztion/navigation across time/address axes", //
|
||||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.RELEASED, //
|
||||
eventsConsumed = { //
|
||||
TraceActivatedPluginEvent.class //
|
||||
}, //
|
||||
servicesRequired = { //
|
||||
DebuggerTraceManagerService.class //
|
||||
}, //
|
||||
servicesProvided = { //
|
||||
MemviewService.class //
|
||||
} //
|
||||
)
|
||||
@PluginInfo(
|
||||
shortDescription = "Displays memory vs time",
|
||||
description = "Provides visualiztion/navigation across time/address axes",
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.RELEASED,
|
||||
eventsConsumed = {
|
||||
TraceActivatedPluginEvent.class
|
||||
},
|
||||
servicesRequired = {
|
||||
DebuggerTraceManagerService.class
|
||||
},
|
||||
servicesProvided = {
|
||||
MemviewService.class
|
||||
})
|
||||
public class DebuggerMemviewPlugin extends AbstractDebuggerPlugin implements MemviewService {
|
||||
|
||||
protected MemviewProvider provider;
|
||||
@@ -67,12 +66,12 @@ public class DebuggerMemviewPlugin extends AbstractDebuggerPlugin implements Mem
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
if (event instanceof TraceActivatedPluginEvent ev) {
|
||||
listener.coordinatesActivated(ev.getActiveCoordinates());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemviewProvider getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
+4
-8
@@ -65,20 +65,16 @@ public class DebuggerModulesPlugin extends AbstractDebuggerPlugin {
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof ProgramActivatedPluginEvent) {
|
||||
ProgramActivatedPluginEvent ev = (ProgramActivatedPluginEvent) event;
|
||||
if (event instanceof ProgramActivatedPluginEvent ev) {
|
||||
provider.setProgram(ev.getActiveProgram());
|
||||
}
|
||||
else if (event instanceof ProgramLocationPluginEvent) {
|
||||
ProgramLocationPluginEvent ev = (ProgramLocationPluginEvent) event;
|
||||
else if (event instanceof ProgramLocationPluginEvent ev) {
|
||||
provider.setLocation(ev.getLocation());
|
||||
}
|
||||
else if (event instanceof ProgramClosedPluginEvent) {
|
||||
ProgramClosedPluginEvent ev = (ProgramClosedPluginEvent) event;
|
||||
else if (event instanceof ProgramClosedPluginEvent ev) {
|
||||
provider.programClosed(ev.getProgram());
|
||||
}
|
||||
else if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
else if (event instanceof TraceActivatedPluginEvent ev) {
|
||||
provider.coordinatesActivated(ev.getActiveCoordinates());
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -64,12 +64,10 @@ public class DebuggerStaticMappingPlugin extends AbstractDebuggerPlugin {
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
if (event instanceof TraceActivatedPluginEvent ev) {
|
||||
provider.setTrace(ev.getActiveCoordinates().getTrace());
|
||||
}
|
||||
if (event instanceof ProgramActivatedPluginEvent) {
|
||||
ProgramActivatedPluginEvent ev = (ProgramActivatedPluginEvent) event;
|
||||
if (event instanceof ProgramActivatedPluginEvent ev) {
|
||||
provider.setProgram(ev.getActiveProgram());
|
||||
}
|
||||
}
|
||||
|
||||
+6
-12
@@ -112,38 +112,32 @@ public class DebuggerObjectsPlugin extends AbstractDebuggerPlugin
|
||||
provider.traceOpened(ev.getTrace());
|
||||
}
|
||||
}
|
||||
else if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
else if (event instanceof TraceActivatedPluginEvent ev) {
|
||||
for (DebuggerObjectsProvider provider : providers) {
|
||||
provider.traceActivated(ev.getActiveCoordinates());
|
||||
}
|
||||
}
|
||||
else if (event instanceof TraceClosedPluginEvent) {
|
||||
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
|
||||
else if (event instanceof TraceClosedPluginEvent ev) {
|
||||
for (DebuggerObjectsProvider provider : providers) {
|
||||
provider.traceClosed(ev.getTrace());
|
||||
}
|
||||
}
|
||||
else if (event instanceof ModelActivatedPluginEvent) {
|
||||
ModelActivatedPluginEvent ev = (ModelActivatedPluginEvent) event;
|
||||
else if (event instanceof ModelActivatedPluginEvent ev) {
|
||||
for (DebuggerObjectsProvider provider : providers) {
|
||||
provider.modelActivated(ev.getActiveModel());
|
||||
}
|
||||
}
|
||||
else if (event instanceof ProgramActivatedPluginEvent) {
|
||||
ProgramActivatedPluginEvent ev = (ProgramActivatedPluginEvent) event;
|
||||
else if (event instanceof ProgramActivatedPluginEvent ev) {
|
||||
for (DebuggerObjectsProvider provider : providers) {
|
||||
provider.setProgram(ev.getActiveProgram());
|
||||
}
|
||||
}
|
||||
else if (event instanceof ProgramOpenedPluginEvent) {
|
||||
ProgramOpenedPluginEvent ev = (ProgramOpenedPluginEvent) event;
|
||||
else if (event instanceof ProgramOpenedPluginEvent ev) {
|
||||
for (DebuggerObjectsProvider provider : providers) {
|
||||
provider.setProgram(ev.getProgram());
|
||||
}
|
||||
}
|
||||
else if (event instanceof ProgramSelectionPluginEvent) {
|
||||
ProgramSelectionPluginEvent ev = (ProgramSelectionPluginEvent) event;
|
||||
else if (event instanceof ProgramSelectionPluginEvent ev) {
|
||||
for (DebuggerObjectsProvider provider : providers) {
|
||||
provider.setProgram(ev.getProgram());
|
||||
}
|
||||
|
||||
+1
-2
@@ -53,8 +53,7 @@ public class DebuggerPcodeStepperPlugin extends AbstractDebuggerPlugin {
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
if (event instanceof TraceActivatedPluginEvent ev) {
|
||||
provider.coordinatesActivated(ev.getActiveCoordinates());
|
||||
}
|
||||
}
|
||||
|
||||
+8
-7
@@ -29,7 +29,8 @@ import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.*;
|
||||
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.DebuggerTraceManagerService;
|
||||
import ghidra.debug.api.platform.DebuggerPlatformMapper;
|
||||
@@ -307,14 +308,14 @@ public class DebuggerPlatformPlugin extends Plugin {
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof TraceActivatedPluginEvent evt) {
|
||||
coordinatesActivated(evt.getActiveCoordinates());
|
||||
if (event instanceof TraceActivatedPluginEvent ev) {
|
||||
coordinatesActivated(ev.getActiveCoordinates());
|
||||
}
|
||||
if (event instanceof TraceClosedPluginEvent evt) {
|
||||
traceClosed(evt.getTrace());
|
||||
if (event instanceof TraceClosedPluginEvent ev) {
|
||||
traceClosed(ev.getTrace());
|
||||
}
|
||||
if (event instanceof DebuggerPlatformPluginEvent evt) {
|
||||
mapperActivated(evt.getTrace(), evt.getMapper());
|
||||
if (event instanceof DebuggerPlatformPluginEvent ev) {
|
||||
mapperActivated(ev.getTrace(), ev.getMapper());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-4
@@ -101,12 +101,10 @@ public class DebuggerRegistersPlugin extends AbstractDebuggerPlugin {
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
if (event instanceof TraceActivatedPluginEvent ev) {
|
||||
connectedProvider.coordinatesActivated(ev.getActiveCoordinates());
|
||||
}
|
||||
if (event instanceof TraceClosedPluginEvent) {
|
||||
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
|
||||
if (event instanceof TraceClosedPluginEvent ev) {
|
||||
traceClosed(ev.getTrace());
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -18,7 +18,8 @@ package ghidra.app.plugin.core.debug.gui.thread;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
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.TraceActivatedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
@@ -57,8 +58,7 @@ public class DebuggerThreadsPlugin extends AbstractDebuggerPlugin {
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
if (event instanceof TraceActivatedPluginEvent ev) {
|
||||
provider.coordinatesActivated(ev.getActiveCoordinates());
|
||||
}
|
||||
}
|
||||
|
||||
+17
-9
@@ -21,6 +21,7 @@ import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.*;
|
||||
@@ -141,7 +142,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
SnapshotRow row = (SnapshotRow) data.getRowObject();
|
||||
if (row != null && row.getSnap() == currentSnap) {
|
||||
if (row != null && currentSnap != null && currentSnap.longValue() == row.getSnap()) {
|
||||
setBold();
|
||||
}
|
||||
return this;
|
||||
@@ -154,7 +155,7 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
||||
protected boolean hideScratch = true;
|
||||
|
||||
private Trace currentTrace;
|
||||
private Long currentSnap;
|
||||
private volatile Long currentSnap;
|
||||
|
||||
protected final SnapshotListener listener = new SnapshotListener();
|
||||
|
||||
@@ -237,8 +238,11 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
||||
Collection<? extends TraceSnapshot> snapshots =
|
||||
hideScratch ? manager.getSnapshots(0, true, Long.MAX_VALUE, true)
|
||||
: manager.getAllSnapshots();
|
||||
snapshotTableModel
|
||||
.addAll(snapshots.stream().map(s -> new SnapshotRow(currentTrace, s)).toList());
|
||||
// Use .collect instead of .toList to avoid size/sync issues
|
||||
// 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() {
|
||||
@@ -250,10 +254,11 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
||||
return;
|
||||
}
|
||||
TraceTimeManager manager = currentTrace.getTimeManager();
|
||||
snapshotTableModel.addAll(manager.getSnapshots(Long.MIN_VALUE, true, 0, false)
|
||||
.stream()
|
||||
Collection<? extends TraceSnapshot> sratch =
|
||||
manager.getSnapshots(Long.MIN_VALUE, true, 0, false);
|
||||
snapshotTableModel.addAll(sratch.stream()
|
||||
.map(s -> new SnapshotRow(currentTrace, s))
|
||||
.toList());
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public ListSelectionModel getSelectionModel() {
|
||||
@@ -265,8 +270,12 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
||||
return row == null ? null : row.getSnap();
|
||||
}
|
||||
|
||||
public void setSelectedSnapshot(Long snap) {
|
||||
public void setCurrentSnapshot(Long snap) {
|
||||
currentSnap = snap;
|
||||
snapshotTableModel.fireTableDataChanged();
|
||||
}
|
||||
|
||||
public void setSelectedSnapshot(Long snap) {
|
||||
if (snap == null) {
|
||||
snapshotTable.clearSelection();
|
||||
return;
|
||||
@@ -283,6 +292,5 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
||||
return;
|
||||
}
|
||||
snapshotFilterPanel.setSelectedItem(row);
|
||||
snapshotTableModel.fireTableDataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -122,8 +122,7 @@ public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
if (event instanceof TraceActivatedPluginEvent ev) {
|
||||
provider.coordinatesActivated(ev.getActiveCoordinates());
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -209,7 +209,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||
current = coordinates;
|
||||
|
||||
mainPanel.setTrace(current.getTrace());
|
||||
mainPanel.setSelectedSnapshot(current.getSnap());
|
||||
mainPanel.setCurrentSnapshot(current.getSnap());
|
||||
}
|
||||
|
||||
public void writeConfigState(SaveState saveState) {
|
||||
|
||||
+12
-12
@@ -1360,23 +1360,23 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
|
||||
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
if (event instanceof ProgramOpenedPluginEvent evt) {
|
||||
programOpened(evt.getProgram());
|
||||
if (event instanceof ProgramOpenedPluginEvent ev) {
|
||||
programOpened(ev.getProgram());
|
||||
}
|
||||
else if (event instanceof ProgramClosedPluginEvent evt) {
|
||||
programClosed(evt.getProgram());
|
||||
else if (event instanceof ProgramClosedPluginEvent ev) {
|
||||
programClosed(ev.getProgram());
|
||||
}
|
||||
else if (event instanceof TraceOpenedPluginEvent evt) {
|
||||
traceOpened(evt.getTrace());
|
||||
else if (event instanceof TraceOpenedPluginEvent ev) {
|
||||
traceOpened(ev.getTrace());
|
||||
}
|
||||
else if (event instanceof TraceActivatedPluginEvent evt) {
|
||||
traceSnapChanged(evt.getActiveCoordinates());
|
||||
else if (event instanceof TraceActivatedPluginEvent ev) {
|
||||
traceSnapChanged(ev.getActiveCoordinates());
|
||||
}
|
||||
else if (event instanceof TraceInactiveCoordinatesPluginEvent evt) {
|
||||
traceSnapChanged(evt.getCoordinates());
|
||||
else if (event instanceof TraceInactiveCoordinatesPluginEvent ev) {
|
||||
traceSnapChanged(ev.getCoordinates());
|
||||
}
|
||||
else if (event instanceof TraceClosedPluginEvent evt) {
|
||||
traceClosed(evt.getTrace());
|
||||
else if (event instanceof TraceClosedPluginEvent ev) {
|
||||
traceClosed(ev.getTrace());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-7
@@ -20,7 +20,8 @@ import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
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.services.*;
|
||||
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
|
||||
@@ -342,14 +343,14 @@ public class DebuggerControlServicePlugin extends AbstractDebuggerPlugin
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof TraceOpenedPluginEvent evt) {
|
||||
installAllMemoryEditors(evt.getTrace());
|
||||
if (event instanceof TraceOpenedPluginEvent ev) {
|
||||
installAllMemoryEditors(ev.getTrace());
|
||||
}
|
||||
else if (event instanceof TraceActivatedPluginEvent evt) {
|
||||
coordinatesActivated(evt.getActiveCoordinates(), evt.getCause());
|
||||
else if (event instanceof TraceActivatedPluginEvent ev) {
|
||||
coordinatesActivated(ev.getActiveCoordinates(), ev.getCause());
|
||||
}
|
||||
else if (event instanceof TraceClosedPluginEvent evt) {
|
||||
uninstallAllMemoryEditors(evt.getTrace());
|
||||
else if (event instanceof TraceClosedPluginEvent ev) {
|
||||
uninstallAllMemoryEditors(ev.getTrace());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+26
-5
@@ -83,6 +83,24 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||
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() {
|
||||
return mapping.getTrace();
|
||||
}
|
||||
@@ -232,11 +250,14 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||
|
||||
private void objectRestored() {
|
||||
synchronized (lock) {
|
||||
doAffectedByTraceClosed(trace);
|
||||
var old = Map.copyOf(outbound);
|
||||
outbound.clear();
|
||||
loadOutboundEntries(); // Also places/updates corresponding inbound entries
|
||||
// TODO: What about removed corresponding inbound entries?
|
||||
doAffectedByTraceOpened(trace);
|
||||
if (!old.equals(outbound)) {
|
||||
// TODO: What about removed corresponding inbound entries?
|
||||
doAffectedByTraceClosed(trace);
|
||||
doAffectedByTraceOpened(trace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -811,13 +832,13 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||
}
|
||||
|
||||
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)");
|
||||
return null;
|
||||
}
|
||||
|
||||
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)");
|
||||
return null;
|
||||
}
|
||||
|
||||
+2
-2
@@ -269,7 +269,7 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||
waitForPass(() -> assertTableSize(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
// @Test // Not gonna with write-behind cache
|
||||
public void testUndoRedo() throws Exception {
|
||||
createAndOpenTrace();
|
||||
|
||||
@@ -304,7 +304,7 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
// @Test // Not gonna with write-behind cache
|
||||
public void testAbort() throws Exception {
|
||||
createAndOpenTrace();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
|
||||
+4
-2
@@ -1128,7 +1128,8 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerTest
|
||||
}
|
||||
waitForTasks();
|
||||
|
||||
assertEquals(4, modelProvider.attributesTablePanel.tableModel.getModelData().size());
|
||||
waitForPass(() -> assertEquals(4,
|
||||
modelProvider.attributesTablePanel.tableModel.getModelData().size()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1158,6 +1159,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerTest
|
||||
// TODO: Should I collapse entries that are links to the same object?
|
||||
// Would use the "Life" column to display span for each included entry.
|
||||
// Neat, but not sure it's worth it
|
||||
assertEquals(14, modelProvider.elementsTablePanel.tableModel.getModelData().size());
|
||||
waitForPass(() -> assertEquals(14,
|
||||
modelProvider.elementsTablePanel.tableModel.getModelData().size()));
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -401,7 +401,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
// @Test // Not gonna with write-behind cache
|
||||
public void testUndoRedoCausesUpdateInProvider() throws Exception {
|
||||
createAndOpenTrace();
|
||||
|
||||
|
||||
+1
-1
@@ -444,7 +444,7 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerTes
|
||||
thread1.getObject().getAttribute(0, TraceObjectThread.KEY_COMMENT).getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
// @Test // Not gonna with write-behind cache
|
||||
public void testUndoRedoCausesUpdateInProvider() throws Exception {
|
||||
createAndOpenTrace();
|
||||
addThreads();
|
||||
|
||||
+3
-30
@@ -15,7 +15,8 @@
|
||||
*/
|
||||
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.List;
|
||||
@@ -286,35 +287,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerTest {
|
||||
tb.trace.getTimeManager().getSnapshot(0, false).getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
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());
|
||||
}
|
||||
// TODO: Test activation bolds the row
|
||||
|
||||
@Test
|
||||
public void testDoubleClickRowActivatesSnap() throws Exception {
|
||||
|
||||
+3
-2
@@ -47,6 +47,7 @@ import ghidra.trace.model.target.*;
|
||||
import ghidra.trace.model.thread.*;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.trace.model.time.TraceTimeManager;
|
||||
import ghidra.util.StreamUtils;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerTest {
|
||||
@@ -88,7 +89,7 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerTe
|
||||
|
||||
protected void dumpObjects() {
|
||||
System.err.println("All objects:");
|
||||
for (TraceObject object : objects.getAllObjects()) {
|
||||
for (TraceObject object : StreamUtils.iter(objects.getAllObjects())) {
|
||||
System.err.println(" " + object);
|
||||
}
|
||||
}
|
||||
@@ -99,7 +100,7 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerTe
|
||||
|
||||
waitForPass(noExc(() -> {
|
||||
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
|
||||
* 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.
|
||||
*
|
||||
* @param <T> the value type
|
||||
*/
|
||||
public class AsyncDebouncer<T> {
|
||||
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
|
||||
* 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) {
|
||||
lastContact = val;
|
||||
@@ -141,4 +143,16 @@ public class AsyncDebouncer<T> {
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-1
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package ghidra.trace.database;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
@@ -149,6 +150,8 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
||||
protected ListenerSet<TraceProgramViewListener> viewListeners =
|
||||
new ListenerSet<>(TraceProgramViewListener.class, true);
|
||||
|
||||
private volatile boolean closing;
|
||||
|
||||
public DBTrace(String name, CompilerSpec baseCompilerSpec, Object consumer)
|
||||
throws IOException, LanguageNotFoundException {
|
||||
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;
|
||||
try (LockHold hold = lockRead()) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
@@ -871,4 +874,29 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
||||
public void updateViewportsSnapshotDeleted(TraceSnapshot 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();
|
||||
}
|
||||
}
|
||||
|
||||
+29
-13
@@ -49,6 +49,21 @@ public abstract class AbstractDBTraceProgramViewMemory
|
||||
|
||||
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) {
|
||||
this.program = program;
|
||||
this.memoryManager = program.trace.getMemoryManager();
|
||||
@@ -301,24 +316,25 @@ public abstract class AbstractDBTraceProgramViewMemory
|
||||
|
||||
@Override
|
||||
public byte getByte(Address addr) throws MemoryAccessException {
|
||||
MemoryBlock block = getBlock(addr);
|
||||
if (block == null) {
|
||||
return 0; // Memory assumed initialized to 0
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return cache.read(addr);
|
||||
}
|
||||
return block.getByte(addr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(Address addr, byte[] dest, int destIndex, int size)
|
||||
throws MemoryAccessException {
|
||||
MemoryBlock block = getBlock(addr);
|
||||
if (block == null) {
|
||||
int avail = MathUtilities.unsignedMin(Math.max(0, size),
|
||||
addr.getAddressSpace().getMaxAddress().subtract(addr));
|
||||
Arrays.fill(dest, destIndex, avail, (byte) 0);
|
||||
return avail;
|
||||
public int getBytes(Address addr, byte[] b, int off, int len) throws MemoryAccessException {
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
if (cache.canCache(addr, len)) {
|
||||
return cache.read(addr, ByteBuffer.wrap(b, off, len));
|
||||
}
|
||||
AddressSpace as = addr.getAddressSpace();
|
||||
DBTraceMemorySpace space = program.trace.getMemoryManager().getMemorySpace(as, false);
|
||||
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
|
||||
|
||||
+9
-45
@@ -26,7 +26,6 @@ import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.mem.*;
|
||||
import ghidra.trace.database.memory.DBTraceMemorySpace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpaceInputStream;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.MathUtilities;
|
||||
|
||||
public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlock {
|
||||
@@ -97,19 +96,6 @@ public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlo
|
||||
private final List<MemoryBlockSourceInfo> info =
|
||||
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) {
|
||||
this.program = program;
|
||||
}
|
||||
@@ -120,15 +106,6 @@ public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlo
|
||||
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() {
|
||||
return program.trace.getMemoryManager().getMemorySpace(getAddressSpace(), false);
|
||||
}
|
||||
@@ -191,13 +168,11 @@ public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlo
|
||||
|
||||
@Override
|
||||
public byte getByte(Address addr) throws MemoryAccessException {
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
AddressRange range = getAddressRange();
|
||||
if (!range.contains(addr)) {
|
||||
throw new MemoryAccessException();
|
||||
}
|
||||
return cache.read(addr);
|
||||
AddressRange range = getAddressRange();
|
||||
if (!range.contains(addr)) {
|
||||
throw new MemoryAccessException();
|
||||
}
|
||||
return program.memory.getByte(addr);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -207,22 +182,12 @@ public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlo
|
||||
|
||||
@Override
|
||||
public int getBytes(Address addr, byte[] b, int off, int len) throws MemoryAccessException {
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
AddressRange range = getAddressRange();
|
||||
if (!range.contains(addr)) {
|
||||
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));
|
||||
AddressRange range = getAddressRange();
|
||||
if (!range.contains(addr)) {
|
||||
throw new MemoryAccessException();
|
||||
}
|
||||
len = MathUtilities.unsignedMin(len, range.getMaxAddress().subtract(addr) + 1);
|
||||
return program.memory.getBytes(addr, b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -267,7 +232,6 @@ public abstract class AbstractDBTraceProgramViewMemoryBlock implements MemoryBlo
|
||||
|
||||
@Override
|
||||
public boolean isOverlay() {
|
||||
// TODO: What effect does this have? Does it makes sense for trace "overlays"?
|
||||
return getAddressSpace().isOverlaySpace();
|
||||
}
|
||||
|
||||
|
||||
+2
-5
@@ -20,6 +20,7 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.util.LockHold;
|
||||
@@ -202,10 +203,6 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
||||
if (regionBlocks == null) { // <init> order
|
||||
return;
|
||||
}
|
||||
for (AbstractDBTraceProgramViewMemoryBlock block : forceFullView
|
||||
? spaceBlocks.values()
|
||||
: regionBlocks.values()) {
|
||||
block.invalidateBytesCache(range);
|
||||
}
|
||||
cache.invalidate(range);
|
||||
}
|
||||
}
|
||||
|
||||
+217
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
+168
-138
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -22,7 +22,7 @@ import db.LongField;
|
||||
import ghidra.util.database.DBAnnotatedObject;
|
||||
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> {
|
||||
public DBTraceObjectDBFieldCodec(Class<OV> objectType, Field field, int 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();
|
||||
}
|
||||
|
||||
protected static DBTraceObject decode(InternalTraceObjectValue ent, long enc) {
|
||||
protected static DBTraceObject decode(TraceObjectValueStorage ent, long enc) {
|
||||
return enc == -1 ? null : ent.getManager().getObjectById(enc);
|
||||
}
|
||||
|
||||
|
||||
+134
-74
@@ -31,7 +31,6 @@ import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.dbg.util.*;
|
||||
import ghidra.lifecycle.Internal;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.trace.database.DBTrace;
|
||||
@@ -57,8 +56,7 @@ import ghidra.trace.model.thread.TraceObjectThread;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.TraceChangeRecord;
|
||||
import ghidra.trace.util.TraceEvents;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.PrimitiveCodec;
|
||||
@@ -159,11 +157,11 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||
protected final DBCachedObjectStore<DBTraceObject> objectStore;
|
||||
protected final DBTraceObjectValueRStarTree valueTree;
|
||||
protected final DBTraceObjectValueMap valueMap;
|
||||
protected final DBTraceObjectValueWriteBehindCache valueWbCache;
|
||||
|
||||
protected final DBCachedObjectIndex<TraceObjectKeyPath, DBTraceObject> objectsByPath;
|
||||
|
||||
protected final Collection<TraceObject> objectsView;
|
||||
protected final Collection<TraceObjectValue> valuesView;
|
||||
|
||||
protected TargetObjectSchema rootSchema;
|
||||
|
||||
@@ -198,8 +196,9 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||
objectsByPath =
|
||||
objectStore.getIndex(TraceObjectKeyPath.class, DBTraceObject.PATH_COLUMN);
|
||||
|
||||
valueWbCache = new DBTraceObjectValueWriteBehindCache(this);
|
||||
|
||||
objectsView = Collections.unmodifiableCollection(objectStore.asMap().values());
|
||||
valuesView = Collections.unmodifiableCollection(valueMap.values());
|
||||
}
|
||||
|
||||
protected void loadRootSchema() {
|
||||
@@ -228,29 +227,42 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||
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) {
|
||||
if (!(object instanceof DBTraceObject)) {
|
||||
if (!(object instanceof DBTraceObject dbObject)) {
|
||||
throw new IllegalArgumentException("Object " + object + " is not part of this trace");
|
||||
}
|
||||
DBTraceObject dbObject = (DBTraceObject) object;
|
||||
if (dbObject.manager != this) {
|
||||
throw new IllegalArgumentException("Object " + object + " is not part of this trace");
|
||||
}
|
||||
if (!getAllObjects().contains(dbObject)) {
|
||||
if (!checkMyObject(dbObject)) {
|
||||
throw new IllegalArgumentException("Object " + object + " is not part of this trace");
|
||||
}
|
||||
return dbObject;
|
||||
}
|
||||
|
||||
protected Object validatePrimitive(Object child) {
|
||||
protected Object validatePrimitive(Object value) {
|
||||
try {
|
||||
PrimitiveCodec.getCodec(child.getClass());
|
||||
PrimitiveCodec.getCodec(value.getClass());
|
||||
}
|
||||
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
|
||||
@@ -267,7 +279,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||
rootSchema = schema;
|
||||
}
|
||||
|
||||
protected void emitValueCreated(DBTraceObject parent, InternalTraceObjectValue entry) {
|
||||
protected void emitValueCreated(DBTraceObject parent, DBTraceObjectValue entry) {
|
||||
if (parent == null) {
|
||||
// Don't need event for root value created
|
||||
return;
|
||||
@@ -275,24 +287,30 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||
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) {
|
||||
InternalTraceObjectValue entry = valueTree.asSpatialMap()
|
||||
.put(new ImmutableValueShape(parent, value, key, lifespan), null);
|
||||
// Root is never in write-behind cache
|
||||
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) {
|
||||
child.notifyParentValueCreated(entry);
|
||||
}
|
||||
else {
|
||||
entry.doSetPrimitive(value);
|
||||
}
|
||||
|
||||
if (parent != null) { // Root
|
||||
parent.notifyValueCreated(entry);
|
||||
}
|
||||
|
||||
// TODO: Perhaps a little drastic
|
||||
invalidateObjectsContainingCache();
|
||||
|
||||
emitValueCreated(parent, entry);
|
||||
return entry;
|
||||
}
|
||||
@@ -326,13 +344,13 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObjectValue createRootObject(TargetObjectSchema schema) {
|
||||
public DBTraceObjectValue createRootObject(TargetObjectSchema schema) {
|
||||
try (LockHold hold = trace.lockWrite()) {
|
||||
setSchema(schema);
|
||||
DBTraceObject root = doCreateObject(TraceObjectKeyPath.of());
|
||||
assert root.getKey() == 0;
|
||||
InternalTraceObjectValue val = doCreateValue(Lifespan.ALL, null, "", root);
|
||||
assert val.getKey() == 0;
|
||||
DBTraceObjectValue val = doCreateValue(Lifespan.ALL, null, "", root);
|
||||
assert val.getWrapped() instanceof DBTraceObjectValueData data && data.getKey() == 0;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
@@ -344,9 +362,10 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||
}
|
||||
}
|
||||
|
||||
public DBTraceObjectValueData getRootValue() {
|
||||
public DBTraceObjectValue getRootValue() {
|
||||
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,
|
||||
PathPredicates predicates) {
|
||||
try (LockHold hold = trace.lockRead()) {
|
||||
DBTraceObjectValueData rootVal = getRootValue();
|
||||
DBTraceObjectValue rootVal = getRootValue();
|
||||
if (rootVal == null) {
|
||||
return Stream.of();
|
||||
}
|
||||
@@ -390,29 +409,60 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends TraceObject> getAllObjects() {
|
||||
return objectsView;
|
||||
public Stream<DBTraceObject> getAllObjects() {
|
||||
return objectStore.asMap().values().stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends TraceObjectValue> getAllValues() {
|
||||
return valuesView;
|
||||
public int getObjectCount() {
|
||||
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
|
||||
public Collection<? extends TraceObjectValue> getValuesIntersecting(Lifespan span,
|
||||
AddressRange range, String entryKey) {
|
||||
return Collections
|
||||
.unmodifiableCollection(valueMap.reduce(TraceObjectValueQuery.intersecting(
|
||||
entryKey != null ? entryKey : EntryKeyDimension.INSTANCE.absoluteMin(),
|
||||
entryKey != null ? entryKey : EntryKeyDimension.INSTANCE.absoluteMax(),
|
||||
span, range)).values());
|
||||
return Stream.concat(
|
||||
streamValuesIntersectingData(span, range, entryKey).map(v -> v.getWrapper()),
|
||||
streamValuesIntersectingBehind(span, range, entryKey).map(v -> v.getWrapper()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
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,
|
||||
String entryKey) {
|
||||
return Collections.unmodifiableCollection(
|
||||
valueMap.reduce(TraceObjectValueQuery.at(entryKey, snap, address)).values());
|
||||
return Stream.concat(
|
||||
streamValuesAtData(snap, address, entryKey).map(v -> v.getWrapper()),
|
||||
streamValuesAtBehind(snap, address, entryKey).map(v -> v.getWrapper()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -450,6 +500,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||
public void clear() {
|
||||
try (LockHold hold = trace.lockWrite()) {
|
||||
valueMap.clear();
|
||||
valueWbCache.clear();
|
||||
objectStore.deleteAll();
|
||||
schemaStore.deleteAll();
|
||||
rootSchema = null;
|
||||
@@ -463,8 +514,8 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||
object.emitEvents(new TraceChangeRecord<>(TraceEvents.OBJECT_DELETED, null, object));
|
||||
}
|
||||
|
||||
protected void doDeleteEdge(DBTraceObjectValueData edge) {
|
||||
valueTree.doDeleteEntry(edge);
|
||||
protected void doDeleteValue(DBTraceObjectValueData value) {
|
||||
valueTree.doDeleteEntry(value);
|
||||
|
||||
// TODO: Perhaps a little drastic....
|
||||
/**
|
||||
@@ -475,6 +526,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||
invalidateObjectsContainingCache();
|
||||
}
|
||||
|
||||
protected void doDeleteCachedValue(DBTraceObjectValueBehind value) {
|
||||
valueWbCache.remove(value);
|
||||
// Ditto NB from doDeleteValue
|
||||
invalidateObjectsContainingCache();
|
||||
}
|
||||
|
||||
public boolean hasSchema() {
|
||||
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,
|
||||
String key, Class<I> ifaceCls, Predicate<? super I> predicate) {
|
||||
return valueMap.getAddressSetView(Lifespan.at(snap), v -> {
|
||||
if (!v.hasEntryKey(key)) {
|
||||
return false;
|
||||
}
|
||||
TraceObject parent = v.getParent();
|
||||
I iface = parent.queryInterface(ifaceCls);
|
||||
if (iface == null) {
|
||||
return false;
|
||||
}
|
||||
if (!predicate.test(iface)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return new UnionAddressSetView(
|
||||
valueMap.getAddressSetView(Lifespan.at(snap),
|
||||
v -> acceptValue(v.getWrapper(), key, ifaceCls, predicate)),
|
||||
valueWbCache.getObjectsAddresSet(snap, key, ifaceCls, predicate));
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!(thread instanceof DBTraceObjectThread)) {
|
||||
if (!(thread instanceof DBTraceObjectThread dbThread)) {
|
||||
throw new AssertionError("Thread " + thread + " is not an object in this trace");
|
||||
}
|
||||
DBTraceObjectThread dbThread = (DBTraceObjectThread) thread;
|
||||
if (!checkMyObject(dbThread.getObject())) {
|
||||
throw new AssertionError("Thread " + thread + " is not an object in this trace");
|
||||
}
|
||||
return dbThread;
|
||||
}
|
||||
|
||||
public void flushWbCaches() {
|
||||
valueWbCache.flush();
|
||||
}
|
||||
|
||||
public void waitWbWorkers() {
|
||||
valueWbCache.waitWorkers();
|
||||
}
|
||||
}
|
||||
|
||||
+14
-14
@@ -28,18 +28,18 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
public static DBTraceObjectValPath of(Collection<InternalTraceObjectValue> entryList) {
|
||||
public static DBTraceObjectValPath of(Collection<DBTraceObjectValue> 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));
|
||||
}
|
||||
|
||||
private final List<InternalTraceObjectValue> entryList;
|
||||
private final List<DBTraceObjectValue> entryList;
|
||||
private List<String> keyList; // lazily computed
|
||||
|
||||
private DBTraceObjectValPath(List<InternalTraceObjectValue> entryList) {
|
||||
private DBTraceObjectValPath(List<DBTraceObjectValue> entryList) {
|
||||
this.entryList = entryList;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends InternalTraceObjectValue> getEntryList() {
|
||||
public List<DBTraceObjectValue> getEntryList() {
|
||||
return entryList;
|
||||
}
|
||||
|
||||
@@ -77,10 +77,10 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
|
||||
if (!entryList.isEmpty() && entry.getTrace() != entryList.get(0).getTrace()) {
|
||||
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");
|
||||
}
|
||||
InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()];
|
||||
DBTraceObjectValue[] arr = new DBTraceObjectValue[1 + entryList.size()];
|
||||
arr[0] = val;
|
||||
for (int i = 1; i < arr.length; i++) {
|
||||
arr[i] = entryList.get(i - 1);
|
||||
@@ -93,10 +93,10 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
|
||||
if (!entryList.isEmpty() && entry.getTrace() != entryList.get(0).getTrace()) {
|
||||
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");
|
||||
}
|
||||
InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()];
|
||||
DBTraceObjectValue[] arr = new DBTraceObjectValue[1 + entryList.size()];
|
||||
for (int i = 0; i < arr.length - 1; i++) {
|
||||
arr[i] = entryList.get(i);
|
||||
}
|
||||
@@ -105,7 +105,7 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalTraceObjectValue getFirstEntry() {
|
||||
public DBTraceObjectValue getFirstEntry() {
|
||||
if (entryList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
@@ -114,12 +114,12 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
|
||||
|
||||
@Override
|
||||
public TraceObject getSource(TraceObject ifEmpty) {
|
||||
InternalTraceObjectValue first = getFirstEntry();
|
||||
DBTraceObjectValue first = getFirstEntry();
|
||||
return first == null ? ifEmpty : first.getParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalTraceObjectValue getLastEntry() {
|
||||
public DBTraceObjectValue getLastEntry() {
|
||||
if (entryList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
@@ -128,13 +128,13 @@ public class DBTraceObjectValPath implements TraceObjectValPath {
|
||||
|
||||
@Override
|
||||
public Object getDestinationValue(Object ifEmpty) {
|
||||
InternalTraceObjectValue last = getLastEntry();
|
||||
DBTraceObjectValue last = getLastEntry();
|
||||
return last == null ? ifEmpty : last.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObject getDestination(TraceObject ifEmpty) {
|
||||
InternalTraceObjectValue last = getLastEntry();
|
||||
DBTraceObjectValue last = getLastEntry();
|
||||
return last == null ? ifEmpty : last.getChild();
|
||||
}
|
||||
}
|
||||
|
||||
+400
@@ -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;
|
||||
}
|
||||
}
|
||||
+101
@@ -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;
|
||||
}
|
||||
}
|
||||
+33
-134
@@ -17,17 +17,12 @@ package ghidra.trace.database.target;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import db.DBRecord;
|
||||
import ghidra.program.model.address.Address;
|
||||
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.Trace;
|
||||
import ghidra.trace.model.target.*;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.util.database.DBCachedObjectStore;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.*;
|
||||
import ghidra.util.database.DBObjectColumn;
|
||||
@@ -36,8 +31,8 @@ import ghidra.util.database.spatial.DBTreeDataRecord;
|
||||
|
||||
@DBAnnotatedObjectInfo(version = 1)
|
||||
public class DBTraceObjectValueData
|
||||
extends DBTreeDataRecord<ValueShape, ValueBox, InternalTraceObjectValue>
|
||||
implements InternalTraceObjectValue, ValueShape {
|
||||
extends DBTreeDataRecord<ValueShape, ValueBox, DBTraceObjectValueData>
|
||||
implements TraceObjectValueStorage, ValueShape {
|
||||
static final String TABLE_NAME = "ObjectValue";
|
||||
|
||||
static final String PARENT_COLUMN_NAME = "Parent"; // R*-Tree parent
|
||||
@@ -89,6 +84,8 @@ public class DBTraceObjectValueData
|
||||
protected Address address;
|
||||
protected AddressRange range;
|
||||
|
||||
private DBTraceObjectValue wrapper;
|
||||
|
||||
public DBTraceObjectValueData(DBTraceObjectManager manager, DBTraceObjectValueRStarTree tree,
|
||||
DBCachedObjectStore<?> store, DBRecord record) {
|
||||
super(store, record);
|
||||
@@ -96,8 +93,7 @@ public class DBTraceObjectValueData
|
||||
this.tree = tree;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doSetPrimitive(Object primitive) {
|
||||
void doSetPrimitive(Object primitive) {
|
||||
if (primitive instanceof TraceObject) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
@@ -193,11 +189,6 @@ public class DBTraceObjectValueData
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return manager.trace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject getParent() {
|
||||
return objParent;
|
||||
@@ -208,51 +199,18 @@ public class DBTraceObjectValueData
|
||||
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
|
||||
public Object getValue() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
if (child != null) {
|
||||
return child;
|
||||
}
|
||||
if (address != null) {
|
||||
return address;
|
||||
}
|
||||
if (range != null) {
|
||||
return range;
|
||||
}
|
||||
return child != null ? child : primitive;
|
||||
if (child != null) {
|
||||
return child;
|
||||
}
|
||||
if (address != null) {
|
||||
return address;
|
||||
}
|
||||
if (range != null) {
|
||||
return range;
|
||||
}
|
||||
return child != null ? child : primitive;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -260,64 +218,9 @@ public class DBTraceObjectValueData
|
||||
return (DBTraceObject) getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObject() {
|
||||
return child != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lifespan getLifespan() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
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);
|
||||
}
|
||||
return lifespan;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -335,12 +238,12 @@ public class DBTraceObjectValueData
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setRecordValue(InternalTraceObjectValue value) {
|
||||
protected void setRecordValue(DBTraceObjectValueData value) {
|
||||
// Nothing. Entry is the value
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InternalTraceObjectValue getRecordValue() {
|
||||
protected DBTraceObjectValueData getRecordValue() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -396,38 +299,34 @@ public class DBTraceObjectValueData
|
||||
|
||||
@Override
|
||||
public void doSetLifespan(Lifespan lifespan) {
|
||||
if (minSnap == lifespan.lmin() && maxSnap == lifespan.lmax()) {
|
||||
return;
|
||||
}
|
||||
// NB. Wrapper would not call if lifespan weren't different
|
||||
DBTraceObjectValueRStarTree tree = this.tree;
|
||||
tree.doUnparentEntry(this);
|
||||
objParent.notifyValueDeleted(this);
|
||||
if (child != null) {
|
||||
child.notifyParentValueDeleted(this);
|
||||
}
|
||||
minSnap = lifespan.lmin();
|
||||
maxSnap = lifespan.lmax();
|
||||
update(MIN_SNAP_COLUMN, MAX_SNAP_COLUMN);
|
||||
this.lifespan = lifespan;
|
||||
updateBounds();
|
||||
tree.doInsertDataEntry(this);
|
||||
objParent.notifyValueCreated(this);
|
||||
if (child != null) {
|
||||
child.notifyParentValueCreated(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDelete() {
|
||||
objParent.notifyValueDeleted(this);
|
||||
if (child != null) {
|
||||
child.notifyParentValueDeleted(this);
|
||||
}
|
||||
manager.doDeleteEdge(this);
|
||||
manager.doDeleteValue(this);
|
||||
}
|
||||
|
||||
protected Stream<? extends TraceObjectValPath> doStreamVisitor(Lifespan span,
|
||||
Visitor visitor) {
|
||||
return TreeTraversal.INSTANCE.walkValue(visitor, this, span, null);
|
||||
@Override
|
||||
public DBTraceObjectValue getWrapper() {
|
||||
if (wrapper == null) {
|
||||
wrapper = new DBTraceObjectValue(manager, this);
|
||||
}
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
void setWrapper(DBTraceObjectValue wrapper) {
|
||||
if (this.wrapper != null) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -35,8 +35,8 @@ public class DBTraceObjectValueMapAddressSetView extends AbstractAddressSetView
|
||||
|
||||
private final AddressFactory factory;
|
||||
private final ReadWriteLock lock;
|
||||
private final SpatialMap<ValueShape, InternalTraceObjectValue, TraceObjectValueQuery> map;
|
||||
private final Predicate<? super InternalTraceObjectValue> predicate;
|
||||
private final SpatialMap<ValueShape, DBTraceObjectValueData, TraceObjectValueQuery> map;
|
||||
private final Predicate<? super DBTraceObjectValueData> 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
|
||||
*/
|
||||
public DBTraceObjectValueMapAddressSetView(AddressFactory factory, ReadWriteLock lock,
|
||||
SpatialMap<ValueShape, InternalTraceObjectValue, TraceObjectValueQuery> map,
|
||||
Predicate<? super InternalTraceObjectValue> predicate) {
|
||||
SpatialMap<ValueShape, DBTraceObjectValueData, TraceObjectValueQuery> map,
|
||||
Predicate<? super DBTraceObjectValueData> predicate) {
|
||||
this.factory = factory;
|
||||
this.lock = lock;
|
||||
this.map = map;
|
||||
@@ -63,7 +63,7 @@ public class DBTraceObjectValueMapAddressSetView extends AbstractAddressSetView
|
||||
@Override
|
||||
public boolean contains(Address addr) {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
for (InternalTraceObjectValue value : map
|
||||
for (DBTraceObjectValueData value : map
|
||||
.reduce(TraceObjectValueQuery.intersecting(Lifespan.ALL,
|
||||
new AddressRangeImpl(addr, addr)))
|
||||
.values()) {
|
||||
@@ -95,7 +95,7 @@ public class DBTraceObjectValueMapAddressSetView extends AbstractAddressSetView
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||
for (InternalTraceObjectValue value : map.values()) {
|
||||
for (DBTraceObjectValueData value : map.values()) {
|
||||
if (predicate.test(value)) {
|
||||
return false;
|
||||
}
|
||||
@@ -107,7 +107,7 @@ public class DBTraceObjectValueMapAddressSetView extends AbstractAddressSetView
|
||||
@Override
|
||||
public Address getMinAddress() {
|
||||
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))
|
||||
.orderedEntries()) {
|
||||
if (predicate.test(entry.getValue())) {
|
||||
@@ -121,7 +121,7 @@ public class DBTraceObjectValueMapAddressSetView extends AbstractAddressSetView
|
||||
@Override
|
||||
public Address getMaxAddress() {
|
||||
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))
|
||||
.orderedEntries()) {
|
||||
if (predicate.test(entry.getValue())) {
|
||||
@@ -153,14 +153,14 @@ public class DBTraceObjectValueMapAddressSetView extends AbstractAddressSetView
|
||||
|
||||
protected AddressRangeIterator doGetAddressRanges(RecAddress start, RecAddress end,
|
||||
boolean forward) {
|
||||
Iterator<Entry<ValueShape, InternalTraceObjectValue>> mapIt = map
|
||||
Iterator<Entry<ValueShape, DBTraceObjectValueData>> mapIt = map
|
||||
.reduce(TraceObjectValueQuery
|
||||
.intersecting(EntryKeyDimension.INSTANCE.absoluteMin(),
|
||||
EntryKeyDimension.INSTANCE.absoluteMax(), Lifespan.ALL, start, end)
|
||||
.starting(forward ? AddressDimension.FORWARD : AddressDimension.BACKWARD))
|
||||
.orderedEntries()
|
||||
.iterator();
|
||||
Iterator<Entry<ValueShape, InternalTraceObjectValue>> fltIt =
|
||||
Iterator<Entry<ValueShape, DBTraceObjectValueData>> fltIt =
|
||||
IteratorUtils.filteredIterator(mapIt, e -> predicate.test(e.getValue()));
|
||||
Iterator<AddressRange> rawIt =
|
||||
IteratorUtils.transformedIterator(fltIt, e -> e.getKey().getRange(factory));
|
||||
|
||||
+4
-4
@@ -36,16 +36,16 @@ public class DBTraceObjectValueRStarTree extends AbstractHyperRStarTree< //
|
||||
ValueTriple, //
|
||||
ValueShape, DBTraceObjectValueData, //
|
||||
ValueBox, DBTraceObjectValueNode, //
|
||||
InternalTraceObjectValue, TraceObjectValueQuery> {
|
||||
DBTraceObjectValueData, TraceObjectValueQuery> {
|
||||
|
||||
public static class DBTraceObjectValueMap extends AsSpatialMap<ValueShape, //
|
||||
DBTraceObjectValueData, ValueBox, InternalTraceObjectValue, TraceObjectValueQuery> {
|
||||
DBTraceObjectValueData, ValueBox, DBTraceObjectValueData, TraceObjectValueQuery> {
|
||||
|
||||
private final AddressFactory factory;
|
||||
private final ReadWriteLock lock;
|
||||
|
||||
public DBTraceObjectValueMap(AbstractConstraintsTree<ValueShape, DBTraceObjectValueData, //
|
||||
ValueBox, ?, InternalTraceObjectValue, TraceObjectValueQuery> tree,
|
||||
ValueBox, ?, DBTraceObjectValueData, TraceObjectValueQuery> tree,
|
||||
TraceObjectValueQuery query, AddressFactory factory, ReadWriteLock lock) {
|
||||
super(tree, query);
|
||||
this.factory = factory;
|
||||
@@ -59,7 +59,7 @@ public class DBTraceObjectValueRStarTree extends AbstractHyperRStarTree< //
|
||||
}
|
||||
|
||||
public AddressSetView getAddressSetView(Lifespan at,
|
||||
Predicate<? super InternalTraceObjectValue> predicate) {
|
||||
Predicate<? super DBTraceObjectValueData> predicate) {
|
||||
return new DBTraceObjectValueMapAddressSetView(factory, lock,
|
||||
this.reduce(TraceObjectValueQuery.intersecting(
|
||||
EntryKeyDimension.INSTANCE.absoluteMin(),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user