mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-06-02 11:02:37 +08:00
GP-1969: Add 'Model' provider for inspecting object-based traces.
This commit is contained in:
+1
-1
@@ -100,7 +100,7 @@
|
||||
and Service</A>. <B>Note:</B> Only the raw "Value" column can be edited directly. The "Repr"
|
||||
column cannot be edited, yet.</P>
|
||||
|
||||
<H3><A name="snapshot_window"></A>Snapshot Window</H3>
|
||||
<H3><A name="clone_window"></A>Clone Window</H3>
|
||||
|
||||
<P>This button is analogous to the "snapshot" action of other Ghidra windows. It generates a
|
||||
clone of this window. The clone will no longer follow the current thread, but it will follow
|
||||
|
||||
+144
-12
@@ -39,6 +39,7 @@ import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.memory.DebuggerMemoryBytesPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionsPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.model.DebuggerModelPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerStaticMappingPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsPlugin;
|
||||
@@ -132,6 +133,9 @@ public interface DebuggerResources {
|
||||
ImageIcon ICON_SELECT_ROWS = ResourceManager.loadImage("images/table_go.png");
|
||||
ImageIcon ICON_AUTOREAD = ResourceManager.loadImage("images/autoread.png");
|
||||
|
||||
ImageIcon ICON_OBJECT_POPULATED = ResourceManager.loadImage("images/object-populated.png");
|
||||
ImageIcon ICON_OBJECT_UNPOPULATED = ResourceManager.loadImage("images/object-unpopulated.png");
|
||||
|
||||
// TODO: Draw a real icon.
|
||||
ImageIcon ICON_REFRESH_MEMORY = ICON_REFRESH;
|
||||
|
||||
@@ -255,6 +259,11 @@ public interface DebuggerResources {
|
||||
HelpLocation HELP_PROVIDER_OBJECTS = new HelpLocation(
|
||||
PluginUtils.getPluginNameFromClass(DebuggerObjectsPlugin.class), HELP_ANCHOR_PLUGIN);
|
||||
|
||||
String TITLE_PROVIDER_MODEL = "Model"; // TODO: An icon
|
||||
ImageIcon ICON_PROVIDER_MODEL = ResourceManager.loadImage("images/function_graph.png");
|
||||
HelpLocation HELP_PROVIDER_MODEL = new HelpLocation(
|
||||
PluginUtils.getPluginNameFromClass(DebuggerModelPlugin.class), HELP_ANCHOR_PLUGIN);
|
||||
|
||||
String TITLE_PROVIDER_WATCHES = "Watches";
|
||||
ImageIcon ICON_PROVIDER_WATCHES = ICON_AUTOREAD; // TODO: Another icon?
|
||||
HelpLocation HELP_PROVIDER_WATCHES = new HelpLocation(
|
||||
@@ -275,6 +284,9 @@ public interface DebuggerResources {
|
||||
Color DEFAULT_COLOR_REGISTER_MARKERS = new Color(0.75f, 0.875f, 0.75f);
|
||||
ImageIcon ICON_REGISTER_MARKER = ResourceManager.loadImage("images/register-marker.png");
|
||||
|
||||
ImageIcon ICON_EVENT_MARKER = ICON_REGISTER_MARKER; // TODO: Another icon?
|
||||
// At least rename to "marker-arrow", and then have both ref it.
|
||||
|
||||
String OPTION_NAME_COLORS_REGISTER_STALE = "Colors.Stale Registers";
|
||||
Color DEFAULT_COLOR_REGISTER_STALE = Color.GRAY;
|
||||
String OPTION_NAME_COLORS_REGISTER_STALE_SEL = "Colors.Stale Registers (selected)";
|
||||
@@ -293,6 +305,11 @@ public interface DebuggerResources {
|
||||
String OPTION_NAME_COLORS_WATCH_CHANGED_SEL = "Colors.Changed Watches (selected)";
|
||||
Color DEFAULT_COLOR_WATCH_CHANGED_SEL = ColorUtils.blend(Color.RED, Color.WHITE, 0.5f);
|
||||
|
||||
String OPTION_NAME_COLORS_VALUE_CHANGED = "Colors.Changed Values";
|
||||
Color DEFAULT_COLOR_VALUE_CHANGED = Color.RED;
|
||||
String OPTION_NAME_COLORS_VALUE_CHANGED_SEL = "Colors.Changed Values (selected)";
|
||||
Color DEFAULT_COLOR_VALUE_CHANGED_SEL = ColorUtils.blend(Color.RED, Color.WHITE, 0.5f);
|
||||
|
||||
String OPTION_NAME_COLORS_PCODE_COUNTER = "Colors.Pcode Counter";
|
||||
Color DEFAULT_COLOR_PCODE_COUNTER = new Color(0.75f, 0.875f, 0.75f);
|
||||
|
||||
@@ -994,12 +1011,12 @@ public interface DebuggerResources {
|
||||
}
|
||||
}
|
||||
|
||||
interface CreateSnapshotAction {
|
||||
String NAME = "Create Snapshot";
|
||||
String DESCRIPTION = "Create a (disconnected) snapshot copy of this window";
|
||||
interface CloneWindowAction {
|
||||
String NAME = "Clone Window";
|
||||
String DESCRIPTION = "Create a disconnected copy of this window";
|
||||
String GROUP = "zzzz";
|
||||
Icon ICON = ResourceManager.loadImage("images/camera-photo.png");
|
||||
String HELP_ANCHOR = "snapshot_window";
|
||||
String HELP_ANCHOR = "clone_window";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
@@ -1624,14 +1641,31 @@ public interface DebuggerResources {
|
||||
}
|
||||
}
|
||||
|
||||
interface StepSnapForwardAction {
|
||||
String NAME = "Step Trace Snap Forward";
|
||||
String DESCRIPTION = "Navigate the recording forward one snap";
|
||||
Icon ICON = ICON_SNAP_FORWARD;
|
||||
String GROUP = GROUP_CONTROL;
|
||||
String HELP_ANCHOR = "step_trace_snap_forward";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.toolBarIcon(ICON)
|
||||
.toolBarGroup(GROUP, "4")
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractStepSnapForwardAction extends DockingAction {
|
||||
public static final String NAME = "Step Trace Snap Forward";
|
||||
public static final Icon ICON = ICON_SNAP_FORWARD;
|
||||
public static final String HELP_ANCHOR = "step_trace_snap_forward";
|
||||
public static final String NAME = StepSnapForwardAction.NAME;
|
||||
public static final Icon ICON = StepSnapForwardAction.ICON;
|
||||
public static final String HELP_ANCHOR = StepSnapForwardAction.HELP_ANCHOR;
|
||||
|
||||
public AbstractStepSnapForwardAction(Plugin owner) {
|
||||
super(NAME, owner.getName());
|
||||
setDescription("Navigate the recording forward one snap");
|
||||
setDescription(StepSnapForwardAction.DESCRIPTION);
|
||||
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
@@ -1677,14 +1711,31 @@ public interface DebuggerResources {
|
||||
}
|
||||
}
|
||||
|
||||
interface StepSnapBackwardAction {
|
||||
String NAME = "Step Trace Snap Backward";
|
||||
String DESCRIPTION = "Navigate the recording backward one snap";
|
||||
Icon ICON = ICON_SNAP_BACKWARD;
|
||||
String GROUP = GROUP_CONTROL;
|
||||
String HELP_ANCHOR = "step_trace_snap_backward";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.toolBarIcon(ICON)
|
||||
.toolBarGroup(GROUP, "1")
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractStepSnapBackwardAction extends DockingAction {
|
||||
public static final String NAME = "Step Trace Snap Backward";
|
||||
public static final Icon ICON = ICON_SNAP_BACKWARD;
|
||||
public static final String HELP_ANCHOR = "step_trace_snap_backward";
|
||||
public static final String NAME = StepSnapBackwardAction.NAME;
|
||||
public static final Icon ICON = StepSnapBackwardAction.ICON;;
|
||||
public static final String HELP_ANCHOR = StepSnapBackwardAction.HELP_ANCHOR;
|
||||
|
||||
public AbstractStepSnapBackwardAction(Plugin owner) {
|
||||
super(NAME, owner.getName());
|
||||
setDescription("Navigate the recording backward one snap");
|
||||
setDescription(StepSnapBackwardAction.DESCRIPTION);
|
||||
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
@@ -2107,6 +2158,87 @@ public interface DebuggerResources {
|
||||
}
|
||||
}
|
||||
|
||||
interface LimitToCurrentSnapAction {
|
||||
String NAME = "Limit to Current Snap";
|
||||
String DESCRIPTION = "Choose whether displayed objects must be alive at the current snap";
|
||||
String GROUP = GROUP_GENERAL;
|
||||
Icon ICON = ICON_TIME; // TODO
|
||||
String HELP_ANCHOR = "limit_to_current_snap";
|
||||
|
||||
static ToggleActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ToggleActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.toolBarGroup(GROUP)
|
||||
.toolBarIcon(ICON)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface ShowHiddenAction {
|
||||
String NAME = "Show Hidden";
|
||||
String DESCRIPTION = "Choose whether to display hidden children";
|
||||
String GROUP = GROUP_GENERAL;
|
||||
String HELP_ANCHOR = "show_hidden";
|
||||
|
||||
static ToggleActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ToggleActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuPath(NAME)
|
||||
.menuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface ShowPrimitivesInTreeAction {
|
||||
String NAME = "Show Primitives in Tree";
|
||||
String DESCRIPTION = "Choose whether to display primitive values in the tree";
|
||||
String GROUP = GROUP_GENERAL;
|
||||
String HELP_ANCHOR = "show_primitives";
|
||||
|
||||
static ToggleActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ToggleActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuPath(NAME)
|
||||
.menuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface ShowMethodsInTreeAction {
|
||||
String NAME = "Show Methods in Tree";
|
||||
String DESCRIPTION = "Choose whether to display methods in the tree";
|
||||
String GROUP = GROUP_GENERAL;
|
||||
String HELP_ANCHOR = "show_methods";
|
||||
|
||||
static ToggleActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ToggleActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuPath(NAME)
|
||||
.menuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface FollowLinkAction {
|
||||
String NAME = "Follow Link";
|
||||
String DESCRIPTION = "Navigate to the link target";
|
||||
String GROUP = GROUP_GENERAL;
|
||||
String HELP_ANCHOR = "follow_link";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.popupMenuPath(NAME)
|
||||
.popupMenuGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AbstractDebuggerConnectionsNode extends GTreeNode {
|
||||
@Override
|
||||
public String getName() {
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/* ###
|
||||
* 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.gui;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.jdom.Element;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.MultiProviderSaveBehavior.SaveableProvider;
|
||||
import ghidra.framework.options.SaveState;
|
||||
|
||||
public abstract class MultiProviderSaveBehavior<P extends SaveableProvider> {
|
||||
private static final String KEY_CONNECTED_PROVIDER = "connectedProvider";
|
||||
private static final String KEY_DISCONNECTED_COUNT = "disconnectedCount";
|
||||
private static final String PREFIX_DISCONNECTED_PROVIDER = "disconnectedProvider";
|
||||
|
||||
public interface SaveableProvider {
|
||||
void writeConfigState(SaveState saveState);
|
||||
|
||||
void readConfigState(SaveState saveState);
|
||||
|
||||
void writeDataState(SaveState saveState);
|
||||
|
||||
void readDataState(SaveState saveState);
|
||||
}
|
||||
|
||||
protected abstract P getConnectedProvider();
|
||||
|
||||
protected abstract List<P> getDisconnectedProviders();
|
||||
|
||||
protected abstract P createDisconnectedProvider();
|
||||
|
||||
protected abstract void removeDisconnectedProvider(P p);
|
||||
|
||||
protected void doWrite(SaveState saveState, BiConsumer<? super P, ? super SaveState> writer) {
|
||||
P cp = getConnectedProvider();
|
||||
SaveState cpState = new SaveState();
|
||||
writer.accept(cp, cpState);
|
||||
saveState.putXmlElement(KEY_CONNECTED_PROVIDER, cpState.saveToXml());
|
||||
|
||||
List<P> disconnectedProviders = getDisconnectedProviders();
|
||||
List<P> disconnected;
|
||||
synchronized (disconnectedProviders) {
|
||||
disconnected = List.copyOf(disconnectedProviders);
|
||||
}
|
||||
saveState.putInt(KEY_DISCONNECTED_COUNT, disconnected.size());
|
||||
for (int i = 0; i < disconnected.size(); i++) {
|
||||
P dp = disconnected.get(i);
|
||||
String stateName = PREFIX_DISCONNECTED_PROVIDER + i;
|
||||
SaveState dpState = new SaveState();
|
||||
writer.accept(dp, dpState);
|
||||
saveState.putXmlElement(stateName, dpState.saveToXml());
|
||||
}
|
||||
}
|
||||
|
||||
protected void doRead(SaveState saveState, BiConsumer<? super P, ? super SaveState> reader,
|
||||
boolean matchCount) {
|
||||
Element cpElement = saveState.getXmlElement(KEY_CONNECTED_PROVIDER);
|
||||
if (cpElement != null) {
|
||||
P cp = getConnectedProvider();
|
||||
SaveState cpState = new SaveState(cpElement);
|
||||
reader.accept(cp, cpState);
|
||||
}
|
||||
|
||||
int disconnectedCount = saveState.getInt(KEY_DISCONNECTED_COUNT, 0);
|
||||
List<P> disconnectedProviders = getDisconnectedProviders();
|
||||
while (matchCount && disconnectedProviders.size() < disconnectedCount) {
|
||||
createDisconnectedProvider();
|
||||
}
|
||||
while (matchCount && disconnectedProviders.size() > disconnectedCount) {
|
||||
removeDisconnectedProvider(disconnectedProviders.get(disconnectedProviders.size() - 1));
|
||||
}
|
||||
|
||||
int count = Math.min(disconnectedCount, disconnectedProviders.size());
|
||||
for (int i = 0; i < count; i++) {
|
||||
String stateName = PREFIX_DISCONNECTED_PROVIDER + i;
|
||||
Element dpElement = saveState.getXmlElement(stateName);
|
||||
if (dpElement != null) {
|
||||
P dp = disconnectedProviders.get(i);
|
||||
SaveState dpState = new SaveState(dpElement);
|
||||
reader.accept(dp, dpState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void writeConfigState(SaveState saveState) {
|
||||
doWrite(saveState, P::writeConfigState);
|
||||
}
|
||||
|
||||
public void readConfigState(SaveState saveState) {
|
||||
doRead(saveState, P::readConfigState, true);
|
||||
}
|
||||
|
||||
public void writeDataState(SaveState saveState) {
|
||||
doWrite(saveState, P::writeDataState);
|
||||
}
|
||||
|
||||
public void readDataState(SaveState saveState) {
|
||||
doRead(saveState, P::readDataState, false);
|
||||
}
|
||||
}
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
/* ###
|
||||
* 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.gui.model;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import docking.widgets.table.threaded.ThreadedTableModel;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
||||
import ghidra.trace.model.TraceDomainObjectListener;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, Trace>
|
||||
implements DisplaysModified {
|
||||
|
||||
protected class ListenerForChanges extends TraceDomainObjectListener {
|
||||
public ListenerForChanges() {
|
||||
listenFor(TraceObjectChangeType.VALUE_CREATED, this::valueCreated);
|
||||
listenFor(TraceObjectChangeType.VALUE_DELETED, this::valueDeleted);
|
||||
listenFor(TraceObjectChangeType.VALUE_LIFESPAN_CHANGED, this::valueLifespanChanged);
|
||||
|
||||
listenFor(TraceSnapshotChangeType.ADDED, this::maxSnapChanged);
|
||||
listenFor(TraceSnapshotChangeType.DELETED, this::maxSnapChanged);
|
||||
}
|
||||
|
||||
protected void valueCreated(TraceObjectValue value) {
|
||||
if (query != null && query.includes(span, value)) {
|
||||
reload(); // Can I be more surgical?
|
||||
}
|
||||
}
|
||||
|
||||
protected void valueDeleted(TraceObjectValue value) {
|
||||
if (query != null && query.includes(span, value)) {
|
||||
reload();
|
||||
}
|
||||
}
|
||||
|
||||
protected void valueLifespanChanged(TraceObjectValue value, Range<Long> oldSpan,
|
||||
Range<Long> newSpan) {
|
||||
if (query == null) {
|
||||
return;
|
||||
}
|
||||
boolean inOld = DBTraceUtils.intersect(oldSpan, span);
|
||||
boolean inNew = DBTraceUtils.intersect(newSpan, span);
|
||||
boolean queryIncludes = query.includes(Range.all(), value);
|
||||
if (queryIncludes) {
|
||||
if (inOld != inNew) {
|
||||
reload();
|
||||
}
|
||||
else if (inOld || inNew) {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void maxSnapChanged() {
|
||||
AbstractQueryTableModel.this.maxSnapChanged();
|
||||
}
|
||||
}
|
||||
|
||||
protected class TableDisplaysObjectValues implements DisplaysObjectValues {
|
||||
@Override
|
||||
public long getSnap() {
|
||||
return snap;
|
||||
}
|
||||
}
|
||||
|
||||
protected class DiffTableDisplaysObjectValues implements DisplaysObjectValues {
|
||||
@Override
|
||||
public long getSnap() {
|
||||
return diffSnap;
|
||||
}
|
||||
}
|
||||
|
||||
private Trace trace;
|
||||
private long snap;
|
||||
private Trace diffTrace;
|
||||
private long diffSnap;
|
||||
private ModelQuery query;
|
||||
private Range<Long> span = Range.all();
|
||||
private boolean showHidden;
|
||||
|
||||
private final ListenerForChanges listenerForChanges = newListenerForChanges();
|
||||
protected final DisplaysObjectValues display = new TableDisplaysObjectValues();
|
||||
protected final DisplaysObjectValues diffDisplay = new DiffTableDisplaysObjectValues();
|
||||
|
||||
protected AbstractQueryTableModel(String name, Plugin plugin) {
|
||||
super(name, plugin.getTool(), null, true);
|
||||
}
|
||||
|
||||
protected ListenerForChanges newListenerForChanges() {
|
||||
return new ListenerForChanges();
|
||||
}
|
||||
|
||||
protected void maxSnapChanged() {
|
||||
// Extension point
|
||||
}
|
||||
|
||||
private void removeOldTraceListener() {
|
||||
if (trace != null) {
|
||||
trace.removeListener(listenerForChanges);
|
||||
}
|
||||
}
|
||||
|
||||
private void addNewTraceListener() {
|
||||
if (trace != null) {
|
||||
trace.addListener(listenerForChanges);
|
||||
}
|
||||
}
|
||||
|
||||
protected void traceChanged() {
|
||||
reload();
|
||||
}
|
||||
|
||||
public void setTrace(Trace trace) {
|
||||
if (Objects.equals(this.trace, trace)) {
|
||||
return;
|
||||
}
|
||||
removeOldTraceListener();
|
||||
this.trace = trace;
|
||||
addNewTraceListener();
|
||||
|
||||
traceChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
protected void snapChanged() {
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void setSnap(long snap) {
|
||||
if (this.snap == snap) {
|
||||
return;
|
||||
}
|
||||
this.snap = snap;
|
||||
|
||||
snapChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSnap() {
|
||||
return snap;
|
||||
}
|
||||
|
||||
protected void diffTraceChanged() {
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set alternative trace to colorize values that differ
|
||||
*
|
||||
* <p>
|
||||
* The same trace can be used, but with an alternative snap, if desired. See
|
||||
* {@link #setDiffSnap(long)}. One common use is to compare with the previous snap of the same
|
||||
* trace. Another common use is to compare with the previous navigation.
|
||||
*
|
||||
* @param diffTrace the alternative trace
|
||||
*/
|
||||
public void setDiffTrace(Trace diffTrace) {
|
||||
if (this.diffTrace == diffTrace) {
|
||||
return;
|
||||
}
|
||||
this.diffTrace = diffTrace;
|
||||
diffTraceChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getDiffTrace() {
|
||||
return diffTrace;
|
||||
}
|
||||
|
||||
protected void diffSnapChanged() {
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set alternative snap to colorize values that differ
|
||||
*
|
||||
* <p>
|
||||
* The diff trace must be set, even if it's the same as the trace being displayed. See
|
||||
* {@link #setDiffTrace(Trace)}.
|
||||
*
|
||||
* @param diffSnap the alternative snap
|
||||
*/
|
||||
public void setDiffSnap(long diffSnap) {
|
||||
if (this.diffSnap == diffSnap) {
|
||||
return;
|
||||
}
|
||||
this.diffSnap = diffSnap;
|
||||
diffSnapChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDiffSnap() {
|
||||
return diffSnap;
|
||||
}
|
||||
|
||||
protected void queryChanged() {
|
||||
reload();
|
||||
}
|
||||
|
||||
public void setQuery(ModelQuery query) {
|
||||
if (Objects.equals(this.query, query)) {
|
||||
return;
|
||||
}
|
||||
this.query = query;
|
||||
|
||||
queryChanged();
|
||||
}
|
||||
|
||||
public ModelQuery getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
protected void spanChanged() {
|
||||
reload();
|
||||
}
|
||||
|
||||
public void setSpan(Range<Long> span) {
|
||||
if (Objects.equals(this.span, span)) {
|
||||
return;
|
||||
}
|
||||
this.span = span;
|
||||
|
||||
spanChanged();
|
||||
}
|
||||
|
||||
public Range<Long> getSpan() {
|
||||
return span;
|
||||
}
|
||||
|
||||
protected void showHiddenChanged() {
|
||||
reload();
|
||||
}
|
||||
|
||||
public void setShowHidden(boolean showHidden) {
|
||||
if (this.showHidden == showHidden) {
|
||||
return;
|
||||
}
|
||||
this.showHidden = showHidden;
|
||||
|
||||
showHiddenChanged();
|
||||
}
|
||||
|
||||
public boolean isShowHidden() {
|
||||
return showHidden;
|
||||
}
|
||||
|
||||
protected abstract Stream<T> streamRows(Trace trace, ModelQuery query, Range<Long> span);
|
||||
|
||||
@Override
|
||||
protected void doLoad(Accumulator<T> accumulator, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
if (trace == null || query == null || trace.getObjectManager().getRootSchema() == null) {
|
||||
return;
|
||||
}
|
||||
for (T t : (Iterable<T>) streamRows(trace, query, span)::iterator) {
|
||||
accumulator.add(t);
|
||||
monitor.checkCanceled();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Trace getDataSource() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEdgesDiffer(TraceObjectValue newEdge, TraceObjectValue oldEdge) {
|
||||
if (DisplaysModified.super.isEdgesDiffer(newEdge, oldEdge)) {
|
||||
return true;
|
||||
}
|
||||
// Hack to incorporate _display logic to differencing.
|
||||
// This ensures "boxed" primitives show as differing at the object level
|
||||
return !Objects.equals(diffDisplay.getEdgeDisplay(oldEdge),
|
||||
display.getEdgeDisplay(newEdge));
|
||||
}
|
||||
|
||||
public abstract void setDiffColor(Color diffColor);
|
||||
|
||||
public abstract void setDiffColorSel(Color diffColorSel);
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/* ###
|
||||
* 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.gui.model;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
|
||||
public abstract class AbstractQueryTablePanel<T> extends JPanel {
|
||||
|
||||
protected final AbstractQueryTableModel<T> tableModel;
|
||||
protected final GhidraTable table;
|
||||
private final GhidraTableFilterPanel<T> filterPanel;
|
||||
|
||||
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||
protected boolean limitToSnap = false;
|
||||
protected boolean showHidden = false;
|
||||
|
||||
public AbstractQueryTablePanel(Plugin plugin) {
|
||||
super(new BorderLayout());
|
||||
tableModel = createModel(plugin);
|
||||
table = new GhidraTable(tableModel);
|
||||
filterPanel = new GhidraTableFilterPanel<>(table, tableModel);
|
||||
|
||||
add(new JScrollPane(table), BorderLayout.CENTER);
|
||||
add(filterPanel, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
protected abstract AbstractQueryTableModel<T> createModel(Plugin plugin);
|
||||
|
||||
public void goToCoordinates(DebuggerCoordinates coords) {
|
||||
if (DebuggerCoordinates.equalsIgnoreRecorderAndView(current, coords)) {
|
||||
return;
|
||||
}
|
||||
DebuggerCoordinates previous = current;
|
||||
this.current = coords;
|
||||
tableModel.setDiffTrace(previous.getTrace());
|
||||
tableModel.setTrace(current.getTrace());
|
||||
tableModel.setDiffSnap(previous.getSnap());
|
||||
tableModel.setSnap(current.getSnap());
|
||||
if (limitToSnap) {
|
||||
tableModel.setSpan(Range.singleton(current.getSnap()));
|
||||
}
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
tableModel.reload();
|
||||
}
|
||||
|
||||
public void setQuery(ModelQuery query) {
|
||||
tableModel.setQuery(query);
|
||||
}
|
||||
|
||||
public ModelQuery getQuery() {
|
||||
return tableModel.getQuery();
|
||||
}
|
||||
|
||||
public void setLimitToSnap(boolean limitToSnap) {
|
||||
if (this.limitToSnap == limitToSnap) {
|
||||
return;
|
||||
}
|
||||
this.limitToSnap = limitToSnap;
|
||||
tableModel.setSpan(limitToSnap ? Range.singleton(current.getSnap()) : Range.all());
|
||||
}
|
||||
|
||||
public boolean isLimitToSnap() {
|
||||
return limitToSnap;
|
||||
}
|
||||
|
||||
public void setShowHidden(boolean showHidden) {
|
||||
if (this.showHidden == showHidden) {
|
||||
return;
|
||||
}
|
||||
this.showHidden = showHidden;
|
||||
tableModel.setShowHidden(showHidden);
|
||||
}
|
||||
|
||||
public boolean isShowHidden() {
|
||||
return showHidden;
|
||||
}
|
||||
|
||||
public void addSelectionListener(ListSelectionListener listener) {
|
||||
table.getSelectionModel().addListSelectionListener(listener);
|
||||
}
|
||||
|
||||
public void removeSelectionListener(ListSelectionListener listener) {
|
||||
table.getSelectionModel().removeListSelectionListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void addMouseListener(MouseListener l) {
|
||||
super.addMouseListener(l);
|
||||
// HACK?
|
||||
table.addMouseListener(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void removeMouseListener(MouseListener l) {
|
||||
super.removeMouseListener(l);
|
||||
// HACK?
|
||||
table.removeMouseListener(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void addKeyListener(KeyListener l) {
|
||||
super.addKeyListener(l);
|
||||
// HACK?
|
||||
table.addKeyListener(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void removeKeyListener(KeyListener l) {
|
||||
super.removeKeyListener(l);
|
||||
// HACK?
|
||||
table.removeKeyListener(l);
|
||||
}
|
||||
|
||||
public void setSelectionMode(int selectionMode) {
|
||||
table.setSelectionMode(selectionMode);
|
||||
}
|
||||
|
||||
public int getSelectionMode() {
|
||||
return table.getSelectionModel().getSelectionMode();
|
||||
}
|
||||
|
||||
// TODO: setSelectedItems? Is a bit more work than expected:
|
||||
// see filterPanel.getTableFilterModel();
|
||||
// see table.getSelectionMode().addSelectionInterval()
|
||||
// seems like setSelectedItems should be in filterPanel?
|
||||
|
||||
public void setSelectedItem(T item) {
|
||||
filterPanel.setSelectedItem(item);
|
||||
}
|
||||
|
||||
public List<T> getSelectedItems() {
|
||||
return filterPanel.getSelectedItems();
|
||||
}
|
||||
|
||||
public T getSelectedItem() {
|
||||
return filterPanel.getSelectedItem();
|
||||
}
|
||||
|
||||
public void setDiffColor(Color diffColor) {
|
||||
tableModel.setDiffColor(diffColor);
|
||||
}
|
||||
|
||||
public void setDiffColorSel(Color diffColorSel) {
|
||||
tableModel.setDiffColorSel(diffColorSel);
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/* ###
|
||||
* 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.gui.model;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.tree.TreeCellRenderer;
|
||||
|
||||
public interface ColorsModified<P extends JComponent> {
|
||||
|
||||
Color getDiffForeground(P p);
|
||||
|
||||
Color getDiffSelForeground(P p);
|
||||
|
||||
Color getForeground(P p);
|
||||
|
||||
Color getSelForeground(P p);
|
||||
|
||||
default Color getForegroundFor(P p, boolean isModified, boolean isSelected) {
|
||||
return isModified ? isSelected ? getDiffSelForeground(p) : getDiffForeground(p)
|
||||
: isSelected ? getSelForeground(p) : getForeground(p);
|
||||
}
|
||||
|
||||
interface InTable extends ColorsModified<JTable> {
|
||||
@Override
|
||||
default Color getForeground(JTable table) {
|
||||
return table.getForeground();
|
||||
}
|
||||
|
||||
@Override
|
||||
default Color getSelForeground(JTable table) {
|
||||
return table.getSelectionForeground();
|
||||
}
|
||||
}
|
||||
|
||||
interface InTree extends ColorsModified<JTree>, TreeCellRenderer {
|
||||
|
||||
Color getTextNonSelectionColor();
|
||||
|
||||
Color getTextSelectionColor();
|
||||
|
||||
@Override
|
||||
default Color getForeground(JTree tree) {
|
||||
return getTextNonSelectionColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
default Color getSelForeground(JTree tree) {
|
||||
return getTextSelectionColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
/* ###
|
||||
* 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.gui.model;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.gui.MultiProviderSaveBehavior;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
@PluginInfo(
|
||||
shortDescription = "Debugger model browser",
|
||||
description = "GUI to browse objects recorded to the trace",
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.STABLE,
|
||||
eventsConsumed = {
|
||||
TraceActivatedPluginEvent.class,
|
||||
TraceClosedPluginEvent.class,
|
||||
},
|
||||
servicesRequired = {
|
||||
DebuggerTraceManagerService.class,
|
||||
})
|
||||
public class DebuggerModelPlugin extends Plugin {
|
||||
|
||||
private final class ForModelMultiProviderSaveBehavior
|
||||
extends MultiProviderSaveBehavior<DebuggerModelProvider> {
|
||||
@Override
|
||||
protected DebuggerModelProvider getConnectedProvider() {
|
||||
return connectedProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<DebuggerModelProvider> getDisconnectedProviders() {
|
||||
return disconnectedProviders;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DebuggerModelProvider createDisconnectedProvider() {
|
||||
return DebuggerModelPlugin.this.createDisconnectedProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeDisconnectedProvider(DebuggerModelProvider p) {
|
||||
p.removeFromTool();
|
||||
}
|
||||
}
|
||||
|
||||
private DebuggerModelProvider connectedProvider;
|
||||
private final List<DebuggerModelProvider> disconnectedProviders = new ArrayList<>();
|
||||
private final ForModelMultiProviderSaveBehavior saveBehavior =
|
||||
new ForModelMultiProviderSaveBehavior();
|
||||
|
||||
public DebuggerModelPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
this.connectedProvider = newProvider(false);
|
||||
super.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispose() {
|
||||
tool.removeComponentProvider(connectedProvider);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
protected DebuggerModelProvider newProvider(boolean isClone) {
|
||||
return new DebuggerModelProvider(this, isClone);
|
||||
}
|
||||
|
||||
protected DebuggerModelProvider createDisconnectedProvider() {
|
||||
DebuggerModelProvider p = newProvider(true);
|
||||
synchronized (disconnectedProviders) {
|
||||
disconnectedProviders.add(p);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
public DebuggerModelProvider getConnectedProvider() {
|
||||
return connectedProvider;
|
||||
}
|
||||
|
||||
public List<DebuggerModelProvider> getDisconnectedProviders() {
|
||||
return Collections.unmodifiableList(disconnectedProviders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
connectedProvider.coordinatesActivated(ev.getActiveCoordinates());
|
||||
}
|
||||
if (event instanceof TraceClosedPluginEvent) {
|
||||
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
|
||||
traceClosed(ev.getTrace());
|
||||
}
|
||||
}
|
||||
|
||||
private void traceClosed(Trace trace) {
|
||||
connectedProvider.traceClosed(trace);
|
||||
synchronized (disconnectedProviders) {
|
||||
for (DebuggerModelProvider p : disconnectedProviders) {
|
||||
p.traceClosed(trace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void providerRemoved(DebuggerModelProvider p) {
|
||||
synchronized (disconnectedProviders) {
|
||||
disconnectedProviders.remove(p);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeConfigState(SaveState saveState) {
|
||||
saveBehavior.writeConfigState(saveState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readConfigState(SaveState saveState) {
|
||||
saveBehavior.readConfigState(saveState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDataState(SaveState saveState) {
|
||||
saveBehavior.writeDataState(saveState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readDataState(SaveState saveState) {
|
||||
saveBehavior.readDataState(saveState);
|
||||
}
|
||||
}
|
||||
+681
File diff suppressed because it is too large
Load Diff
+38
@@ -0,0 +1,38 @@
|
||||
/* ###
|
||||
* 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.gui.model;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.ComponentProvider;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
|
||||
public class DebuggerObjectActionContext extends ActionContext {
|
||||
private final List<TraceObjectValue> objectValues;
|
||||
|
||||
public DebuggerObjectActionContext(Collection<TraceObjectValue> objectValues,
|
||||
ComponentProvider provider, Component sourceComponent) {
|
||||
super(provider, sourceComponent);
|
||||
this.objectValues = List.copyOf(objectValues);
|
||||
}
|
||||
|
||||
public List<TraceObjectValue> getObjectValues() {
|
||||
return objectValues;
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
/* ###
|
||||
* 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.gui.model;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.dbg.util.PathPredicates;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
|
||||
public interface DisplaysModified {
|
||||
/**
|
||||
* Get the current trace
|
||||
*
|
||||
* @return the trace
|
||||
*/
|
||||
Trace getTrace();
|
||||
|
||||
/**
|
||||
* Get the current snap
|
||||
*
|
||||
* @return the snap
|
||||
*/
|
||||
long getSnap();
|
||||
|
||||
/**
|
||||
* Get the trace for comparison, which may be the same as the current trace
|
||||
*
|
||||
* @return the trace, or null to disable comparison
|
||||
*/
|
||||
Trace getDiffTrace();
|
||||
|
||||
/**
|
||||
* Get the snap for comparison
|
||||
*
|
||||
* @return the snap
|
||||
*/
|
||||
long getDiffSnap();
|
||||
|
||||
/**
|
||||
* Determine whether two objects differ
|
||||
*
|
||||
* <p>
|
||||
* By default the objects are considered equal if their canonical paths agree, without regard to
|
||||
* the source trace or child values. To compare child values would likely recurse all the way to
|
||||
* the leaves, which is costly and not exactly informative. This method should only be called
|
||||
* for objects at the same path, meaning the two objects have at least one path in common. If
|
||||
* this path is the canonical path, then the two objects (by default) cannot differ. This will
|
||||
* detect changes in object links, though.
|
||||
*
|
||||
* @param newObject the current object
|
||||
* @param oldObject the previous object
|
||||
* @return true if the objects differ, i.e., should be displayed in red
|
||||
*/
|
||||
default boolean isObjectsDiffer(TraceObject newObject, TraceObject oldObject) {
|
||||
if (newObject == oldObject) {
|
||||
return false;
|
||||
}
|
||||
return !Objects.equals(newObject.getCanonicalPath(), oldObject.getCanonicalPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether two values differ
|
||||
*
|
||||
* <p>
|
||||
* By default this defers to the values' Object{@link #equals(Object)} methods, or in case both
|
||||
* are of type {@link TraceObject}, to {@link #isObjectsDiffer(TraceObject, TraceObject)}. This
|
||||
* method should only be called for values at the same path.
|
||||
*
|
||||
* @param newValue the current value
|
||||
* @param oldValue the previous value
|
||||
* @return true if the values differ, i.e., should be displayed in red
|
||||
*/
|
||||
default boolean isValuesDiffer(Object newValue, Object oldValue) {
|
||||
if (newValue instanceof TraceObject && oldValue instanceof TraceObject) {
|
||||
return isObjectsDiffer((TraceObject) newValue, (TraceObject) oldValue);
|
||||
}
|
||||
return !Objects.equals(newValue, oldValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether two object values (edges) differ
|
||||
*
|
||||
* <p>
|
||||
* By default, this behaves as in {@link Objects#equals(Object)}, deferring to
|
||||
* {@link #isValuesDiffer(Object, Object)}. Note that newEdge can be null because span may
|
||||
* include more than the current snap. It will be null for edges that are displayed but do not
|
||||
* contains the current snap.
|
||||
*
|
||||
* @param newEdge the current edge, possibly null
|
||||
* @param oldEdge the previous edge, possibly null
|
||||
* @return true if the edges' values differ
|
||||
*/
|
||||
default boolean isEdgesDiffer(TraceObjectValue newEdge, TraceObjectValue oldEdge) {
|
||||
if (newEdge == oldEdge) { // Covers case where both are null
|
||||
return false;
|
||||
}
|
||||
if (newEdge == null || oldEdge == null) {
|
||||
return true;
|
||||
}
|
||||
return isValuesDiffer(newEdge.getValue(), oldEdge.getValue());
|
||||
}
|
||||
|
||||
default boolean isValueModified(TraceObjectValue value) {
|
||||
if (value == null || value.getParent() == null) {
|
||||
return false;
|
||||
}
|
||||
Trace diffTrace = getDiffTrace();
|
||||
if (diffTrace == null) {
|
||||
return false;
|
||||
}
|
||||
Trace trace = getTrace();
|
||||
long snap = getSnap();
|
||||
long diffSnap = getDiffSnap();
|
||||
if (diffTrace == trace && diffSnap == snap) {
|
||||
return false;
|
||||
}
|
||||
if (diffTrace == trace) {
|
||||
boolean newContains = value.getLifespan().contains(snap);
|
||||
boolean oldContains = value.getLifespan().contains(diffSnap);
|
||||
if (newContains == oldContains) {
|
||||
return newContains ? isEdgesDiffer(value, value) : true;
|
||||
}
|
||||
TraceObjectValue diffEdge = value.getParent().getValue(diffSnap, value.getEntryKey());
|
||||
return isEdgesDiffer(newContains ? value : null, diffEdge);
|
||||
}
|
||||
TraceObjectValue diffEdge = diffTrace.getObjectManager()
|
||||
.getValuePaths(Range.singleton(diffSnap),
|
||||
PathPredicates.pattern(value.getCanonicalPath().getKeyList()))
|
||||
.findAny()
|
||||
.map(p -> p.getLastEntry())
|
||||
.orElse(null);
|
||||
return isEdgesDiffer(value, diffEdge);
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/* ###
|
||||
* 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.gui.model;
|
||||
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
|
||||
public interface DisplaysObjectValues {
|
||||
long getSnap();
|
||||
|
||||
default String getNullDisplay() {
|
||||
return "";
|
||||
}
|
||||
|
||||
default String getPrimitiveValueDisplay(Object value) {
|
||||
assert !(value instanceof TraceObject);
|
||||
assert !(value instanceof TraceObjectValue);
|
||||
// TODO: Choose decimal or hex for integral types?
|
||||
if (value == null) {
|
||||
return getNullDisplay();
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
default String getPrimitiveEdgeType(TraceObjectValue edge) {
|
||||
return edge.getTargetSchema().getName() + ":" + edge.getValue().getClass().getSimpleName();
|
||||
}
|
||||
|
||||
default String getPrimitiveEdgeToolTip(TraceObjectValue edge) {
|
||||
return getPrimitiveValueDisplay(edge.getValue()) + " (" + getPrimitiveEdgeType(edge) + ")";
|
||||
}
|
||||
|
||||
default String getObjectLinkDisplay(TraceObjectValue edge) {
|
||||
return getObjectDisplay(edge);
|
||||
}
|
||||
|
||||
default String getObjectType(TraceObjectValue edge) {
|
||||
TraceObject object = edge.getChild();
|
||||
return object.getTargetSchema().getName().toString();
|
||||
}
|
||||
|
||||
default String getObjectLinkToolTip(TraceObjectValue edge) {
|
||||
return "Link to " + getObjectToolTip(edge);
|
||||
}
|
||||
|
||||
default String getRawObjectDisplay(TraceObjectValue edge) {
|
||||
TraceObject object = edge.getChild();
|
||||
if (object.isRoot()) {
|
||||
return "<root>";
|
||||
}
|
||||
return object.getCanonicalPath().toString();
|
||||
}
|
||||
|
||||
default String getObjectDisplay(TraceObjectValue edge) {
|
||||
TraceObject object = edge.getChild();
|
||||
TraceObjectValue displayAttr =
|
||||
object.getAttribute(getSnap(), TargetObject.DISPLAY_ATTRIBUTE_NAME);
|
||||
if (displayAttr != null) {
|
||||
return displayAttr.getValue().toString();
|
||||
}
|
||||
return getRawObjectDisplay(edge);
|
||||
}
|
||||
|
||||
default String getObjectToolTip(TraceObjectValue edge) {
|
||||
String display = getObjectDisplay(edge);
|
||||
String raw = getRawObjectDisplay(edge);
|
||||
if (display.equals(raw)) {
|
||||
return display + " (" + getObjectType(edge) + ")";
|
||||
}
|
||||
return display + " (" + getObjectType(edge) + ":" + raw + ")";
|
||||
}
|
||||
|
||||
default String getEdgeDisplay(TraceObjectValue edge) {
|
||||
if (edge == null) {
|
||||
return "";
|
||||
}
|
||||
if (edge.isCanonical()) {
|
||||
return getObjectDisplay(edge);
|
||||
}
|
||||
if (edge.isObject()) {
|
||||
return getObjectLinkDisplay(edge);
|
||||
}
|
||||
return getPrimitiveValueDisplay(edge.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an HTML string representing how the edge's value should be displayed
|
||||
*
|
||||
* @return the display string
|
||||
*/
|
||||
default String getEdgeHtmlDisplay(TraceObjectValue edge) {
|
||||
if (edge == null) {
|
||||
return "";
|
||||
}
|
||||
if (!edge.isObject()) {
|
||||
return "<html>" + HTMLUtilities.escapeHTML(getPrimitiveValueDisplay(edge.getValue()));
|
||||
}
|
||||
if (edge.isCanonical()) {
|
||||
return "<html>" + HTMLUtilities.escapeHTML(getObjectDisplay(edge));
|
||||
}
|
||||
return "<html><em>" + HTMLUtilities.escapeHTML(getObjectLinkDisplay(edge)) + "</em>";
|
||||
}
|
||||
|
||||
default String getEdgeToolTip(TraceObjectValue edge) {
|
||||
if (edge == null) {
|
||||
return null;
|
||||
}
|
||||
if (edge.isCanonical()) {
|
||||
return getObjectToolTip(edge);
|
||||
}
|
||||
if (edge.isObject()) {
|
||||
return getObjectLinkToolTip(edge);
|
||||
}
|
||||
return getPrimitiveEdgeToolTip(edge);
|
||||
}
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
/* ###
|
||||
* 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.gui.model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
||||
import ghidra.dbg.util.*;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.*;
|
||||
|
||||
public class ModelQuery {
|
||||
// TODO: A more capable query language, e.g., with WHERE clauses.
|
||||
// Could also want math expressions for the conditionals... Hmm.
|
||||
// They need to be user enterable, so just a Java API won't suffice.
|
||||
|
||||
public static ModelQuery parse(String queryString) {
|
||||
return new ModelQuery(PathPredicates.parse(queryString));
|
||||
}
|
||||
|
||||
public static ModelQuery elementsOf(TraceObjectKeyPath path) {
|
||||
return new ModelQuery(new PathPattern(PathUtils.extend(path.getKeyList(), "[]")));
|
||||
}
|
||||
|
||||
public static ModelQuery attributesOf(TraceObjectKeyPath path) {
|
||||
return new ModelQuery(new PathPattern(PathUtils.extend(path.getKeyList(), "")));
|
||||
}
|
||||
|
||||
private final PathPredicates predicates;
|
||||
|
||||
/**
|
||||
* TODO: This should probably be more capable, but for now, just support simple path patterns
|
||||
*
|
||||
* @param predicates the patterns
|
||||
*/
|
||||
public ModelQuery(PathPredicates predicates) {
|
||||
this.predicates = predicates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "<ModelQuery: " + predicates.toString() + ">";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof ModelQuery)) {
|
||||
return false;
|
||||
}
|
||||
ModelQuery that = (ModelQuery) obj;
|
||||
if (!Objects.equals(this.predicates, that.predicates)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the query as a string as in {@link #parse(String)}
|
||||
*
|
||||
* @return the string
|
||||
*/
|
||||
public String toQueryString() {
|
||||
return predicates.getSingletonPattern().toPatternString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query
|
||||
*
|
||||
* @param trace the data source
|
||||
* @param span the span of snapshots to search, usually all or a singleton
|
||||
* @return the stream of resulting objects
|
||||
*/
|
||||
public Stream<TraceObject> streamObjects(Trace trace, Range<Long> span) {
|
||||
TraceObjectManager objects = trace.getObjectManager();
|
||||
TraceObject root = objects.getRootObject();
|
||||
return objects.getValuePaths(span, predicates)
|
||||
.map(p -> p.getDestinationValue(root))
|
||||
.filter(v -> v instanceof TraceObject)
|
||||
.map(v -> (TraceObject) v);
|
||||
}
|
||||
|
||||
public Stream<TraceObjectValue> streamValues(Trace trace, Range<Long> span) {
|
||||
TraceObjectManager objects = trace.getObjectManager();
|
||||
return objects.getValuePaths(span, predicates).map(p -> {
|
||||
TraceObjectValue last = p.getLastEntry();
|
||||
return last == null ? objects.getRootObject().getCanonicalParent(0) : last;
|
||||
});
|
||||
}
|
||||
|
||||
public Stream<TraceObjectValPath> streamPaths(Trace trace, Range<Long> span) {
|
||||
return trace.getObjectManager().getValuePaths(span, predicates).map(p -> p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the named attributes for resulting objects, according to the schema
|
||||
*
|
||||
* <p>
|
||||
* This does not include the "default attribute schema."
|
||||
*
|
||||
* @param trace the data source
|
||||
* @return the list of attributes
|
||||
*/
|
||||
public Stream<AttributeSchema> computeAttributes(Trace trace) {
|
||||
TraceObjectManager objects = trace.getObjectManager();
|
||||
TargetObjectSchema schema =
|
||||
objects.getRootSchema().getSuccessorSchema(predicates.getSingletonPattern().asPath());
|
||||
return schema.getAttributeSchemas()
|
||||
.values()
|
||||
.stream()
|
||||
.filter(as -> !"".equals(as.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this query would include the given value in its result
|
||||
*
|
||||
* <p>
|
||||
* More precisely, determine whether it would traverse the given value, accept it, and include
|
||||
* its child in the result. It's possible the child could be included via another value, but
|
||||
* this only considers the given value.
|
||||
*
|
||||
* @param span the span to consider
|
||||
* @param value the value to examine
|
||||
* @return true if the value would be accepted
|
||||
*/
|
||||
public boolean includes(Range<Long> span, TraceObjectValue value) {
|
||||
List<String> path = predicates.getSingletonPattern().asPath();
|
||||
if (path.isEmpty()) {
|
||||
return value.getParent() == null;
|
||||
}
|
||||
if (!PathPredicates.keyMatches(PathUtils.getKey(path), value.getEntryKey())) {
|
||||
return false;
|
||||
}
|
||||
if (!DBTraceUtils.intersect(span, value.getLifespan())) {
|
||||
return false;
|
||||
}
|
||||
TraceObject parent = value.getParent();
|
||||
if (parent == null) {
|
||||
return false;
|
||||
}
|
||||
return parent.getAncestors(span, predicates.removeRight(1))
|
||||
.anyMatch(v -> v.getSource(parent).isRoot());
|
||||
}
|
||||
}
|
||||
+412
@@ -0,0 +1,412 @@
|
||||
/* ###
|
||||
* 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.gui.model;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
|
||||
import docking.widgets.table.DynamicTableColumn;
|
||||
import docking.widgets.table.TableColumnDescriptor;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||
import ghidra.app.plugin.core.debug.gui.model.columns.*;
|
||||
import ghidra.dbg.target.schema.SchemaContext;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
|
||||
public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
||||
/** Initialized in {@link #createTableColumnDescriptor()}, which precedes this. */
|
||||
private TraceValueValColumn valueColumn;
|
||||
private TraceValueLifePlotColumn lifePlotColumn;
|
||||
|
||||
protected static Stream<? extends TraceObjectValue> distinctCanonical(
|
||||
Stream<? extends TraceObjectValue> stream) {
|
||||
Set<TraceObject> seen = new HashSet<>();
|
||||
return stream.filter(value -> {
|
||||
if (!value.isCanonical()) {
|
||||
return true;
|
||||
}
|
||||
return seen.add(value.getChild());
|
||||
});
|
||||
}
|
||||
|
||||
public interface ValueRow {
|
||||
String getKey();
|
||||
|
||||
RangeSet<Long> getLife();
|
||||
|
||||
TraceObjectValue getValue();
|
||||
|
||||
/**
|
||||
* Get a non-HTML string representing how this row's value should be sorted, filtered, etc.
|
||||
*
|
||||
* @return the display string
|
||||
*/
|
||||
String getDisplay();
|
||||
|
||||
/**
|
||||
* Get an HTML string representing how this row's value should be displayed
|
||||
*
|
||||
* @return the display string
|
||||
*/
|
||||
String getHtmlDisplay();
|
||||
|
||||
String getToolTip();
|
||||
|
||||
/**
|
||||
* Determine whether the value in the row has changed since the diff coordinates
|
||||
*
|
||||
* @return true if they differ, i.e., should be rendered in red
|
||||
*/
|
||||
boolean isModified();
|
||||
|
||||
TraceObjectValue getAttribute(String attributeName);
|
||||
|
||||
String getAttributeDisplay(String attributeName);
|
||||
|
||||
String getAttributeHtmlDisplay(String attributeName);
|
||||
|
||||
String getAttributeToolTip(String attributeName);
|
||||
|
||||
boolean isAttributeModified(String attributeName);
|
||||
|
||||
}
|
||||
|
||||
protected abstract class AbstractValueRow implements ValueRow {
|
||||
protected final TraceObjectValue value;
|
||||
|
||||
public AbstractValueRow(TraceObjectValue value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObjectValue getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return value.getEntryKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RangeSet<Long> getLife() {
|
||||
RangeSet<Long> life = TreeRangeSet.create();
|
||||
life.add(value.getLifespan());
|
||||
return life;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return isValueModified(getValue());
|
||||
}
|
||||
}
|
||||
|
||||
protected class PrimitiveRow extends AbstractValueRow {
|
||||
public PrimitiveRow(TraceObjectValue value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplay() {
|
||||
return display.getPrimitiveValueDisplay(value.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHtmlDisplay() {
|
||||
return "<html>" +
|
||||
HTMLUtilities.escapeHTML(display.getPrimitiveValueDisplay(value.getValue()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
return display.getPrimitiveEdgeToolTip(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObjectValue getAttribute(String attributeName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributeDisplay(String attributeName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributeHtmlDisplay(String attributeName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributeToolTip(String attributeName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAttributeModified(String attributeName) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected class ObjectRow extends AbstractValueRow {
|
||||
private final TraceObject object;
|
||||
|
||||
public ObjectRow(TraceObjectValue value) {
|
||||
super(value);
|
||||
this.object = value.getChild();
|
||||
}
|
||||
|
||||
public TraceObject getTraceObject() {
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplay() {
|
||||
return display.getEdgeDisplay(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHtmlDisplay() {
|
||||
return display.getEdgeHtmlDisplay(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
return display.getEdgeToolTip(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObjectValue getAttribute(String attributeName) {
|
||||
return object.getAttribute(getSnap(), attributeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributeDisplay(String attributeName) {
|
||||
return display.getEdgeDisplay(getAttribute(attributeName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributeHtmlDisplay(String attributeName) {
|
||||
return display.getEdgeHtmlDisplay(getAttribute(attributeName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributeToolTip(String attributeName) {
|
||||
return display.getEdgeToolTip(getAttribute(attributeName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAttributeModified(String attributeName) {
|
||||
return isValueModified(getAttribute(attributeName));
|
||||
}
|
||||
}
|
||||
|
||||
protected ValueRow rowForValue(TraceObjectValue value) {
|
||||
if (value.getValue() instanceof TraceObject) {
|
||||
return new ObjectRow(value);
|
||||
}
|
||||
return new PrimitiveRow(value);
|
||||
}
|
||||
|
||||
protected static class ColKey {
|
||||
public static ColKey fromSchema(SchemaContext ctx, AttributeSchema attributeSchema) {
|
||||
String name = attributeSchema.getName();
|
||||
Class<?> type = TraceValueObjectAttributeColumn.computeColumnType(ctx, attributeSchema);
|
||||
return new ColKey(name, type);
|
||||
}
|
||||
|
||||
private final String name;
|
||||
private final Class<?> type;
|
||||
private final int hash;
|
||||
|
||||
public ColKey(String name, Class<?> type) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.hash = Objects.hash(name, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof ColKey)) {
|
||||
return false;
|
||||
}
|
||||
ColKey that = (ColKey) obj;
|
||||
if (!Objects.equals(this.name, that.name)) {
|
||||
return false;
|
||||
}
|
||||
if (this.type != that.type) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Save and restore these between sessions, esp., their settings
|
||||
private Map<ColKey, TraceValueObjectAttributeColumn> columnCache = new HashMap<>();
|
||||
|
||||
protected ObjectTableModel(Plugin plugin) {
|
||||
super("Object Model", plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void traceChanged() {
|
||||
reloadAttributeColumns();
|
||||
updateTimelineMax();
|
||||
super.traceChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void queryChanged() {
|
||||
reloadAttributeColumns();
|
||||
super.queryChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showHiddenChanged() {
|
||||
reloadAttributeColumns();
|
||||
super.showHiddenChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void maxSnapChanged() {
|
||||
updateTimelineMax();
|
||||
refresh();
|
||||
}
|
||||
|
||||
protected void updateTimelineMax() {
|
||||
Long max = getTrace() == null ? null : getTrace().getTimeManager().getMaxSnap();
|
||||
Range<Long> fullRange = Range.closed(0L, max == null ? 1 : max + 1);
|
||||
lifePlotColumn.setFullRange(fullRange);
|
||||
}
|
||||
|
||||
protected List<AttributeSchema> computeAttributeSchemas() {
|
||||
Trace trace = getTrace();
|
||||
ModelQuery query = getQuery();
|
||||
if (trace == null || query == null) {
|
||||
return List.of();
|
||||
}
|
||||
TargetObjectSchema rootSchema = trace.getObjectManager().getRootSchema();
|
||||
if (rootSchema == null) {
|
||||
return List.of();
|
||||
}
|
||||
SchemaContext ctx = rootSchema.getContext();
|
||||
return query.computeAttributes(trace)
|
||||
.filter(a -> isShowHidden() || !a.isHidden())
|
||||
.filter(a -> !ctx.getSchema(a.getSchema()).isCanonicalContainer())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
protected void reloadAttributeColumns() {
|
||||
List<AttributeSchema> attributes;
|
||||
Trace trace = getTrace();
|
||||
ModelQuery query = getQuery();
|
||||
if (trace == null || query == null || trace.getObjectManager().getRootSchema() == null) {
|
||||
attributes = List.of();
|
||||
}
|
||||
else {
|
||||
SchemaContext ctx = trace.getObjectManager().getRootSchema().getContext();
|
||||
attributes = query.computeAttributes(trace)
|
||||
.filter(a -> isShowHidden() || !a.isHidden())
|
||||
.filter(a -> !ctx.getSchema(a.getSchema()).isCanonicalContainer())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
resyncAttributeColumns(attributes);
|
||||
}
|
||||
|
||||
protected Set<DynamicTableColumn<ValueRow, ?, ?>> computeAttributeColumns(
|
||||
Collection<AttributeSchema> attributes) {
|
||||
Trace trace = getTrace();
|
||||
if (trace == null) {
|
||||
return Set.of();
|
||||
}
|
||||
TargetObjectSchema rootSchema = trace.getObjectManager().getRootSchema();
|
||||
if (rootSchema == null) {
|
||||
return Set.of();
|
||||
}
|
||||
SchemaContext ctx = rootSchema.getContext();
|
||||
return attributes.stream()
|
||||
.map(as -> columnCache.computeIfAbsent(ColKey.fromSchema(ctx, as),
|
||||
ck -> TraceValueObjectAttributeColumn.fromSchema(ctx, as)))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
protected void resyncAttributeColumns(Collection<AttributeSchema> attributes) {
|
||||
Set<DynamicTableColumn<ValueRow, ?, ?>> columns =
|
||||
new HashSet<>(computeAttributeColumns(attributes));
|
||||
Set<DynamicTableColumn<ValueRow, ?, ?>> toRemove = new HashSet<>();
|
||||
for (int i = 0; i < getColumnCount(); i++) {
|
||||
DynamicTableColumn<ValueRow, ?, ?> exists = getColumn(i);
|
||||
if (!(exists instanceof TraceValueObjectAttributeColumn)) {
|
||||
continue;
|
||||
}
|
||||
if (!columns.remove(exists)) {
|
||||
toRemove.add(exists);
|
||||
}
|
||||
}
|
||||
removeTableColumns(toRemove);
|
||||
addTableColumns(columns);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Stream<ValueRow> streamRows(Trace trace, ModelQuery query, Range<Long> span) {
|
||||
return distinctCanonical(query.streamValues(trace, span)
|
||||
.filter(v -> isShowHidden() || !v.isHidden()))
|
||||
.map(this::rowForValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<ValueRow> createTableColumnDescriptor() {
|
||||
TableColumnDescriptor<ValueRow> descriptor = new TableColumnDescriptor<>();
|
||||
descriptor.addVisibleColumn(new TraceValueKeyColumn());
|
||||
descriptor.addVisibleColumn(valueColumn = new TraceValueValColumn());
|
||||
descriptor.addVisibleColumn(new TraceValueLifeColumn());
|
||||
descriptor.addHiddenColumn(lifePlotColumn = new TraceValueLifePlotColumn());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDiffColor(Color diffColor) {
|
||||
valueColumn.setDiffColor(diffColor);
|
||||
for (TraceValueObjectAttributeColumn column : columnCache.values()) {
|
||||
column.setDiffColor(diffColor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDiffColorSel(Color diffColorSel) {
|
||||
valueColumn.setDiffColorSel(diffColorSel);
|
||||
for (TraceValueObjectAttributeColumn column : columnCache.values()) {
|
||||
column.setDiffColorSel(diffColorSel);
|
||||
}
|
||||
}
|
||||
}
|
||||
+777
File diff suppressed because it is too large
Load Diff
+30
@@ -0,0 +1,30 @@
|
||||
/* ###
|
||||
* 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.gui.model;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
|
||||
public class ObjectsTablePanel extends AbstractQueryTablePanel<ValueRow> {
|
||||
public ObjectsTablePanel(Plugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractQueryTableModel<ValueRow> createModel(Plugin plugin) {
|
||||
return new ObjectTableModel(plugin);
|
||||
}
|
||||
}
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
/* ###
|
||||
* 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.gui.model;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.stream.*;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTree;
|
||||
import javax.swing.tree.TreePath;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import docking.widgets.tree.*;
|
||||
import docking.widgets.tree.support.GTreeRenderer;
|
||||
import docking.widgets.tree.support.GTreeSelectionListener;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.AbstractNode;
|
||||
import ghidra.trace.model.target.TraceObjectKeyPath;
|
||||
|
||||
public class ObjectsTreePanel extends JPanel {
|
||||
|
||||
protected class ObjectsTreeRenderer extends GTreeRenderer implements ColorsModified.InTree {
|
||||
{
|
||||
setHTMLRenderingEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
|
||||
boolean expanded, boolean leaf, int row, boolean hasFocus) {
|
||||
super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row,
|
||||
hasFocus);
|
||||
if (!(value instanceof AbstractNode)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
AbstractNode node = (AbstractNode) value;
|
||||
setForeground(getForegroundFor(tree, node.isModified(), selected));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getDiffForeground(JTree tree) {
|
||||
return diffColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getDiffSelForeground(JTree tree) {
|
||||
return diffColorSel;
|
||||
}
|
||||
}
|
||||
|
||||
protected final ObjectTreeModel treeModel;
|
||||
protected final GTree tree;
|
||||
|
||||
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||
protected boolean limitToSnap = true;
|
||||
protected boolean showHidden = false;
|
||||
protected boolean showPrimitives = false;
|
||||
protected boolean showMethods = false;
|
||||
|
||||
protected Color diffColor = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED;
|
||||
protected Color diffColorSel = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED_SEL;
|
||||
|
||||
public ObjectsTreePanel() {
|
||||
super(new BorderLayout());
|
||||
treeModel = createModel();
|
||||
tree = new GTree(treeModel.getRoot());
|
||||
|
||||
tree.setCellRenderer(new ObjectsTreeRenderer());
|
||||
add(tree, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
protected ObjectTreeModel createModel() {
|
||||
return new ObjectTreeModel();
|
||||
}
|
||||
|
||||
protected class KeepTreeState implements AutoCloseable {
|
||||
private final GTreeState state;
|
||||
|
||||
public KeepTreeState() {
|
||||
this.state = tree.getTreeState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
tree.restoreTreeState(state);
|
||||
}
|
||||
}
|
||||
|
||||
public void goToCoordinates(DebuggerCoordinates coords) {
|
||||
// TODO: thread should probably become a TraceObject once we transition
|
||||
if (DebuggerCoordinates.equalsIgnoreRecorderAndView(current, coords)) {
|
||||
return;
|
||||
}
|
||||
DebuggerCoordinates previous = current;
|
||||
this.current = coords;
|
||||
try (KeepTreeState keep = new KeepTreeState()) {
|
||||
treeModel.setDiffTrace(previous.getTrace());
|
||||
treeModel.setTrace(current.getTrace());
|
||||
treeModel.setDiffSnap(previous.getSnap());
|
||||
treeModel.setSnap(current.getSnap());
|
||||
if (limitToSnap) {
|
||||
treeModel.setSpan(Range.singleton(current.getSnap()));
|
||||
}
|
||||
tree.filterChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void setLimitToSnap(boolean limitToSnap) {
|
||||
if (this.limitToSnap == limitToSnap) {
|
||||
return;
|
||||
}
|
||||
this.limitToSnap = limitToSnap;
|
||||
try (KeepTreeState keep = new KeepTreeState()) {
|
||||
treeModel.setSpan(limitToSnap ? Range.singleton(current.getSnap()) : Range.all());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLimitToSnap() {
|
||||
return limitToSnap;
|
||||
}
|
||||
|
||||
public void setShowHidden(boolean showHidden) {
|
||||
if (this.showHidden == showHidden) {
|
||||
return;
|
||||
}
|
||||
this.showHidden = showHidden;
|
||||
try (KeepTreeState keep = new KeepTreeState()) {
|
||||
treeModel.setShowHidden(showHidden);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isShowHidden() {
|
||||
return showHidden;
|
||||
}
|
||||
|
||||
public void setShowPrimitives(boolean showPrimitives) {
|
||||
if (this.showPrimitives == showPrimitives) {
|
||||
return;
|
||||
}
|
||||
this.showPrimitives = showPrimitives;
|
||||
try (KeepTreeState keep = new KeepTreeState()) {
|
||||
treeModel.setShowPrimitives(showPrimitives);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isShowPrimitives() {
|
||||
return showPrimitives;
|
||||
}
|
||||
|
||||
public void setShowMethods(boolean showMethods) {
|
||||
if (this.showMethods == showMethods) {
|
||||
return;
|
||||
}
|
||||
this.showMethods = showMethods;
|
||||
try (KeepTreeState keep = new KeepTreeState()) {
|
||||
treeModel.setShowMethods(showMethods);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isShowMethods() {
|
||||
return showMethods;
|
||||
}
|
||||
|
||||
public void setDiffColor(Color diffColor) {
|
||||
if (Objects.equals(this.diffColor, diffColor)) {
|
||||
return;
|
||||
}
|
||||
this.diffColor = diffColor;
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void setDiffColorSel(Color diffColorSel) {
|
||||
if (Objects.equals(this.diffColorSel, diffColorSel)) {
|
||||
return;
|
||||
}
|
||||
this.diffColorSel = diffColorSel;
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void addTreeSelectionListener(GTreeSelectionListener listener) {
|
||||
tree.addGTreeSelectionListener(listener);
|
||||
}
|
||||
|
||||
public void removeTreeSelectionListener(GTreeSelectionListener listener) {
|
||||
tree.removeGTreeSelectionListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void addMouseListener(MouseListener l) {
|
||||
super.addMouseListener(l);
|
||||
// Is this a HACK?
|
||||
tree.addMouseListener(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void removeMouseListener(MouseListener l) {
|
||||
super.removeMouseListener(l);
|
||||
// HACK?
|
||||
tree.removeMouseListener(l);
|
||||
}
|
||||
|
||||
public void setSelectionMode(int selectionMode) {
|
||||
tree.getSelectionModel().setSelectionMode(selectionMode);
|
||||
}
|
||||
|
||||
public int getSelectionMode() {
|
||||
return tree.getSelectionModel().getSelectionMode();
|
||||
}
|
||||
|
||||
protected <R, A> R getItemsFromPaths(TreePath[] paths,
|
||||
Collector<? super AbstractNode, A, R> collector) {
|
||||
return Stream.of(paths)
|
||||
.map(p -> (AbstractNode) p.getLastPathComponent())
|
||||
.collect(collector);
|
||||
}
|
||||
|
||||
protected AbstractNode getItemFromPath(TreePath path) {
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
return (AbstractNode) path.getLastPathComponent();
|
||||
}
|
||||
|
||||
public List<AbstractNode> getSelectedItems() {
|
||||
return getItemsFromPaths(tree.getSelectionPaths(), Collectors.toList());
|
||||
}
|
||||
|
||||
public AbstractNode getSelectedItem() {
|
||||
return getItemFromPath(tree.getSelectionPath());
|
||||
}
|
||||
|
||||
public AbstractNode getNode(TraceObjectKeyPath path) {
|
||||
return treeModel.getNode(path);
|
||||
}
|
||||
|
||||
public void setSelectedKeyPaths(Collection<TraceObjectKeyPath> keyPaths) {
|
||||
List<GTreeNode> nodes = new ArrayList<>();
|
||||
for (TraceObjectKeyPath path : keyPaths) {
|
||||
AbstractNode node = getNode(path);
|
||||
if (node != null) {
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
tree.setSelectedNodes(nodes);
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/* ###
|
||||
* 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.gui.model;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import docking.widgets.table.TableColumnDescriptor;
|
||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||
import ghidra.app.plugin.core.debug.gui.model.columns.*;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObjectValPath;
|
||||
|
||||
public class PathTableModel extends AbstractQueryTableModel<PathRow> {
|
||||
/** Initialized in {@link #createTableColumnDescriptor()}, which precedes this. */
|
||||
private TracePathValueColumn valueColumn;
|
||||
private TracePathLastLifespanPlotColumn lifespanPlotColumn;
|
||||
|
||||
protected static Stream<? extends TraceObjectValPath> distinctKeyPath(
|
||||
Stream<? extends TraceObjectValPath> stream) {
|
||||
Set<List<String>> seen = new HashSet<>();
|
||||
return stream.filter(path -> seen.add(path.getKeyList()));
|
||||
}
|
||||
|
||||
public class PathRow {
|
||||
private final TraceObjectValPath path;
|
||||
private final Object value;
|
||||
|
||||
public PathRow(TraceObjectValPath path) {
|
||||
this.path = path;
|
||||
this.value = computeValue();
|
||||
}
|
||||
|
||||
public TraceObjectValPath getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public Object computeValue() {
|
||||
// Spare fetching the root unless it's really needed
|
||||
if (path.getLastEntry() == null) {
|
||||
return getTrace().getObjectManager().getRootObject();
|
||||
}
|
||||
return path.getDestinationValue(null);
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a non-HTML string representing how this row's value should be sorted, filtered, etc.
|
||||
*
|
||||
* @return the display string
|
||||
*/
|
||||
public String getDisplay() {
|
||||
return display.getEdgeDisplay(path.getLastEntry());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an HTML string representing how this row's value should be displayed
|
||||
*
|
||||
* @return the display string
|
||||
*/
|
||||
public String getHtmlDisplay() {
|
||||
return display.getEdgeHtmlDisplay(path.getLastEntry());
|
||||
}
|
||||
|
||||
public String getToolTip() {
|
||||
return display.getEdgeToolTip(path.getLastEntry());
|
||||
}
|
||||
|
||||
public boolean isModified() {
|
||||
return isValueModified(path.getLastEntry());
|
||||
}
|
||||
}
|
||||
|
||||
public PathTableModel(Plugin plugin) {
|
||||
super("Attribute Model", plugin);
|
||||
}
|
||||
|
||||
protected void updateTimelineMax() {
|
||||
Long max = getTrace() == null ? null : getTrace().getTimeManager().getMaxSnap();
|
||||
Range<Long> fullRange = Range.closed(0L, max == null ? 1 : max + 1);
|
||||
lifespanPlotColumn.setFullRange(fullRange);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void traceChanged() {
|
||||
updateTimelineMax();
|
||||
super.traceChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showHiddenChanged() {
|
||||
reload();
|
||||
super.showHiddenChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void maxSnapChanged() {
|
||||
updateTimelineMax();
|
||||
refresh();
|
||||
}
|
||||
|
||||
protected static boolean isAnyHidden(TraceObjectValPath path) {
|
||||
return path.getEntryList().stream().anyMatch(v -> v.isHidden());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Stream<PathRow> streamRows(Trace trace, ModelQuery query, Range<Long> span) {
|
||||
// TODO: For queries with early wildcards, this is not efficient
|
||||
// May need to incorporate filtering hidden into the query execution itself.
|
||||
return distinctKeyPath(query.streamPaths(trace, span)
|
||||
.filter(p -> isShowHidden() || !isAnyHidden(p)))
|
||||
.map(PathRow::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<PathRow> createTableColumnDescriptor() {
|
||||
TableColumnDescriptor<PathRow> descriptor = new TableColumnDescriptor<>();
|
||||
descriptor.addHiddenColumn(new TracePathStringColumn());
|
||||
descriptor.addVisibleColumn(new TracePathLastKeyColumn());
|
||||
descriptor.addVisibleColumn(valueColumn = new TracePathValueColumn());
|
||||
descriptor.addVisibleColumn(new TracePathLastLifespanColumn());
|
||||
descriptor.addHiddenColumn(lifespanPlotColumn = new TracePathLastLifespanPlotColumn());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDiffColor(Color diffColor) {
|
||||
valueColumn.setDiffColor(diffColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDiffColorSel(Color diffColorSel) {
|
||||
valueColumn.setDiffColorSel(diffColorSel);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/* ###
|
||||
* 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.gui.model;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
|
||||
public class PathsTablePanel extends AbstractQueryTablePanel<PathRow> {
|
||||
public PathsTablePanel(Plugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractQueryTableModel<PathRow> createModel(Plugin plugin) {
|
||||
return new PathTableModel(plugin);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/* ###
|
||||
* 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.gui.model.columns;
|
||||
|
||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObjectValPath;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
|
||||
public class TracePathLastKeyColumn extends AbstractDynamicTableColumn<PathRow, String, Trace> {
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Key";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(PathRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
TraceObjectValPath path = rowObject.getPath();
|
||||
TraceObjectValue lastEntry = path.getLastEntry();
|
||||
if (lastEntry == null) {
|
||||
return "<root>";
|
||||
}
|
||||
return lastEntry.getEntryKey();
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/* ###
|
||||
* 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.gui.model.columns;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
|
||||
public class TracePathLastLifespanColumn
|
||||
extends AbstractDynamicTableColumn<PathRow, Range<Long>, Trace> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Life";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range<Long> getValue(PathRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
TraceObjectValue lastEntry = rowObject.getPath().getLastEntry();
|
||||
if (lastEntry == null) {
|
||||
return Range.all();
|
||||
}
|
||||
return lastEntry.getLifespan();
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/* ###
|
||||
* 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.gui.model.columns;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||
import docking.widgets.table.RangeTableCellRenderer;
|
||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
|
||||
public class TracePathLastLifespanPlotColumn
|
||||
extends AbstractDynamicTableColumn<PathRow, Range<Long>, Trace> {
|
||||
|
||||
private final RangeTableCellRenderer<Long> cellRenderer = new RangeTableCellRenderer<>();
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Plot";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range<Long> getValue(PathRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
TraceObjectValue lastEntry = rowObject.getPath().getLastEntry();
|
||||
if (lastEntry == null) {
|
||||
return Range.all();
|
||||
}
|
||||
return lastEntry.getLifespan();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<Range<Long>> getColumnRenderer() {
|
||||
return cellRenderer;
|
||||
}
|
||||
|
||||
// TODO: header renderer
|
||||
|
||||
public void setFullRange(Range<Long> fullRange) {
|
||||
cellRenderer.setFullRange(fullRange);
|
||||
// TODO: header, too
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/* ###
|
||||
* 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.gui.model.columns;
|
||||
|
||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
public class TracePathStringColumn extends AbstractDynamicTableColumn<PathRow, String, Trace> {
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Path";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(PathRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return PathUtils.toString(rowObject.getPath().getKeyList());
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/* ###
|
||||
* 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.gui.model.columns;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.JTable;
|
||||
|
||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||
import docking.widgets.table.GTableCellRenderingData;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ColorsModified;
|
||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
|
||||
public class TracePathValueColumn extends AbstractDynamicTableColumn<PathRow, PathRow, Trace> {
|
||||
private final class ValueRenderer extends AbstractGColumnRenderer<PathRow>
|
||||
implements ColorsModified.InTable {
|
||||
{
|
||||
setHTMLRenderingEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(PathRow t, Settings settings) {
|
||||
return t.getDisplay();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
PathRow row = (PathRow) data.getValue();
|
||||
setText(row.getHtmlDisplay());
|
||||
setToolTipText(row.getToolTip());
|
||||
setForeground(getForegroundFor(data.getTable(), row.isModified(), data.isSelected()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getDiffForeground(JTable table) {
|
||||
return diffColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getDiffSelForeground(JTable table) {
|
||||
return diffColorSel;
|
||||
}
|
||||
}
|
||||
|
||||
private Color diffColor = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED;
|
||||
private Color diffColorSel = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED_SEL;
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Value";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathRow getValue(PathRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return rowObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<PathRow> getColumnRenderer() {
|
||||
return new ValueRenderer();
|
||||
}
|
||||
|
||||
public void setDiffColor(Color diffColor) {
|
||||
this.diffColor = diffColor;
|
||||
}
|
||||
|
||||
public void setDiffColorSel(Color diffColorSel) {
|
||||
this.diffColorSel = diffColorSel;
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/* ###
|
||||
* 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.gui.model.columns;
|
||||
|
||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
public class TraceValueKeyColumn extends AbstractDynamicTableColumn<ValueRow, String, Trace> {
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Key";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return rowObject.getKey();
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/* ###
|
||||
* 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.gui.model.columns;
|
||||
|
||||
import com.google.common.collect.RangeSet;
|
||||
|
||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
||||
public class TraceValueLifeColumn
|
||||
extends AbstractDynamicTableColumn<ValueRow, RangeSet<Long>, Trace> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Life";
|
||||
}
|
||||
|
||||
@Override
|
||||
public RangeSet<Long> getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return rowObject.getLife();
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/* ###
|
||||
* 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.gui.model.columns;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.collect.RangeSet;
|
||||
|
||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||
import docking.widgets.table.RangeSetTableCellRenderer;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
|
||||
public class TraceValueLifePlotColumn
|
||||
extends AbstractDynamicTableColumn<ValueRow, RangeSet<Long>, Trace> {
|
||||
|
||||
private final RangeSetTableCellRenderer<Long> cellRenderer = new RangeSetTableCellRenderer<>();
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Plot";
|
||||
}
|
||||
|
||||
@Override
|
||||
public RangeSet<Long> getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return rowObject.getLife();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<RangeSet<Long>> getColumnRenderer() {
|
||||
return cellRenderer;
|
||||
}
|
||||
|
||||
// TODO: The header renderer
|
||||
|
||||
public void setFullRange(Range<Long> fullRange) {
|
||||
cellRenderer.setFullRange(fullRange);
|
||||
// TODO: set header's full range, too
|
||||
}
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
/* ###
|
||||
* 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.gui.model.columns;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.JTable;
|
||||
|
||||
import docking.widgets.table.*;
|
||||
import docking.widgets.table.sort.ColumnRenderedValueBackupComparator;
|
||||
import docking.widgets.table.sort.DefaultColumnComparator;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ColorsModified;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||
import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet;
|
||||
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
|
||||
import ghidra.dbg.target.schema.SchemaContext;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
|
||||
public class TraceValueObjectAttributeColumn
|
||||
extends AbstractDynamicTableColumn<ValueRow, ValueRow, Trace> {
|
||||
|
||||
public class AttributeRenderer extends AbstractGColumnRenderer<ValueRow>
|
||||
implements ColorsModified.InTable {
|
||||
{
|
||||
setHTMLRenderingEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(ValueRow t, Settings settings) {
|
||||
return t.getAttributeDisplay(attributeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
ValueRow row = (ValueRow) data.getValue();
|
||||
setText(row.getAttributeHtmlDisplay(attributeName));
|
||||
setToolTipText(row.getAttributeToolTip(attributeName));
|
||||
setForeground(getForegroundFor(data.getTable(), row.isAttributeModified(attributeName),
|
||||
data.isSelected()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getDiffForeground(JTable table) {
|
||||
return diffColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getDiffSelForeground(JTable table) {
|
||||
return diffColorSel;
|
||||
}
|
||||
}
|
||||
|
||||
public static Class<?> computeColumnType(SchemaContext ctx, AttributeSchema attributeSchema) {
|
||||
TargetObjectSchema schema = ctx.getSchema(attributeSchema.getSchema());
|
||||
Class<?> type = schema.getType();
|
||||
if (type == TargetObject.class) {
|
||||
return TraceObject.class;
|
||||
}
|
||||
if (type == TargetExecutionState.class) {
|
||||
return String.class;
|
||||
}
|
||||
if (type == TargetParameterMap.class) {
|
||||
return String.class;
|
||||
}
|
||||
if (type == TargetAttachKindSet.class) {
|
||||
return String.class;
|
||||
}
|
||||
if (type == TargetBreakpointKindSet.class) {
|
||||
return String.class;
|
||||
}
|
||||
if (type == TargetStepKindSet.class) {
|
||||
return String.class;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
public static TraceValueObjectAttributeColumn fromSchema(SchemaContext ctx,
|
||||
AttributeSchema attributeSchema) {
|
||||
String name = attributeSchema.getName();
|
||||
Class<?> type = computeColumnType(ctx, attributeSchema);
|
||||
return new TraceValueObjectAttributeColumn(name, type);
|
||||
}
|
||||
|
||||
private final String attributeName;
|
||||
private final Class<?> attributeType;
|
||||
private final AttributeRenderer renderer = new AttributeRenderer();
|
||||
private final Comparator<ValueRow> comparator;
|
||||
|
||||
private Color diffColor = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED;
|
||||
private Color diffColorSel = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED_SEL;
|
||||
|
||||
public TraceValueObjectAttributeColumn(String attributeName, Class<?> attributeType) {
|
||||
this.attributeName = attributeName;
|
||||
this.attributeType = attributeType;
|
||||
this.comparator = newTypedComparator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
/**
|
||||
* TODO: These are going to have "_"-prefixed things.... Sure, they're "hidden", but if we
|
||||
* remove them, we're going to hide important info. I'd like a way in the schema to specify
|
||||
* which "interface attribute" an attribute satisfies. That way, the name can be
|
||||
* human-friendly, but the interface can still find what it needs.
|
||||
*/
|
||||
return attributeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueRow getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return rowObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<ValueRow> getColumnRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<ValueRow> getComparator(DynamicColumnTableModel<?> model, int columnIndex) {
|
||||
return comparator == null ? null
|
||||
: comparator.thenComparing(
|
||||
new ColumnRenderedValueBackupComparator<>(model, columnIndex));
|
||||
}
|
||||
|
||||
protected Object getAttributeValue(ValueRow row) {
|
||||
TraceObjectValue edge = row.getAttribute(attributeName);
|
||||
return edge == null ? null : edge.getValue();
|
||||
}
|
||||
|
||||
protected <C extends Comparable<C>> Comparator<ValueRow> newTypedComparator() {
|
||||
if (Comparable.class.isAssignableFrom(attributeType)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<C> cls = (Class<C>) attributeType.asSubclass(Comparable.class);
|
||||
Function<ValueRow, C> keyExtractor = r -> cls.cast(getAttributeValue(r));
|
||||
return Comparator.comparing(keyExtractor, new DefaultColumnComparator());
|
||||
}
|
||||
return null; // Opt for the default filter-string-based comparator
|
||||
}
|
||||
|
||||
public void setDiffColor(Color diffColor) {
|
||||
this.diffColor = diffColor;
|
||||
}
|
||||
|
||||
public void setDiffColorSel(Color diffColorSel) {
|
||||
this.diffColorSel = diffColorSel;
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/* ###
|
||||
* 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.gui.model.columns;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.util.Comparator;
|
||||
|
||||
import javax.swing.JTable;
|
||||
|
||||
import docking.widgets.table.*;
|
||||
import docking.widgets.table.sort.ColumnRenderedValueBackupComparator;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ColorsModified;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
|
||||
public class TraceValueValColumn extends AbstractDynamicTableColumn<ValueRow, ValueRow, Trace> {
|
||||
private final class ValRenderer extends AbstractGColumnRenderer<ValueRow>
|
||||
implements ColorsModified.InTable {
|
||||
{
|
||||
setHTMLRenderingEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(ValueRow t, Settings settings) {
|
||||
return t.getDisplay();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
ValueRow row = (ValueRow) data.getValue();
|
||||
setText(row.getHtmlDisplay());
|
||||
setToolTipText(row.getToolTip());
|
||||
setForeground(getForegroundFor(data.getTable(), row.isModified(), data.isSelected()));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getDiffForeground(JTable table) {
|
||||
return diffColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getDiffSelForeground(JTable table) {
|
||||
return diffColorSel;
|
||||
}
|
||||
}
|
||||
|
||||
private Color diffColor = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED;
|
||||
private Color diffColorSel = DebuggerResources.DEFAULT_COLOR_VALUE_CHANGED_SEL;
|
||||
private final ValRenderer renderer = new ValRenderer();
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Value";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueRow getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
return rowObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<ValueRow> getColumnRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<ValueRow> getComparator(DynamicColumnTableModel<?> model, int columnIndex) {
|
||||
return getComparator()
|
||||
.thenComparing(new ColumnRenderedValueBackupComparator<>(model, columnIndex));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Comparator<ValueRow> getComparator() {
|
||||
return (r1, r2) -> {
|
||||
Object v1 = r1.getValue().getValue();
|
||||
Object v2 = r2.getValue().getValue();
|
||||
if (v1 instanceof Comparable) {
|
||||
if (v1.getClass() == v2.getClass()) {
|
||||
return ((Comparable<Object>) v1).compareTo(v2);
|
||||
}
|
||||
}
|
||||
return 0; // Defer to backup comparator
|
||||
};
|
||||
}
|
||||
|
||||
public void setDiffColor(Color diffColor) {
|
||||
this.diffColor = diffColor;
|
||||
}
|
||||
|
||||
public void setDiffColorSel(Color diffColorSel) {
|
||||
this.diffColorSel = diffColorSel;
|
||||
}
|
||||
}
|
||||
+11
-5
@@ -608,9 +608,9 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||
.onAction(c -> selectRegistersActivated())
|
||||
.buildAndInstallLocal(this);
|
||||
if (!isClone) {
|
||||
actionCreateSnapshot = DebuggerResources.CreateSnapshotAction.builder(plugin)
|
||||
actionCreateSnapshot = DebuggerResources.CloneWindowAction.builder(plugin)
|
||||
.enabledWhen(c -> current.getThread() != null)
|
||||
.onAction(c -> createSnapshotActivated())
|
||||
.onAction(c -> cloneWindowActivated())
|
||||
.buildAndInstallLocal(this);
|
||||
}
|
||||
actionEnableEdits = DebuggerResources.EnableEditsAction.builder(plugin)
|
||||
@@ -639,7 +639,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||
tool.showDialog(availableRegsDialog);
|
||||
}
|
||||
|
||||
private void createSnapshotActivated() {
|
||||
private void cloneWindowActivated() {
|
||||
DebuggerRegistersProvider clone = cloneAsDisconnected();
|
||||
clone.setIntraGroupPosition(WindowPosition.RIGHT);
|
||||
tool.showComponentProvider(clone, true);
|
||||
@@ -961,8 +961,14 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||
public static LinkedHashSet<Register> collectCommonRegisters(CompilerSpec cSpec) {
|
||||
Language lang = cSpec.getLanguage();
|
||||
LinkedHashSet<Register> result = new LinkedHashSet<>();
|
||||
result.add(cSpec.getStackPointer());
|
||||
result.add(lang.getProgramCounter());
|
||||
Register sp = cSpec.getStackPointer();
|
||||
if (sp != null) {
|
||||
result.add(sp);
|
||||
}
|
||||
Register pc = lang.getProgramCounter();
|
||||
if (pc != null) {
|
||||
result.add(pc);
|
||||
}
|
||||
for (Register reg : lang.getRegisters()) {
|
||||
//if (reg.getGroup() != null) {
|
||||
// continue;
|
||||
|
||||
+3
@@ -392,6 +392,9 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
|
||||
|
||||
@Override
|
||||
public TargetThread getTargetThread(TraceThread thread) {
|
||||
if (thread == null) {
|
||||
return null;
|
||||
}
|
||||
return objectRecorder.getTargetInterface(thread, TraceObjectThread.class,
|
||||
TargetThread.class);
|
||||
}
|
||||
|
||||
+22
@@ -23,6 +23,7 @@ import org.apache.commons.collections4.bidimap.DualHashBidiMap;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.target.TargetAttacher.TargetAttachKind;
|
||||
import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
@@ -75,6 +76,23 @@ class ObjectRecorder {
|
||||
return targetObject == null ? null : targetObject.obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* List the names of interfaces on the object not already covered by the schema
|
||||
*
|
||||
* @param object the object
|
||||
* @return the comma-separated list of interface names
|
||||
*/
|
||||
protected String computeExtraInterfaces(TargetObject object) {
|
||||
Set<String> result = new LinkedHashSet<>(object.getInterfaceNames());
|
||||
for (Class<? extends TargetObject> iface : object.getSchema().getInterfaces()) {
|
||||
result.remove(DebuggerObjectModel.requireIfaceName(iface));
|
||||
}
|
||||
if (result.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return result.stream().collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
protected void recordCreated(long snap, TargetObject object) {
|
||||
TraceObject traceObject;
|
||||
if (object.isRoot()) {
|
||||
@@ -91,6 +109,10 @@ class ObjectRecorder {
|
||||
Msg.error(this, "Received created for an object that already exists: " + exists);
|
||||
}
|
||||
}
|
||||
String extras = computeExtraInterfaces(object);
|
||||
// Note: null extras will erase previous value, if necessary.
|
||||
traceObject.setAttribute(Range.atLeast(snap),
|
||||
TraceObject.EXTRA_INTERFACES_ATTRIBUTE_NAME, extras);
|
||||
}
|
||||
|
||||
protected void recordInvalidated(long snap, TargetObject object) {
|
||||
|
||||
+766
File diff suppressed because it is too large
Load Diff
+61
@@ -0,0 +1,61 @@
|
||||
/* ###
|
||||
* 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.gui.model;
|
||||
|
||||
import static ghidra.app.plugin.core.debug.gui.model.DebuggerModelProviderTest.CTX;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||
import ghidra.trace.database.target.DBTraceObjectManager;
|
||||
import ghidra.trace.model.target.TraceObject.ConflictResolution;
|
||||
import ghidra.trace.model.target.TraceObjectKeyPath;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
|
||||
public class ModelQueryTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
@Test
|
||||
public void testIncludes() throws Throwable {
|
||||
createTrace();
|
||||
|
||||
ModelQuery rootQuery = ModelQuery.parse("");
|
||||
ModelQuery threadQuery = ModelQuery.parse("Processes[].Threads[]");
|
||||
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(tb.trace, "Init", true)) {
|
||||
DBTraceObjectManager objects = tb.trace.getObjectManager();
|
||||
|
||||
TraceObjectValue rootVal =
|
||||
objects.createRootObject(CTX.getSchema(new SchemaName("Session")));
|
||||
|
||||
TraceObjectValue thread0Val =
|
||||
objects.createObject(TraceObjectKeyPath.parse("Processes[0].Threads[0]"))
|
||||
.insert(Range.atLeast(0L), ConflictResolution.DENY)
|
||||
.getLastEntry();
|
||||
|
||||
assertTrue(rootQuery.includes(Range.all(), rootVal));
|
||||
assertFalse(rootQuery.includes(Range.all(), thread0Val));
|
||||
|
||||
assertFalse(threadQuery.includes(Range.all(), rootVal));
|
||||
assertTrue(threadQuery.includes(Range.all(), thread0Val));
|
||||
assertFalse(threadQuery.includes(Range.lessThan(0L), thread0Val));
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -414,7 +414,8 @@ public interface TargetObjectSchema {
|
||||
*
|
||||
* <p>
|
||||
* If this is the schema of the root object, then this gives the schema of the object at the
|
||||
* given path in the model.
|
||||
* given path in the model. This will always give a non-null result, though that result might be
|
||||
* {@link EnumerableTargetObjectSchema#VOID}.
|
||||
*
|
||||
* @param path the relative path from an object having this schema to the desired successor
|
||||
* @return the schema for the successor
|
||||
|
||||
@@ -1,81 +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.dbg.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public enum AllPathsMatcher implements PathPredicates {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public PathPredicates or(PathPredicates that) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(List<String> path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean successorCouldMatch(List<String> path, boolean strict) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ancestorMatches(List<String> path, boolean strict) {
|
||||
if (path.isEmpty() && strict) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getNextKeys(List<String> path) {
|
||||
return Set.of("", "[]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getNextNames(List<String> path) {
|
||||
return Set.of("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getNextIndices(List<String> path) {
|
||||
return Set.of("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSingletonPath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathPattern getSingletonPattern() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathPredicates applyKeys(List<String> keys) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,21 @@ public class PathMatcher implements PathPredicates {
|
||||
return String.format("<PathMatcher\n %s\n>", StringUtils.join(patterns, "\n "));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof PathMatcher)) {
|
||||
return false;
|
||||
}
|
||||
PathMatcher that = (PathMatcher) obj;
|
||||
if (!Objects.equals(this.patterns, that.patterns)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathPredicates or(PathPredicates that) {
|
||||
PathMatcher result = new PathMatcher();
|
||||
@@ -82,6 +97,11 @@ public class PathMatcher implements PathPredicates {
|
||||
return anyPattern(p -> p.ancestorMatches(path, strict));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ancestorCouldMatchRight(List<String> path, boolean strict) {
|
||||
return anyPattern(p -> p.ancestorCouldMatchRight(path, strict));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSingletonPath() {
|
||||
if (patterns.size() != 1) {
|
||||
@@ -98,12 +118,7 @@ public class PathMatcher implements PathPredicates {
|
||||
return patterns.iterator().next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getNextKeys(List<String> path) {
|
||||
Set<String> result = new HashSet<>();
|
||||
for (PathPattern pattern : patterns) {
|
||||
result.addAll(pattern.getNextKeys(path));
|
||||
}
|
||||
protected void coalesceWilds(Set<String> result) {
|
||||
if (result.contains("")) {
|
||||
result.removeIf(PathUtils::isName);
|
||||
result.add("");
|
||||
@@ -112,6 +127,15 @@ public class PathMatcher implements PathPredicates {
|
||||
result.removeIf(PathUtils::isIndex);
|
||||
result.add("[]");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getNextKeys(List<String> path) {
|
||||
Set<String> result = new HashSet<>();
|
||||
for (PathPattern pattern : patterns) {
|
||||
result.addAll(pattern.getNextKeys(path));
|
||||
}
|
||||
coalesceWilds(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -139,6 +163,16 @@ public class PathMatcher implements PathPredicates {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getPrevKeys(List<String> path) {
|
||||
Set<String> result = new HashSet<>();
|
||||
for (PathPattern pattern : patterns) {
|
||||
result.addAll(pattern.getPrevKeys(path));
|
||||
}
|
||||
coalesceWilds(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return patterns.isEmpty();
|
||||
@@ -152,4 +186,13 @@ public class PathMatcher implements PathPredicates {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathMatcher removeRight(int count) {
|
||||
PathMatcher result = new PathMatcher();
|
||||
for (PathPattern pat : patterns) {
|
||||
pat.doRemoveRight(count, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,13 +48,25 @@ public class PathPattern implements PathPredicates {
|
||||
return String.format("<PathPattern %s>", PathUtils.toString(pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this pattern to a string as in {@link PathPredicates#parse(String)}.
|
||||
*
|
||||
* @return the string
|
||||
*/
|
||||
public String toPatternString() {
|
||||
return PathUtils.toString(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof PathPattern)) {
|
||||
return false;
|
||||
}
|
||||
PathPattern that = (PathPattern) obj;
|
||||
return Objects.equals(this.pattern, that.pattern);
|
||||
if (!Objects.equals(this.pattern, that.pattern)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,6 +107,17 @@ public class PathPattern implements PathPredicates {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean matchesBackTo(List<String> path, int length) {
|
||||
int patternMax = pattern.size() - 1;
|
||||
int pathMax = path.size() - 1;
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (!PathPredicates.keyMatches(pattern.get(patternMax - i), path.get(pathMax - i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(List<String> path) {
|
||||
if (path.size() != pattern.size()) {
|
||||
@@ -125,6 +148,17 @@ public class PathPattern implements PathPredicates {
|
||||
return matchesUpTo(path, pattern.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ancestorCouldMatchRight(List<String> path, boolean strict) {
|
||||
if (path.size() > pattern.size()) {
|
||||
return false;
|
||||
}
|
||||
if (strict && path.size() == pattern.size()) {
|
||||
return false;
|
||||
}
|
||||
return matchesBackTo(path, path.size());
|
||||
}
|
||||
|
||||
protected static boolean containsWildcards(List<String> pattern) {
|
||||
for (String pat : pattern) {
|
||||
if (isWildcard(pat)) {
|
||||
@@ -142,6 +176,20 @@ public class PathPattern implements PathPredicates {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the pattern as a list of key patterns
|
||||
*
|
||||
* @return the list of key patterns
|
||||
*/
|
||||
public List<String> asPath() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of wildcard keys in this pattern
|
||||
*
|
||||
* @return the count
|
||||
*/
|
||||
public int countWildcards() {
|
||||
return (int) pattern.stream().filter(k -> isWildcard(k)).count();
|
||||
}
|
||||
@@ -192,6 +240,17 @@ public class PathPattern implements PathPredicates {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getPrevKeys(List<String> path) {
|
||||
if (path.size() >= pattern.size()) {
|
||||
return Set.of();
|
||||
}
|
||||
if (!matchesBackTo(path, path.size())) {
|
||||
return Set.of();
|
||||
}
|
||||
return Set.of(pattern.get(pattern.size() - 1 - path.size()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return false;
|
||||
@@ -254,4 +313,18 @@ public class PathPattern implements PathPredicates {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void doRemoveRight(int count, PathMatcher result) {
|
||||
if (count > pattern.size()) {
|
||||
return;
|
||||
}
|
||||
result.addPattern(pattern.subList(0, pattern.size() - count));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathMatcher removeRight(int count) {
|
||||
PathMatcher result = new PathMatcher();
|
||||
doRemoveRight(count, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,10 +54,6 @@ public interface PathPredicates {
|
||||
return new PathPattern(PathUtils.parse(pattern));
|
||||
}
|
||||
|
||||
static PathPredicates all() {
|
||||
return AllPathsMatcher.INSTANCE;
|
||||
}
|
||||
|
||||
PathPredicates or(PathPredicates that);
|
||||
|
||||
/**
|
||||
@@ -95,6 +91,18 @@ public interface PathPredicates {
|
||||
*/
|
||||
boolean ancestorMatches(List<String> path, boolean strict);
|
||||
|
||||
/**
|
||||
* Check if the given path <em>could</em> have a matching ancestor, right to left
|
||||
*
|
||||
* <p>
|
||||
* This essentially checks if the given path is a viable postfix to the matcher.
|
||||
*
|
||||
* @param path the path (postfix) to check
|
||||
* @param strict true to exclude the case where {@link #matches(List)} would return true
|
||||
* @return true if an ancestor could match, false otherwise
|
||||
*/
|
||||
boolean ancestorCouldMatchRight(List<String> path, boolean strict);
|
||||
|
||||
/**
|
||||
* Get the patterns for the next possible key
|
||||
*
|
||||
@@ -130,6 +138,17 @@ public interface PathPredicates {
|
||||
*/
|
||||
Set<String> getNextIndices(List<String> path);
|
||||
|
||||
/**
|
||||
* Get the patterns for the previous possible key (right-to-left matching)
|
||||
*
|
||||
* <p>
|
||||
* If an ancestor of the given path cannot match this pattern, the empty set is returned.
|
||||
*
|
||||
* @param path the successor path
|
||||
* @return a set of patterns where indices are enclosed in brackets ({@code [])
|
||||
*/
|
||||
Set<String> getPrevKeys(List<String> path);
|
||||
|
||||
/**
|
||||
* If this predicate is known to match only one path, i.e., no wildcards, get that path
|
||||
*
|
||||
@@ -144,6 +163,14 @@ public interface PathPredicates {
|
||||
*/
|
||||
PathPattern getSingletonPattern();
|
||||
|
||||
/**
|
||||
* Remove count elements from the right
|
||||
*
|
||||
* @param count the number of elements to remove
|
||||
* @return the resulting predicates
|
||||
*/
|
||||
PathPredicates removeRight(int count);
|
||||
|
||||
default NavigableMap<List<String>, ?> getCachedValues(TargetObject seed) {
|
||||
return getCachedValues(List.of(), seed);
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/* ###
|
||||
* 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.dbg.util;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class PathPredicatesTest {
|
||||
@Test
|
||||
public void testGetPrevKeys() {
|
||||
PathPredicates pred = PathPredicates.parse("Processes[0].Threads[].Stack");
|
||||
|
||||
assertEquals(Set.of("Stack"), pred.getPrevKeys(PathUtils.parse("")));
|
||||
assertEquals(Set.of("[]"), pred.getPrevKeys(PathUtils.parse("Stack")));
|
||||
assertEquals(Set.of("Threads"), pred.getPrevKeys(PathUtils.parse("[].Stack")));
|
||||
assertEquals(Set.of("[0]"), pred.getPrevKeys(PathUtils.parse("Threads[].Stack")));
|
||||
assertEquals(Set.of("Processes"), pred.getPrevKeys(PathUtils.parse("[0].Threads[].Stack")));
|
||||
assertEquals(Set.of(), pred.getPrevKeys(PathUtils.parse("Processes[0].Threads[].Stack")));
|
||||
|
||||
assertEquals(Set.of(),
|
||||
pred.getPrevKeys(PathUtils.parse("Foo.Processes[0].Threads[].Stack")));
|
||||
assertEquals(Set.of(), pred.getPrevKeys(PathUtils.parse("Foo")));
|
||||
assertEquals(Set.of(), pred.getPrevKeys(PathUtils.parse("[]")));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -277,7 +277,7 @@ public class DBTraceObjectBreakpointLocation
|
||||
}
|
||||
|
||||
PathMatcher procMatcher = schema.searchFor(TargetProcess.class, false);
|
||||
return object.getAncestors(getLifespan(), procMatcher)
|
||||
return object.getAncestorsRoot(getLifespan(), procMatcher)
|
||||
.flatMap(proc -> proc.getSource(object)
|
||||
.querySuccessorsInterface(getLifespan(),
|
||||
TraceObjectThread.class))
|
||||
|
||||
+34
-14
@@ -203,6 +203,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||
|
||||
@Override
|
||||
public RangeSet<Long> getLife() {
|
||||
// TODO: This should really be cached
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
RangeSet<Long> result = TreeRangeSet.create();
|
||||
// NOTE: connected ranges should already be coalesced
|
||||
@@ -220,19 +221,20 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||
return manager.doGetObject(path.parent());
|
||||
}
|
||||
|
||||
protected void doInsert(Range<Long> lifespan, ConflictResolution resolution) {
|
||||
protected DBTraceObjectValPath doInsert(Range<Long> lifespan, ConflictResolution resolution) {
|
||||
if (path.isRoot()) {
|
||||
return;
|
||||
return DBTraceObjectValPath.of();
|
||||
}
|
||||
DBTraceObject parent = doCreateCanonicalParentObject();
|
||||
parent.setValue(lifespan, path.key(), this, resolution);
|
||||
parent.doInsert(lifespan, resolution);
|
||||
InternalTraceObjectValue value = parent.setValue(lifespan, path.key(), this, resolution);
|
||||
DBTraceObjectValPath path = parent.doInsert(lifespan, resolution);
|
||||
return path.append(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(Range<Long> lifespan, ConflictResolution resolution) {
|
||||
public DBTraceObjectValPath insert(Range<Long> lifespan, ConflictResolution resolution) {
|
||||
try (LockHold hold = manager.trace.lockWrite()) {
|
||||
doInsert(lifespan, resolution);
|
||||
return doInsert(lifespan, resolution);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,6 +255,9 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||
}
|
||||
|
||||
protected void doRemoveTree(Range<Long> span) {
|
||||
for (DBTraceObjectValue parent : getParents()) {
|
||||
parent.doTruncateOrDeleteAndEmitLifeChange(span);
|
||||
}
|
||||
for (InternalTraceObjectValue value : getValues()) {
|
||||
value.doTruncateOrDeleteAndEmitLifeChange(span);
|
||||
if (value.isCanonical()) {
|
||||
@@ -264,7 +269,6 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||
@Override
|
||||
public void removeTree(Range<Long> span) {
|
||||
try (LockHold hold = manager.trace.lockWrite()) {
|
||||
getCanonicalParents(span).forEach(v -> v.doTruncateOrDeleteAndEmitLifeChange(span));
|
||||
doRemoveTree(span);
|
||||
}
|
||||
}
|
||||
@@ -327,10 +331,14 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||
return ifCls.cast(ifaces.get(ifCls));
|
||||
}
|
||||
|
||||
protected Collection<? extends DBTraceObjectValue> doGetParents() {
|
||||
return manager.valuesByChild.get(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends DBTraceObjectValue> getParents() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return manager.valuesByChild.get(this);
|
||||
return doGetParents();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,23 +634,35 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<? extends DBTraceObjectValPath> getAncestors(
|
||||
public Stream<? extends TraceObjectValPath> getAncestors(Range<Long> span,
|
||||
PathPredicates relativePredicates) {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
Stream<? extends DBTraceObjectValPath> ancestors =
|
||||
doStreamVisitor(span, new InternalAncestorsRelativeVisitor(relativePredicates));
|
||||
if (relativePredicates.matches(List.of())) {
|
||||
return Stream.concat(Stream.of(DBTraceObjectValPath.of()), ancestors);
|
||||
}
|
||||
return ancestors;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<? extends DBTraceObjectValPath> getAncestorsRoot(
|
||||
Range<Long> span, PathPredicates rootPredicates) {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return doStreamVisitor(span, new InternalAncestorsVisitor(rootPredicates));
|
||||
return doStreamVisitor(span, new InternalAncestorsRootVisitor(rootPredicates));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<? extends DBTraceObjectValPath> getSuccessors(
|
||||
Range<Long> span, PathPredicates relativePredicates) {
|
||||
DBTraceObjectValPath empty = DBTraceObjectValPath.of();
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
Stream<? extends DBTraceObjectValPath> succcessors =
|
||||
doStreamVisitor(span, new InternalSuccessorsVisitor(relativePredicates));
|
||||
doStreamVisitor(span, new InternalSuccessorsRelativeVisitor(relativePredicates));
|
||||
if (relativePredicates.matches(List.of())) {
|
||||
// Pre-cat the empty path (not the empty stream)
|
||||
return Stream.concat(Stream.of(empty), succcessors);
|
||||
return Stream.concat(Stream.of(DBTraceObjectValPath.of()), succcessors);
|
||||
}
|
||||
return succcessors;
|
||||
}
|
||||
@@ -794,7 +814,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
|
||||
Class<? extends TargetObject> targetIf) {
|
||||
// This is a sort of meet-in-the-middle. The type search must originate from the root
|
||||
PathMatcher matcher = getManager().getRootSchema().searchFor(targetIf, false);
|
||||
return getAncestors(span, matcher);
|
||||
return getAncestorsRoot(span, matcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+19
@@ -23,6 +23,7 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
|
||||
import ghidra.trace.database.target.DBTraceObjectValue.DBTraceObjectDBFieldCodec;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObjectKeyPath;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
import ghidra.util.LockHold;
|
||||
@@ -66,6 +67,12 @@ public class DBTraceObjectAddressRangeValue
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + ": parent=" + parent + ", key=" + entryKey +
|
||||
", lifespan=" + getLifespan() + ", value=" + getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setRecordValue(DBTraceObjectAddressRangeValue value) {
|
||||
// Nothing to do. I am the value
|
||||
@@ -121,11 +128,23 @@ public class DBTraceObjectAddressRangeValue
|
||||
throw new ClassCastException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObject() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject getChildOrNull() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObjectKeyPath getCanonicalPath() {
|
||||
try (LockHold hold = manager.trace.lockRead()) {
|
||||
return parent.getCanonicalPath().extend(entryKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCanonical() {
|
||||
return false;
|
||||
|
||||
+1
-1
@@ -368,7 +368,7 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager
|
||||
if (rootVal == null) {
|
||||
return Stream.of();
|
||||
}
|
||||
return rootVal.doStreamVisitor(span, new InternalSuccessorsVisitor(predicates));
|
||||
return rootVal.doStreamVisitor(span, new InternalSuccessorsRelativeVisitor(predicates));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+18
-1
@@ -31,6 +31,7 @@ import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.database.target.InternalTreeTraversal.Visitor;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectKeyPath;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
|
||||
@@ -275,6 +276,11 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
||||
return (DBTraceObject) getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObject() {
|
||||
return child != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject getChildOrNull() {
|
||||
return child;
|
||||
@@ -320,6 +326,10 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
||||
return InternalTreeTraversal.INSTANCE.walkValue(visitor, this, span, null);
|
||||
}
|
||||
|
||||
protected TraceObjectKeyPath doGetCanonicalPath() {
|
||||
return triple.parent.getCanonicalPath().extend(triple.key);
|
||||
}
|
||||
|
||||
protected boolean doIsCanonical() {
|
||||
if (child == null) {
|
||||
return false;
|
||||
@@ -327,7 +337,14 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
||||
if (triple.parent == null) {
|
||||
return true;
|
||||
}
|
||||
return triple.parent.getCanonicalPath().extend(triple.key).equals(child.getCanonicalPath());
|
||||
return doGetCanonicalPath().equals(child.getCanonicalPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObjectKeyPath getCanonicalPath() {
|
||||
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
|
||||
return doGetCanonicalPath();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/* ###
|
||||
* 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.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.dbg.util.PathPredicates;
|
||||
import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor;
|
||||
import ghidra.trace.database.target.InternalTreeTraversal.VisitResult;
|
||||
|
||||
public class InternalAncestorsRelativeVisitor implements SpanIntersectingVisitor {
|
||||
|
||||
protected final PathPredicates predicates;
|
||||
|
||||
public InternalAncestorsRelativeVisitor(PathPredicates predicates) {
|
||||
this.predicates = predicates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObjectValPath composePath(DBTraceObjectValPath pre,
|
||||
InternalTraceObjectValue value) {
|
||||
return pre == null ? DBTraceObjectValPath.of() : pre.prepend(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path) {
|
||||
List<String> keyList = path.getKeyList();
|
||||
return VisitResult.result(predicates.matches(keyList),
|
||||
predicates.ancestorCouldMatchRight(keyList, true) && value.getChildOrNull() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceObject continueObject(InternalTraceObjectValue value) {
|
||||
return value.getParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<? extends InternalTraceObjectValue> continueValues(DBTraceObject object,
|
||||
Range<Long> span, DBTraceObjectValPath pre) {
|
||||
Set<String> prevKeys = predicates.getPrevKeys(pre.getKeyList());
|
||||
if (prevKeys.isEmpty()) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
return object.doGetParents()
|
||||
.stream()
|
||||
.filter(v -> PathPredicates.anyMatches(prevKeys, v.getEntryKey()));
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -23,11 +23,11 @@ import ghidra.dbg.util.PathPredicates;
|
||||
import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor;
|
||||
import ghidra.trace.database.target.InternalTreeTraversal.VisitResult;
|
||||
|
||||
public class InternalAncestorsVisitor implements SpanIntersectingVisitor {
|
||||
public class InternalAncestorsRootVisitor implements SpanIntersectingVisitor {
|
||||
|
||||
protected final PathPredicates predicates;
|
||||
|
||||
public InternalAncestorsVisitor(PathPredicates predicates) {
|
||||
public InternalAncestorsRootVisitor(PathPredicates predicates) {
|
||||
this.predicates = predicates;
|
||||
}
|
||||
|
||||
+2
-2
@@ -26,11 +26,11 @@ import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor;
|
||||
import ghidra.trace.database.target.InternalTreeTraversal.VisitResult;
|
||||
|
||||
public class InternalSuccessorsVisitor implements SpanIntersectingVisitor {
|
||||
public class InternalSuccessorsRelativeVisitor implements SpanIntersectingVisitor {
|
||||
|
||||
protected final PathPredicates predicates;
|
||||
|
||||
public InternalSuccessorsVisitor(PathPredicates predicates) {
|
||||
public InternalSuccessorsRelativeVisitor(PathPredicates predicates) {
|
||||
this.predicates = predicates;
|
||||
}
|
||||
|
||||
+8
@@ -125,6 +125,14 @@ public class TraceDomainObjectListener implements DomainObjectListener {
|
||||
typedMap.put(type, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for the given event, taking the affected object, the old value, and the new value
|
||||
*
|
||||
* @param <T> the type of the affected object
|
||||
* @param <U> the type of the values
|
||||
* @param type the event type
|
||||
* @param handler the handler
|
||||
*/
|
||||
protected <T, U> void listenFor(TraceChangeType<T, U> type,
|
||||
AffectedAndValuesOnlyHandler<? super T, ? super U> handler) {
|
||||
typedMap.put(type, handler);
|
||||
|
||||
+46
-5
@@ -21,6 +21,7 @@ import java.util.stream.Stream;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.collect.RangeSet;
|
||||
|
||||
import ghidra.dbg.target.TargetMethod;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.util.PathPredicates;
|
||||
@@ -38,6 +39,8 @@ import ghidra.trace.model.TraceUniqueObject;
|
||||
* In many cases, such interfaces are just wrappers.
|
||||
*/
|
||||
public interface TraceObject extends TraceUniqueObject {
|
||||
String EXTRA_INTERFACES_ATTRIBUTE_NAME = "_extra_ifs";
|
||||
|
||||
/**
|
||||
* Get the trace containing this object
|
||||
*
|
||||
@@ -79,8 +82,9 @@ public interface TraceObject extends TraceUniqueObject {
|
||||
*
|
||||
* @param the minimum lifespan of edges from the root to this object
|
||||
* @param resolution the rule for handling duplicate keys when setting values.
|
||||
* @return the value path from root to the newly inserted object
|
||||
*/
|
||||
void insert(Range<Long> lifespan, ConflictResolution resolution);
|
||||
TraceObjectValPath insert(Range<Long> lifespan, ConflictResolution resolution);
|
||||
|
||||
/**
|
||||
* Remove this object from its canonical path for the given lifespan
|
||||
@@ -97,9 +101,9 @@ public interface TraceObject extends TraceUniqueObject {
|
||||
* Remove this object and its successors from their canonical paths for the given span
|
||||
*
|
||||
* <p>
|
||||
* Truncate the lifespans of this object's canonical parent value and all canonical values
|
||||
* succeeding this object. If a truncated value's lifespan is contained in the given span, the
|
||||
* value will be deleted.
|
||||
* Truncate the lifespans of this object's parent values and all canonical values succeeding
|
||||
* this object. If a truncated value's lifespan is contained in the given span, the value will
|
||||
* be deleted.
|
||||
*
|
||||
* @param span the span during which this object and its canonical successors should be removed
|
||||
*/
|
||||
@@ -282,9 +286,20 @@ public interface TraceObject extends TraceUniqueObject {
|
||||
* @param rootPredicates the predicates for matching path keys, relative to the root
|
||||
* @return the stream of matching paths to values
|
||||
*/
|
||||
Stream<? extends TraceObjectValPath> getAncestors(Range<Long> span,
|
||||
Stream<? extends TraceObjectValPath> getAncestorsRoot(Range<Long> span,
|
||||
PathPredicates rootPredicates);
|
||||
|
||||
/**
|
||||
* Stream all ancestor values of this object matching the given predicates, intersecting the
|
||||
* given span
|
||||
*
|
||||
* @param span a span which values along the path must intersect
|
||||
* @param relativePredicates the predicates for matching path keys, relative to this object
|
||||
* @return the stream of matching paths to values
|
||||
*/
|
||||
Stream<? extends TraceObjectValPath> getAncestors(Range<Long> span,
|
||||
PathPredicates relativePredicates);
|
||||
|
||||
/**
|
||||
* Stream all successor values of this object matching the given predicates, intersecting the
|
||||
* given span
|
||||
@@ -466,4 +481,30 @@ public interface TraceObject extends TraceUniqueObject {
|
||||
*/
|
||||
@Override
|
||||
boolean isDeleted();
|
||||
|
||||
/**
|
||||
* Check if the child represents a method at the given snap
|
||||
*
|
||||
* @param snap the snap
|
||||
* @return true if a method
|
||||
*/
|
||||
default boolean isMethod(long snap) {
|
||||
if (getTargetSchema().getInterfaces().contains(TargetMethod.class)) {
|
||||
return true;
|
||||
}
|
||||
TraceObjectValue extras = getAttribute(snap, TraceObject.EXTRA_INTERFACES_ATTRIBUTE_NAME);
|
||||
if (extras == null) {
|
||||
return false;
|
||||
}
|
||||
Object val = extras.getValue();
|
||||
if (!(val instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
String valStr = (String) val;
|
||||
// Not ideal, but it's not a substring of any other schema interface....
|
||||
if (valStr.contains("Method")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+19
-2
@@ -225,9 +225,26 @@ public final class TraceObjectKeyPath implements Comparable<TraceObjectKeyPath>
|
||||
if (!predicates.ancestorMatches(keyList, false)) {
|
||||
return Stream.of();
|
||||
}
|
||||
Stream<TraceObjectKeyPath> ancestry =
|
||||
isRoot() ? Stream.of() : parent().streamMatchingAncestry(predicates);
|
||||
if (predicates.matches(keyList)) {
|
||||
return Stream.concat(Stream.of(this), parent().streamMatchingAncestry(predicates));
|
||||
return Stream.concat(Stream.of(this), ancestry);
|
||||
}
|
||||
return parent().streamMatchingAncestry(predicates);
|
||||
return ancestry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this path is an ancestor of the given path
|
||||
*
|
||||
* <p>
|
||||
* Equivalently, check if the given path is a successor of this path. A path is considered an
|
||||
* ancestor of itself. To check for a strict ancestor, use
|
||||
* {@code this.isAncestor(that) && !this.equals(that)}.
|
||||
*
|
||||
* @param that the supposed successor to this path
|
||||
* @return true if the given path is in fact a successor
|
||||
*/
|
||||
public boolean isAncestor(TraceObjectKeyPath that) {
|
||||
return PathUtils.isAncestor(keyList, that.keyList);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user