GP-3839: Various speed improvements for Trace RMI

This commit is contained in:
Dan 2024-02-14 15:53:59 -05:00
parent bc24351495
commit b34aaa4952
67 changed files with 2698 additions and 955 deletions

View file

@ -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.

View file

@ -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

View file

@ -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;
};
}

View file

@ -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";

View file

@ -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() {

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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) {

View file

@ -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

View file

@ -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;

View file

@ -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) {

View file

@ -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) {

View file

@ -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;
}

View file

@ -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());
}
}

View file

@ -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;
}

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -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());
}

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -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();
}
}

View file

@ -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());
}
}

View file

@ -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) {

View file

@ -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());
}
}
}

View file

@ -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());
}
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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()));
}
}

View file

@ -401,7 +401,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerTes
});
}
@Test
// @Test // Not gonna with write-behind cache
public void testUndoRedoCausesUpdateInProvider() throws Exception {
createAndOpenTrace();

View file

@ -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();

View file

@ -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 {

View file

@ -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());
}));
}

View file

@ -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();
}
}
}

View file

@ -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();
}
}

View file

@ -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

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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());
}
}
}

View file

@ -35,7 +35,8 @@ import ghidra.trace.database.memory.DBTraceObjectRegister;
import ghidra.trace.database.module.*;
import ghidra.trace.database.stack.DBTraceObjectStack;
import ghidra.trace.database.stack.DBTraceObjectStackFrame;
import ghidra.trace.database.target.InternalTraceObjectValue.ValueLifespanSetter;
import ghidra.trace.database.target.CachePerDBTraceObject.Cached;
import ghidra.trace.database.target.DBTraceObjectValue.ValueLifespanSetter;
import ghidra.trace.database.target.ValueSpace.EntryKeyDimension;
import ghidra.trace.database.target.ValueSpace.SnapDimension;
import ghidra.trace.database.target.visitors.*;
@ -55,8 +56,7 @@ import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils;
import ghidra.trace.model.thread.TraceObjectThread;
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.annot.*;
@ -65,8 +65,6 @@ import ghidra.util.database.annot.*;
public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
protected static final String TABLE_NAME = "Objects";
private static final int VALUE_CACHE_SIZE = 50;
protected static <T extends TraceObjectInterface> //
Map.Entry<Class<? extends T>, Function<DBTraceObject, ? extends T>> safeEntry(
Class<T> cls, Function<DBTraceObject, ? extends T> ctor) {
@ -120,9 +118,6 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
}
}
record CachedLifespanValues(Lifespan span, Set<InternalTraceObjectValue> values) {
}
// Canonical path
static final String PATH_COLUMN_NAME = "Path";
@ -140,17 +135,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
private TargetObjectSchema targetSchema;
private Map<Class<? extends TraceObjectInterface>, TraceObjectInterface> ifaces;
private final Map<String, InternalTraceObjectValue> valueCache = new LinkedHashMap<>() {
protected boolean removeEldestEntry(Map.Entry<String, InternalTraceObjectValue> eldest) {
return size() > VALUE_CACHE_SIZE;
}
};
private final Map<String, Long> nullCache = new LinkedHashMap<>() {
protected boolean removeEldestEntry(Map.Entry<String, Long> eldest) {
return size() > VALUE_CACHE_SIZE;
}
};
private CachedLifespanValues cachedLifespanValues = null;
private final CachePerDBTraceObject cache = new CachePerDBTraceObject();
private volatile MutableLifeSet cachedLife = null;
public DBTraceObject(DBTraceObjectManager manager, DBCachedObjectStore<?> store,
@ -245,7 +230,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
return DBTraceObjectValPath.of();
}
DBTraceObject parent = doCreateCanonicalParentObject();
InternalTraceObjectValue value = parent.setValue(lifespan, path.key(), this, resolution);
DBTraceObjectValue value = parent.setValue(lifespan, path.key(), this, resolution);
// TODO: Should I re-order the recursion, so values are inserted from root to this?
// TODO: Should child lifespans be allowed to exceed the parent's?
DBTraceObjectValPath path = parent.doInsert(lifespan, resolution);
@ -276,10 +261,10 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
}
protected void doRemoveTree(Lifespan span) {
for (InternalTraceObjectValue parent : getParents(span)) {
for (DBTraceObjectValue parent : getParents(span)) {
parent.doTruncateOrDeleteAndEmitLifeChange(span);
}
for (InternalTraceObjectValue value : getValues(span)) {
for (DBTraceObjectValue value : getValues(span)) {
value.doTruncateOrDeleteAndEmitLifeChange(span);
if (value.isCanonical()) {
value.getChild().doRemoveTree(span);
@ -294,28 +279,39 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
}
}
protected Stream<DBTraceObjectValueData> streamCanonicalParentsData(Lifespan lifespan) {
return manager.valueMap.reduce(TraceObjectValueQuery.canonicalParents(this, lifespan))
.values()
.stream();
}
protected Stream<DBTraceObjectValueBehind> streamCanonicalParentsBehind(Lifespan lifespan) {
return manager.valueWbCache.streamCanonicalParents(this, lifespan);
}
protected Stream<DBTraceObjectValue> streamCanonicalParents(Lifespan lifespan) {
return Stream.concat(
streamCanonicalParentsData(lifespan).map(v -> v.getWrapper()),
streamCanonicalParentsBehind(lifespan).map(v -> v.getWrapper()));
}
@Override
public TraceObjectValue getCanonicalParent(long snap) {
try (LockHold hold = manager.trace.lockRead()) {
if (isRoot()) {
return manager.getRootValue();
}
return manager.valueMap
.reduce(TraceObjectValueQuery.canonicalParents(this, Lifespan.at(snap)))
.firstValue();
return streamCanonicalParents(Lifespan.at(snap)).findAny().orElse(null);
}
}
@Override
public Stream<? extends InternalTraceObjectValue> getCanonicalParents(Lifespan lifespan) {
public Stream<DBTraceObjectValue> getCanonicalParents(Lifespan lifespan) {
try (LockHold hold = manager.trace.lockRead()) {
if (isRoot()) {
return Stream.of(manager.getRootValue());
}
List<InternalTraceObjectValue> list = List.copyOf(
manager.valueMap.reduce(TraceObjectValueQuery.canonicalParents(this, lifespan))
.values());
return list.stream();
return streamCanonicalParents(lifespan).toList().stream();
}
}
@ -350,56 +346,63 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
return ifCls.cast(ifaces.get(ifCls));
}
protected Collection<? extends InternalTraceObjectValue> doGetParents(Lifespan lifespan) {
return List.copyOf(
manager.valueMap.reduce(TraceObjectValueQuery.parents(this, lifespan)).values());
protected Stream<DBTraceObjectValueData> streamParentsData(Lifespan lifespan) {
return manager.valueMap.reduce(TraceObjectValueQuery.parents(this, lifespan))
.values()
.stream();
}
protected Stream<DBTraceObjectValueBehind> streamParentsBehind(Lifespan lifespan) {
return manager.valueWbCache.streamParents(this, lifespan);
}
protected Stream<DBTraceObjectValue> streamParents(Lifespan lifespan) {
return Stream.concat(
streamParentsData(lifespan).map(v -> v.getWrapper()),
streamParentsBehind(lifespan).map(v -> v.getWrapper()));
}
@Override
public Collection<? extends InternalTraceObjectValue> getParents(Lifespan lifespan) {
public Collection<DBTraceObjectValue> getParents(Lifespan lifespan) {
try (LockHold hold = manager.trace.lockRead()) {
return doGetParents(lifespan);
return streamParents(lifespan).toList();
}
}
protected boolean doHasAnyValues() {
return !manager.valueMap.reduce(TraceObjectValueQuery.values(this, Lifespan.ALL))
.isEmpty();
return streamValuesW(Lifespan.ALL).findAny().isPresent();
}
protected Collection<? extends InternalTraceObjectValue> doGetValues(Lifespan lifespan) {
protected Stream<DBTraceObjectValueData> streamValuesData(Lifespan lifespan) {
return manager.valueMap
.reduce(TraceObjectValueQuery.values(this, lifespan)
.starting(EntryKeyDimension.FORWARD))
.values();
.values()
.stream();
}
protected Collection<? extends InternalTraceObjectValue> cachedDoGetValues(Lifespan lifespan) {
if (Long.compareUnsigned(lifespan.lmax() - lifespan.lmin(), 10) > 0) {
return List.copyOf(doGetValues(lifespan));
protected Stream<DBTraceObjectValueBehind> streamValuesBehind(Lifespan lifespan) {
return manager.valueWbCache.streamValues(this, lifespan);
}
protected Stream<DBTraceObjectValue> streamValuesW(Lifespan lifespan) {
return Stream.concat(
streamValuesData(lifespan).map(d -> d.getWrapper()),
streamValuesBehind(lifespan).map(b -> b.getWrapper()));
}
protected Stream<DBTraceObjectValue> streamValuesR(Lifespan lifespan) {
Cached<Stream<DBTraceObjectValue>> cached = cache.streamValues(lifespan);
if (!cached.isMiss()) {
return cached.value();
}
if (cachedLifespanValues == null || !cachedLifespanValues.span.encloses(lifespan)) {
// Expand the query to take advantage of spatial locality (in the time dimension)
long min = lifespan.lmin() - 10;
if (min > lifespan.lmin()) {
min = Lifespan.ALL.lmin();
}
long max = lifespan.lmax() + 10;
if (max < lifespan.lmax()) {
max = Lifespan.ALL.lmax();
}
Lifespan expanded = Lifespan.span(min, max);
cachedLifespanValues =
new CachedLifespanValues(expanded, new HashSet<>(doGetValues(expanded)));
}
return cachedLifespanValues.values.stream()
.filter(v -> v.getLifespan().intersects(lifespan))
.toList();
Lifespan expanded = cache.expandLifespan(lifespan);
Stream<DBTraceObjectValue> stream = streamValuesW(expanded);
return cache.offerStreamAnyKey(expanded, stream, lifespan);
}
protected boolean doHasAnyParents() {
return !manager.valueMap.reduce(TraceObjectValueQuery.parents(this, Lifespan.ALL))
.isEmpty();
return streamParents(Lifespan.ALL).findAny().isPresent();
}
protected boolean doIsConnected() {
@ -407,28 +410,32 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
}
@Override
public Collection<? extends InternalTraceObjectValue> getValues(Lifespan lifespan) {
public Collection<DBTraceObjectValue> getValues(Lifespan lifespan) {
try (LockHold hold = manager.trace.lockRead()) {
return cachedDoGetValues(lifespan);
return streamValuesR(lifespan).toList();
}
}
@Override
public Collection<? extends InternalTraceObjectValue> getElements(Lifespan lifespan) {
return getValues(lifespan).stream()
.filter(v -> PathUtils.isIndex(v.getEntryKey()))
.toList();
public Collection<DBTraceObjectValue> getElements(Lifespan lifespan) {
try (LockHold hold = manager.trace.lockRead()) {
return streamValuesR(lifespan)
.filter(v -> PathUtils.isIndex(v.getEntryKey()))
.toList();
}
}
@Override
public Collection<? extends InternalTraceObjectValue> getAttributes(Lifespan lifespan) {
return getValues(lifespan).stream()
.filter(v -> PathUtils.isName(v.getEntryKey()))
.toList();
public Collection<DBTraceObjectValue> getAttributes(Lifespan lifespan) {
try (LockHold hold = manager.trace.lockRead()) {
return streamValuesR(lifespan)
.filter(v -> PathUtils.isName(v.getEntryKey()))
.toList();
}
}
protected void doCheckConflicts(Lifespan span, String key, Object value) {
for (InternalTraceObjectValue val : doGetValues(span, key, true)) {
for (DBTraceObjectValue val : StreamUtils.iter(streamValuesR(span, key, true))) {
if (!Objects.equals(value, val.getValue())) {
throw new DuplicateKeyException(key);
}
@ -438,7 +445,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
protected Lifespan doAdjust(Lifespan span, String key, Object value) {
// Ordered by min, so I only need to consider the first conflict
// If start is contained in an entry, assume the user means to overwrite it.
for (InternalTraceObjectValue val : doGetValues(span, key, true)) {
for (DBTraceObjectValue val : StreamUtils.iter(streamValuesR(span, key, true))) {
if (Objects.equals(value, val.getValue())) {
continue; // not a conflict
}
@ -451,63 +458,94 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
return span;
}
protected Collection<? extends InternalTraceObjectValue> doGetValues(Lifespan span,
String key, boolean forward) {
protected Stream<DBTraceObjectValueData> streamValuesData(Lifespan span, String key,
boolean forward) {
return manager.valueMap
.reduce(TraceObjectValueQuery.values(this, key, key, span)
.starting(forward ? SnapDimension.FORWARD : SnapDimension.BACKWARD))
.orderedValues();
.orderedValues()
.stream();
}
protected Stream<DBTraceObjectValueBehind> streamValuesBehind(Lifespan span, String key,
boolean forward) {
return manager.valueWbCache.streamValues(this, key, span, forward);
}
protected Stream<DBTraceObjectValue> streamValuesW(Lifespan span, String key, boolean forward) {
return StreamUtils.merge(List.of(
streamValuesData(span, key, forward).map(d -> d.getWrapper()),
streamValuesBehind(span, key, forward).map(b -> b.getWrapper())),
Comparator.comparing(forward ? v -> v.getMinSnap() : v -> -v.getMaxSnap()));
}
protected Stream<DBTraceObjectValue> streamValuesR(Lifespan span, String key, boolean forward) {
Cached<Stream<DBTraceObjectValue>> cached = cache.streamValues(span, key, forward);
if (!cached.isMiss()) {
return cached.value();
}
Lifespan expanded = cache.expandLifespan(span);
Stream<DBTraceObjectValue> stream = streamValuesW(expanded, key, forward);
return cache.offerStreamPerKey(expanded, stream, span, key, forward);
}
@Override
public Collection<? extends InternalTraceObjectValue> getValues(Lifespan span, String key) {
public Collection<? extends DBTraceObjectValue> getValues(Lifespan span, String key) {
try (LockHold hold = manager.trace.lockRead()) {
String k = getTargetSchema().checkAliasedAttribute(key);
return doGetValues(span, k, true);
return streamValuesR(span, k, true).toList();
}
}
protected DBTraceObjectValue getValueW(long snap, String key) {
DBTraceObjectValueBehind behind = manager.valueWbCache.get(this, key, snap);
if (behind != null) {
return behind.getWrapper();
}
DBTraceObjectValueData data = manager.valueMap
.reduce(TraceObjectValueQuery.values(this, key, key, Lifespan.at(snap)))
.firstValue();
if (data != null) {
return data.getWrapper();
}
return null;
}
protected DBTraceObjectValue getValueR(long snap, String key) {
Cached<DBTraceObjectValue> cached = cache.getValue(snap, key);
if (!cached.isMiss()) {
return cached.value();
}
Lifespan expanded = cache.expandLifespan(Lifespan.at(snap));
Stream<DBTraceObjectValue> stream = streamValuesW(expanded, key, true);
return cache.offerGetValue(expanded, stream, snap, key);
}
@Override
public DBTraceObjectValue getValue(long snap, String key) {
try (LockHold hold = manager.trace.lockRead()) {
String k = getTargetSchema().checkAliasedAttribute(key);
return getValueR(snap, k);
}
}
@Override
public InternalTraceObjectValue getValue(long snap, String key) {
try (LockHold hold = manager.trace.lockRead()) {
String k = getTargetSchema().checkAliasedAttribute(key);
InternalTraceObjectValue cached = valueCache.get(k);
if (cached != null && !cached.isDeleted() && cached.getLifespan().contains(snap)) {
return cached;
}
Long nullSnap = nullCache.get(k);
if (nullSnap != null && nullSnap.longValue() == snap) {
return null;
}
InternalTraceObjectValue found = manager.valueMap
.reduce(TraceObjectValueQuery.values(this, k, k, Lifespan.at(snap)))
.firstValue();
if (found == null) {
nullCache.put(k, snap);
}
else {
valueCache.put(k, found);
}
return found;
}
}
@Override
public Stream<? extends InternalTraceObjectValue> getOrderedValues(Lifespan span, String key,
public Stream<DBTraceObjectValue> getOrderedValues(Lifespan span, String key,
boolean forward) {
try (LockHold hold = manager.trace.lockRead()) {
String k = getTargetSchema().checkAliasedAttribute(key);
return doGetValues(span, k, forward).stream();
// Locking issue if we stream lazily. Capture to list with lock
return streamValuesR(span, k, forward).toList().stream();
}
}
@Override
public InternalTraceObjectValue getElement(long snap, String index) {
public DBTraceObjectValue getElement(long snap, String index) {
return getValue(snap, PathUtils.makeKey(index));
}
@Override
public InternalTraceObjectValue getElement(long snap, long index) {
public DBTraceObjectValue getElement(long snap, long index) {
return getElement(snap, PathUtils.makeIndex(index));
}
@ -521,8 +559,9 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
protected Stream<? extends TraceObjectValPath> doStreamVisitor(Lifespan span,
Visitor visitor) {
// Capturing to list with lock
return TreeTraversal.INSTANCE.walkObject(visitor, this, span,
DBTraceObjectValPath.of());
DBTraceObjectValPath.of()).toList().stream();
}
@Override
@ -566,7 +605,8 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
DBTraceObjectValPath empty = DBTraceObjectValPath.of();
try (LockHold hold = manager.trace.lockRead()) {
if (relativePath.isRoot()) {
return Stream.of(empty); // Not the empty stream
// Singleton of empty path (not the empty stream)
return Stream.of(empty);
}
return doStreamVisitor(span,
new OrderedSuccessorsVisitor(relativePath, forward));
@ -587,17 +627,12 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
}
}
protected InternalTraceObjectValue doCreateValue(Lifespan lifespan, String key,
Object value) {
Long nullSnap = nullCache.get(key);
if (nullSnap != null && lifespan.contains(nullSnap)) {
nullCache.remove(key);
}
protected DBTraceObjectValue doCreateValue(Lifespan lifespan, String key, Object value) {
return manager.doCreateValue(lifespan, this, key, value);
}
@Override
public InternalTraceObjectValue setValue(Lifespan lifespan, String key, Object value,
public DBTraceObjectValue setValue(Lifespan lifespan, String key, Object value,
ConflictResolution resolution) {
try (LockHold hold = manager.trace.lockWrite()) {
if (isDeleted()) {
@ -614,14 +649,13 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
DBTraceObject canonicalLifeChanged = null;
@Override
protected Iterable<InternalTraceObjectValue> getIntersecting(Long lower,
protected Iterable<DBTraceObjectValue> getIntersecting(Long lower,
Long upper) {
return Collections.unmodifiableCollection(
doGetValues(Lifespan.span(lower, upper), k, true));
return StreamUtils.iter(streamValuesR(Lifespan.span(lower, upper), k, true));
}
@Override
protected void remove(InternalTraceObjectValue entry) {
protected void remove(DBTraceObjectValue entry) {
if (entry.isCanonical()) {
canonicalLifeChanged = entry.getChild();
}
@ -629,8 +663,8 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
}
@Override
protected InternalTraceObjectValue put(Lifespan range, Object value) {
InternalTraceObjectValue entry = super.put(range, value);
protected DBTraceObjectValue put(Lifespan range, Object value) {
DBTraceObjectValue entry = super.put(range, value);
if (entry != null && entry.isCanonical()) {
canonicalLifeChanged = entry.getChild();
}
@ -638,11 +672,11 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
}
@Override
protected InternalTraceObjectValue create(Lifespan range, Object value) {
protected DBTraceObjectValue create(Lifespan range, Object value) {
return doCreateValue(range, k, value);
}
};
InternalTraceObjectValue result = setter.set(lifespan, value);
DBTraceObjectValue result = setter.set(lifespan, value);
DBTraceObject child = setter.canonicalLifeChanged;
if (child != null) {
@ -767,10 +801,10 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
}
protected void doDeleteReferringValues() {
for (InternalTraceObjectValue child : getValues(Lifespan.ALL)) {
for (DBTraceObjectValue child : getValues(Lifespan.ALL)) {
child.doDeleteAndEmit();
}
for (InternalTraceObjectValue parent : getParents(Lifespan.ALL)) {
for (DBTraceObjectValue parent : getParents(Lifespan.ALL)) {
parent.doDeleteAndEmit();
}
}
@ -800,21 +834,16 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
}
}
protected void notifyValueCreated(InternalTraceObjectValue value) {
if (cachedLifespanValues != null) {
if (cachedLifespanValues.span.intersects(value.getLifespan())) {
cachedLifespanValues.values.add(value);
}
}
protected void notifyValueCreated(DBTraceObjectValue value) {
cache.notifyValueCreated(value);
}
protected void notifyValueDeleted(InternalTraceObjectValue value) {
if (cachedLifespanValues != null) {
cachedLifespanValues.values.remove(value);
}
protected void notifyValueDeleted(DBTraceObjectValue value) {
cache.notifyValueDeleted(value);
}
protected void notifyParentValueCreated(InternalTraceObjectValue parent) {
protected void notifyParentValueCreated(DBTraceObjectValue parent) {
Objects.requireNonNull(parent);
if (cachedLife != null && parent.isCanonical()) {
synchronized (cachedLife) {
cachedLife.add(parent.getLifespan());
@ -822,7 +851,8 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
}
}
protected void notifyParentValueDeleted(InternalTraceObjectValue parent) {
protected void notifyParentValueDeleted(DBTraceObjectValue parent) {
Objects.requireNonNull(parent);
if (cachedLife != null && parent.isCanonical()) {
synchronized (cachedLife) {
cachedLife.remove(parent.getLifespan());

View file

@ -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);
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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));

View file

@ -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(),

View file

@ -0,0 +1,380 @@
/* ###
* 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.concurrent.ExecutionException;
import java.util.function.Predicate;
import java.util.stream.Stream;
import db.Transaction;
import ghidra.async.AsyncReference;
import ghidra.program.model.address.*;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.target.TraceObjectInterface;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.util.*;
class DBTraceObjectValueWriteBehindCache {
public static final int INITIAL_CACHE_SIZE = 1000;
public static final int BATCH_SIZE = 100;
public static final int DELAY_MS = 10000;
private final DBTraceObjectManager manager;
private final Thread worker;
private volatile long mark = 0;
private final AsyncReference<Boolean, Void> busy = new AsyncReference<>(false);
private volatile boolean flushing = false;
private final Map<DBTraceObject, Map<String, NavigableMap<Long, DBTraceObjectValueBehind>>> cachedValues =
new HashMap<>();
public DBTraceObjectValueWriteBehindCache(DBTraceObjectManager manager) {
this.manager = manager;
worker = new Thread(this::workLoop, "WriteBehind for " + manager.trace.getName());
worker.start();
}
private void workLoop() {
while (!manager.trace.isClosed()) {
try {
synchronized (cachedValues) {
if (cachedValues.isEmpty()) {
busy.set(false, null);
flushing = false;
cachedValues.wait();
}
while (!flushing) {
long left = mark - System.currentTimeMillis();
if (left <= 0) {
break;
}
Msg.trace(this,
"Waiting %d ms. Cache is %d big".formatted(left, cachedValues.size()));
cachedValues.wait(left);
}
}
if (manager.trace.isClosed()) {
break;
}
writeBatch();
if (!flushing && !manager.trace.isClosing()) {
Thread.sleep(100);
}
}
catch (InterruptedException e) {
}
}
busy.set(false, null);
flushing = false;
}
private List<DBTraceObjectValueBehind> getBatch() {
synchronized (cachedValues) {
return doStreamAllValues()
.limit(BATCH_SIZE)
.toList();
}
}
private Stream<DBTraceObjectValueBehind> doStreamAllValues() {
return cachedValues.values()
.stream()
.flatMap(v -> v.values().stream())
.flatMap(v -> v.values().stream());
}
private void doAdd(DBTraceObjectValueBehind behind) {
var keys = cachedValues.computeIfAbsent(behind.getParent(), k -> new HashMap<>());
var values = keys.computeIfAbsent(behind.getEntryKey(), k -> new TreeMap<>());
values.put(behind.getLifespan().min(), behind);
}
NavigableMap<Long, DBTraceObjectValueBehind> doRemoveNoCleanup(
DBTraceObjectValueBehind behind) {
var keys = cachedValues.get(behind.getParent());
var values = keys.get(behind.getEntryKey());
values.remove(behind.getLifespan().min());
return values;
}
void doAddDirect(NavigableMap<Long, DBTraceObjectValueBehind> values,
DBTraceObjectValueBehind b) {
values.put(b.getLifespan().min(), b);
}
private void doRemove(DBTraceObjectValueBehind behind) {
var keys = cachedValues.get(behind.getParent());
var values = keys.get(behind.getEntryKey());
values.remove(behind.getLifespan().min());
if (values.isEmpty()) {
keys.remove(behind.getEntryKey());
if (keys.isEmpty()) {
cachedValues.remove(behind.getParent());
}
}
}
private void writeBatch() {
try (Transaction tx = manager.trace.openTransaction("Write Behind")) {
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
for (DBTraceObjectValueBehind behind : getBatch()) {
synchronized (cachedValues) {
doRemove(behind);
}
DBTraceObjectValueData value = manager.doCreateValueData(behind.getLifespan(),
behind.getParent(), behind.getEntryKey(), behind.getValue());
behind.getWrapper().setWrapped(value);
}
}
}
manager.trace.clearUndo();
Msg.trace(this, "Wrote a batch. %d parents remain.".formatted(cachedValues.size()));
}
public DBTraceObjectValueBehind doCreateValue(Lifespan lifespan, DBTraceObject parent,
String key, Object value) {
if (manager.trace.isClosing()) {
throw new IllegalStateException("Trace is closing");
}
DBTraceObjectValueBehind entry =
new DBTraceObjectValueBehind(manager, parent, key, lifespan,
manager.validateValue(value));
synchronized (cachedValues) {
doAdd(entry);
mark = System.currentTimeMillis() + DELAY_MS;
busy.set(true, null);
cachedValues.notify();
}
return entry;
}
public void remove(DBTraceObjectValueBehind value) {
synchronized (cachedValues) {
doRemove(value);
}
}
public void clear() {
synchronized (cachedValues) {
cachedValues.clear();
}
}
public Stream<DBTraceObjectValueBehind> streamAllValues() {
return doStreamAllValues();
}
public DBTraceObjectValueBehind get(DBTraceObject parent, String key, long snap) {
var keys = cachedValues.get(parent);
if (keys == null) {
return null;
}
var values = keys.get(key);
if (values == null) {
return null;
}
var floor = values.floorEntry(snap);
if (floor == null) {
return null;
}
if (!floor.getValue().getLifespan().contains(snap)) {
return null;
}
return floor.getValue();
}
public Stream<DBTraceObjectValueBehind> streamParents(DBTraceObject child, Lifespan lifespan) {
// TODO: Optimize/index this?
return streamAllValues()
.filter(v -> v.getValue() == child && v.getLifespan().intersects(lifespan));
}
private Stream<DBTraceObjectValueBehind> streamSub(
NavigableMap<Long, DBTraceObjectValueBehind> map, Lifespan span, boolean forward) {
Long floor = map.floorKey(span.min());
if (floor == null) {
floor = span.min();
}
var sub = map.subMap(floor, true, span.max(), true);
if (!forward) {
sub = sub.descendingMap();
}
return sub.values().stream();
}
public Stream<DBTraceObjectValueBehind> streamCanonicalParents(DBTraceObject child,
Lifespan lifespan) {
TraceObjectKeyPath path = child.getCanonicalPath();
if (path.isRoot()) {
return Stream.of();
}
String entryKey = path.key();
// TODO: Better indexing?
return cachedValues.values()
.stream()
.flatMap(v -> v.entrySet()
.stream()
.filter(e -> entryKey.equals(e.getKey()))
.map(e -> e.getValue()))
.flatMap(v -> streamSub(v, lifespan, true));
}
public Stream<DBTraceObjectValueBehind> streamValues(DBTraceObject parent, Lifespan lifespan) {
// TODO: Better indexing?
var keys = cachedValues.get(parent);
if (keys == null) {
return Stream.of();
}
return keys.values().stream().flatMap(v -> streamSub(v, lifespan, true));
}
public Stream<DBTraceObjectValueBehind> streamValues(DBTraceObject parent, String key,
Lifespan lifespan, boolean forward) {
var keys = cachedValues.get(parent);
if (keys == null) {
return Stream.of();
}
var values = keys.get(key);
if (values == null) {
return Stream.of();
}
return streamSub(values, lifespan, forward);
}
static boolean intersectsRange(Object value, AddressRange range) {
return (value instanceof Address av && range.contains(av)) ||
(value instanceof AddressRange rv && range.intersects(rv));
}
private Stream<DBTraceObjectValueBehind> streamValuesIntersectingLifespan(Lifespan lifespan,
String entryKey) {
// TODO: In-memory spatial index?
var top = cachedValues.values().stream();
var keys = entryKey == null
? top.flatMap(v -> v.values().stream())
: top.flatMap(v -> v.entrySet()
.stream()
.filter(e -> entryKey.equals(e.getKey()))
.map(e -> e.getValue()));
return keys.flatMap(v -> streamSub(v, lifespan, true));
}
public Stream<DBTraceObjectValueBehind> streamValuesIntersecting(Lifespan lifespan,
AddressRange range, String entryKey) {
return streamValuesIntersectingLifespan(lifespan, entryKey)
.filter(v -> intersectsRange(v.getValue(), range));
}
static boolean atAddress(Object value, Address address) {
return (value instanceof Address av && address.equals(av)) ||
(value instanceof AddressRange rv && rv.contains(address));
}
public Stream<DBTraceObjectValueBehind> streamValuesAt(long snap, Address address,
String entryKey) {
return streamValuesIntersectingLifespan(Lifespan.at(snap), entryKey)
.filter(v -> atAddress(v.getValue(), address));
}
static AddressRange getIfRangeOrAddress(Object v) {
if (v instanceof AddressRange rv) {
return rv;
}
if (v instanceof Address av) {
return new AddressRangeImpl(av, av);
}
return null;
}
public <I extends TraceObjectInterface> AddressSetView getObjectsAddresSet(long snap,
String key, Class<I> ifaceCls, Predicate<? super I> predicate) {
return new AbstractAddressSetView() {
AddressSet collectRanges() {
AddressSet result = new AddressSet();
for (DBTraceObjectValueBehind v : StreamUtils
.iter(streamValuesIntersectingLifespan(Lifespan.at(snap), key))) {
AddressRange range = getIfRangeOrAddress(v.getValue());
if (range == null) {
continue;
}
if (!DBTraceObjectManager.acceptValue(v.getWrapper(), key, ifaceCls,
predicate)) {
continue;
}
result.add(range);
}
return result;
}
@Override
public boolean contains(Address addr) {
for (DBTraceObjectValueBehind v : StreamUtils
.iter(streamValuesIntersectingLifespan(Lifespan.at(snap), key))) {
if (!addr.equals(v.getValue())) {
continue;
}
if (!DBTraceObjectManager.acceptValue(v.getWrapper(), key, ifaceCls,
predicate)) {
continue;
}
return true;
}
return false;
}
@Override
public AddressRangeIterator getAddressRanges() {
return collectRanges().getAddressRanges();
}
@Override
public AddressRangeIterator getAddressRanges(boolean forward) {
return collectRanges().getAddressRanges(forward);
}
@Override
public AddressRangeIterator getAddressRanges(Address start, boolean forward) {
// TODO: Could cull during collection
return collectRanges().getAddressRanges(start, forward);
}
};
}
public void flush() {
flushing = true;
worker.interrupt();
try {
busy.waitValue(false).get();
}
catch (InterruptedException | ExecutionException e) {
throw new AssertionError(e);
}
}
public void waitWorkers() {
worker.interrupt();
try {
worker.join(10000);
}
catch (InterruptedException e) {
throw new AssertionError(e);
}
}
}

View file

@ -1,198 +0,0 @@
/* ###
* 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 org.apache.commons.collections4.IterableUtils;
import ghidra.trace.database.DBTraceUtils.LifespanMapSetter;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.target.TraceObject.ConflictResolution;
import ghidra.trace.model.target.TraceObjectValue;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.trace.util.TraceEvents;
import ghidra.util.LockHold;
interface InternalTraceObjectValue extends TraceObjectValue {
abstract class ValueLifespanSetter
extends LifespanMapSetter<InternalTraceObjectValue, Object> {
protected final Lifespan range;
protected final Object value;
protected InternalTraceObjectValue keep = null;
protected Collection<InternalTraceObjectValue> kept = new ArrayList<>(2);
public ValueLifespanSetter(Lifespan range, Object value) {
this.range = range;
this.value = value;
}
public ValueLifespanSetter(Lifespan range, Object value,
InternalTraceObjectValue keep) {
this(range, value);
this.keep = keep;
}
@Override
protected Lifespan getRange(InternalTraceObjectValue entry) {
return entry.getLifespan();
}
@Override
protected Object getValue(InternalTraceObjectValue entry) {
return entry.getValue();
}
@Override
protected void remove(InternalTraceObjectValue entry) {
if (Objects.equals(entry.getValue(), value)) {
if (keep == null) {
keep = entry;
}
else {
entry.doDeleteAndEmit();
}
}
else {
InternalTraceObjectValue created = entry.doTruncateOrDelete(range);
if (!entry.isDeleted()) {
kept.add(entry);
}
if (created != null) {
kept.add(created);
}
}
}
@Override
protected InternalTraceObjectValue put(Lifespan range, Object value) {
if (value == null) {
return null;
}
if (keep != null && Objects.equals(this.value, value)) {
keep.doSetLifespanAndEmit(range);
return keep;
}
for (InternalTraceObjectValue k : kept) {
if (Objects.equals(value, k.getValue()) && Objects.equals(range, k.getLifespan())) {
kept.remove(k);
return k;
}
}
return create(range, value);
}
protected abstract InternalTraceObjectValue create(Lifespan range, Object value);
}
void doSetPrimitive(Object primitive);
DBTraceObjectManager getManager();
/**
* Get the database key
*
* @return the key
*/
long getKey();
@Override
DBTraceObject getChild();
DBTraceObject getChildOrNull();
void doSetLifespan(Lifespan lifespan);
default void doSetLifespanAndEmit(Lifespan lifespan) {
Lifespan oldLifespan = getLifespan();
doSetLifespan(lifespan);
getParent().emitEvents(new TraceChangeRecord<>(TraceEvents.VALUE_LIFESPAN_CHANGED, null,
this, oldLifespan, lifespan));
}
@Override
default void setLifespan(Lifespan lifespan) {
setLifespan(lifespan, ConflictResolution.TRUNCATE);
}
@Override
default 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<InternalTraceObjectValue> getIntersecting(Long lower,
Long upper) {
Collection<InternalTraceObjectValue> col = Collections.unmodifiableCollection(
getParent().doGetValues(Lifespan.span(lower, upper), getEntryKey(), true));
return IterableUtils.filteredIterable(col, v -> v != keep);
}
@Override
protected InternalTraceObjectValue 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 doDelete();
default void doDeleteAndEmit() {
DBTraceObject parent = getParent();
doDelete();
parent.emitEvents(new TraceChangeRecord<>(TraceEvents.VALUE_DELETED, null, this));
}
@Override
DBTraceObject getParent();
default InternalTraceObjectValue doTruncateOrDeleteAndEmitLifeChange(Lifespan span) {
if (!isCanonical()) {
return doTruncateOrDelete(span);
}
DBTraceObject child = getChildOrNull();
InternalTraceObjectValue result = doTruncateOrDelete(span);
child.emitEvents(new TraceChangeRecord<>(TraceEvents.OBJECT_LIFE_CHANGED, null, child));
return result;
}
default InternalTraceObjectValue 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;
}
}

View file

@ -0,0 +1,48 @@
/* ###
* 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 ghidra.trace.model.Lifespan;
interface TraceObjectValueStorage {
DBTraceObjectManager getManager();
DBTraceObjectValue getWrapper();
DBTraceObject getParent();
String getEntryKey();
/**
* Just set the lifespan, no notifications
*
* <p>
* The wrapper will notify the parent and child, if necessary.
*
* @param lifespan the new lifespan
*/
void doSetLifespan(Lifespan lifespan);
Lifespan getLifespan();
DBTraceObject getChildOrNull();
Object getValue();
boolean isDeleted();
void doDelete();
}

View file

@ -77,10 +77,9 @@ public class DBTraceThreadManager implements TraceThreadManager, DBTraceManager
if (objectManager.hasSchema()) {
return objectManager.assertMyThread(thread);
}
if (!(thread instanceof DBTraceThread)) {
if (!(thread instanceof DBTraceThread dbThread)) {
throw new IllegalArgumentException("Thread " + thread + " is not part of this trace");
}
DBTraceThread dbThread = (DBTraceThread) thread;
if (dbThread.manager != this) {
throw new IllegalArgumentException("Thread " + thread + " is not part of this trace");
}

View file

@ -123,16 +123,23 @@ public interface TraceObjectManager {
/**
* Get all the objects in the database
*
* @return the collection of all objects
* @return the stream of all objects
*/
Collection<? extends TraceObject> getAllObjects();
Stream<? extends TraceObject> getAllObjects();
/**
* Get the number of objects in the database
*
* @return the number of objects
*/
int getObjectCount();
/**
* Get all the values (edges) in the database
*
* @return the collect of all values
* @return the stream of all values
*/
Collection<? extends TraceObjectValue> getAllValues();
Stream<? extends TraceObjectValue> getAllValues();
/**
* Get all address-ranged values intersecting the given span and address range

View file

@ -729,6 +729,7 @@ public class ToyDBTraceBuilder implements AutoCloseable {
public File save() throws IOException, CancelledException {
Path tmp = Files.createTempFile("test", ".db");
Files.delete(tmp); // saveAs must create the file
trace.objectManager.flushWbCaches();
trace.getDBHandle().saveAs(tmp.toFile(), false, new ConsoleTaskMonitor());
return tmp.toFile();
}

View file

@ -327,42 +327,42 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT
@Test
public void testClear() {
populateModel(3);
assertEquals(5, manager.getAllObjects().size());
assertEquals(5, manager.getObjectCount());
try (Transaction tx = b.startTransaction()) {
manager.clear();
}
assertEquals(0, manager.getAllObjects().size());
assertEquals(0, manager.getObjectCount());
populateModel(3);
assertEquals(5, manager.getAllObjects().size());
assertEquals(5, manager.getObjectCount());
}
@Test
// @Test // Write-behind cache does not implement undo or redo
public void testUndoRedo() throws Exception {
populateModel(3);
assertEquals(5, manager.getAllObjects().size());
assertEquals(5, manager.getObjectCount());
b.trace.undo();
assertEquals(0, manager.getAllObjects().size());
assertEquals(0, manager.getObjectCount());
b.trace.redo();
assertEquals(5, manager.getAllObjects().size());
assertEquals(5, manager.getObjectCount());
}
@Test
// @Test // Write-behind cache does not implement abort
public void testAbort() throws Exception {
try (Transaction tx = b.startTransaction()) {
populateModel(3);
assertEquals(5, manager.getAllObjects().size());
assertEquals(5, manager.getObjectCount());
tx.abort();
}
assertEquals(0, manager.getAllObjects().size());
assertEquals(0, manager.getObjectCount());
populateModel(3);
assertEquals(5, manager.getAllObjects().size());
assertEquals(5, manager.getObjectCount());
}
@Test

View file

@ -15,13 +15,14 @@
*/
package ghidra.util;
import java.util.Collection;
import java.util.Comparator;
import java.util.*;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public enum StreamUtils {
;
public class StreamUtils {
private StreamUtils() {
}
@SuppressWarnings("unchecked")
public static <T> Stream<T> merge(Collection<? extends Stream<? extends T>> streams,
Comparator<? super T> comparator) {
@ -31,4 +32,9 @@ public enum StreamUtils {
return StreamSupport.stream(new MergeSortingSpliterator<>(
streams.stream().map(s -> s.spliterator()).toList(), comparator), false);
}
@SuppressWarnings("unchecked")
public static <T> Iterable<T> iter(Stream<? extends T> stream) {
return () -> (Iterator<T>) stream.iterator();
}
}

View file

@ -0,0 +1,123 @@
/* ###
* 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.util.database;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.locks.ReadWriteLock;
import ghidra.util.LockHold;
public class DBSynchronizedCollection<E> implements Collection<E> {
private final Collection<E> delegate;
private final ReadWriteLock lock;
public DBSynchronizedCollection(Collection<E> delegate, ReadWriteLock lock) {
this.delegate = delegate;
this.lock = lock;
}
@Override
public int size() {
try (LockHold hold = LockHold.lock(lock.readLock())) {
return delegate.size();
}
}
@Override
public boolean isEmpty() {
try (LockHold hold = LockHold.lock(lock.readLock())) {
return delegate.isEmpty();
}
}
@Override
public boolean contains(Object o) {
try (LockHold hold = LockHold.lock(lock.readLock())) {
return delegate.contains(o);
}
}
@Override
public Iterator<E> iterator() {
try (LockHold hold = LockHold.lock(lock.readLock())) {
return new DBSynchronizedIterator<>(delegate.iterator(), lock);
}
}
@Override
public Object[] toArray() {
try (LockHold hold = LockHold.lock(lock.readLock())) {
return delegate.toArray();
}
}
@Override
public <T> T[] toArray(T[] a) {
try (LockHold hold = LockHold.lock(lock.readLock())) {
return delegate.toArray(a);
}
}
@Override
public boolean add(E e) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
return delegate.add(e);
}
}
@Override
public boolean remove(Object o) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
return delegate.remove(o);
}
}
@Override
public boolean containsAll(Collection<?> c) {
try (LockHold hold = LockHold.lock(lock.readLock())) {
return delegate.containsAll(c);
}
}
@Override
public boolean addAll(Collection<? extends E> c) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
return delegate.addAll(c);
}
}
@Override
public boolean removeAll(Collection<?> c) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
return delegate.removeAll(c);
}
}
@Override
public boolean retainAll(Collection<?> c) {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
return delegate.retainAll(c);
}
}
@Override
public void clear() {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
delegate.clear();
}
}
}

View file

@ -0,0 +1,52 @@
/* ###
* 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.util.database;
import java.util.Iterator;
import java.util.concurrent.locks.ReadWriteLock;
import ghidra.util.LockHold;
public class DBSynchronizedIterator<T> implements Iterator<T> {
private final Iterator<T> iterator;
private final ReadWriteLock lock;
public DBSynchronizedIterator(Iterator<T> iterator, ReadWriteLock lock) {
this.iterator = iterator;
this.lock = lock;
}
@Override
public boolean hasNext() {
try (LockHold hold = LockHold.lock(lock.readLock())) {
return iterator.hasNext();
}
}
@Override
public T next() {
try (LockHold hold = LockHold.lock(lock.readLock())) {
return iterator.next();
}
}
@Override
public void remove() {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
iterator.remove();
}
}
}

View file

@ -18,12 +18,12 @@ package ghidra.util.database.spatial;
import java.lang.reflect.Array;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.function.Consumer;
import org.apache.commons.collections4.IteratorUtils;
import ghidra.util.LockHold;
import ghidra.util.database.DBSynchronizedIterator;
import ghidra.util.database.spatial.DBTreeDataRecord.RecordEntry;
public abstract class AbstractConstraintsTreeSpatialMap< //
@ -98,37 +98,6 @@ public abstract class AbstractConstraintsTreeSpatialMap< //
return (int) l;
}
protected static class SynchronizedIterator<T> implements Iterator<T> {
private final Iterator<T> iterator;
private final ReadWriteLock lock;
public SynchronizedIterator(Iterator<T> iterator, ReadWriteLock lock) {
this.iterator = iterator;
this.lock = lock;
}
@Override
public boolean hasNext() {
try (LockHold hold = LockHold.lock(lock.readLock())) {
return iterator.hasNext();
}
}
@Override
public T next() {
try (LockHold hold = LockHold.lock(lock.readLock())) {
return iterator.next();
}
}
@Override
public void remove() {
try (LockHold hold = LockHold.lock(lock.writeLock())) {
iterator.remove();
}
}
}
protected abstract static class ToArrayConsumer<A, T, U extends A> implements Consumer<T> {
protected final A[] arr;
protected int i = 0;
@ -172,7 +141,7 @@ public abstract class AbstractConstraintsTreeSpatialMap< //
@Override
public Iterator<Entry<DS, T>> iterator() {
try (LockHold hold = LockHold.lock(tree.dataStore.readLock())) {
return new SynchronizedIterator<>(
return new DBSynchronizedIterator<>(
IteratorUtils.transformedIterator(tree.iterator(query), r -> r.asEntry()),
tree.dataStore.getLock());
}
@ -220,6 +189,12 @@ public abstract class AbstractConstraintsTreeSpatialMap< //
public boolean isEmpty() {
return AbstractConstraintsTreeSpatialMap.this.isEmpty();
}
@Override
public Spliterator<Entry<DS, T>> spliterator() {
// Size estimate is more expensive than benefits of knowing size
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
};
}
@ -229,7 +204,7 @@ public abstract class AbstractConstraintsTreeSpatialMap< //
@Override
public Iterator<Entry<DS, T>> iterator() {
try (LockHold hold = LockHold.lock(tree.dataStore.readLock())) {
return new SynchronizedIterator<>(
return new DBSynchronizedIterator<>(
IteratorUtils.transformedIterator(tree.orderedIterator(query),
r -> r.asEntry()),
tree.dataStore.getLock());
@ -245,6 +220,12 @@ public abstract class AbstractConstraintsTreeSpatialMap< //
public boolean isEmpty() {
return AbstractConstraintsTreeSpatialMap.this.isEmpty();
}
@Override
public Spliterator<Entry<DS, T>> spliterator() {
// Size estimate is more expensive than benefits of knowing size
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
};
}
@ -254,7 +235,7 @@ public abstract class AbstractConstraintsTreeSpatialMap< //
@Override
public Iterator<DS> iterator() {
try (LockHold hold = LockHold.lock(tree.dataStore.readLock())) {
return new SynchronizedIterator<>(
return new DBSynchronizedIterator<>(
IteratorUtils.transformedIterator(tree.iterator(query), r -> r.getShape()),
tree.dataStore.getLock());
}
@ -301,6 +282,12 @@ public abstract class AbstractConstraintsTreeSpatialMap< //
public boolean isEmpty() {
return AbstractConstraintsTreeSpatialMap.this.isEmpty();
}
@Override
public Spliterator<DS> spliterator() {
// Size estimate is more expensive than benefits of knowing size
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
};
}
@ -310,7 +297,7 @@ public abstract class AbstractConstraintsTreeSpatialMap< //
@Override
public Iterator<DS> iterator() {
try (LockHold hold = LockHold.lock(tree.dataStore.readLock())) {
return new SynchronizedIterator<>(
return new DBSynchronizedIterator<>(
IteratorUtils.transformedIterator(tree.orderedIterator(query),
r -> r.getShape()),
tree.dataStore.getLock());
@ -326,6 +313,12 @@ public abstract class AbstractConstraintsTreeSpatialMap< //
public boolean isEmpty() {
return AbstractConstraintsTreeSpatialMap.this.isEmpty();
}
@Override
public Spliterator<DS> spliterator() {
// Size estimate is more expensive than benefits of knowing size
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
};
}
@ -335,7 +328,7 @@ public abstract class AbstractConstraintsTreeSpatialMap< //
@Override
public Iterator<T> iterator() {
try (LockHold hold = LockHold.lock(tree.dataStore.readLock())) {
return new SynchronizedIterator<>(
return new DBSynchronizedIterator<>(
IteratorUtils.transformedIterator(tree.iterator(query),
r -> r.getRecordValue()),
tree.dataStore.getLock());
@ -383,6 +376,12 @@ public abstract class AbstractConstraintsTreeSpatialMap< //
public boolean isEmpty() {
return AbstractConstraintsTreeSpatialMap.this.isEmpty();
}
@Override
public Spliterator<T> spliterator() {
// Size estimate is more expensive than benefits of knowing size
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
};
}
@ -392,7 +391,7 @@ public abstract class AbstractConstraintsTreeSpatialMap< //
@Override
public Iterator<T> iterator() {
try (LockHold hold = LockHold.lock(tree.dataStore.readLock())) {
return new SynchronizedIterator<>(
return new DBSynchronizedIterator<>(
IteratorUtils.transformedIterator(tree.orderedIterator(query),
r -> r.getRecordValue()),
tree.dataStore.getLock());
@ -408,6 +407,12 @@ public abstract class AbstractConstraintsTreeSpatialMap< //
public boolean isEmpty() {
return AbstractConstraintsTreeSpatialMap.this.isEmpty();
}
@Override
public Spliterator<T> spliterator() {
// Size estimate is more expensive than benefits of knowing size
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
};
}

View file

@ -629,9 +629,9 @@ public class RStarTreeMapTest {
private static final int MAX_CHILDREN = 5;
private final DBCachedObjectStoreFactory storeFactory;
private final IntRStarTree tree;
private final SpatialMap<IntRect, String, IntRectQuery> map;
public final SpatialMap<IntRect, String, IntRectQuery> map;
protected MyDomainObject(Object consumer) throws IOException, VersionException {
public MyDomainObject(Object consumer) throws IOException, VersionException {
super(new DBHandle(), DBOpenMode.CREATE, new ConsoleTaskMonitor(), "Testing", 500, 1000,
consumer);
storeFactory = new DBCachedObjectStoreFactory(this);

View file

@ -52,6 +52,17 @@ public abstract class RangeMapSetter<E, D, R, V> {
*/
protected abstract V getValue(E entry);
/**
* Check if two values are equal
*
* @param v1 the first value
* @param v2 the second value
* @return true if equal, false if not
*/
protected boolean valuesEqual(V v1, V v2) {
return Objects.equals(v1, v2);
}
/**
* Remove an entry from the map
*
@ -190,7 +201,7 @@ public abstract class RangeMapSetter<E, D, R, V> {
R r = getRange(entry);
int cmpMin = compare(getLower(r), lower);
int cmpMax = compare(getUpper(r), upper);
boolean sameVal = Objects.equals(getValue(entry), value);
boolean sameVal = valuesEqual(getValue(entry), value);
if (cmpMin <= 0 && cmpMax >= 0 && sameVal) {
return entry; // The value in this range is already set as specified
}

View file

@ -32,7 +32,7 @@ import generic.ULongSpan.*;
* impose behaviors and properties that aren't otherwise present on the type of endpoints. For
* example, the domain may be {@link Long}s, but using unsigned attributes. The domain also provides
* a factory for new spans. While nominally, this only supports closed intervals, the domain can
* define a custom endpoint type to obtain mixed intervals, as in {@link End}.
* define a custom endpoint type to obtain mixed intervals.
*
* @param <N> the type of endpoints
* @param <S> the type of spans (recursive)
@ -387,7 +387,7 @@ public interface Span<N, S extends Span<N, S>> extends Comparable<S> {
* existing entries are truncated or deleted (or coalesced if they share the same value as the
* new entry) so that the new entry can fit.
*
* @implNote It is recommended to create an interface (having only the {@link V} parameter)
* @implNote It is recommended to create an interface (having only the {@code <V>} parameter)
* extending this one specific to your domain and span type, then implement it using
* an extension of {@link DefaultSpanMap}. See {@link ULongSpanMap} for an example.
* @param <N> the type of endpoints
@ -835,7 +835,7 @@ public interface Span<N, S extends Span<N, S>> extends Comparable<S> {
if (this == obj) {
return true;
}
if (!(obj instanceof @SuppressWarnings("rawtypes") DefaultSpanMap that)) {
if (!(obj instanceof DefaultSpanMap that)) {
return false;
}
if (this.domain != that.domain) {
@ -995,7 +995,7 @@ public interface Span<N, S extends Span<N, S>> extends Comparable<S> {
if (this == obj) {
return true;
}
if (!(obj instanceof @SuppressWarnings("rawtypes") DefaultSpanSet that)) {
if (!(obj instanceof DefaultSpanSet that)) {
return false;
}
if (!Objects.equals(this.map, that.map)) {
@ -1081,6 +1081,11 @@ public interface Span<N, S extends Span<N, S>> extends Comparable<S> {
}
}
/**
* Provides a default {@link #toString} implementation
*
* @return the string
*/
@SuppressWarnings("unchecked")
default String doToString() {
return domain().toString((S) this);

View file

@ -63,6 +63,7 @@ dependencies {
testImplementation project(path: ':PDB', configuration: 'testArtifacts')
testImplementation project(path: ':GnuDemangler', configuration: 'testArtifacts')
testImplementation project(path: ':ProposedUtils', configuration: 'testArtifacts')
testImplementation project(path: ':Framework-TraceModeling', configuration: 'testArtifacts')
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')

View file

@ -1051,7 +1051,7 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest<T, MR>
waitForPass(() -> assertLogicalBreakpointForMappedBookmarkAnd1TraceBreakpoint(trace1));
}
@Test
@Test // Mappings are not write-behind cached
public void testAbortAddMapping() throws Throwable {
createTarget1();
Trace trace = getTrace(target1);
@ -1092,7 +1092,7 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest<T, MR>
});
}
@Test
// @Test // Not gonna with write-behind cache
public void testAbortAddBreakpointAndMapping() throws Throwable {
createTarget1();
Trace trace = getTrace(target1);
@ -1124,7 +1124,7 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest<T, MR>
assertTrue(one.getTraceBreakpoints().isEmpty());
}
@Test
@Test // Abort is on program, not trace
public void testAbortAddBookmarks() throws Throwable {
createTarget1();
Trace trace = getTrace(target1);
@ -1152,7 +1152,7 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest<T, MR>
assertTrue(breakpointService.getAllBreakpoints().isEmpty());
}
@Test
// @Test // Not gonna with write-behind cache
public void testUndoRedoAddBreakpointAndMapping() throws Throwable {
createTarget1();
Trace trace = getTrace(target1);
@ -1563,8 +1563,11 @@ public abstract class AbstractDebuggerLogicalBreakpointServiceTest<T, MR>
/**
* With the addition of emulated breakpoints (which are stored in the trace), this test is now
* sane.
*
* <p>
* With the addition of the write-behind cache, this test is no longer sane.
*/
@Test
// @Test
public void testAbortAddBreakpointSetSleigh() throws Throwable {
DebuggerControlService controlService =
addPlugin(tool, DebuggerControlServicePlugin.class);

View file

@ -0,0 +1,628 @@
/* ###
* 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.app.plugin.core.debug.service.tracermi;
import static org.junit.Assert.assertEquals;
import java.io.*;
import java.net.*;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.junit.Ignore;
import org.junit.Test;
import db.*;
import generic.Unique;
import generic.test.rule.Repeated;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerIntegrationTest;
import ghidra.app.services.TraceRmiService;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.debug.api.tracermi.TraceRmiAcceptor;
import ghidra.debug.api.tracermi.TraceRmiConnection;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.rmi.trace.TraceRmi.*;
import ghidra.rmi.trace.TraceRmi.Compiler;
import ghidra.rmi.trace.TraceRmi.RootMessage.MsgCase;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.target.DBTraceObject;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject.ConflictResolution;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.util.LockHold;
import ghidra.util.Msg;
import ghidra.util.database.*;
import ghidra.util.database.annot.*;
import ghidra.util.database.spatial.RStarTreeMapTest.IntRect;
import ghidra.util.database.spatial.RStarTreeMapTest.MyDomainObject;
import ghidra.util.task.TaskMonitor;
@Ignore // Only want for manual testing
public class TraceRmiPerformanceTest extends AbstractGhidraHeadedDebuggerIntegrationTest {
public static final int REGION_COUNT = 1000;
public static final int RECORD_COUNT = 1000 * 6;
interface Tx extends AutoCloseable {
}
interface TraceHandle extends AutoCloseable {
Trace getTrace() throws Throwable;
Tx startTx() throws Throwable;
default void addLotsOfRegions() throws Throwable {
try (Tx tx = startTx()) {
AddressFactory af = getTrace().getBaseAddressFactory();
AddressSpace space = af.getDefaultAddressSpace();
for (int i = 0; i < REGION_COUNT; i++) {
AddressRange range = new AddressRangeImpl(space.getAddress(i * 1024), 1024);
addRegion(Integer.toString(i), range, "rw");
}
}
}
void addRegion(String key, AddressRange range, String perms) throws Throwable;
}
interface TraceMaker {
TraceHandle createTrace() throws Throwable;
}
static class ApiTx implements Tx {
private final Transaction tx;
private ApiTx(Transaction tx) {
this.tx = tx;
}
@Override
public void close() throws Exception {
tx.close();
}
}
static class ApiTraceHandle implements TraceHandle {
private final ToyDBTraceBuilder tb;
public ApiTraceHandle(ToyDBTraceBuilder tb) {
this.tb = tb;
}
@Override
public Trace getTrace() throws Throwable {
return tb.trace;
}
@Override
public Tx startTx() throws Throwable {
return new ApiTx(tb.trace.openTransaction("Test"));
}
@Override
public void addRegion(String key, AddressRange range, String perms) {
DBTraceObject region = tb.trace.getObjectManager()
.createObject(TraceObjectKeyPath.parse("Processes[0].Memory[" + key + "]"));
region.setValue(Lifespan.nowOn(0), "Range", range);
region.setValue(Lifespan.nowOn(0), "R", perms.contains("r"));
region.setValue(Lifespan.nowOn(0), "W", perms.contains("w"));
region.setValue(Lifespan.nowOn(0), "X", perms.contains("x"));
region.insert(Lifespan.nowOn(0), ConflictResolution.ADJUST);
}
@Override
public void close() throws Exception {
tb.close();
}
}
class ApiTraceMaker implements TraceMaker {
@Override
public TraceHandle createTrace() throws Throwable {
tb = new ToyDBTraceBuilder(name.getMethodName(), LANGID_TOYBE64);
try (Transaction tx = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(SCHEMA_SESSION);
}
return new ApiTraceHandle(tb);
}
}
static class RmiTraceHandle implements TraceHandle {
private static final int RESULT_COUNT = REGION_COUNT * 6 + 2;
private final TraceRmiConnection connection;
private final Socket client;
private final List<CompletableFuture<?>> results = new ArrayList<>(RESULT_COUNT * 5);
private final OutputStream out;
private final InputStream in;
private final ExecutorService receiver = Executors.newSingleThreadExecutor();
private int seq = 1;
private int txid = 1000;
public RmiTraceHandle(TraceRmiConnection connection, Socket client) throws IOException {
this.connection = connection;
this.client = client;
this.out = client.getOutputStream();
this.in = client.getInputStream();
}
private CompletableFuture<RootMessage> receive() {
return CompletableFuture.supplyAsync(() -> {
try {
return TraceRmiHandler.recvDelimited(in);
}
catch (IOException e) {
return ExceptionUtils.rethrow(e);
}
}, receiver);
}
private CompletableFuture<RootMessage> request(RootMessage msg) throws IOException {
TraceRmiHandler.sendDelimited(out, msg, seq++);
return receive();
}
private static void assertMsgCase(MsgCase expected, RootMessage msg) {
if (msg.getMsgCase() == MsgCase.ERROR) {
throw new AssertionError("Got RMI error: " + msg.getError().getMessage());
}
assertEquals(expected, msg.getMsgCase());
}
private CompletableFuture<Void> finishNegotiate() {
return receive().thenAccept(reply -> {
assertMsgCase(MsgCase.REPLY_NEGOTIATE, reply);
});
}
class RmiTx implements Tx {
private final TxId txid;
public RmiTx(TxId txid) {
this.txid = txid;
}
@Override
public void close() throws Exception {
results.add(doEndTx(txid));
assertEquals(RESULT_COUNT, results.size());
for (CompletableFuture<?> r : results) {
r.get(1000, TimeUnit.MILLISECONDS);
}
}
}
protected TxId nextTx() {
return TxId.newBuilder().setId(txid).build();
}
protected CompletableFuture<Void> doStartTx(TxId txid) throws IOException {
RootMessage msg = RootMessage.newBuilder()
.setRequestStartTx(RequestStartTx.newBuilder()
.setDescription("Test")
.setOid(DomObjId.newBuilder().setId(0))
.setTxid(txid))
.build();
return request(msg).thenAccept(reply -> {
assertMsgCase(MsgCase.REPLY_START_TX, reply);
});
}
protected CompletableFuture<Void> doEndTx(TxId txid) throws IOException {
RootMessage msg = RootMessage.newBuilder()
.setRequestEndTx(RequestEndTx.newBuilder()
.setAbort(false)
.setOid(DomObjId.newBuilder().setId(0))
.setTxid(txid))
.build();
return request(msg).thenAccept(reply -> {
assertMsgCase(MsgCase.REPLY_END_TX, reply);
});
}
@Override
public Tx startTx() throws Throwable {
TxId txid = nextTx();
results.add(doStartTx(txid));
return new RmiTx(txid);
}
private CompletableFuture<Void> createTrace(String name) throws IOException {
RootMessage msg = RootMessage.newBuilder()
.setRequestCreateTrace(RequestCreateTrace.newBuilder()
.setPath(FilePath.newBuilder().setPath("Test/" + name))
.setLanguage(Language.newBuilder().setId(LANGID_TOYBE64))
.setCompiler(Compiler.newBuilder().setId("default"))
.setOid(DomObjId.newBuilder().setId(0)))
.build();
return request(msg).thenAccept(reply -> {
assertMsgCase(MsgCase.REPLY_CREATE_TRACE, reply);
});
}
private CompletableFuture<Void> createRootObject() throws IOException {
RootMessage msg = RootMessage.newBuilder()
.setRequestCreateRootObject(RequestCreateRootObject.newBuilder()
.setOid(DomObjId.newBuilder().setId(0))
.setSchemaContext(XmlSchemaContext.serialize(SCHEMA_CTX))
.setRootSchema(SCHEMA_SESSION.getName().toString()))
.build();
return request(msg).thenAccept(reply -> {
assertMsgCase(MsgCase.REPLY_CREATE_OBJECT, reply);
});
}
private CompletableFuture<Void> requestSetValue(DomObjId oid, ObjPath path,
Lifespan lifespan, String key, Object value) throws IOException {
RootMessage msg = RootMessage.newBuilder()
.setRequestSetValue(RequestSetValue.newBuilder()
.setOid(oid)
.setValue(ValSpec.newBuilder()
.setParent(ObjSpec.newBuilder()
.setPath(path))
.setKey(key)
.setSpan(TraceRmiHandler.makeSpan(lifespan))
.setValue(TraceRmiHandler.makeValue(value)))
.setResolution(Resolution.CR_ADJUST))
.build();
return request(msg).thenAccept(reply -> {
assertMsgCase(MsgCase.REPLY_SET_VALUE, reply);
});
}
@Override
public void addRegion(String key, AddressRange range, String perms) throws IOException {
DomObjId oid0 = DomObjId.newBuilder().setId(0).build();
ObjPath path = ObjPath.newBuilder().setPath("Processes[0].Memory[" + key + "]").build();
RootMessage msgCreate = RootMessage.newBuilder()
.setRequestCreateObject(RequestCreateObject.newBuilder()
.setOid(oid0)
.setPath(path))
.build();
results.add(request(msgCreate).thenAccept(reply -> {
assertMsgCase(MsgCase.REPLY_CREATE_OBJECT, reply);
}));
results.add(requestSetValue(oid0, path, Lifespan.nowOn(0), "Range", range));
results.add(requestSetValue(oid0, path, Lifespan.nowOn(0), "R", perms.contains("r")));
results.add(requestSetValue(oid0, path, Lifespan.nowOn(0), "W", perms.contains("w")));
results.add(requestSetValue(oid0, path, Lifespan.nowOn(0), "X", perms.contains("x")));
RootMessage msgInsert = RootMessage.newBuilder()
.setRequestInsertObject(RequestInsertObject.newBuilder()
.setOid(oid0)
.setObject(ObjSpec.newBuilder().setPath(path))
.setSpan(TraceRmiHandler.makeSpan(Lifespan.nowOn(0)))
.setResolution(Resolution.CR_ADJUST))
.build();
results.add(request(msgInsert).thenAccept(reply -> {
assertMsgCase(MsgCase.REPLY_INSERT_OBJECT, reply);
}));
}
@Override
public Trace getTrace() throws Throwable {
return Unique.assertOne(connection.getTargets()).getTrace();
}
@Override
public void close() throws Exception {
client.close();
connection.close();
}
}
class RmiTraceMaker implements TraceMaker {
protected TraceRmiService getService() throws Throwable {
TraceRmiService service = tool.getService(TraceRmiService.class);
if (service != null) {
return service;
}
return addPlugin(tool, TraceRmiPlugin.class);
}
private void startNegotiate(OutputStream out) throws IOException {
RootMessage msg = RootMessage.newBuilder()
.setRequestNegotiate(RequestNegotiate.newBuilder()
.setDescription("Test")
.setVersion(TraceRmiHandler.VERSION)
.addAllMethods(List.of()))
.build();
TraceRmiHandler.sendDelimited(out, msg, 0);
}
@Override
public TraceHandle createTrace() throws Throwable {
TraceRmiService service = getService();
TraceRmiAcceptor acceptor = service.acceptOne(new InetSocketAddress("localhost", 0));
Socket client = new Socket();
client.connect(acceptor.getAddress());
startNegotiate(client.getOutputStream());
TraceRmiConnection connection = acceptor.accept();
RmiTraceHandle th = new RmiTraceHandle(connection, client);
th.finishNegotiate();
TxId txid = th.nextTx();
th.createTrace(name.getMethodName()).get(10000, TimeUnit.MILLISECONDS);
th.doStartTx(txid).get(1000, TimeUnit.MILLISECONDS);
th.createRootObject().get(1000, TimeUnit.MILLISECONDS);
th.doEndTx(txid).get(1000, TimeUnit.MILLISECONDS);
tb = new ToyDBTraceBuilder(th.getTrace());
return th;
}
}
class NerfedRmiTraceMaker extends RmiTraceMaker {
@Override
protected TraceRmiService getService() throws Throwable {
tool.removePlugins(tool.getManagedPlugins()
.stream()
.filter(p -> p instanceof TraceRmiPlugin)
.toList());
return addPlugin(tool, NerfedTraceRmiPlugin.class);
}
}
static class TimeMe implements AutoCloseable {
private final String name;
private final long start;
public TimeMe(String name) {
this.name = name;
Msg.info(this, "Starting: " + name);
this.start = System.currentTimeMillis();
}
@Override
public void close() throws Exception {
long elapsed = System.currentTimeMillis() - start;
Msg.info(this, "Finished: " + name + ". Took " + elapsed + " ms");
}
}
@Test
@Repeated(20) // First time will measure warm-up
public void testMeasureViaApiIsolated() throws Throwable {
TraceMaker tm = new ApiTraceMaker();
try (TraceHandle th = tm.createTrace()) {
try (TimeMe __ = new TimeMe("Add via API. Isolated.")) {
th.addLotsOfRegions();
}
}
}
@Test
@Repeated(2) // First time will measure warm-up
public void testMeasureViaRmiIsolated() throws Throwable {
TraceMaker tm = new RmiTraceMaker();
try (TraceHandle th = tm.createTrace()) {
try (TimeMe __ = new TimeMe("Add via RMI. Isolated.")) {
th.addLotsOfRegions();
}
}
}
static class NerfedTraceRmiHandler extends TraceRmiHandler {
public NerfedTraceRmiHandler(TraceRmiPlugin plugin, Socket socket) throws IOException {
super(plugin, socket);
}
@Override
protected ReplyCreateObject handleCreateObject(RequestCreateObject req) {
return ReplyCreateObject.getDefaultInstance();
}
@Override
protected ReplySetValue handleSetValue(RequestSetValue req)
throws AddressOverflowException {
return ReplySetValue.getDefaultInstance();
}
@Override
protected ReplyInsertObject handleInsertObject(RequestInsertObject req) {
return ReplyInsertObject.getDefaultInstance();
}
}
static class NerfedTraceRmiAcceptor extends DefaultTraceRmiAcceptor {
public NerfedTraceRmiAcceptor(TraceRmiPlugin plugin, SocketAddress address) {
super(plugin, address);
}
@Override
public NerfedTraceRmiHandler doAccept(TraceRmiAcceptor acceptor)
throws IOException {
Socket client = socket.accept();
NerfedTraceRmiHandler handler = new NerfedTraceRmiHandler(plugin, client);
handler.start();
plugin.listeners.invoke().connected(handler, getConnectMode(), acceptor);
return handler;
}
}
public static class NerfedTraceRmiPlugin extends TraceRmiPlugin {
public NerfedTraceRmiPlugin(PluginTool tool) {
super(tool);
}
@Override
public DefaultTraceRmiAcceptor acceptOne(SocketAddress address) throws IOException {
NerfedTraceRmiAcceptor acceptor = new NerfedTraceRmiAcceptor(this, address);
acceptor.start();
listeners.invoke().waitingAccept(acceptor);
return acceptor;
}
}
@Test
@Repeated(2)
public void testMeasureRmiNetworkOnly() throws Throwable {
TraceMaker tm = new NerfedRmiTraceMaker();
try (TraceHandle th = tm.createTrace()) {
try (TimeMe __ = new TimeMe("Add via RMI. Isolated.")) {
th.addLotsOfRegions();
}
}
}
@Test
@Repeated(2)
public void testMeasurePlainTable() throws Exception {
DBHandle handle = new DBHandle();
// Some comparable number of fields to a DBTraceObjectValue
Schema schema = new SchemaBuilder()
.field("Parent", LongField.class)
.field("EntryKey", StringField.class)
.field("MinSnap", LongField.class)
.field("MaxSnap", LongField.class)
.field("Value", BinaryField.class)
.build();
try (TimeMe tm = new TimeMe("Plain table")) {
try (Transaction tx = handle.openTransaction(null)) {
Table table = handle.createTable("Test", schema);
for (int i = 0; i < RECORD_COUNT; i++) {
DBRecord rec = schema.createRecord(0);
rec.setLongValue(0, 0);
rec.setString(1, "Whatever");
rec.setLongValue(2, 0);
rec.setLongValue(3, Long.MAX_VALUE);
ByteBuffer rangeEnc = ByteBuffer.allocate(18);
rangeEnc.putShort((short) 0x204); // Made up "space id"
rangeEnc.putLong(1024 * i);
rangeEnc.putLong(1024 * i + 1023);
rec.setBinaryData(4, rangeEnc.array());
table.putRecord(rec);
}
}
}
}
@DBAnnotatedObjectInfo(version = 0)
public static class TestObject extends DBAnnotatedObject {
static final String NAME_PARENT = "Parent";
static final String NAME_ENTRY_KEY = "EntryKey";
static final String NAME_MIN_SNAP = "MinSnap";
static final String NAME_MAX_SNAP = "MaxSnap";
static final String NAME_VALUE = "Value";
@DBAnnotatedColumn(NAME_PARENT)
static DBObjectColumn COL_PARENT;
@DBAnnotatedColumn(NAME_ENTRY_KEY)
static DBObjectColumn COL_ENTRY_KEY;
@DBAnnotatedColumn(NAME_MIN_SNAP)
static DBObjectColumn COL_MIN_SNAP;
@DBAnnotatedColumn(NAME_MAX_SNAP)
static DBObjectColumn COL_MAX_SNAP;
@DBAnnotatedColumn(NAME_VALUE)
static DBObjectColumn COL_VALUE;
@DBAnnotatedField(column = NAME_PARENT)
private long parent;
@DBAnnotatedField(column = NAME_ENTRY_KEY)
private String entryKey;
@DBAnnotatedField(column = NAME_MIN_SNAP)
private long minSnap;
@DBAnnotatedField(column = NAME_MAX_SNAP)
private long maxSnap;
@DBAnnotatedField(column = NAME_VALUE)
private byte[] value;
protected TestObject(DBCachedObjectStore<?> store, DBRecord record) {
super(store, record);
}
public void set(long parent, String entryKey, long minSnap, long maxSnap, byte[] value) {
this.parent = parent;
this.entryKey = entryKey;
this.minSnap = minSnap;
this.maxSnap = maxSnap;
this.value = value;
update(COL_PARENT, COL_ENTRY_KEY, COL_MIN_SNAP, COL_MAX_SNAP, COL_VALUE);
}
}
public static class TestDomainObject extends DBCachedDomainObjectAdapter {
protected TestDomainObject(DBHandle dbh, DBOpenMode openMode, TaskMonitor monitor,
String name, int timeInterval, int bufSize, Object consumer) {
super(dbh, openMode, monitor, name, timeInterval, bufSize, consumer);
}
@Override
public boolean isChangeable() {
return true;
}
@Override
public String getDescription() {
return "Test Domain Object";
}
}
@Test
@Repeated(2)
public void testMeasureObjectStore() throws Exception {
DBHandle handle = new DBHandle();
TestDomainObject domObj =
new TestDomainObject(handle, DBOpenMode.CREATE, monitor, "Test", 500, 1000, this);
DBCachedObjectStoreFactory factory = new DBCachedObjectStoreFactory(domObj);
try (TimeMe tm = new TimeMe("Object Store")) {
try (Transaction tx = domObj.openTransaction("Test")) {
DBCachedObjectStore<TestObject> store = factory.getOrCreateCachedStore("Test",
TestObject.class, TestObject::new, false);
for (int i = 0; i < RECORD_COUNT; i++) {
try (LockHold hold = LockHold.lock(domObj.getReadWriteLock().writeLock())) {
TestObject obj = store.create();
ByteBuffer rangeEnc = ByteBuffer.allocate(18);
rangeEnc.putShort((short) 0x204); // Made up "space id"
rangeEnc.putLong(1024 * i);
rangeEnc.putLong(1024 * i + 1023);
obj.set(0, "Whatever", 0, Long.MAX_VALUE, rangeEnc.array());
}
}
}
}
}
@Test
@Repeated(20)
public void testMeasureRTree() throws Exception {
MyDomainObject domObj = new MyDomainObject(this);
try (TimeMe tm = new TimeMe("Object Store")) {
try (Transaction tx = domObj.openTransaction("Test")) {
for (int i = 0; i < RECORD_COUNT; i++) {
domObj.map.put(
IntRect.ALL.immutable(0, Integer.MAX_VALUE, i * 1024, i * 1024 + 1023),
"Whatever");
}
}
}
}
}