Merge remote-tracking branch 'origin/GP-1222_Dan_traceDiff--REBASED-2--SQUASHED' into Ghidra_10.1

This commit is contained in:
ghidra1 2021-12-08 21:23:39 -05:00
commit f68c8fa992
42 changed files with 2622 additions and 518 deletions

View file

@ -31,6 +31,7 @@ dependencies {
api project(':ProposedUtils') api project(':ProposedUtils')
helpPath project(path: ':Base', configuration: 'helpPath') helpPath project(path: ':Base', configuration: 'helpPath')
helpPath project(path: ':ProgramDiff', configuration: 'helpPath')
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts') testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts') testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')

View file

@ -119,6 +119,9 @@ src/main/help/help/topics/DebuggerThreadsPlugin/images/stepinto.png||GHIDRA||||E
src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerTimePlugin/images/DebuggerTimePlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerTimePlugin/images/DebuggerTimePlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTimeSelectionDialog.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTraceViewDiffPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerWatchesPlugin/images/DebuggerWatchesPlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerWatchesPlugin/images/DebuggerWatchesPlugin.png||GHIDRA||||END|
src/main/resources/defaultTools/Debugger.tool||GHIDRA||||END| src/main/resources/defaultTools/Debugger.tool||GHIDRA||||END|
@ -184,6 +187,7 @@ src/main/resources/images/stop.png||GHIDRA||||END|
src/main/resources/images/sync_enabled.png||GHIDRA||||END| src/main/resources/images/sync_enabled.png||GHIDRA||||END|
src/main/resources/images/system-switch-user.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END| src/main/resources/images/system-switch-user.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/resources/images/table.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| src/main/resources/images/table.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/resources/images/table_relationship.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/text-xml.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END| src/main/resources/images/text-xml.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/resources/images/thread.png||GHIDRA||||END| src/main/resources/images/thread.png||GHIDRA||||END|
src/main/resources/images/time.png||FAMFAMFAM Icons - CC 2.5||||END| src/main/resources/images/time.png||FAMFAMFAM Icons - CC 2.5||||END|

View file

@ -157,6 +157,10 @@
sortgroup="p" sortgroup="p"
target="help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html" /> target="help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html" />
<tocdef id="DebuggerTraceDiffPlugin" text="Comparing Times"
sortgroup="p1"
target="help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html" />
<tocdef id="DebuggerBots" text="Bots: Workflow Automation" <tocdef id="DebuggerBots" text="Bots: Workflow Automation"
sortgroup="q" sortgroup="q"
target="help/topics/DebuggerBots/DebuggerBots.html" /> target="help/topics/DebuggerBots/DebuggerBots.html" />

View file

@ -56,15 +56,20 @@
<H2>Actions</H2> <H2>Actions</H2>
<P>The time window provides the following action:</P> <H3><A name="rename_snapshot"></A>Rename Snapshot</H3>
<P>This action is available in the <SPAN class="menu">Debugger</SPAN> menu whenever the focused
window has an associated snapshot. It will prompt for a new description for the current
snapshot. This is a shortcut to modifying the description in the time table, but can be
accessed outside of the time window.</P>
<H3><A name="hide_scratch"></A>Hide Scratch</H3> <H3><A name="hide_scratch"></A>Hide Scratch</H3>
<P>This toggle action is always available. It is enabled by default. The emulation service, <P>This toggle action is always available in the drop-down actions of the Time window. It is
which enables trace extrapolation and interpolation, writes emulated state into the trace's enabled by default. The emulation service, which enables trace extrapolation and interpolation,
"scratch space," which comprises all negative snaps. When this toggle is enabled, those writes emulated state into the trace's "scratch space," which comprises all negative snaps.
snapshots are hidden. They can be displayed by disabling this toggle. Note that navigating into When this toggle is enabled, those snapshots are hidden. They can be displayed by disabling
scratch space may cause temporary undefined behavior in some windows, and may prevent this toggle. Note that navigating into scratch space may cause temporary undefined behavior in
interaction with the target.</P> some windows, and may prevent interaction with the target.</P>
</BODY> </BODY>
</HTML> </HTML>

View file

@ -0,0 +1,158 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Debugger: Comparing Times</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
</HEAD>
<BODY lang="EN-US">
<H1><A name="plugin"></A>Debugger: Comparing Times</H1>
<TABLE width="100%">
<TBODY>
<TR>
<TD align="center" width="100%"><IMG alt="" border="1" src=
"images/DebuggerTraceViewDiffPlugin.png"></TD>
</TR>
</TBODY>
</TABLE>
<P><A name="Toggle_Header"></A>A common strategy in dynamic analysis is to compare machine
state between two points in time. To this end, to support comparison of bytes in memory, the
"trace diff" plugin extends the <A href=
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Dynamic Listing</A> to provide
side-by-side comparison of two different points in time. When active, listings for both points
in time are displayed and the byte value differences between them are highlighted. <B>NOTE:</B>
This does not compare annotations. It only compares raw byte values. Additionally, all stale
values are ignored, i.e., to show as a difference, the memory must be observed at <EM>both</EM>
points in time, and the values must differ.</P>
<P><B>NOTE:</B> This plugin only facilitates the comparison of memory displayed in listings. To
compare registers or SLEIGH expressions, use the respective windows: <A href=
"help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> and <A href=
"help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html">Watches</A>. By navigating back
and forth between two points in time, using the <A href=
"help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html">Time Window</A>, the differences are
displayed in <FONT color="red">red</FONT>.</P>
<H2>Actions</H2>
<P>The plugin adds actions to the main Dynamic Listing. When active, additional actions are
present.</P>
<H3><A name="compare"></A>Compare</H3>
<P>This action is available whenever a trace is active in the main listing. It prompts for an
alternative point in time:</P>
<TABLE width="100%">
<TBODY>
<TR>
<TD align="center" width="100%"><IMG alt="" src=
"images/DebuggerTimeSelectionDialog.png"></TD>
</TR>
</TBODY>
</TABLE>
<P>The snapshot table is exactly the same as that in the <A href=
"help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html">Time Window</A>. In most cases, simply
selecting a snapshot suffices.</P>
<P>Perhaps the most common use of this action is to identify where a given variable is stored
in memory. The trace saves a record of observed memory from the debugging session. Comparing
snapshots thus identifies changes over time; however, there is no guarantee that the desired
variable was ever observed. Assuming the general vicinity of the variable is known, e.g.,
"somewhere in the .data section," the <A href=
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html#read_memory">Read Selected
Memory</A> action can ensure its value is recorded. Of course, it can also read "all memory,"
but that operation and the follow-on comparison could take time. In general, the procedure to
locate a variable is to capture a baseline, execute the target until the variable has changed,
capture again, then compare:</P>
<OL>
<LI>Execute the target up to a baseline, and take note of the variable's value, as displayed
by the target program.</LI>
<LI>Consider naming the current snapshot for later reference, using the <A href=
"help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html#rename_snapshot">Rename Current
Snapshot</A> action. Ideally, the name should indicate the variable's value.</LI>
<LI>Select the range of memory believed to contain the variable. Consider using the <A href=
"help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html">Modules</A> or <A href=
"help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html">Regions</A> window to form the
selection.</LI>
<LI>Use the <A href=
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html#read_memory">Read Selected
Memory</A> action to ensure the variable's value is stored in the trace.</LI>
<LI>Allow the target to execute until the variable has changed. Ideally, execute as little as
necessary, so that few or no other variables change.</LI>
<LI>Execution will cause the trace to advance some number of snapshots. Once suspended, it's
a good idea to rename the current snapshot, again indicating the variable's new value and/or
the cause of its change.</LI>
<LI>Repeat the selection and capture steps to ensure the variable's new value is stored in
the trace.</LI>
<LI>Use this <B>Compare</B> action and select the baseline snapshot. It's easy to locate in
the table if named appropriately.</LI>
</OL>
<P>Assuming the variable is actually contained in the captured memory ranges, then it should be
among the differences shown. If too many differences appear, repeat the experiment. Consider
executing less code, establishing a new baseline, taking the intersection of the results, etc.
Remember, the variable's storage should encode its value.</P>
<P>Optionally, the specified time may also include emulation. See the <A href=
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html#goto_time">Go To Time</A> action
for the syntax of the <B>Time Schedule</B> expression. For simple schedules, the step buttons
provide convenient forward and backward changes to the emulation schedule. Perhaps the most
common use of this is to see what changes from executing an isolated block of code. Ideally,
the baseline is a relatively complete capture or represents the present in a live session, so
that the emulator does not depend on un-recorded state:</P>
<OL>
<LI>Execute the target up to a baseline, probably using a breakpoint at the start of the
interesting block of code.</LI>
<LI>Keeping the target alive, use the <A href=
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html#emu_trace_tick_forward">Emulate
Forward</A> and/or <A href=
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html#goto_time">Go To Time</A>
actions to reach the end of the interesting block.</LI>
<LI>Use this <B>Compare</B> action and select the baseline snapshot.</LI>
</OL>
<P>Alternatively, if the number of steps to reach the end of the block is already known, just
use the emulation expression in the <B>Compare</B> action's dialog. <B>NOTE:</B> When used this
way, the baseline snapshot will be in the left pane, and the emulated snapshot in the right,
which is opposite the result from the steps above.</P>
<P>In either case, this will highlight any memory that was modified by the emulated code. Of
course, this could also be accomplished by setting a second breakpoint and allowing the target
to execute; however, emulation does not necessarily require large memory captures. It only
observes what it needs, and its internal state contains everything that changed. Furthermore,
if establishing the baseline is difficult, emulation allows the target to remain at that
baseline. Assuming sufficient state is captured, emulation can also be performed offline,
without a live target.</P>
<H3><A name="next_diff"></A><A name="prev_diff"></A>Previous / Next Difference</H3>
<P>These actions are only present when the comparison listing is visible. Each is available
when there exists a previous or next range from the main listing's cursor. Clicking the action
navigates to the nearest address in that range.</P>
<H2><A name="colors"></A>Tool Options: Colors</H2>
<P>The difference highlight color is replicated from the <A href=
"help/topics/Diff/Diff.htm">Program Differences</A> plugin.</P>
</BODY>
</HTML>

View file

@ -241,6 +241,10 @@ public class DebuggerCoordinates {
return all(trace, recorder, thread, view, newTime, frame); return all(trace, recorder, thread, view, newTime, frame);
} }
public DebuggerCoordinates withView(TraceProgramView newView) {
return all(trace, recorder, thread, newView, time, frame);
}
public TraceSchedule getTime() { public TraceSchedule getTime() {
return time; return time;
} }

View file

@ -147,6 +147,8 @@ public interface DebuggerResources {
ImageIcon ICON_READ_MEMORY = ICON_REGIONS; ImageIcon ICON_READ_MEMORY = ICON_REGIONS;
//ResourceManager.loadImage("images/read-memory.png"); //ResourceManager.loadImage("images/read-memory.png");
ImageIcon ICON_RENAME_SNAPSHOT = ICON_TIME;
// TODO: Draw an icon // TODO: Draw an icon
ImageIcon ICON_MAP_IDENTICALLY = ResourceManager.loadImage("images/doubleArrow.png"); ImageIcon ICON_MAP_IDENTICALLY = ResourceManager.loadImage("images/doubleArrow.png");
ImageIcon ICON_MAP_MODULES = ResourceManager.loadImage("images/modules.png"); ImageIcon ICON_MAP_MODULES = ResourceManager.loadImage("images/modules.png");
@ -175,6 +177,10 @@ public interface DebuggerResources {
ImageIcon ICON_CONFIG = ResourceManager.loadImage("images/conf.png"); ImageIcon ICON_CONFIG = ResourceManager.loadImage("images/conf.png");
ImageIcon ICON_TOGGLE = ResourceManager.loadImage("images/system-switch-user.png"); ImageIcon ICON_TOGGLE = ResourceManager.loadImage("images/system-switch-user.png");
ImageIcon ICON_DIFF = ResourceManager.loadImage("images/table_relationship.png");
ImageIcon ICON_DIFF_PREV = ResourceManager.loadImage("images/up.png");
ImageIcon ICON_DIFF_NEXT = ResourceManager.loadImage("images/down.png");
HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package"); HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package");
String HELP_ANCHOR_PLUGIN = "plugin"; String HELP_ANCHOR_PLUGIN = "plugin";
@ -367,6 +373,7 @@ public interface DebuggerResources {
String GROUP_TRACE_CLOSE = "Dbg7.b. Trace Close"; String GROUP_TRACE_CLOSE = "Dbg7.b. Trace Close";
String GROUP_MAINTENANCE = "Dbg8. Maintenance"; String GROUP_MAINTENANCE = "Dbg8. Maintenance";
String GROUP_MAPPING = "Dbg9. Map Modules/Sections"; String GROUP_MAPPING = "Dbg9. Map Modules/Sections";
String GROUP_DIFF_NAV = "DiffNavigate";
static void tableRowActivationAction(GTable table, Runnable runnable) { static void tableRowActivationAction(GTable table, Runnable runnable) {
table.addMouseListener(new MouseAdapter() { table.addMouseListener(new MouseAdapter() {
@ -1587,6 +1594,26 @@ public interface DebuggerResources {
} }
} }
// TODO: Perhaps to reduce overloading of "snapshot" we should use "event" instead?
interface RenameSnapshotAction {
String NAME = "Rename Current Snapshot";
String DESCRIPTION =
"Modify the description of the snapshot (event) in the current view";
String GROUP = GROUP_TRACE;
Icon ICON = ICON_RENAME_SNAPSHOT;
String HELP_ANCHOR = "rename_snapshot";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.menuPath(DebuggerPluginPackage.NAME, NAME)
.menuGroup(GROUP, "zzz")
.keyBinding("CTRL SHIFT N")
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface SynchronizeFocusAction { interface SynchronizeFocusAction {
String NAME = "Synchronize Focus"; String NAME = "Synchronize Focus";
String DESCRIPTION = "Synchronize trace activation with debugger focus/select"; String DESCRIPTION = "Synchronize trace activation with debugger focus/select";
@ -1824,6 +1851,57 @@ public interface DebuggerResources {
} }
} }
interface CompareTimesAction {
String NAME = "Compare";
String DESCRIPTION = "Compare this point in time to another";
String GROUP = "zzz"; // Same as for "Diff" action
Icon ICON = ICON_DIFF;
String HELP_ANCHOR = "compare";
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 PrevDifferenceAction {
String NAME = "Previous Difference";
String DESCRIPTION = "Go to the previous highlighted difference";
String GROUP = GROUP_DIFF_NAV;
Icon ICON = ICON_DIFF_PREV;
String HELP_ANCHOR = "prev_diff";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarGroup(GROUP)
.toolBarIcon(ICON)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface NextDifferenceAction {
String NAME = "Next Difference";
String DESCRIPTION = "Go to the next highlighted difference";
String GROUP = GROUP_DIFF_NAV;
Icon ICON = ICON_DIFF_NEXT;
String HELP_ANCHOR = "next_diff";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarGroup(GROUP)
.toolBarIcon(ICON)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
public abstract class AbstractDebuggerConnectionsNode extends GTreeNode { public abstract class AbstractDebuggerConnectionsNode extends GTreeNode {
@Override @Override
public String getName() { public String getName() {

View file

@ -16,16 +16,22 @@
package ghidra.app.plugin.core.debug.gui; package ghidra.app.plugin.core.debug.gui;
import docking.ActionContext; import docking.ActionContext;
import ghidra.trace.model.Trace;
public class DebuggerSnapActionContext extends ActionContext { public class DebuggerSnapActionContext extends ActionContext {
private final long tick; private final Trace trace;
private final long snap;
public DebuggerSnapActionContext(long tick) { public DebuggerSnapActionContext(Trace trace, long snap) {
// TODO: Also require track object? this.trace = trace;
this.tick = tick; this.snap = snap;
} }
public long getTick() { public Trace getTrace() {
return tick; return trace;
}
public long getSnap() {
return snap;
} }
} }

View file

@ -227,7 +227,7 @@ public class DebuggerTrackLocationTrait {
protected void doSetSpec(LocationTrackingSpec spec) { protected void doSetSpec(LocationTrackingSpec spec) {
if (this.spec != spec) { if (this.spec != spec) {
this.spec = spec; this.spec = spec;
specChanged(); specChanged(spec);
} }
doTrack(); doTrack();
} }
@ -299,7 +299,7 @@ public class DebuggerTrackLocationTrait {
// Listener method // Listener method
} }
protected void specChanged() { protected void specChanged(LocationTrackingSpec spec) {
// Listener method // Listener method
} }
} }

View file

@ -0,0 +1,630 @@
/* ###
* 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.diff;
import java.awt.Color;
import java.nio.ByteBuffer;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiPredicate;
import java.util.function.Function;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
import ghidra.app.plugin.core.debug.*;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.action.DebuggerTrackLocationTrait;
import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
import ghidra.app.plugin.core.debug.gui.listing.MultiBlendedListingBackgroundColorModel;
import ghidra.app.plugin.core.debug.gui.time.DebuggerTimeSelectionDialog;
import ghidra.app.plugin.core.debug.utils.BackgroundUtils.PluginToolExecutorService;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerListingService.LocationTrackingSpecChangeListener;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.async.AsyncUtils;
import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.annotation.AutoOptionConsumed;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryManager;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.Msg;
@PluginInfo(
shortDescription = "Compare memory state between times in a trace",
description = "Provides a side-by-side diff view between snapshots (points in time) in a " +
"trace. The comparison is limited to raw bytes.",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
eventsConsumed = {
TraceClosedPluginEvent.class,
},
eventsProduced = {},
servicesRequired = {
DebuggerListingService.class,
},
servicesProvided = {})
public class DebuggerTraceViewDiffPlugin extends AbstractDebuggerPlugin {
protected static final String MARKER_NAME = "Trace Diff";
protected static final String MARKER_DESCRIPTION = "Difference between snapshots in this trace";
public static final String DIFF_COLOR_CATEGORY = "Listing Fields";
public static final String DIFF_COLOR_NAME = "Selection Colors.Difference Color";
public static final Color DEFAULT_DIFF_COLOR = new Color(255, 230, 180); // light orange
protected class ListingCoordinationListener implements CoordinatedListingPanelListener {
@Override
public boolean listingClosed() {
return endComparison();
}
@Override
public void activeProgramChanged(Program activeProgram) {
endComparison();
}
}
protected class ForAltListingTrackingTrait extends DebuggerTrackLocationTrait {
public ForAltListingTrackingTrait() {
super(DebuggerTraceViewDiffPlugin.this.getTool(), DebuggerTraceViewDiffPlugin.this,
null);
}
@Override
protected void locationTracked() {
if (altListingPanel == null) {
return;
}
// NB. Don't goTo here. The left listing controls navigation
altListingPanel.getFieldPanel().repaint();
}
}
protected class SyncAltListingTrackingSpecChangeListener
implements LocationTrackingSpecChangeListener {
@Override
public void locationTrackingSpecChanged(LocationTrackingSpec spec) {
trackingTrait.setSpec(spec);
}
}
protected class MarkerSetChangeListener implements ChangeListener {
@Override
public void stateChanged(ChangeEvent e) {
if (altListingPanel == null) {
return;
}
altListingPanel.getFieldPanel().repaint();
}
}
// @AutoServiceConsumed via method
private DebuggerListingService listingService;
@AutoServiceConsumed
private DebuggerTraceManagerService traceManager;
//@AutoServiceConsumed via method
private MarkerService markerService;
@AutoOptionConsumed(category = DIFF_COLOR_CATEGORY, name = DIFF_COLOR_NAME)
private Color diffColor = DEFAULT_DIFF_COLOR;
@SuppressWarnings("unused")
private final AutoOptions.Wiring autoOptions;
protected final DebuggerTimeSelectionDialog timeDialog;
protected ToggleDockingAction actionCompare;
protected DockingAction actionPrevDiff;
protected DockingAction actionNextDiff;
protected ListingPanel altListingPanel;
protected final ForAltListingTrackingTrait trackingTrait;
protected boolean sessionActive;
protected final ListingCoordinationListener coordinationListener =
new ListingCoordinationListener();
protected final SyncAltListingTrackingSpecChangeListener syncTrackingSpecListener =
new SyncAltListingTrackingSpecChangeListener();
protected MultiBlendedListingBackgroundColorModel colorModel;
protected final MarkerSetChangeListener markerChangeListener = new MarkerSetChangeListener();
protected MarkerServiceBackgroundColorModel markerServiceColorModel;
protected MarkerSet diffMarkersL;
protected MarkerSet diffMarkersR;
public DebuggerTraceViewDiffPlugin(PluginTool tool) {
super(tool);
autoOptions = AutoOptions.wireOptions(this);
timeDialog = new DebuggerTimeSelectionDialog(tool);
trackingTrait = new ForAltListingTrackingTrait();
createActions();
}
protected void createActions() {
actionCompare = CompareTimesAction.builder(this)
.enabled(false)
.enabledWhen(ctx -> traceManager != null && traceManager.getCurrentTrace() != null)
.onAction(this::activatedCompare)
.build();
actionPrevDiff = PrevDifferenceAction.builder(this)
.enabled(false)
.enabledWhen(ctx -> hasPrevDiff())
.onAction(ctx -> gotoPrevDiff())
.build();
actionNextDiff = NextDifferenceAction.builder(this)
.enabled(false)
.enabledWhen(ctx -> hasNextDiff())
.onAction(ctx -> gotoNextDiff())
.build();
}
protected void activatedCompare(ActionContext ctx) {
if (!actionCompare.isSelected()) {
endComparison();
return;
}
if (sessionActive) {
return;
}
DebuggerCoordinates current = traceManager.getCurrent();
TraceSchedule time = timeDialog.promptTime(current.getTrace(), current.getTime());
if (time == null) {
// Cancelled
return;
}
if (traceManager == null) {
// Can happen if tool is closed while dialog was up
return;
}
if (traceManager.getCurrentTrace() != current.getTrace()) {
Msg.warn(this, "Trace changed during time prompt. Aborting");
return;
}
// NB. startComparison will handle failure
startComparison(time);
}
/**
* Begin a snapshot/time comparison session
*
* <p>
* NOTE: This method handles asynchronous errors by popping an error dialog. Callers need not
* handle exceptional completion.
*
* @param time the alternative time
* @return a future which completes when the alternative listing and difference is presented
*/
public CompletableFuture<Void> startComparison(TraceSchedule time) {
sessionActive = true; // prevents the action from performing anything
actionCompare.setSelected(true);
DebuggerCoordinates current = traceManager.getCurrent();
DebuggerCoordinates alternate =
traceManager.resolveCoordinates(DebuggerCoordinates.time(time));
PluginToolExecutorService toolExecutorService =
new PluginToolExecutorService(tool, "Computing diff", true, true, false, 500);
return traceManager.materialize(alternate).thenApplyAsync(snap -> {
clearMarkers();
TraceProgramView altView = alternate.getTrace().getFixedProgramView(snap);
altListingPanel.setProgram(altView);
trackingTrait.goToCoordinates(alternate.withView(altView));
listingService.setListingPanel(altListingPanel);
return altView;
}, AsyncUtils.SWING_EXECUTOR).thenApplyAsync(altView -> {
return computeDiff(current.getView(), altView);
}, toolExecutorService).thenAcceptAsync(diffSet -> {
addMarkers(diffSet);
listingService.addLocalAction(actionNextDiff);
listingService.addLocalAction(actionPrevDiff);
updateActions();
}, AsyncUtils.SWING_EXECUTOR).exceptionally(ex -> {
Msg.showError(this, null, "Compare", "Could not compare trace snapshots/times", ex);
return null;
});
}
protected void updateActions() {
// May not be necessary often, since contextChanged in ListingProvider should do it
actionNextDiff.setEnabled(actionNextDiff.isEnabledForContext(null));
actionPrevDiff.setEnabled(actionPrevDiff.isEnabledForContext(null));
}
public boolean endComparison() {
sessionActive = false;
actionCompare.setSelected(false);
clearMarkers();
if (altListingPanel.getProgram() != null) {
listingService.removeListingPanel(altListingPanel);
altListingPanel.setProgram(null);
listingService.removeLocalAction(actionNextDiff);
listingService.removeLocalAction(actionPrevDiff);
return true;
}
return false;
}
protected Address getCurrentAddress() {
if (listingService == null) {
return null;
}
ProgramLocation loc = listingService.getCurrentLocation();
if (loc == null) {
return null;
}
return loc.getAddress();
}
public AddressSetView getDiffs() {
if (diffMarkersL == null) {
return null;
}
return diffMarkersL.getAddressSet();
}
protected boolean hasSeqDiff(Function<AddressSetView, AddressRange> getExtremeRange,
BiPredicate<AddressRange, Address> checkRange) {
Address cur = getCurrentAddress();
if (cur == null) {
return false;
}
AddressSetView set = getDiffs();
if (set == null) {
return false;
}
AddressRange extreme = getExtremeRange.apply(set);
if (extreme == null) {
return false;
}
return checkRange.test(extreme, cur);
}
public boolean hasPrevDiff() {
return hasSeqDiff(AddressSetView::getFirstRange,
(first, cur) -> first.getMaxAddress().compareTo(cur) < 0);
}
public boolean hasNextDiff() {
return hasSeqDiff(AddressSetView::getLastRange,
(last, cur) -> cur.compareTo(last.getMinAddress()) < 0);
}
protected Address getSeqDiff(boolean forward,
Function<AddressRange, Address> getFarthestAddress,
Function<Address, Address> getStepped) {
Address cur = getCurrentAddress();
if (cur == null) {
return null;
}
AddressSetView set = getDiffs();
if (set == null) {
return null;
}
AddressRange range = set.getRangeContaining(cur);
if (range != null) {
cur = getFarthestAddress.apply(range);
}
cur = getStepped.apply(cur);
if (cur == null) {
return null;
}
AddressIterator it = set.getAddresses(cur, forward);
if (!it.hasNext()) {
return null;
}
return it.next();
}
public Address getPrevDiff() {
return getSeqDiff(false, AddressRange::getMinAddress, Address::previous);
}
public Address getNextDiff() {
return getSeqDiff(true, AddressRange::getMaxAddress, Address::next);
}
public boolean gotoPrevDiff() {
Address prevDiff = getPrevDiff();
if (prevDiff == null) {
return false;
}
return listingService.goTo(prevDiff, true) && altListingPanel.goTo(prevDiff);
}
public boolean gotoNextDiff() {
Address nextDiff = getNextDiff();
if (nextDiff == null) {
return false;
}
return listingService.goTo(nextDiff, true) && altListingPanel.goTo(nextDiff);
}
protected void injectOnListingService() {
if (listingService != null) {
listingService.addLocalAction(actionCompare);
altListingPanel = new ListingPanel(listingService.getFormatManager());
listingService.setCoordinatedListingPanelListener(coordinationListener);
listingService.addTrackingSpecChangeListener(syncTrackingSpecListener);
colorModel = listingService.createListingBackgroundColorModel(altListingPanel);
colorModel.addModel(trackingTrait.createListingBackgroundColorModel(altListingPanel));
altListingPanel.setBackgroundColorModel(colorModel);
updateMarkerServiceColorModel();
}
}
protected void ejectFromListingService() {
if (altListingPanel != null) {
altListingPanel.dispose();
altListingPanel = null;
}
colorModel = null;
if (listingService != null) {
listingService.removeLocalAction(actionCompare);
listingService.setCoordinatedListingPanelListener(null);
listingService.removeTrackingSpecChangeListener(syncTrackingSpecListener);
}
}
@AutoServiceConsumed
private void setListingService(DebuggerListingService listingService) {
ejectFromListingService();
this.listingService = listingService;
injectOnListingService();
}
protected void updateMarkerServiceColorModel() {
if (colorModel == null) {
return;
}
colorModel.removeModel(markerServiceColorModel);
if (markerService != null && altListingPanel != null) {
colorModel.addModel(markerServiceColorModel = new MarkerServiceBackgroundColorModel(
markerService, altListingPanel.getProgram(), altListingPanel.getAddressIndexMap()));
}
}
protected void createMarkers() {
if (diffMarkersL != null) {
return;
}
if (markerService == null) {
diffMarkersL = null;
diffMarkersR = null;
return;
}
if (altListingPanel == null) {
diffMarkersL = null;
diffMarkersR = null;
return;
}
Program viewR = altListingPanel.getProgram();
if (viewR == null) {
diffMarkersR = null;
diffMarkersL = null;
return;
}
Color diffColor = this.diffColor == null ? DEFAULT_DIFF_COLOR : this.diffColor;
TraceProgramView viewL = traceManager.getCurrentView();
diffMarkersL = markerService.createAreaMarker(MARKER_NAME, MARKER_DESCRIPTION, viewL, 0,
true, true, true, diffColor, true);
diffMarkersR = markerService.createAreaMarker(MARKER_NAME, MARKER_DESCRIPTION, viewR, 0,
true, true, true, diffColor, true);
return;
}
protected void addMarkers(AddressSetView diffSet) {
createMarkers();
if (diffMarkersL != null) {
diffMarkersL.add(diffSet);
}
if (diffMarkersR != null) {
diffMarkersR.add(diffSet);
}
}
protected void clearMarkers() {
if (diffMarkersL != null) {
diffMarkersL.clearAll();
}
if (diffMarkersR != null) {
diffMarkersR.clearAll();
}
}
protected void deleteMarkers() {
if (diffMarkersL == null) {
return;
}
if (markerService == null) {
return;
}
if (altListingPanel == null) {
return;
}
Program altView = altListingPanel.getProgram();
if (altView == null) {
return;
}
markerService.removeMarker(diffMarkersL, altView);
markerService.removeMarker(diffMarkersR, altView);
}
@AutoServiceConsumed
private void setMarkerService(MarkerService markerService) {
if (this.markerService != null) {
this.markerService.removeChangeListener(markerChangeListener);
deleteMarkers();
}
this.markerService = markerService;
updateMarkerServiceColorModel();
if (this.markerService != null) {
this.markerService.addChangeListener(markerChangeListener);
}
}
@AutoOptionConsumed(category = DIFF_COLOR_CATEGORY, name = DIFF_COLOR_NAME)
private void setDiffColor(Color diffColor) {
if (diffMarkersL != null) {
diffMarkersL.setMarkerColor(diffColor);
}
if (diffMarkersR != null) {
diffMarkersR.setMarkerColor(diffColor);
}
}
@Override
public void processEvent(PluginEvent event) {
super.processEvent(event);
if (event instanceof TraceClosedPluginEvent) {
TraceClosedPluginEvent evt = (TraceClosedPluginEvent) event;
if (timeDialog.getTrace() == evt.getTrace()) {
timeDialog.close();
}
}
}
public static int lenRemainsBlock(int blockSize, long off) {
return blockSize - (int) (off % blockSize);
}
public static long minOfBlock(int blockSize, long off) {
return off / blockSize * blockSize;
}
public static long maxOfBlock(int blockSize, long off) {
return (off + blockSize - 1) / blockSize * blockSize - 1;
}
public static Address maxOfBlock(int blockSize, Address address) {
long off = address.getOffset();
long max = maxOfBlock(blockSize, off);
AddressSpace space = address.getAddressSpace();
return space.getAddress(max);
}
public static AddressRange blockFor(int blockSize, Address address) {
long off = address.getOffset();
// TODO: Require powers of 2?
long min = minOfBlock(blockSize, off);
long max = maxOfBlock(blockSize, off);
AddressSpace space = address.getAddressSpace();
return new AddressRangeImpl(space.getAddress(min), space.getAddress(max));
}
protected AddressSetView computeDiff(TraceProgramView view1, TraceProgramView view2) {
Trace trace = view1.getTrace();
assert trace == view2.getTrace();
long snap1 = view1.getSnap();
long snap2 = view2.getSnap();
if (snap1 == snap2) {
// Punt on the degenerate case
return new AddressSet();
}
TraceMemoryManager mm = trace.getMemoryManager();
AddressSetView known1 = mm.getAddressesWithState(snap1, s -> s == TraceMemoryState.KNOWN);
AddressSetView known2 = mm.getAddressesWithState(snap2, s -> s == TraceMemoryState.KNOWN);
//AddressSet knownEither = known1.union(known2);
AddressSet knownBoth = known1.intersect(known2); // Will need byte-by-byte examination
// Symmetric difference in state counts as difference?
// TODO: Should that be togglable?
AddressSet diff = new AddressSet(); //knownEither;
//knownEither = null; // Don't need knownEither anymore. Avoid accidental use
//diff.delete(knownBoth);
int blockSize = mm.getBlockSize();
if (blockSize == 0) {
throw new UnsupportedOperationException("TODO: Unoptimized byte diff");
}
ByteBuffer buf1 = ByteBuffer.allocate(blockSize);
ByteBuffer buf2 = ByteBuffer.allocate(blockSize);
while (!knownBoth.isEmpty()) {
Address next = knownBoth.getMinAddress();
Long mrs1 = mm.getSnapOfMostRecentChangeToBlock(snap1, next);
Long mrs2 = mm.getSnapOfMostRecentChangeToBlock(snap2, next);
if (Objects.equals(mrs1, mrs2)) {
knownBoth.delete(blockFor(blockSize, next));
continue;
}
int len = lenRemainsBlock(blockSize, next.getOffset());
buf1.clear();
buf1.limit(len);
if (len != mm.getBytes(snap1, next, buf1)) {
throw new AssertionError("Read failed");
}
buf2.clear();
buf2.limit(len);
if (len != mm.getBytes(snap2, next, buf2)) {
throw new AssertionError("Read failed");
}
compareBytes(diff, next, buf1, buf2);
knownBoth.delete(blockFor(blockSize, next));
}
return diff;
}
protected void compareBytes(AddressSet diff, Address addr, ByteBuffer buf1, ByteBuffer buf2) {
int len = buf1.limit();
byte[] arr1 = buf1.array();
byte[] arr2 = buf2.array();
Address rngStart = null;
for (int i = 0; i < len; i++) {
if (arr1[i] != arr2[i]) {
if (rngStart == null) {
rngStart = addr.add(i);
}
}
else {
if (rngStart != null) {
diff.add(rngStart, addr.add(i - 1));
rngStart = null;
}
}
}
if (rngStart != null) {
diff.add(rngStart, addr.add(len - 1));
}
}
}

View file

@ -26,6 +26,7 @@ import ghidra.app.util.viewer.util.AddressIndexMap;
import ghidra.framework.options.AutoOptions; import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.AutoOptions.Wiring; import ghidra.framework.options.AutoOptions.Wiring;
import ghidra.framework.options.annotation.AutoOptionConsumed; import ghidra.framework.options.annotation.AutoOptionConsumed;
import ghidra.framework.plugintool.Plugin;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
@ -41,7 +42,7 @@ class CursorBackgroundColorModel implements ListingBackgroundColorModel {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final Wiring autoOptionsWiring; private final Wiring autoOptionsWiring;
public CursorBackgroundColorModel(DebuggerListingPlugin plugin, ListingPanel listingPanel) { public CursorBackgroundColorModel(Plugin plugin, ListingPanel listingPanel) {
autoOptionsWiring = AutoOptions.wireOptions(plugin, this); autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
modelDataChanged(listingPanel); modelDataChanged(listingPanel);
} }

View file

@ -38,6 +38,7 @@ import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec; import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.util.viewer.format.FormatManager; import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.framework.options.AutoOptions; import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.options.annotation.AutoOptionDefined; import ghidra.framework.options.annotation.AutoOptionDefined;
@ -162,6 +163,16 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
createActions(); createActions();
} }
@Override
public MultiBlendedListingBackgroundColorModel createListingBackgroundColorModel(
ListingPanel listingPanel) {
MultiBlendedListingBackgroundColorModel colorModel =
new MultiBlendedListingBackgroundColorModel();
colorModel.addModel(new MemoryStateListingBackgroundColorModel(this, listingPanel));
colorModel.addModel(new CursorBackgroundColorModel(this, listingPanel));
return colorModel;
}
@Override @Override
protected DebuggerListingProvider createProvider(FormatManager formatManager, protected DebuggerListingProvider createProvider(FormatManager formatManager,
boolean isConnected) { boolean isConnected) {
@ -322,6 +333,21 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
connectedProvider.setTrackingSpec(spec); connectedProvider.setTrackingSpec(spec);
} }
@Override
public LocationTrackingSpec getTrackingSpec() {
return connectedProvider.getTrackingSpec();
}
@Override
public void addTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener) {
connectedProvider.addTrackingSpecChangeListener(listener);
}
@Override
public void removeTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener) {
connectedProvider.removeTrackingSpecChangeListener(listener);
}
@Override @Override
public void setCurrentSelection(ProgramSelection selection) { public void setCurrentSelection(ProgramSelection selection) {
connectedProvider.setSelection(selection); connectedProvider.setSelection(selection);

View file

@ -53,6 +53,7 @@ import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionConte
import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils; import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils;
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils; import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.services.DebuggerListingService.LocationTrackingSpecChangeListener;
import ghidra.app.util.viewer.format.FormatManager; import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
@ -71,8 +72,9 @@ import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.modules.*; import ghidra.trace.model.modules.*;
import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.program.TraceProgramView;
import ghidra.util.HTMLUtilities; import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.Swing; import ghidra.util.*;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException; import ghidra.util.exception.VersionException;
import ghidra.util.task.*; import ghidra.util.task.*;
@ -182,6 +184,11 @@ public class DebuggerListingProvider extends CodeViewerProvider {
DebuggerListingProvider.this); DebuggerListingProvider.this);
} }
@Override
protected void specChanged(LocationTrackingSpec spec) {
trackingSpecChangeListeners.fire.locationTrackingSpecChanged(spec);
}
@Override @Override
protected void locationTracked() { protected void locationTracked() {
doGoToTracked(); doGoToTracked();
@ -247,9 +254,12 @@ public class DebuggerListingProvider extends CodeViewerProvider {
protected boolean followsCurrentThread = true; protected boolean followsCurrentThread = true;
// TODO: followsCurrentSnap? // TODO: followsCurrentSnap?
protected DebuggerGoToTrait goToTrait; protected final DebuggerGoToTrait goToTrait;
protected ForListingTrackingTrait trackingTrait; protected final ForListingTrackingTrait trackingTrait;
protected ForListingReadsMemoryTrait readsMemTrait; protected final ForListingReadsMemoryTrait readsMemTrait;
protected final ListenerSet<LocationTrackingSpecChangeListener> trackingSpecChangeListeners =
new ListenerSet<>(LocationTrackingSpecChangeListener.class);
protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel(); protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel();
@ -275,10 +285,8 @@ public class DebuggerListingProvider extends CodeViewerProvider {
readsMemTrait = new ForListingReadsMemoryTrait(); readsMemTrait = new ForListingReadsMemoryTrait();
ListingPanel listingPanel = getListingPanel(); ListingPanel listingPanel = getListingPanel();
colorModel = new MultiBlendedListingBackgroundColorModel(); colorModel = plugin.createListingBackgroundColorModel(listingPanel);
colorModel.addModel(trackingTrait.createListingBackgroundColorModel(listingPanel)); colorModel.addModel(trackingTrait.createListingBackgroundColorModel(listingPanel));
colorModel.addModel(new MemoryStateListingBackgroundColorModel(plugin, listingPanel));
colorModel.addModel(new CursorBackgroundColorModel(plugin, listingPanel));
listingPanel.setBackgroundColorModel(colorModel); listingPanel.setBackgroundColorModel(colorModel);
autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
@ -493,12 +501,10 @@ public class DebuggerListingProvider extends CodeViewerProvider {
if (this.markerService != null) { if (this.markerService != null) {
this.markerService.removeChangeListener(markerChangeListener); this.markerService.removeChangeListener(markerChangeListener);
} }
this.markerService = markerService;
updateMarkerServiceColorModel();
removeOldStaticTrackingMarker(); removeOldStaticTrackingMarker();
this.markerService = markerService; this.markerService = markerService;
createNewStaticTrackingMarker(); createNewStaticTrackingMarker();
updateMarkerServiceColorModel();
if (this.markerService != null && !isMainListing()) { if (this.markerService != null && !isMainListing()) {
// NOTE: Connected provider marker listener is taken care of by CodeBrowserPlugin // NOTE: Connected provider marker listener is taken care of by CodeBrowserPlugin
@ -594,6 +600,38 @@ public class DebuggerListingProvider extends CodeViewerProvider {
setSubTitle(computeSubTitle()); setSubTitle(computeSubTitle());
} }
@Override
protected String computePanelTitle(Program panelProgram) {
if (!(panelProgram instanceof TraceProgramView)) {
// really shouldn't happen anyway...
return super.computePanelTitle(panelProgram);
}
TraceProgramView view = (TraceProgramView) panelProgram;
TraceSnapshot snapshot =
view.getTrace().getTimeManager().getSnapshot(view.getSnap(), false);
if (snapshot == null) {
return Long.toString(view.getSnap());
}
String description = snapshot.getDescription();
String schedule = snapshot.getScheduleString();
if (description == null) {
description = "";
}
if (schedule == null) {
schedule = "";
}
if (!description.isBlank() && !schedule.isBlank()) {
return description + " (" + schedule + ")";
}
if (!description.isBlank()) {
return description;
}
if (!schedule.isBlank()) {
return schedule;
}
return DateUtils.formatDateTimestamp(new Date(snapshot.getRealTime()));
}
protected void createActions() { protected void createActions() {
if (isMainListing()) { if (isMainListing()) {
actionSyncToStaticListing = new SyncToStaticListingAction(); actionSyncToStaticListing = new SyncToStaticListingAction();
@ -842,6 +880,14 @@ public class DebuggerListingProvider extends CodeViewerProvider {
return trackingTrait.getSpec(); return trackingTrait.getSpec();
} }
public void addTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener) {
trackingSpecChangeListeners.add(listener);
}
public void removeTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener) {
trackingSpecChangeListeners.remove(listener);
}
public void setSyncToStaticListing(boolean sync) { public void setSyncToStaticListing(boolean sync) {
if (!isMainListing()) { if (!isMainListing()) {
throw new IllegalStateException( throw new IllegalStateException(

View file

@ -25,6 +25,7 @@ import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.app.util.viewer.util.AddressIndexMap; import ghidra.app.util.viewer.util.AddressIndexMap;
import ghidra.framework.options.AutoOptions; import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.annotation.AutoOptionConsumed; import ghidra.framework.options.annotation.AutoOptionConsumed;
import ghidra.framework.plugintool.Plugin;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.TraceAddressSnapRange;
@ -47,7 +48,7 @@ public class MemoryStateListingBackgroundColorModel implements ListingBackground
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final AutoOptions.Wiring autoOptionsWiring; private final AutoOptions.Wiring autoOptionsWiring;
public MemoryStateListingBackgroundColorModel(DebuggerListingPlugin plugin, public MemoryStateListingBackgroundColorModel(Plugin plugin,
ListingPanel listingPanel) { ListingPanel listingPanel) {
autoOptionsWiring = AutoOptions.wireOptions(plugin, this); autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
modelDataChanged(listingPanel); modelDataChanged(listingPanel);

View file

@ -379,7 +379,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
// TODO: Should I receive clicks on that renderer to seek to a given snap? // TODO: Should I receive clicks on that renderer to seek to a given snap?
setDefaultWindowPosition(WindowPosition.BOTTOM); setDefaultWindowPosition(WindowPosition.BOTTOM);
myActionContext = new DebuggerSnapActionContext(0); myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getViewSnap());
createActions(); createActions();
contextChanged(); contextChanged();
@ -617,7 +617,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
snap = 0; snap = 0;
} }
traceManager.activateSnap(snap); traceManager.activateSnap(snap);
myActionContext = new DebuggerSnapActionContext(snap); myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
contextChanged(); contextChanged();
}); });
} }

View file

@ -0,0 +1,262 @@
/* ###
* 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.time;
import java.awt.BorderLayout;
import java.util.Collection;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Function;
import javax.swing.*;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import com.google.common.collect.Collections2;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.framework.model.DomainObject;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.util.table.GhidraTableFilterPanel;
public class DebuggerSnapshotTablePanel extends JPanel {
protected enum SnapshotTableColumns
implements EnumeratedTableColumn<SnapshotTableColumns, SnapshotRow> {
SNAP("Snap", Long.class, SnapshotRow::getSnap),
TIMESTAMP("Timestamp", String.class, SnapshotRow::getTimeStamp), // TODO: Use Date type here
EVENT_THREAD("Event Thread", String.class, SnapshotRow::getEventThreadName),
SCHEDULE("Schedule", String.class, SnapshotRow::getSchedule),
DESCRIPTION("Description", String.class, SnapshotRow::getDescription, SnapshotRow::setDescription);
private final String header;
private final Function<SnapshotRow, ?> getter;
private final BiConsumer<SnapshotRow, Object> setter;
private final Class<?> cls;
<T> SnapshotTableColumns(String header, Class<T> cls, Function<SnapshotRow, T> getter) {
this(header, cls, getter, null);
}
@SuppressWarnings("unchecked")
<T> SnapshotTableColumns(String header, Class<T> cls, Function<SnapshotRow, T> getter,
BiConsumer<SnapshotRow, T> setter) {
this.header = header;
this.cls = cls;
this.getter = getter;
this.setter = (BiConsumer<SnapshotRow, Object>) setter;
}
@Override
public Class<?> getValueClass() {
return cls;
}
@Override
public Object getValueOf(SnapshotRow row) {
return getter.apply(row);
}
@Override
public String getHeader() {
return header;
}
@Override
public boolean isEditable(SnapshotRow row) {
return setter != null;
}
@Override
public void setValueOf(SnapshotRow row, Object value) {
setter.accept(row, value);
}
}
private class SnapshotListener extends TraceDomainObjectListener {
public SnapshotListener() {
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
listenFor(TraceSnapshotChangeType.ADDED, this::snapAdded);
listenFor(TraceSnapshotChangeType.CHANGED, this::snapChanged);
listenFor(TraceSnapshotChangeType.DELETED, this::snapDeleted);
}
private void objectRestored() {
loadSnapshots();
}
private void snapAdded(TraceSnapshot snapshot) {
if (snapshot.getKey() < 0 && hideScratch) {
return;
}
SnapshotRow row = new SnapshotRow(currentTrace, snapshot);
snapshotTableModel.add(row);
if (currentSnap == snapshot.getKey()) {
snapshotFilterPanel.setSelectedItem(row);
}
}
private void snapChanged(TraceSnapshot snapshot) {
if (snapshot.getKey() < 0 && hideScratch) {
return;
}
snapshotTableModel.notifyUpdatedWith(row -> row.getSnapshot() == snapshot);
}
private void snapDeleted(TraceSnapshot snapshot) {
if (snapshot.getKey() < 0 && hideScratch) {
return;
}
snapshotTableModel.deleteWith(row -> row.getSnapshot() == snapshot);
}
}
protected final EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel =
new DefaultEnumeratedColumnTableModel<>("Snapshots", SnapshotTableColumns.class);
protected final GTable snapshotTable;
protected final GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel;
protected boolean hideScratch = true;
private Trace currentTrace;
private Long currentSnap;
protected final SnapshotListener listener = new SnapshotListener();
public DebuggerSnapshotTablePanel() {
super(new BorderLayout());
snapshotTable = new GTable(snapshotTableModel);
snapshotTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
add(new JScrollPane(snapshotTable));
snapshotFilterPanel = new GhidraTableFilterPanel<>(snapshotTable, snapshotTableModel);
add(snapshotFilterPanel, BorderLayout.SOUTH);
TableColumnModel columnModel = snapshotTable.getColumnModel();
TableColumn snapCol = columnModel.getColumn(SnapshotTableColumns.SNAP.ordinal());
snapCol.setPreferredWidth(40);
TableColumn timeCol = columnModel.getColumn(SnapshotTableColumns.TIMESTAMP.ordinal());
timeCol.setPreferredWidth(200);
TableColumn etCol = columnModel.getColumn(SnapshotTableColumns.EVENT_THREAD.ordinal());
etCol.setPreferredWidth(40);
TableColumn schdCol = columnModel.getColumn(SnapshotTableColumns.SCHEDULE.ordinal());
schdCol.setPreferredWidth(60);
TableColumn descCol = columnModel.getColumn(SnapshotTableColumns.DESCRIPTION.ordinal());
descCol.setPreferredWidth(200);
}
private void addNewListeners() {
if (currentTrace == null) {
return;
}
currentTrace.addListener(listener);
}
private void removeOldListeners() {
if (currentTrace == null) {
return;
}
currentTrace.removeListener(listener);
}
public void setTrace(Trace trace) {
if (currentTrace == trace) {
return;
}
removeOldListeners();
currentTrace = trace;
addNewListeners();
loadSnapshots();
}
public Trace getTrace() {
return currentTrace;
}
public void setHideScratchSnapshots(boolean hideScratch) {
if (this.hideScratch == hideScratch) {
return;
}
this.hideScratch = hideScratch;
if (hideScratch) {
deleteScratchSnapshots();
}
else {
loadScratchSnapshots();
}
}
protected void loadSnapshots() {
snapshotTableModel.clear();
if (currentTrace == null) {
return;
}
TraceTimeManager manager = currentTrace.getTimeManager();
Collection<? extends TraceSnapshot> snapshots = hideScratch
? manager.getSnapshots(0, true, Long.MAX_VALUE, true)
: manager.getAllSnapshots();
snapshotTableModel.addAll(Collections2.transform(snapshots,
s -> new SnapshotRow(currentTrace, s)));
}
protected void deleteScratchSnapshots() {
snapshotTableModel.deleteWith(s -> s.getSnap() < 0);
}
protected void loadScratchSnapshots() {
if (currentTrace == null) {
return;
}
TraceTimeManager manager = currentTrace.getTimeManager();
snapshotTableModel.addAll(Collections2.transform(
manager.getSnapshots(Long.MIN_VALUE, true, 0, false),
s -> new SnapshotRow(currentTrace, s)));
}
public ListSelectionModel getSelectionModel() {
return snapshotTable.getSelectionModel();
}
public Long getSelectedSnapshot() {
SnapshotRow row = snapshotFilterPanel.getSelectedItem();
return row == null ? null : row.getSnap();
}
public void setSelectedSnapshot(Long snap) {
currentSnap = snap;
if (snap == null) {
snapshotTable.clearSelection();
return;
}
SnapshotRow sel = snapshotFilterPanel.getSelectedItem();
Long curSnap = sel == null ? null : sel.getSnap();
if (Objects.equals(curSnap, snap)) {
return;
}
SnapshotRow row = snapshotTableModel.findFirst(r -> r.getSnap() == snap);
if (row == null) {
snapshotTable.clearSelection();
return;
}
snapshotFilterPanel.setSelectedItem(row);
}
}

View file

@ -15,14 +15,29 @@
*/ */
package ghidra.app.plugin.core.debug.gui.time; package ghidra.app.plugin.core.debug.gui.time;
import java.util.Map;
import java.util.Map.Entry;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.widgets.dialogs.InputDialog;
import ghidra.app.context.ProgramLocationActionContext;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin; import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent; import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.RenameSnapshotAction;
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.util.database.UndoableTransaction;
@PluginInfo( @PluginInfo(
shortDescription = "Lists recorded snapshots in a trace", shortDescription = "Lists recorded snapshots in a trace",
@ -39,8 +54,12 @@ import ghidra.framework.plugintool.util.PluginStatus;
public class DebuggerTimePlugin extends AbstractDebuggerPlugin { public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
protected DebuggerTimeProvider provider; protected DebuggerTimeProvider provider;
protected DockingAction actionRenameSnapshot;
public DebuggerTimePlugin(PluginTool tool) { public DebuggerTimePlugin(PluginTool tool) {
super(tool); super(tool);
createActions();
} }
@Override @Override
@ -49,6 +68,58 @@ public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
super.init(); super.init();
} }
protected void createActions() {
actionRenameSnapshot = RenameSnapshotAction.builder(this)
.enabled(false)
.enabledWhen(ctx -> contextGetTraceSnap(ctx) != null)
.onAction(this::activatedRenameSnapshot)
.buildAndInstall(tool);
}
protected Entry<Trace, Long> contextGetTraceSnap(ActionContext context) {
if (context instanceof ProgramLocationActionContext) {
ProgramLocationActionContext ctx = (ProgramLocationActionContext) context;
Program program = ctx.getProgram();
if (program instanceof TraceProgramView) {
TraceProgramView view = (TraceProgramView) program;
return Map.entry(view.getTrace(), view.getSnap());
}
return null;
}
if (context instanceof DebuggerSnapActionContext) {
DebuggerSnapActionContext ctx = (DebuggerSnapActionContext) context;
if (ctx.getTrace() != null) {
return Map.entry(ctx.getTrace(), ctx.getSnap());
}
return null;
}
return null;
}
protected void activatedRenameSnapshot(ActionContext context) {
Entry<Trace, Long> traceSnap = contextGetTraceSnap(context);
if (traceSnap == null) {
return;
}
Trace trace = traceSnap.getKey();
long snap = traceSnap.getValue();
TraceTimeManager manager = trace.getTimeManager();
TraceSnapshot snapshot = manager.getSnapshot(snap, false);
InputDialog dialog = new InputDialog("Rename Snapshot", "Description",
snapshot == null ? "" : snapshot.getDescription());
tool.showDialog(dialog);
if (dialog.isCanceled()) {
return;
}
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Rename Snapshot", true)) {
if (snapshot == null) {
snapshot = manager.getSnapshot(snap, true);
}
snapshot.setDescription(dialog.getValue());
}
}
@Override @Override
protected void dispose() { protected void dispose() {
tool.removeComponentProvider(provider); tool.removeComponentProvider(provider);

View file

@ -17,138 +17,30 @@ package ghidra.app.plugin.core.debug.gui.time;
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import java.awt.BorderLayout;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.Objects; import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Function;
import javax.swing.*; import javax.swing.JComponent;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import com.google.common.collect.Collections2;
import docking.ActionContext; import docking.ActionContext;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.action.ToggleDockingAction; import docking.action.ToggleDockingAction;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext; import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.framework.model.DomainObject;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.AutoService.Wiring; import ghidra.framework.plugintool.AutoService.Wiring;
import ghidra.framework.plugintool.annotation.AutoConfigStateField; import ghidra.framework.plugintool.annotation.AutoConfigStateField;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.util.table.GhidraTableFilterPanel;
public class DebuggerTimeProvider extends ComponentProviderAdapter { public class DebuggerTimeProvider extends ComponentProviderAdapter {
private static final AutoConfigState.ClassHandler<DebuggerTimeProvider> CONFIG_STATE_HANDLER = private static final AutoConfigState.ClassHandler<DebuggerTimeProvider> CONFIG_STATE_HANDLER =
AutoConfigState.wireHandler(DebuggerTimeProvider.class, MethodHandles.lookup()); AutoConfigState.wireHandler(DebuggerTimeProvider.class, MethodHandles.lookup());
protected enum SnapshotTableColumns
implements EnumeratedTableColumn<SnapshotTableColumns, SnapshotRow> {
SNAP("Snap", Long.class, SnapshotRow::getSnap),
TIMESTAMP("Timestamp", String.class, SnapshotRow::getTimeStamp), // TODO: Use Date type here
EVENT_THREAD("Event Thread", String.class, SnapshotRow::getEventThreadName),
SCHEDULE("Schedule", String.class, SnapshotRow::getSchedule),
DESCRIPTION("Description", String.class, SnapshotRow::getDescription, SnapshotRow::setDescription);
private final String header;
private final Function<SnapshotRow, ?> getter;
private final BiConsumer<SnapshotRow, Object> setter;
private final Class<?> cls;
<T> SnapshotTableColumns(String header, Class<T> cls, Function<SnapshotRow, T> getter) {
this(header, cls, getter, null);
}
@SuppressWarnings("unchecked")
<T> SnapshotTableColumns(String header, Class<T> cls, Function<SnapshotRow, T> getter,
BiConsumer<SnapshotRow, T> setter) {
this.header = header;
this.cls = cls;
this.getter = getter;
this.setter = (BiConsumer<SnapshotRow, Object>) setter;
}
@Override
public Class<?> getValueClass() {
return cls;
}
@Override
public Object getValueOf(SnapshotRow row) {
return getter.apply(row);
}
@Override
public String getHeader() {
return header;
}
@Override
public boolean isEditable(SnapshotRow row) {
return setter != null;
}
@Override
public void setValueOf(SnapshotRow row, Object value) {
setter.accept(row, value);
}
}
private class SnapshotListener extends TraceDomainObjectListener {
public SnapshotListener() {
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
listenFor(TraceSnapshotChangeType.ADDED, this::snapAdded);
listenFor(TraceSnapshotChangeType.CHANGED, this::snapChanged);
listenFor(TraceSnapshotChangeType.DELETED, this::snapDeleted);
}
private void objectRestored() {
loadSnapshots();
}
private void snapAdded(TraceSnapshot snapshot) {
if (snapshot.getKey() < 0 && hideScratch) {
return;
}
SnapshotRow row = new SnapshotRow(current.getTrace(), snapshot);
snapshotTableModel.add(row);
if (current.getSnap() == snapshot.getKey()) {
snapshotFilterPanel.setSelectedItem(row);
}
}
private void snapChanged(TraceSnapshot snapshot) {
if (snapshot.getKey() < 0 && hideScratch) {
return;
}
snapshotTableModel.notifyUpdatedWith(row -> row.getSnapshot() == snapshot);
}
private void snapDeleted(TraceSnapshot snapshot) {
if (snapshot.getKey() < 0 && hideScratch) {
return;
}
snapshotTableModel.deleteWith(row -> row.getSnapshot() == snapshot);
}
}
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) { protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
if (!Objects.equals(a.getTrace(), b.getTrace())) { if (!Objects.equals(a.getTrace(), b.getTrace())) {
return false; return false;
@ -162,28 +54,20 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
protected final DebuggerTimePlugin plugin; protected final DebuggerTimePlugin plugin;
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
private Trace currentTrace; // copy for transition
protected final SnapshotListener listener = new SnapshotListener();
@AutoServiceConsumed @AutoServiceConsumed
protected DebuggerTraceManagerService viewManager; protected DebuggerTraceManagerService viewManager;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final Wiring autoServiceWiring; private final Wiring autoServiceWiring;
private final JPanel mainPanel = new JPanel(new BorderLayout()); /*testing*/ final DebuggerSnapshotTablePanel mainPanel = new DebuggerSnapshotTablePanel();
/* testing */ final EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel =
new DefaultEnumeratedColumnTableModel<>("Snapshots", SnapshotTableColumns.class);
/* testing */ GTable snapshotTable;
/* testing */ GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel;
private DebuggerSnapActionContext myActionContext; private DebuggerSnapActionContext myActionContext;
ToggleDockingAction actionHideScratch; ToggleDockingAction actionHideScratch;
@AutoConfigStateField @AutoConfigStateField
/* testing */ boolean hideScratch = true; /*testing*/ boolean hideScratch = true;
public DebuggerTimeProvider(DebuggerTimePlugin plugin) { public DebuggerTimeProvider(DebuggerTimePlugin plugin) {
super(plugin.getTool(), TITLE_PROVIDER_TIME, plugin.getName()); super(plugin.getTool(), TITLE_PROVIDER_TIME, plugin.getName());
@ -198,7 +82,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
buildMainPanel(); buildMainPanel();
myActionContext = new DebuggerSnapActionContext(0); myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getSnap());
createActions(); createActions();
contextChanged(); contextChanged();
@ -224,42 +108,22 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
} }
protected void buildMainPanel() { protected void buildMainPanel() {
snapshotTable = new GTable(snapshotTableModel); mainPanel.getSelectionModel().addListSelectionListener(evt -> {
snapshotTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mainPanel.add(new JScrollPane(snapshotTable));
snapshotFilterPanel = new GhidraTableFilterPanel<>(snapshotTable, snapshotTableModel);
mainPanel.add(snapshotFilterPanel, BorderLayout.SOUTH);
snapshotTable.getSelectionModel().addListSelectionListener(evt -> {
if (evt.getValueIsAdjusting()) { if (evt.getValueIsAdjusting()) {
return; return;
} }
SnapshotRow row = snapshotFilterPanel.getSelectedItem(); Long snap = mainPanel.getSelectedSnapshot();
if (row == null) { if (snap == null) {
myActionContext = null; myActionContext = null;
return; return;
} }
long snap = row.getSnap(); if (snap.longValue() == current.getSnap().longValue()) {
if (snap == current.getSnap().longValue()) {
return; return;
} }
myActionContext = new DebuggerSnapActionContext(snap); myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
viewManager.activateSnap(snap); viewManager.activateSnap(snap);
contextChanged(); contextChanged();
}); });
TableColumnModel columnModel = snapshotTable.getColumnModel();
TableColumn snapCol = columnModel.getColumn(SnapshotTableColumns.SNAP.ordinal());
snapCol.setPreferredWidth(40);
TableColumn timeCol = columnModel.getColumn(SnapshotTableColumns.TIMESTAMP.ordinal());
timeCol.setPreferredWidth(200);
TableColumn etCol = columnModel.getColumn(SnapshotTableColumns.EVENT_THREAD.ordinal());
etCol.setPreferredWidth(40);
TableColumn schdCol = columnModel.getColumn(SnapshotTableColumns.SCHEDULE.ordinal());
schdCol.setPreferredWidth(60);
TableColumn descCol = columnModel.getColumn(SnapshotTableColumns.DESCRIPTION.ordinal());
descCol.setPreferredWidth(200);
} }
protected void createActions() { protected void createActions() {
@ -271,51 +135,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
private void activatedHideScratch(ActionContext ctx) { private void activatedHideScratch(ActionContext ctx) {
hideScratch = !hideScratch; hideScratch = !hideScratch;
if (hideScratch) { mainPanel.setHideScratchSnapshots(hideScratch);
deleteScratchSnapshots();
}
else {
loadScratchSnapshots();
}
}
private void addNewListeners() {
if (currentTrace == null) {
return;
}
currentTrace.addListener(listener);
}
private void removeOldListeners() {
if (currentTrace == null) {
return;
}
currentTrace.removeListener(listener);
}
protected void doSetTrace(Trace trace) {
if (currentTrace == trace) {
return;
}
removeOldListeners();
currentTrace = trace;
addNewListeners();
loadSnapshots();
}
protected void doSetSnap(long snap) {
SnapshotRow sel = snapshotFilterPanel.getSelectedItem();
Long curSnap = sel == null ? null : sel.getSnap();
if (curSnap != null && curSnap.longValue() == snap) {
return;
}
SnapshotRow row = snapshotTableModel.findFirst(r -> r.getSnap() == snap);
if (row == null) {
snapshotTable.clearSelection();
}
else {
snapshotFilterPanel.setSelectedItem(row);
}
} }
public void coordinatesActivated(DebuggerCoordinates coordinates) { public void coordinatesActivated(DebuggerCoordinates coordinates) {
@ -325,37 +145,8 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
} }
current = coordinates; current = coordinates;
doSetTrace(current.getTrace()); mainPanel.setTrace(current.getTrace());
doSetSnap(current.getSnap()); mainPanel.setSelectedSnapshot(current.getSnap());
}
protected void loadSnapshots() {
snapshotTableModel.clear();
Trace curTrace = current.getTrace();
if (curTrace == null) {
return;
}
TraceTimeManager manager = curTrace.getTimeManager();
Collection<? extends TraceSnapshot> snapshots = hideScratch
? manager.getSnapshots(0, true, Long.MAX_VALUE, true)
: manager.getAllSnapshots();
snapshotTableModel.addAll(Collections2.transform(snapshots,
s -> new SnapshotRow(curTrace, s)));
}
protected void deleteScratchSnapshots() {
snapshotTableModel.deleteWith(s -> s.getSnap() < 0);
}
protected void loadScratchSnapshots() {
Trace curTrace = current.getTrace();
if (curTrace == null) {
return;
}
TraceTimeManager manager = curTrace.getTimeManager();
snapshotTableModel.addAll(Collections2.transform(
manager.getSnapshots(Long.MIN_VALUE, true, 0, false),
s -> new SnapshotRow(curTrace, s)));
} }
public void writeConfigState(SaveState saveState) { public void writeConfigState(SaveState saveState) {
@ -366,5 +157,6 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
CONFIG_STATE_HANDLER.readConfigState(this, saveState); CONFIG_STATE_HANDLER.readConfigState(this, saveState);
actionHideScratch.setSelected(hideScratch); actionHideScratch.setSelected(hideScratch);
mainPanel.setHideScratchSnapshots(hideScratch);
} }
} }

View file

@ -0,0 +1,193 @@
/* ###
* 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.time;
import java.awt.BorderLayout;
import java.util.function.Function;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import docking.DialogComponentProvider;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.Trace;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.MessageType;
import ghidra.util.Msg;
public class DebuggerTimeSelectionDialog extends DialogComponentProvider {
private final PluginTool tool;
DebuggerSnapshotTablePanel snapshotPanel;
JTextField scheduleText;
TraceSchedule schedule;
JButton tickStep;
JButton tickBack;
JButton opStep;
JButton opBack;
public DebuggerTimeSelectionDialog(PluginTool tool) {
super("Select Time", true, true, true, false);
this.tool = tool;
populateComponents();
}
protected void doStep(Function<TraceSchedule, TraceSchedule> stepper) {
try {
TraceSchedule stepped = stepper.apply(schedule);
if (stepped == null) {
return;
}
setScheduleText(stepped.toString());
}
catch (Throwable e) {
Msg.warn(this, e.getMessage());
}
}
protected void populateComponents() {
JPanel workPanel = new JPanel(new BorderLayout());
{
Box hbox = Box.createHorizontalBox();
hbox.setBorder(BorderFactory.createTitledBorder("Schedule"));
hbox.add(new JLabel("Expression: "));
scheduleText = new JTextField();
hbox.add(scheduleText);
hbox.add(new JLabel("Ticks: "));
hbox.add(tickBack = new JButton(DebuggerResources.ICON_STEP_BACK));
hbox.add(tickStep = new JButton(DebuggerResources.ICON_STEP_INTO));
hbox.add(new JLabel("Ops: "));
hbox.add(opBack = new JButton(DebuggerResources.ICON_STEP_BACK));
hbox.add(opStep = new JButton(DebuggerResources.ICON_STEP_INTO));
workPanel.add(hbox, BorderLayout.NORTH);
}
tickBack.addActionListener(evt -> doStep(s -> s.steppedBackward(getTrace(), 1)));
tickStep.addActionListener(evt -> doStep(s -> s.steppedForward(null, 1)));
opBack.addActionListener(evt -> doStep(s -> s.steppedPcodeBackward(1)));
opStep.addActionListener(evt -> doStep(s -> s.steppedPcodeForward(null, 1)));
{
snapshotPanel = new DebuggerSnapshotTablePanel();
workPanel.add(snapshotPanel, BorderLayout.CENTER);
}
snapshotPanel.getSelectionModel().addListSelectionListener(evt -> {
Long snap = snapshotPanel.getSelectedSnapshot();
if (snap == null) {
return;
}
if (schedule.getSnap() == snap.longValue()) {
return;
}
scheduleText.setText(snap.toString());
});
scheduleText.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
scheduleTextChanged();
}
@Override
public void removeUpdate(DocumentEvent e) {
scheduleTextChanged();
}
@Override
public void changedUpdate(DocumentEvent e) {
scheduleTextChanged();
}
});
addWorkPanel(workPanel);
addOKButton();
addCancelButton();
setMinimumSize(600, 600);
}
protected void scheduleTextChanged() {
schedule = null;
try {
schedule = TraceSchedule.parse(scheduleText.getText());
snapshotPanel.setSelectedSnapshot(schedule.getSnap());
schedule.validate(getTrace());
setStatusText("");
setOkEnabled(true);
}
catch (Exception e) {
setStatusText(e.getMessage(), MessageType.ERROR);
setOkEnabled(false);
}
enableStepButtons(schedule != null);
}
protected void enableStepButtons(boolean enabled) {
tickBack.setEnabled(enabled);
tickStep.setEnabled(enabled);
opBack.setEnabled(enabled);
opStep.setEnabled(enabled);
}
@Override // Public for test access
public void okCallback() {
assert schedule != null;
super.okCallback();
close();
}
@Override // Public for test access
public void cancelCallback() {
this.schedule = null;
super.cancelCallback();
}
@Override
public void close() {
super.close();
snapshotPanel.setTrace(null);
snapshotPanel.setSelectedSnapshot(null);
}
/**
* Prompts the user to select a snapshot and optionally specify a full schedule
*
* @param trace the trace from whose snapshots to select
* @param defaultTime, optionally the time to select initially
* @return the schedule, likely specifying just the snapshot selection
*/
public TraceSchedule promptTime(Trace trace, TraceSchedule defaultTime) {
snapshotPanel.setTrace(trace);
schedule = defaultTime;
scheduleText.setText(defaultTime.toString());
tool.showDialog(this);
return schedule;
}
public Trace getTrace() {
return snapshotPanel.getTrace();
}
public void setScheduleText(String text) {
scheduleText.setText(text);
}
}

View file

@ -661,44 +661,39 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return current.getFrame(); return current.getFrame();
} }
@Override
public CompletableFuture<Long> materialize(DebuggerCoordinates coordinates) {
if (coordinates.getTime().isSnapOnly()) {
return CompletableFuture.completedFuture(coordinates.getSnap());
}
Collection<? extends TraceSnapshot> suitable = coordinates.getTrace()
.getTimeManager()
.getSnapshotsWithSchedule(coordinates.getTime());
if (!suitable.isEmpty()) {
TraceSnapshot found = suitable.iterator().next();
return CompletableFuture.completedFuture(found.getKey());
}
if (emulationService == null) {
throw new IllegalStateException(
"Cannot navigate to coordinates with execution schedules, " +
"because the emulation service is not available.");
}
return emulationService.backgroundEmulate(coordinates.getTrace(), coordinates.getTime());
}
protected void prepareViewAndFireEvent(DebuggerCoordinates coordinates) { protected void prepareViewAndFireEvent(DebuggerCoordinates coordinates) {
TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView(); TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView();
if (varView == null) { // Should only happen with NOWHERE if (varView == null) { // Should only happen with NOWHERE
fireLocationEvent(coordinates); fireLocationEvent(coordinates);
return;
} }
else if (coordinates.getTime().isSnapOnly()) { materialize(coordinates).thenAcceptAsync(snap -> {
varView.setSnap(coordinates.getSnap()); if (!coordinates.equals(current)) {
return; // We navigated elsewhere before emulation completed
}
varView.setSnap(snap);
fireLocationEvent(coordinates); fireLocationEvent(coordinates);
} }, AsyncUtils.SWING_EXECUTOR);
else {
Collection<? extends TraceSnapshot> suitable = coordinates.getTrace()
.getTimeManager()
.getSnapshotsWithSchedule(coordinates.getTime());
if (!suitable.isEmpty()) {
TraceSnapshot found = suitable.iterator().next();
varView.setSnap(found.getKey());
fireLocationEvent(coordinates);
return;
}
if (emulationService == null) {
throw new IllegalStateException(
"Cannot navigate to coordinates with execution schedules, " +
"because the emulation service is not available.");
}
CompletableFuture<Long> bg =
emulationService.backgroundEmulate(coordinates.getTrace(), coordinates.getTime());
bg.thenAccept(emuSnap -> Swing.runLater(() -> {
if (!coordinates.equals(current)) {
return; // We navigated elsewhere before emulation completed
}
varView.setSnap(emuSnap);
fireLocationEvent(coordinates);
})).exceptionally(ex -> {
Msg.showError(this, null, "Emulate", "Could not navigate to emulated coordinates",
ex);
return null;
});
}
} }
protected void fireLocationEvent(DebuggerCoordinates coordinates) { protected void fireLocationEvent(DebuggerCoordinates coordinates) {
@ -892,6 +887,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
future.completeExceptionally(e); future.completeExceptionally(e);
} }
} }
}); });
} }
return future; return future;

View file

@ -15,6 +15,7 @@
*/ */
package ghidra.app.plugin.core.debug.utils; package ghidra.app.plugin.core.debug.utils;
import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.function.BiFunction; import java.util.function.BiFunction;
@ -24,8 +25,8 @@ import ghidra.framework.cmd.BackgroundCommand;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.model.UndoableDomainObject; import ghidra.framework.model.UndoableDomainObject;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.util.task.CancelledListener; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.*;
public enum BackgroundUtils { public enum BackgroundUtils {
; ;
@ -82,4 +83,59 @@ public enum BackgroundUtils {
tool.executeBackgroundCommand(cmd, obj); tool.executeBackgroundCommand(cmd, obj);
return cmd; return cmd;
} }
public static class PluginToolExecutorService extends AbstractExecutorService {
private final PluginTool tool;
private String name;
private boolean canCancel;
private boolean hasProgress;
private boolean isModal;
private final int delay;
public PluginToolExecutorService(PluginTool tool, String name, boolean canCancel,
boolean hasProgress, boolean isModal, int delay) {
this.tool = tool;
this.name = name;
this.canCancel = canCancel;
this.hasProgress = hasProgress;
this.isModal = isModal;
this.delay = delay;
}
@Override
public void shutdown() {
throw new UnsupportedOperationException();
}
@Override
public List<Runnable> shutdownNow() {
throw new UnsupportedOperationException();
}
@Override
public boolean isShutdown() {
return false;
}
@Override
public boolean isTerminated() {
return false;
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public void execute(Runnable command) {
Task task = new Task(name, canCancel, hasProgress, isModal) {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
command.run();
}
};
tool.execute(task, delay);
}
}
} }

View file

@ -17,19 +17,89 @@ package ghidra.app.services;
import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec; import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.MultiBlendedListingBackgroundColorModel;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.framework.plugintool.ServiceInfo; import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramSelection; import ghidra.program.util.ProgramSelection;
/**
* A service providing access to the main listing panel
*/
@ServiceInfo( // @ServiceInfo( //
defaultProvider = DebuggerListingPlugin.class, // defaultProvider = DebuggerListingPlugin.class, //
description = "Replacement CodeViewerService for Debugger" // description = "Replacement CodeViewerService for Debugger" //
) )
public interface DebuggerListingService extends CodeViewerService { public interface DebuggerListingService extends CodeViewerService {
/**
* A listener for changes in location tracking specification
*/
interface LocationTrackingSpecChangeListener {
/**
* The specification has changed
*
* @param spec the new specification
*/
void locationTrackingSpecChanged(LocationTrackingSpec spec);
}
/**
* Set the tracking specification of the listing. Navigates immediately.
*
* @param spec the desired specification
*/
void setTrackingSpec(LocationTrackingSpec spec); void setTrackingSpec(LocationTrackingSpec spec);
/**
* Get the tracking specification of the listing.
*
* @return the current specification
*/
LocationTrackingSpec getTrackingSpec();
/**
* Add a listener for changes to the tracking specification.
*
* @param listener the listener to receive change notifications
*/
void addTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener);
/**
* Remove a listener for changes to the tracking specification.
*
* @param listener the listener receiving change notifications
*/
void removeTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener);
/**
* Set the selection of addresses in this listing.
*
* @param selection the desired selection
*/
void setCurrentSelection(ProgramSelection selection); void setCurrentSelection(ProgramSelection selection);
/**
* Navigate to the given address
*
* @param address the desired address
* @param centerOnScreen true to center the cursor in the listing
* @return true if the request was effective
*/
boolean goTo(Address address, boolean centerOnScreen); boolean goTo(Address address, boolean centerOnScreen);
/**
* Obtain a coloring background model suitable for the given listing
*
* <p>
* This may be used, e.g., to style an alternative view in the same manner as listings managed
* by this service. Namely, this provides coloring for memory state and the user's cursor.
* Coloring for tracked locations and the marker service in general must still be added
* separately, since they incorporate additional dependencies.
*
* @param listingPanel the panel to be colored
* @return a blended background color model implementing the common debugger listing style
*/
MultiBlendedListingBackgroundColorModel createListingBackgroundColorModel(
ListingPanel listingPanel);
} }

View file

@ -20,39 +20,117 @@ import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.async.AsyncReference;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
import ghidra.framework.plugintool.ServiceInfo; import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.TriConsumer; import ghidra.util.TriConsumer;
/**
* The interface for managing open traces and navigating among them and their contents
*/
@ServiceInfo(defaultProvider = DebuggerTraceManagerServicePlugin.class) @ServiceInfo(defaultProvider = DebuggerTraceManagerServicePlugin.class)
public interface DebuggerTraceManagerService { public interface DebuggerTraceManagerService {
/**
* An adapter that works nicely with an {@link AsyncReference}
*
* <p>
* TODO: Seems this is still leaking an implementation detail
*/
public interface BooleanChangeAdapter extends TriConsumer<Boolean, Boolean, Void> { public interface BooleanChangeAdapter extends TriConsumer<Boolean, Boolean, Void> {
@Override @Override
default void accept(Boolean oldVal, Boolean newVal, Void cause) { default void accept(Boolean oldVal, Boolean newVal, Void cause) {
changed(newVal); changed(newVal);
} }
/**
* The value has changed
*
* @param value the new value
*/
void changed(Boolean value); void changed(Boolean value);
} }
/**
* Get all the open traces
*
* @return all open traces
*/
Collection<Trace> getOpenTraces(); Collection<Trace> getOpenTraces();
/**
* Get the current coordinates
*
* <p>
* This entails everything except the current address
*
* @return the current coordinates
*/
DebuggerCoordinates getCurrent(); DebuggerCoordinates getCurrent();
/**
* Get the active trace
*
* @return the active trace, or null
*/
Trace getCurrentTrace(); Trace getCurrentTrace();
/**
* Get the active view
*
* <p>
* Every trace has an associated variable-snap view. When the manager navigates to a new point
* in time, it is accomplished by changing the snap of this view. This view is suitable for use
* in most places where a {@link Program} is ordinarily required.
*
* @return the active view, or null
*/
TraceProgramView getCurrentView(); TraceProgramView getCurrentView();
/**
* Get the active thread
*
* <p>
* It is possible to have an active trace, but no active thread.
*
* @return the active thread, or null
*/
TraceThread getCurrentThread(); TraceThread getCurrentThread();
/**
* Get the active thread for a given trace
*
* <p>
* The manager remembers the last active thread for every open trace. If the trace has never
* been active, then the last active thread is null. If trace is the active trace, then this
* will return the currently active thread.
*
* @param trace the trace
* @return the thread, or null
*/
TraceThread getCurrentThreadFor(Trace trace); TraceThread getCurrentThreadFor(Trace trace);
/**
* Get the active snap
*
* <p>
* Note that if emulation was used to materialize the current coordinates, then the current snap
* will differ from the view's snap.
*
* @return the active snap, or 0
*/
long getCurrentSnap(); long getCurrentSnap();
/**
* Get the active frame
*
* @return the active frame, or 0
*/
int getCurrentFrame(); int getCurrentFrame();
/** /**
@ -105,10 +183,23 @@ public interface DebuggerTraceManagerService {
*/ */
CompletableFuture<Void> saveTrace(Trace trace); CompletableFuture<Void> saveTrace(Trace trace);
/**
* Close the given trace
*
* @param trace the trace to close
*/
void closeTrace(Trace trace); void closeTrace(Trace trace);
/**
* Close all traces
*/
void closeAllTraces(); void closeAllTraces();
/**
* Close all traces except the given one
*
* @param keep the trace to keep open
*/
void closeOtherTraces(Trace keep); void closeOtherTraces(Trace keep);
/** /**
@ -120,24 +211,84 @@ public interface DebuggerTraceManagerService {
*/ */
void closeDeadTraces(); void closeDeadTraces();
/**
* Activate the given coordinates
*
* <p>
* This operation may be completed asynchronously, esp., if emulation is required to materialize
* the coordinates. The coordinates are "resolved" as a means of filling in missing parts. For
* example, if the thread is not specified, the manager may activate the last-active thread for
* the desired trace.
*
* @param coordinates the desired coordinates
*/
void activate(DebuggerCoordinates coordinates); void activate(DebuggerCoordinates coordinates);
/**
* Activate the given trace
*
* @param trace the desired trace
*/
void activateTrace(Trace trace); void activateTrace(Trace trace);
/**
* Activate the given thread
*
* @param thread the desired thread
*/
void activateThread(TraceThread thread); void activateThread(TraceThread thread);
/**
* Activate the given snapshot key
*
* @param snap the desired snapshot key
*/
void activateSnap(long snap); void activateSnap(long snap);
/**
* Activate the given point in time, possibly invoking emulation
*
* @param time the desired schedule
*/
void activateTime(TraceSchedule time); void activateTime(TraceSchedule time);
/**
* Activate the given stack frame
*
* @param frameLevel the level of the desired frame, 0 being innermost
*/
void activateFrame(int frameLevel); void activateFrame(int frameLevel);
/**
* Control whether the trace manager automatically activates the "present snapshot"
*
* <p>
* Auto activation only applies when the current trace advances. It never changes to another
* trace.
*
* @param enabled true to enable auto activation
*/
void setAutoActivatePresent(boolean enabled); void setAutoActivatePresent(boolean enabled);
/**
* Check if the trace manager automatically activate the "present snapshot"
*
* @return true if auto activation is enabled
*/
boolean isAutoActivatePresent(); boolean isAutoActivatePresent();
/**
* Add a listener for changes to auto activation enablement
*
* @param listener the listener to receive change notifications
*/
void addAutoActivatePresentChangeListener(BooleanChangeAdapter listener); void addAutoActivatePresentChangeListener(BooleanChangeAdapter listener);
/**
* Remove a listener for changes to auto activation enablement
*
* @param listener the listener receiving change notifications
*/
void removeAutoActivatePresentChangeListener(BooleanChangeAdapter listener); void removeAutoActivatePresentChangeListener(BooleanChangeAdapter listener);
/** /**
@ -154,8 +305,18 @@ public interface DebuggerTraceManagerService {
*/ */
boolean isSynchronizeFocus(); boolean isSynchronizeFocus();
/**
* Add a listener for changes to focus synchronization enablement
*
* @param listener the listener to receive change notifications
*/
void addSynchronizeFocusChangeListener(BooleanChangeAdapter listener); void addSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
/**
* Remove a listener for changes to focus synchronization enablement
*
* @param listener the listener receiving change notifications
*/
void removeSynchronizeFocusChangeListener(BooleanChangeAdapter listener); void removeSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
/** /**
@ -172,8 +333,18 @@ public interface DebuggerTraceManagerService {
*/ */
boolean isSaveTracesByDefault(); boolean isSaveTracesByDefault();
/**
* Add a listener for changes to save-by-default enablement
*
* @param listener the listener to receive change notifications
*/
void addSaveTracesByDefaultChangeListener(BooleanChangeAdapter listener); void addSaveTracesByDefaultChangeListener(BooleanChangeAdapter listener);
/**
* Remove a listener for changes to save-by-default enablement
*
* @param listener the listener receiving change notifications
*/
void removeSaveTracesByDefaultChangeListener(BooleanChangeAdapter listener); void removeSaveTracesByDefaultChangeListener(BooleanChangeAdapter listener);
/** /**
@ -190,15 +361,40 @@ public interface DebuggerTraceManagerService {
*/ */
boolean isAutoCloseOnTerminate(); boolean isAutoCloseOnTerminate();
/**
* Add a listener for changes to close-on-terminate enablement
*
* @param listener the listener to receive change notifications
*/
void addAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener); void addAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener);
/**
* Remove a listener for changes to close-on-terminate enablement
*
* @param listener the listener receiving change notifications
*/
void removeAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener); void removeAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener);
/** /**
* Fill in an incomplete coordinate specification, using the manager's "best judgement" * Fill in an incomplete coordinate specification, using the manager's "best judgment"
* *
* @param coords the possibly-incomplete coordinates * @param coords the possibly-incomplete coordinates
* @return the complete resolved coordinates * @return the complete resolved coordinates
*/ */
DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coords); DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coordinates);
/**
* Materialize the given coordinates to a snapshot in the same trace
*
* <p>
* If the given coordinates do not require emulation, then this must complete immediately with
* the snapshot key given by the coordinates. If the given schedule is already materialized in
* the trace, then this may complete immediately with the previously-materialized snapshot key.
* Otherwise, this must invoke emulation, store the result into a chosen snapshot, and complete
* with its key.
*
* @param coordinates the coordinates to materialize
* @return a future that completes with the snapshot key of the materialized coordinates
*/
CompletableFuture<Long> materialize(DebuggerCoordinates coordinates);
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

View file

@ -0,0 +1,128 @@
/* ###
* 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.diff;
import static org.junit.Assert.assertTrue;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.junit.*;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
import ghidra.app.plugin.core.debug.gui.time.DebuggerTimeSelectionDialog;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.async.AsyncTestUtils;
import ghidra.test.ToyProgramBuilder;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.database.thread.DBTraceThread;
import ghidra.trace.database.time.DBTraceTimeManager;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.Swing;
import ghidra.util.database.UndoableTransaction;
import help.screenshot.GhidraScreenShotGenerator;
public class DebuggerTraceViewDiffPluginScreenShots extends GhidraScreenShotGenerator
implements AsyncTestUtils {
DebuggerTraceManagerService traceManager;
DebuggerTraceViewDiffPlugin diffPlugin;
DebuggerListingPlugin listingPlugin;
DebuggerListingProvider listingProvider;
ToyDBTraceBuilder tb;
@Before
public void setUpMine() throws Throwable {
traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class);
diffPlugin = addPlugin(tool, DebuggerTraceViewDiffPlugin.class);
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
listingProvider = waitForComponentProvider(DebuggerListingProvider.class);
tb = new ToyDBTraceBuilder("tictactoe", ToyProgramBuilder._X64);
}
@After
public void tearDownMine() {
tb.close();
}
@Test
public void testCaptureDebuggerTraceViewDiffPlugin() throws Throwable {
long snap1, snap2;
try (UndoableTransaction tid = tb.startTransaction()) {
DBTraceTimeManager tm = tb.trace.getTimeManager();
snap1 = tm.createSnapshot("Baseline").getKey();
snap2 = tm.createSnapshot("X's first move").getKey();
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
mm.createRegion(".data", snap1, tb.range(0x00600000, 0x0060ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
ByteBuffer buf = ByteBuffer.allocate(0x1000).order(ByteOrder.LITTLE_ENDIAN);
buf.put((byte) 'X');
buf.putInt(3);
buf.putInt(3);
for (int i = 0; i < 9; i++) {
buf.put((byte) ' ');
}
buf.flip();
buf.limit(0x1000);
mm.putBytes(snap1, tb.addr(0x00600000), buf);
buf.put(0, (byte) 'O');
buf.put(13, (byte) 'X');
buf.position(0);
buf.limit(0x1000);
mm.putBytes(snap2, tb.addr(0x00600000), buf);
}
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
traceManager.activateSnap(snap1);
waitForSwing();
waitOn(diffPlugin.startComparison(TraceSchedule.snap(snap2)));
assertTrue(diffPlugin.gotoNextDiff());
captureIsolatedProvider(DebuggerListingProvider.class, 900, 600);
}
@Test
public void testCaptureDebuggerTimeSelectionDialog() throws Throwable {
DBTraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) {
DBTraceTimeManager tm = tb.trace.getTimeManager();
thread = tb.getOrAddThread("main", 0);
tm.createSnapshot("Break on main").setEventThread(thread);
tm.createSnapshot("Game started").setEventThread(thread);
tm.createSnapshot("X's moved").setEventThread(thread);
tm.createSnapshot("O's moved").setEventThread(thread);
}
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
performAction(diffPlugin.actionCompare, false);
DebuggerTimeSelectionDialog dialog =
waitForDialogComponent(DebuggerTimeSelectionDialog.class);
Swing.runNow(() -> dialog.setScheduleText("2"));
captureDialog(dialog);
}
}

View file

@ -19,6 +19,7 @@ import static org.junit.Assert.*;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
@ -40,11 +41,13 @@ import org.junit.runner.Description;
import docking.widgets.tree.GTree; import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode; import docking.widgets.tree.GTreeNode;
import generic.Unique; import generic.Unique;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.mapping.*; import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceInternal; import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceInternal;
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin; import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin;
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.dbg.model.AbstractTestTargetRegisterBank; import ghidra.dbg.model.AbstractTestTargetRegisterBank;
import ghidra.dbg.model.TestDebuggerModelBuilder; import ghidra.dbg.model.TestDebuggerModelBuilder;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
@ -57,6 +60,7 @@ import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.DefaultLanguageService; import ghidra.program.util.DefaultLanguageService;
import ghidra.program.util.ProgramLocation;
import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv; import ghidra.test.TestEnv;
import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.database.ToyDBTraceBuilder;
@ -409,6 +413,64 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
clickMouse(button, m); clickMouse(button, m);
} }
protected static void assertListingBackgroundAt(Color expected, ListingPanel panel,
Address addr, int yAdjust) throws AWTException, InterruptedException {
ProgramLocation oneBack = new ProgramLocation(panel.getProgram(), addr.previous());
runSwing(() -> panel.goTo(addr));
runSwing(() -> panel.goTo(oneBack, false));
waitForPass(() -> {
Rectangle r = panel.getBounds();
// Capture off screen, so that focus/stacking doesn't matter
BufferedImage image = new BufferedImage(r.width, r.height, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
try {
runSwing(() -> panel.paint(g));
}
finally {
g.dispose();
}
Point locP = panel.getLocationOnScreen();
Point locFP = panel.getLocationOnScreen();
locFP.translate(-locP.x, -locP.y);
Rectangle cursor = panel.getCursorBounds();
assertNotNull("Cannot get cursor bounds", cursor);
Color actual = new Color(image.getRGB(locFP.x + cursor.x - 1,
locFP.y + cursor.y + cursor.height * 3 / 2 + yAdjust));
assertEquals(expected, actual);
});
}
protected static void goTo(ListingPanel listingPanel, ProgramLocation location) {
waitForPass(() -> {
runSwing(() -> listingPanel.goTo(location));
ProgramLocation confirm = listingPanel.getCursorLocation();
assertNotNull(confirm);
assertEquals(location.getAddress(), confirm.getAddress());
});
}
protected static LocationTrackingSpec getLocationTrackingSpec(String name) {
return LocationTrackingSpec.fromConfigName(name);
}
protected static AutoReadMemorySpec getAutoReadMemorySpec(String name) {
return AutoReadMemorySpec.fromConfigName(name);
}
protected final LocationTrackingSpec trackNone =
getLocationTrackingSpec(NoneLocationTrackingSpec.CONFIG_NAME);
protected final LocationTrackingSpec trackPc =
getLocationTrackingSpec(PCLocationTrackingSpec.CONFIG_NAME);
protected final LocationTrackingSpec trackSp =
getLocationTrackingSpec(SPLocationTrackingSpec.CONFIG_NAME);
protected final AutoReadMemorySpec readNone =
getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME);
protected final AutoReadMemorySpec readVisible =
getAutoReadMemorySpec(VisibleAutoReadMemorySpec.CONFIG_NAME);
protected final AutoReadMemorySpec readVisROOnce =
getAutoReadMemorySpec(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME);
protected TestEnv env; protected TestEnv env;
protected PluginTool tool; protected PluginTool tool;

View file

@ -0,0 +1,233 @@
/* ###
* 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.diff;
import static org.junit.Assert.*;
import java.nio.ByteBuffer;
import org.junit.Before;
import org.junit.Test;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
import ghidra.app.plugin.core.debug.gui.time.DebuggerTimeSelectionDialog;
import ghidra.async.AsyncTestUtils;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.Swing;
import ghidra.util.database.UndoableTransaction;
public class DebuggerTraceViewDiffPluginTest extends AbstractGhidraHeadedDebuggerGUITest
implements AsyncTestUtils {
protected DebuggerTraceViewDiffPlugin traceDiffPlugin;
protected DebuggerListingPlugin listingPlugin;
protected DebuggerListingProvider listingProvider;
@Before
public void setUpTraceViewDiffPluginTest() throws Exception {
traceDiffPlugin = addPlugin(tool, DebuggerTraceViewDiffPlugin.class);
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
listingProvider = waitForComponentProvider(DebuggerListingProvider.class);
}
@Test
public void testActionCompareConfirm() throws Exception {
assertFalse(traceDiffPlugin.actionCompare.isEnabled());
assertNull(listingPlugin.getProvider().getOtherPanel());
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertTrue(traceDiffPlugin.actionCompare.isEnabled());
performAction(traceDiffPlugin.actionCompare, false);
DebuggerTimeSelectionDialog dialog =
waitForDialogComponent(DebuggerTimeSelectionDialog.class);
Swing.runNow(() -> {
dialog.setScheduleText("0");
dialog.okCallback();
});
waitForSwing();
assertNotNull(listingPlugin.getProvider().getOtherPanel());
}
@Test
public void testActionCompareCancel() throws Exception {
assertFalse(traceDiffPlugin.actionCompare.isEnabled());
assertNull(listingPlugin.getProvider().getOtherPanel());
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertTrue(traceDiffPlugin.actionCompare.isEnabled());
performAction(traceDiffPlugin.actionCompare, false);
DebuggerTimeSelectionDialog dialog =
waitForDialogComponent(DebuggerTimeSelectionDialog.class);
Swing.runNow(() -> {
dialog.setScheduleText("0");
dialog.cancelCallback();
});
waitForSwing();
assertNull(listingPlugin.getProvider().getOtherPanel());
}
// TODO: Test schedule input validation?
// TODO: Test stepping buttons?
@Test
public void testActionCompareClosesWhenAlreadyActive() throws Exception {
assertFalse(traceDiffPlugin.actionCompare.isEnabled());
assertNull(listingPlugin.getProvider().getOtherPanel());
createAndOpenTrace();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertTrue(traceDiffPlugin.actionCompare.isEnabled());
performAction(traceDiffPlugin.actionCompare, false);
DebuggerTimeSelectionDialog dialog =
waitForDialogComponent(DebuggerTimeSelectionDialog.class);
Swing.runNow(() -> {
dialog.setScheduleText("0");
dialog.okCallback();
});
waitForSwing();
assertNotNull(listingPlugin.getProvider().getOtherPanel());
assertTrue(traceDiffPlugin.actionCompare.isEnabled());
performAction(traceDiffPlugin.actionCompare, false);
assertNull(listingPlugin.getProvider().getOtherPanel());
}
@Test
public void testColorsDiffBytes() throws Throwable {
createAndOpenTrace();
try (UndoableTransaction tid = tb.startTransaction()) {
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
mm.createRegion(".text", 0, tb.range(0x00400000, 0x0040ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
ByteBuffer buf = ByteBuffer.allocate(0x1000); // Yes, smaller than .text
buf.limit(0x1000);
mm.putBytes(0, tb.addr(0x00400000), buf);
buf.position(0);
buf.putLong(0x0123, 0x1122334455667788L);
mm.putBytes(1, tb.addr(0x00400000), buf);
}
traceManager.activateTrace(tb.trace);
waitForSwing();
waitOn(traceDiffPlugin.startComparison(TraceSchedule.snap(1)));
assertListingBackgroundAt(DebuggerTraceViewDiffPlugin.DEFAULT_DIFF_COLOR,
traceDiffPlugin.altListingPanel, tb.addr(0x00400123), 0);
assertListingBackgroundAt(DebuggerTraceViewDiffPlugin.DEFAULT_DIFF_COLOR,
listingProvider.getListingPanel(), tb.addr(0x00400123), 0);
AddressSetView expected = tb.set(tb.range(0x00400123, 0x0040012a));
assertEquals(expected, Swing.runNow(() -> traceDiffPlugin.diffMarkersL.getAddressSet()));
assertEquals(expected, Swing.runNow(() -> traceDiffPlugin.diffMarkersR.getAddressSet()));
Swing.runNow(() -> traceDiffPlugin.endComparison());
assertTrue(Swing.runNow(() -> traceDiffPlugin.diffMarkersL.getAddressSet()).isEmpty());
assertTrue(Swing.runNow(() -> traceDiffPlugin.diffMarkersR.getAddressSet()).isEmpty());
}
@Test
public void testActionPrevDiff() throws Throwable {
createAndOpenTrace();
try (UndoableTransaction tid = tb.startTransaction()) {
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
mm.createRegion(".text", 0, tb.range(0x00400000, 0x0040ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
ByteBuffer buf = ByteBuffer.allocate(0x1000); // Yes, smaller than .text
buf.limit(0x1000);
mm.putBytes(0, tb.addr(0x00400000), buf);
buf.position(0);
buf.putLong(0x0123, 0x1122334455667788L);
buf.putLong(0x0321, 0x1122334455667788L);
mm.putBytes(1, tb.addr(0x00400000), buf);
}
traceManager.activateTrace(tb.trace);
waitForSwing();
waitOn(traceDiffPlugin.startComparison(TraceSchedule.snap(1)));
assertFalse(traceDiffPlugin.actionPrevDiff.isEnabled());
goTo(listingProvider.getListingPanel(),
new ProgramLocation(tb.trace.getProgramView(), tb.addr(0x00401000)));
waitForSwing();
assertTrue(traceDiffPlugin.actionPrevDiff.isEnabled());
performAction(traceDiffPlugin.actionPrevDiff);
assertEquals(tb.addr(0x00400328), traceDiffPlugin.getCurrentAddress());
assertTrue(traceDiffPlugin.actionPrevDiff.isEnabled());
performAction(traceDiffPlugin.actionPrevDiff);
assertEquals(tb.addr(0x0040012a), traceDiffPlugin.getCurrentAddress());
assertFalse(traceDiffPlugin.actionPrevDiff.isEnabled());
}
@Test
public void testActionNextDiff() throws Throwable {
createAndOpenTrace();
try (UndoableTransaction tid = tb.startTransaction()) {
DBTraceMemoryManager mm = tb.trace.getMemoryManager();
mm.createRegion(".text", 0, tb.range(0x00400000, 0x0040ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
ByteBuffer buf = ByteBuffer.allocate(0x1000); // Yes, smaller than .text
buf.limit(0x1000);
mm.putBytes(0, tb.addr(0x00400000), buf);
buf.position(0);
buf.putLong(0x0123, 0x1122334455667788L);
buf.putLong(0x0321, 0x1122334455667788L);
mm.putBytes(1, tb.addr(0x00400000), buf);
}
traceManager.activateTrace(tb.trace);
waitForSwing();
waitOn(traceDiffPlugin.startComparison(TraceSchedule.snap(1)));
assertTrue(traceDiffPlugin.actionNextDiff.isEnabled());
performAction(traceDiffPlugin.actionNextDiff);
waitForPass(() -> assertEquals(tb.addr(0x00400123), traceDiffPlugin.getCurrentAddress()));
assertTrue(traceDiffPlugin.actionNextDiff.isEnabled());
performAction(traceDiffPlugin.actionNextDiff);
assertEquals(tb.addr(0x00400321), traceDiffPlugin.getCurrentAddress());
assertFalse(traceDiffPlugin.actionNextDiff.isEnabled());
}
}

View file

@ -18,8 +18,7 @@ package ghidra.app.plugin.core.debug.gui.listing;
import static ghidra.lifecycle.Unfinished.TODO; import static ghidra.lifecycle.Unfinished.TODO;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.awt.*; import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -37,14 +36,13 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction;
import ghidra.app.plugin.core.debug.gui.action.*; import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin; import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction; import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow; import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow;
import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext; import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.async.SwingExecutorService; import ghidra.async.SwingExecutorService;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.plugin.importer.ImporterPlugin; import ghidra.plugin.importer.ImporterPlugin;
@ -68,27 +66,6 @@ import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUITest { public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
static LocationTrackingSpec getLocationTrackingSpec(String name) {
return LocationTrackingSpec.fromConfigName(name);
}
static AutoReadMemorySpec getAutoReadMemorySpec(String name) {
return AutoReadMemorySpec.fromConfigName(name);
}
final LocationTrackingSpec trackNone =
getLocationTrackingSpec(NoneLocationTrackingSpec.CONFIG_NAME);
final LocationTrackingSpec trackPc =
getLocationTrackingSpec(PCLocationTrackingSpec.CONFIG_NAME);
final LocationTrackingSpec trackSp =
getLocationTrackingSpec(SPLocationTrackingSpec.CONFIG_NAME);
final AutoReadMemorySpec readNone =
getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME);
final AutoReadMemorySpec readVisible =
getAutoReadMemorySpec(VisibleAutoReadMemorySpec.CONFIG_NAME);
final AutoReadMemorySpec readVisROOnce =
getAutoReadMemorySpec(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME);
protected DebuggerListingPlugin listingPlugin; protected DebuggerListingPlugin listingPlugin;
protected DebuggerListingProvider listingProvider; protected DebuggerListingProvider listingProvider;
@ -110,12 +87,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
} }
protected void goToDyn(ProgramLocation location) { protected void goToDyn(ProgramLocation location) {
waitForPass(() -> { goTo(listingProvider.getListingPanel(), location);
runSwing(() -> listingProvider.goTo(location.getProgram(), location));
ProgramLocation confirm = listingProvider.getLocation();
assertNotNull(confirm);
assertEquals(location.getAddress(), confirm.getAddress());
});
} }
protected static byte[] incBlock() { protected static byte[] incBlock() {
@ -572,32 +544,6 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertEquals(ss.getAddress(0x00601234), loc.getAddress()); assertEquals(ss.getAddress(0x00601234), loc.getAddress());
} }
protected void assertListingBackgroundAt(Color expected, ListingPanel panel,
Address addr, int yAdjust) throws AWTException, InterruptedException {
ProgramLocation oneBack = new ProgramLocation(panel.getProgram(), addr.previous());
runSwing(() -> panel.goTo(addr));
runSwing(() -> panel.goTo(oneBack, false));
waitForPass(() -> {
Rectangle r = panel.getBounds();
// Capture off screen, so that focus/stacking doesn't matter
BufferedImage image = new BufferedImage(r.width, r.height, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
try {
runSwing(() -> panel.paint(g));
}
finally {
g.dispose();
}
Point locP = panel.getLocationOnScreen();
Point locFP = panel.getLocationOnScreen();
locFP.translate(-locP.x, -locP.y);
Rectangle cursor = panel.getCursorBounds();
Color actual = new Color(image.getRGB(locFP.x + cursor.x - 1,
locFP.y + cursor.y + cursor.height * 3 / 2 + yAdjust));
assertEquals(expected, actual);
});
}
@Test @Test
public void testDynamicListingMarksTrackedRegister() throws Exception { public void testDynamicListingMarksTrackedRegister() throws Exception {
createAndOpenTrace(); createAndOpenTrace();

View file

@ -66,26 +66,6 @@ import ghidra.util.database.UndoableTransaction;
@Category(NightlyCategory.class) @Category(NightlyCategory.class)
public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebuggerGUITest { public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
static LocationTrackingSpec getLocationTrackingSpec(String name) {
return LocationTrackingSpec.fromConfigName(name);
}
static AutoReadMemorySpec getAutoReadMemorySpec(String name) {
return AutoReadMemorySpec.fromConfigName(name);
}
final LocationTrackingSpec trackNone =
getLocationTrackingSpec(NoneLocationTrackingSpec.CONFIG_NAME);
final LocationTrackingSpec trackPc =
getLocationTrackingSpec(PCLocationTrackingSpec.CONFIG_NAME);
final LocationTrackingSpec trackSp =
getLocationTrackingSpec(SPLocationTrackingSpec.CONFIG_NAME);
final AutoReadMemorySpec readNone = getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME);
final AutoReadMemorySpec readVisible =
getAutoReadMemorySpec(VisibleAutoReadMemorySpec.CONFIG_NAME);
final AutoReadMemorySpec readVisROOnce =
getAutoReadMemorySpec(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME);
protected DebuggerMemoryBytesPlugin memBytesPlugin; protected DebuggerMemoryBytesPlugin memBytesPlugin;
protected DebuggerMemoryBytesProvider memBytesProvider; protected DebuggerMemoryBytesProvider memBytesProvider;

View file

@ -23,7 +23,10 @@ import java.util.List;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import docking.widgets.dialogs.InputDialog;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.trace.database.time.DBTraceSnapshot;
import ghidra.trace.database.time.DBTraceTimeManager; import ghidra.trace.database.time.DBTraceTimeManager;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.TraceSnapshot;
@ -64,11 +67,11 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
} }
protected void assertProviderEmpty() { protected void assertProviderEmpty() {
assertTrue(timeProvider.snapshotTableModel.getModelData().isEmpty()); assertTrue(timeProvider.mainPanel.snapshotTableModel.getModelData().isEmpty());
} }
protected void assertProviderPopulated() { protected void assertProviderPopulated() {
List<SnapshotRow> snapsDisplayed = timeProvider.snapshotTableModel.getModelData(); List<SnapshotRow> snapsDisplayed = timeProvider.mainPanel.snapshotTableModel.getModelData();
// I should be able to assume this is sorted by key // I should be able to assume this is sorted by key
assertEquals(2, snapsDisplayed.size()); assertEquals(2, snapsDisplayed.size());
@ -85,6 +88,42 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
// Timestamp is left unchecked, since default is current time // Timestamp is left unchecked, since default is current time
} }
@Test // TODO: Technically, this is a plugin action.... Different test case?
public void testActionRenameSnapshot() throws Exception {
// Need some docked provider to provide action context
addPlugin(tool, DebuggerListingPlugin.class);
assertFalse(timePlugin.actionRenameSnapshot.isEnabled());
createSnaplessTrace();
addSnapshots();
assertFalse(timePlugin.actionRenameSnapshot.isEnabled());
traceManager.openTrace(tb.trace);
waitForSwing();
assertFalse(timePlugin.actionRenameSnapshot.isEnabled());
traceManager.activateTrace(tb.trace);
waitForSwing();
assertTrue(timePlugin.actionRenameSnapshot.isEnabled());
traceManager.activateSnap(10);
waitForSwing();
assertTrue(timePlugin.actionRenameSnapshot.isEnabled());
performAction(timePlugin.actionRenameSnapshot, false);
InputDialog dialog = waitForDialogComponent(InputDialog.class);
assertEquals("Snap 10", dialog.getValue());
dialog.setValue("My Snapshot");
dialog.close(); // isCancelled (private) defaults to false
waitForSwing();
DBTraceSnapshot snapshot = tb.trace.getTimeManager().getSnapshot(10, false);
assertEquals("My Snapshot", snapshot.getDescription());
// TODO: Test cancelled has no effect
}
@Test @Test
public void testEmpty() { public void testEmpty() {
assertProviderEmpty(); assertProviderEmpty();
@ -158,7 +197,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
} }
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
assertEquals(1, timeProvider.snapshotTableModel.getModelData().size()); assertEquals(1, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
} }
@Test @Test
@ -238,7 +277,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
waitForSwing(); waitForSwing();
SnapshotRow row = timeProvider.snapshotTableModel.getModelData().get(0); SnapshotRow row = timeProvider.mainPanel.snapshotTableModel.getModelData().get(0);
runSwing(() -> row.setDescription("Custom Description")); runSwing(() -> row.setDescription("Custom Description"));
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
@ -258,14 +297,14 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
waitForSwing(); waitForSwing();
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData(); List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
timeProvider.snapshotFilterPanel.setSelectedItem(data.get(0)); timeProvider.mainPanel.snapshotFilterPanel.setSelectedItem(data.get(0));
waitForSwing(); waitForSwing();
assertEquals(0, traceManager.getCurrentSnap()); assertEquals(0, traceManager.getCurrentSnap());
timeProvider.snapshotFilterPanel.setSelectedItem(data.get(1)); timeProvider.mainPanel.snapshotFilterPanel.setSelectedItem(data.get(1));
waitForSwing(); waitForSwing();
assertEquals(10, traceManager.getCurrentSnap()); assertEquals(10, traceManager.getCurrentSnap());
@ -283,22 +322,22 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
waitForSwing(); waitForSwing();
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData(); List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
traceManager.activateSnap(0); traceManager.activateSnap(0);
waitForSwing(); waitForSwing();
assertEquals(data.get(0), timeProvider.snapshotFilterPanel.getSelectedItem()); assertEquals(data.get(0), timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
traceManager.activateSnap(10); traceManager.activateSnap(10);
waitForSwing(); waitForSwing();
assertEquals(data.get(1), timeProvider.snapshotFilterPanel.getSelectedItem()); assertEquals(data.get(1), timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
traceManager.activateSnap(5); traceManager.activateSnap(5);
waitForSwing(); waitForSwing();
assertNull(timeProvider.snapshotFilterPanel.getSelectedItem()); assertNull(timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
} }
@Test @Test
@ -312,7 +351,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
waitForSwing(); waitForSwing();
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData(); List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
assertEquals(2, data.size()); assertEquals(2, data.size());
for (SnapshotRow row : data) { for (SnapshotRow row : data) {
assertTrue(row.getSnap() >= 0); assertTrue(row.getSnap() >= 0);
@ -329,12 +368,12 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
waitForSwing(); waitForSwing();
assertEquals(2, timeProvider.snapshotTableModel.getModelData().size()); assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
addScratchSnapshot(); addScratchSnapshot();
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData(); List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
assertEquals(2, data.size()); assertEquals(2, data.size());
for (SnapshotRow row : data) { for (SnapshotRow row : data) {
assertTrue(row.getSnap() >= 0); assertTrue(row.getSnap() >= 0);
@ -353,17 +392,17 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
waitForSwing(); waitForSwing();
assertEquals(true, timeProvider.hideScratch); assertEquals(true, timeProvider.hideScratch);
assertEquals(2, timeProvider.snapshotTableModel.getModelData().size()); assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
performAction(timeProvider.actionHideScratch); performAction(timeProvider.actionHideScratch);
assertEquals(false, timeProvider.hideScratch); assertEquals(false, timeProvider.hideScratch);
assertEquals(3, timeProvider.snapshotTableModel.getModelData().size()); assertEquals(3, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
performAction(timeProvider.actionHideScratch); performAction(timeProvider.actionHideScratch);
assertEquals(true, timeProvider.hideScratch); assertEquals(true, timeProvider.hideScratch);
assertEquals(2, timeProvider.snapshotTableModel.getModelData().size()); assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
} }
@Test @Test
@ -380,6 +419,6 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
waitForSwing(); waitForSwing();
assertEquals(false, timeProvider.hideScratch); assertEquals(false, timeProvider.hideScratch);
assertEquals(3, timeProvider.snapshotTableModel.getModelData().size()); assertEquals(3, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
} }
} }

View file

@ -327,6 +327,17 @@ public class DBTraceMemoryManager
return delegateRead(start.getAddressSpace(), m -> m.getBufferAt(snap, start, byteOrder)); return delegateRead(start.getAddressSpace(), m -> m.getBufferAt(snap, start, byteOrder));
} }
@Override
public Long getSnapOfMostRecentChangeToBlock(long snap, Address address) {
return delegateRead(address.getAddressSpace(),
m -> m.getSnapOfMostRecentChangeToBlock(snap, address));
}
@Override
public int getBlockSize() {
return DBTraceMemorySpace.BLOCK_SIZE;
}
@Override @Override
public void pack() { public void pack() {
delegateWriteAll(getActiveSpaces(), m -> m.pack()); delegateWriteAll(getActiveSpaces(), m -> m.pack());

View file

@ -884,6 +884,26 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
return false; return false;
} }
@Override
public Long getSnapOfMostRecentChangeToBlock(long snap, Address address) {
assertInSpace(address);
try (LockHold hold = LockHold.lock(lock.readLock())) {
long offset = address.getOffset();
long roundOffset = offset & BLOCK_MASK;
OffsetSnap loc = new OffsetSnap(roundOffset, snap);
DBTraceMemoryBlockEntry ent = findMostRecentBlockEntry(loc, true);
if (ent == null) {
return null;
}
return ent.getSnap();
}
}
@Override
public int getBlockSize() {
return BLOCK_SIZE;
}
public long getFirstChange(Range<Long> span, AddressRange range) { public long getFirstChange(Range<Long> span, AddressRange range) {
assertInSpace(range); assertInSpace(range);
long lower = DBTraceUtils.lowerEndpoint(span); long lower = DBTraceUtils.lowerEndpoint(span);

View file

@ -472,6 +472,32 @@ public interface TraceMemoryOperations {
: ByteOrder.LITTLE_ENDIAN); : ByteOrder.LITTLE_ENDIAN);
} }
/**
* Find the internal storage block that most-recently defines the value at the given snap and
* address, and return the block's snap.
*
* <p>
* This method reveals portions of the internal storage so that clients can optimize difference
* computations by eliminating corresponding ranges defined by the same block. If the underlying
* implementation cannot answer this question, this returns the given snap.
*
* @param snap the time
* @param address the location
* @return the most snap for the most recent containing block
*/
Long getSnapOfMostRecentChangeToBlock(long snap, Address address);
/**
* Get the block size used by internal storage.
*
* <p>
* This method reveals portions of the internal storage so that clients can optimize searches.
* If the underlying implementation cannot answer this question, this returns 0.
*
* @return the block size
*/
int getBlockSize();
/** /**
* Optimize storage space * Optimize storage space
* *

View file

@ -368,7 +368,7 @@ public class Sequence implements Comparable<Sequence> {
* *
* @param trace the trace to which the machine is bound * @param trace the trace to which the machine is bound
* @param eventThread the thread for the first step, if it applies to the "last thread" * @param eventThread the thread for the first step, if it applies to the "last thread"
* @param machine the machine to step * @param machine the machine to step, or null to validate the sequence
* @param action the action to step each thread * @param action the action to step each thread
* @param monitor a monitor for cancellation and progress reports * @param monitor a monitor for cancellation and progress reports
* @return the last trace thread stepped during execution * @return the last trace thread stepped during execution
@ -384,6 +384,22 @@ public class Sequence implements Comparable<Sequence> {
return thread; return thread;
} }
/**
* Validate this sequence for the given trace
*
* @param trace the trace
* @param eventThread the thread for the first step, if it applies to the "last thread"
* @return the last trace thread that would be stepped by this sequence
*/
public TraceThread validate(Trace trace, TraceThread eventThread) {
try {
return execute(trace, eventThread, null, null, null);
}
catch (CancelledException e) {
throw new AssertionError(e);
}
}
/** /**
* Get the key of the last thread stepped * Get the key of the last thread stepped
* *

View file

@ -91,8 +91,8 @@ public interface Step extends Comparable<Step> {
TraceThread thread = isEventThread() ? eventThread : tm.getThread(getThreadKey()); TraceThread thread = isEventThread() ? eventThread : tm.getThread(getThreadKey());
if (thread == null) { if (thread == null) {
if (isEventThread()) { if (isEventThread()) {
throw new IllegalArgumentException( throw new IllegalArgumentException("Thread must be given, e.g., 0:t1-3, " +
"Thread key -1 can only be used if last/event thread is given"); "since the last thread or snapshot event thread is not given.");
} }
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Thread with key " + getThreadKey() + " does not exist in given trace"); "Thread with key " + getThreadKey() + " does not exist in given trace");
@ -160,6 +160,10 @@ public interface Step extends Comparable<Step> {
PcodeMachine<T> machine, Consumer<PcodeThread<T>> stepAction, TaskMonitor monitor) PcodeMachine<T> machine, Consumer<PcodeThread<T>> stepAction, TaskMonitor monitor)
throws CancelledException { throws CancelledException {
TraceThread thread = getThread(tm, eventThread); TraceThread thread = getThread(tm, eventThread);
if (machine == null) {
// Just performing validation (specifically thread parts)
return thread;
}
PcodeThread<T> emuThread = machine.getThread(thread.getPath(), true); PcodeThread<T> emuThread = machine.getThread(thread.getPath(), true);
execute(emuThread, stepAction, monitor); execute(emuThread, stepAction, monitor);
return thread; return thread;

View file

@ -344,6 +344,22 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
pSteps.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor); pSteps.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor);
} }
/**
* Validate this schedule for the given trace
*
* <p>
* This performs a dry run of the sequence on the given trace. If the schedule starts on the
* "last thread," it verifies the snapshot gives the event thread. It also checks that every
* thread key in the sequence exists in the trace.
*
* @param trace the trace against which to validate this schedule
*/
public void validate(Trace trace) {
TraceThread lastThread = getEventThread(trace);
lastThread = steps.validate(trace, lastThread);
lastThread = pSteps.validate(trace, lastThread);
}
/** /**
* Realize the machine state for this schedule using the given trace and pre-positioned machine * Realize the machine state for this schedule using the given trace and pre-positioned machine
* *
@ -385,13 +401,13 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
* This schedule is left unmodified. If it had any p-code steps, those steps are dropped in the * This schedule is left unmodified. If it had any p-code steps, those steps are dropped in the
* resulting schedule. * resulting schedule.
* *
* @param thread the thread to step * @param thread the thread to step, or null for the "last thread"
* @param tickCount the number of ticks to take the thread forward * @param tickCount the number of ticks to take the thread forward
* @return the resulting schedule * @return the resulting schedule
*/ */
public TraceSchedule steppedForward(TraceThread thread, long tickCount) { public TraceSchedule steppedForward(TraceThread thread, long tickCount) {
Sequence steps = this.steps.clone(); Sequence steps = this.steps.clone();
steps.advance(new TickStep(thread.getKey(), tickCount)); steps.advance(new TickStep(thread == null ? -1 : thread.getKey(), tickCount));
return new TraceSchedule(snap, steps, new Sequence()); return new TraceSchedule(snap, steps, new Sequence());
} }
@ -441,13 +457,13 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
* Returns the equivalent of executing the schedule followed by stepping the given thread * Returns the equivalent of executing the schedule followed by stepping the given thread
* {@code pTickCount} more p-code operations * {@code pTickCount} more p-code operations
* *
* @param thread the thread to step * @param thread the thread to step, or null for the "last thread"
* @param pTickCount the number of p-code ticks to take the thread forward * @param pTickCount the number of p-code ticks to take the thread forward
* @return the resulting schedule * @return the resulting schedule
*/ */
public TraceSchedule steppedPcodeForward(TraceThread thread, int pTickCount) { public TraceSchedule steppedPcodeForward(TraceThread thread, int pTickCount) {
Sequence pTicks = this.pSteps.clone(); Sequence pTicks = this.pSteps.clone();
pTicks.advance(new TickStep(thread.getKey(), pTickCount)); pTicks.advance(new TickStep(thread == null ? -1 : thread.getKey(), pTickCount));
return new TraceSchedule(snap, steps.clone(), pTicks); return new TraceSchedule(snap, steps.clone(), pTicks);
} }

View file

@ -75,14 +75,16 @@ public interface AutoService {
} }
} }
public static Wiring wireServicesConsumed(Plugin plugin, Object receiver) { public static Wiring wireServicesConsumed(PluginTool tool, Object receiver) {
// TODO: Validate against PluginInfo?
AutoServiceListener<Object> listener = new AutoServiceListener<>(receiver); AutoServiceListener<Object> listener = new AutoServiceListener<>(receiver);
PluginTool tool = plugin.getTool();
tool.addServiceListener(listener); tool.addServiceListener(listener);
listener.notifyCurrentServices(tool); listener.notifyCurrentServices(tool);
return new WiringImpl(listener); return new WiringImpl(listener);
} }
public static Wiring wireServicesConsumed(Plugin plugin, Object receiver) {
// TODO: Validate against PluginInfo?
return wireServicesConsumed(plugin.getTool(), receiver);
}
} }

View file

@ -41,14 +41,14 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
protected static final String OPT2_NAME = "Test Option 2"; protected static final String OPT2_NAME = "Test Option 2";
protected static final String OPT2_DEFAULT = "Default value"; protected static final String OPT2_DEFAULT = "Default value";
protected static final String OPT2_DESC = "Another test option"; protected static final String OPT2_DESC = "Another test option";
protected static final String OPT2_NEW_VALUE = "A new value";
@PluginInfo(// @PluginInfo(
category = "Testing", // category = "Testing",
description = "A plugin class replete with auto option annotations", // description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME, // packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class",// shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN // status = PluginStatus.HIDDEN)
)
public static class AnnotatedWithOptionsPlugin extends Plugin { public static class AnnotatedWithOptionsPlugin extends Plugin {
@AutoOptionDefined(name = OPT1_NAME, description = OPT1_DESC) @AutoOptionDefined(name = OPT1_NAME, description = OPT1_DESC)
private int myIntOption = OPT1_DEFAULT; private int myIntOption = OPT1_DEFAULT;
@ -65,13 +65,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
} }
} }
@PluginInfo(// @PluginInfo(
category = "Testing", // category = "Testing",
description = "A plugin class replete with auto option annotations", // description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME, // packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class", // shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN // status = PluginStatus.HIDDEN)
)
public static class AnnotatedWithOptionsNoParamPlugin extends AnnotatedWithOptionsPlugin { public static class AnnotatedWithOptionsNoParamPlugin extends AnnotatedWithOptionsPlugin {
protected int updateNoParamCount; protected int updateNoParamCount;
@ -85,13 +84,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
} }
} }
@PluginInfo(// @PluginInfo(
category = "Testing", // category = "Testing",
description = "A plugin class replete with auto option annotations", // description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME, // packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class", // shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN // status = PluginStatus.HIDDEN)
)
public static class AnnotatedWithOptionsNewOnlyParamDefaultPlugin public static class AnnotatedWithOptionsNewOnlyParamDefaultPlugin
extends AnnotatedWithOptionsPlugin { extends AnnotatedWithOptionsPlugin {
protected int updateNewOnlyParamDefaultNew; protected int updateNewOnlyParamDefaultNew;
@ -107,13 +105,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
} }
@PluginInfo(// @PluginInfo(
category = "Testing", // category = "Testing",
description = "A plugin class replete with auto option annotations", // description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME, // packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class", // shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN // status = PluginStatus.HIDDEN)
)
public static class AnnotatedWithOptionsNewOnlyParamAnnotatedPlugin public static class AnnotatedWithOptionsNewOnlyParamAnnotatedPlugin
extends AnnotatedWithOptionsPlugin { extends AnnotatedWithOptionsPlugin {
protected int updateNewOnlyParamAnnotatedNew; protected int updateNewOnlyParamAnnotatedNew;
@ -128,13 +125,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
} }
} }
@PluginInfo(// @PluginInfo(
category = "Testing", // category = "Testing",
description = "A plugin class replete with auto option annotations", // description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME, // packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class", // shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN // status = PluginStatus.HIDDEN)
)
public static class AnnotatedWithOptionsOldOnlyParamAnnotatedPlugin public static class AnnotatedWithOptionsOldOnlyParamAnnotatedPlugin
extends AnnotatedWithOptionsPlugin { extends AnnotatedWithOptionsPlugin {
protected int updateOldOnlyParamAnnotatedOld; protected int updateOldOnlyParamAnnotatedOld;
@ -149,13 +145,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
} }
} }
@PluginInfo(// @PluginInfo(
category = "Testing", // category = "Testing",
description = "A plugin class replete with auto option annotations", // description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME, // packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class", // shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN // status = PluginStatus.HIDDEN)
)
public static class AnnotatedWithOptionsNewOldParamDefaultPlugin public static class AnnotatedWithOptionsNewOldParamDefaultPlugin
extends AnnotatedWithOptionsPlugin { extends AnnotatedWithOptionsPlugin {
protected int updateNewOldParamDefaultNew; protected int updateNewOldParamDefaultNew;
@ -172,13 +167,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
} }
} }
@PluginInfo(// @PluginInfo(
category = "Testing", // category = "Testing",
description = "A plugin class replete with auto option annotations", // description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME, // packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class", // shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN // status = PluginStatus.HIDDEN)
)
public static class AnnotatedWithOptionsNewOldParamNewAnnotPlugin public static class AnnotatedWithOptionsNewOldParamNewAnnotPlugin
extends AnnotatedWithOptionsPlugin { extends AnnotatedWithOptionsPlugin {
protected int updateNewOldParamNewAnnotNew; protected int updateNewOldParamNewAnnotNew;
@ -195,13 +189,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
} }
} }
@PluginInfo(// @PluginInfo(
category = "Testing", // category = "Testing",
description = "A plugin class replete with auto option annotations", // description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME, // packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class", // shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN // status = PluginStatus.HIDDEN)
)
public static class AnnotatedWithOptionsNewOldParamOldAnnotPlugin public static class AnnotatedWithOptionsNewOldParamOldAnnotPlugin
extends AnnotatedWithOptionsPlugin { extends AnnotatedWithOptionsPlugin {
protected int updateNewOldParamOldAnnotNew; protected int updateNewOldParamOldAnnotNew;
@ -218,13 +211,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
} }
} }
@PluginInfo(// @PluginInfo(
category = "Testing", // category = "Testing",
description = "A plugin class replete with auto option annotations", // description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME, // packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class", // shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN // status = PluginStatus.HIDDEN)
)
public static class AnnotatedWithOptionsNewOldParamNewOldAnnotPlugin public static class AnnotatedWithOptionsNewOldParamNewOldAnnotPlugin
extends AnnotatedWithOptionsPlugin { extends AnnotatedWithOptionsPlugin {
protected int updateNewOldParamNewOldAnnotNew; protected int updateNewOldParamNewOldAnnotNew;
@ -242,13 +234,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
} }
} }
@PluginInfo(// @PluginInfo(
category = "Testing", // category = "Testing",
description = "A plugin class replete with auto option annotations", // description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME, // packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class", // shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN // status = PluginStatus.HIDDEN)
)
public static class AnnotatedWithOptionsOldNewParamNewAnnotPlugin public static class AnnotatedWithOptionsOldNewParamNewAnnotPlugin
extends AnnotatedWithOptionsPlugin { extends AnnotatedWithOptionsPlugin {
protected int updateOldNewParamNewAnnotNew; protected int updateOldNewParamNewAnnotNew;
@ -265,13 +256,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
} }
} }
@PluginInfo(// @PluginInfo(
category = "Testing", // category = "Testing",
description = "A plugin class replete with auto option annotations", // description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME, // packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class", // shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN // status = PluginStatus.HIDDEN)
)
public static class AnnotatedWithOptionsOldNewParamOldAnnotPlugin public static class AnnotatedWithOptionsOldNewParamOldAnnotPlugin
extends AnnotatedWithOptionsPlugin { extends AnnotatedWithOptionsPlugin {
protected int updateOldNewParamOldAnnotNew; protected int updateOldNewParamOldAnnotNew;
@ -288,13 +278,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
} }
} }
@PluginInfo(// @PluginInfo(
category = "Testing", // category = "Testing",
description = "A plugin class replete with auto option annotations", // description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME, // packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class", // shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN // status = PluginStatus.HIDDEN)
)
public static class AnnotatedWithOptionsOldNewParamOldNewAnnotPlugin public static class AnnotatedWithOptionsOldNewParamOldNewAnnotPlugin
extends AnnotatedWithOptionsPlugin { extends AnnotatedWithOptionsPlugin {
protected int updateOldNewParamOldNewAnnotNew; protected int updateOldNewParamOldNewAnnotNew;
@ -312,13 +301,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
} }
} }
@PluginInfo(// @PluginInfo(
category = "Testing", // category = "Testing",
description = "Consumer-only plugin class with auto option annotations", // description = "Consumer-only plugin class with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME, // packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "A consumer-only plugin class", // shortDescription = "A consumer-only plugin class",
status = PluginStatus.HIDDEN // status = PluginStatus.HIDDEN)
)
public static class AnnotatedConsumerOnlyPlugin extends Plugin { public static class AnnotatedConsumerOnlyPlugin extends Plugin {
@AutoOptionConsumed(name = OPT1_NAME) @AutoOptionConsumed(name = OPT1_NAME)
private int othersIntOption; private int othersIntOption;
@ -387,7 +375,18 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals(6, plugin.myIntOption); assertEquals(6, plugin.myIntOption);
options.setInt(OPT1_NAME, OPT1_NEW_VALUE); options.setInt(OPT1_NAME, OPT1_NEW_VALUE);
assertEquals(10, plugin.myIntOption); assertEquals(OPT1_NEW_VALUE, plugin.myIntOption);
}
@Test
public void testOptionsUpdatedExplicitCategory() throws PluginException {
AnnotatedWithOptionsPlugin plugin = addPlugin(tool, AnnotatedWithOptionsPlugin.class);
ToolOptions options = tool.getOptions(OPT2_CATEGORY);
assertEquals(1, options.getOptionNames().size());
options.setString(OPT2_NAME, OPT2_NEW_VALUE);
assertEquals(OPT2_NEW_VALUE, plugin.myStringOption);
} }
@Test @Test

View file

@ -705,6 +705,16 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
return true; return true;
} }
/**
* Extension point to specify titles when dual panels are active
*
* @param panelProgram the program assigned to the panel whose title is requested
* @return the title of the panel for the given program
*/
protected String computePanelTitle(Program panelProgram) {
return panelProgram.getDomainFile().toString();
}
public void setPanel(ListingPanel lp) { public void setPanel(ListingPanel lp) {
Program myProgram = listingPanel.getListingModel().getProgram(); Program myProgram = listingPanel.getListingModel().getProgram();
Program otherProgram = lp.getListingModel().getProgram(); Program otherProgram = lp.getListingModel().getProgram();
@ -712,10 +722,10 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
String otherName = myName; String otherName = myName;
if (myProgram != null) { if (myProgram != null) {
myName = myProgram.getDomainFile().toString(); myName = computePanelTitle(myProgram);
} }
if (otherProgram != null) { if (otherProgram != null) {
otherName = otherProgram.getDomainFile().toString(); otherName = computePanelTitle(otherProgram);
} }
if (otherPanel != null) { if (otherPanel != null) {
removeHoverServices(otherPanel); removeHoverServices(otherPanel);

View file

@ -43,12 +43,12 @@ public class InputDialog extends DialogComponentProvider {
private InputDialogListener listener; private InputDialogListener listener;
/** /**
* Creates a provider for a generic input dialog with the specified title, a text field, * Creates a provider for a generic input dialog with the specified title, a text field, labeled
* labeled by the specified label. The user should check the value of * by the specified label. The user should check the value of "isCanceled()" to know whether or
* "isCanceled()" to know whether or not the user canceled the operation. * not the user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get
* Otherwise, use the "getValue()" or "getValues()" to get the value(s) * the value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
* entered by the user. Use the tool's "showDialog()" to display the dialog.
* <P> * <P>
*
* @param dialogTitle used as the name of the dialog's title bar * @param dialogTitle used as the name of the dialog's title bar
* @param label value to use for the label of the text field * @param label value to use for the label of the text field
*/ */
@ -57,12 +57,12 @@ public class InputDialog extends DialogComponentProvider {
} }
/** /**
* Creates a generic input dialog with the specified title, a text field, * Creates a generic input dialog with the specified title, a text field, labeled by the
* labeled by the specified label. The user should check the value of * specified label. The user should check the value of "isCanceled()" to know whether or not the
* "isCanceled()" to know whether or not the user canceled the operation. * user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
* Otherwise, use the "getValue()" or "getValues()" to get the value(s) * value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
* entered by the user. Use the tool's "showDialog()" to display the dialog.
* <P> * <P>
*
* @param dialogTitle used as the name of the dialog's title bar * @param dialogTitle used as the name of the dialog's title bar
* @param label value to use for the label of the text field * @param label value to use for the label of the text field
* @param initialValue initial value to use for the text field * @param initialValue initial value to use for the text field
@ -72,12 +72,12 @@ public class InputDialog extends DialogComponentProvider {
} }
/** /**
* Creates a generic input dialog with the specified title, a text field, * Creates a generic input dialog with the specified title, a text field, labeled by the
* labeled by the specified label. The user should check the value of * specified label. The user should check the value of "isCanceled()" to know whether or not the
* "isCanceled()" to know whether or not the user canceled the operation. * user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
* Otherwise, use the "getValue()" or "getValues()" to get the value(s) * value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
* entered by the user. Use the tool's "showDialog()" to display the dialog.
* <P> * <P>
*
* @param dialogTitle used as the name of the dialog's title bar * @param dialogTitle used as the name of the dialog's title bar
* @param label value to use for the label of the text field * @param label value to use for the label of the text field
* @param initialValue initial value to use for the text field * @param initialValue initial value to use for the text field
@ -89,12 +89,12 @@ public class InputDialog extends DialogComponentProvider {
} }
/** /**
* Creates a generic input dialog with the specified title, a text field, * Creates a generic input dialog with the specified title, a text field, labeled by the
* labeled by the specified label. The user should check the value of * specified label. The user should check the value of "isCanceled()" to know whether or not the
* "isCanceled()" to know whether or not the user canceled the operation. * user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
* Otherwise, use the "getValue()" or "getValues()" to get the value(s) * value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
* entered by the user. Use the tool's "showDialog()" to display the dialog.
* <P> * <P>
*
* @param dialogTitle used as the name of the dialog's title bar * @param dialogTitle used as the name of the dialog's title bar
* @param label value to use for the label of the text field * @param label value to use for the label of the text field
* @param initialValue initial value to use for the text field * @param initialValue initial value to use for the text field
@ -105,12 +105,12 @@ public class InputDialog extends DialogComponentProvider {
} }
/** /**
* Creates a generic input dialog with the specified title, a text field, * Creates a generic input dialog with the specified title, a text field, labeled by the
* labeled by the specified label. The user should check the value of * specified label. The user should check the value of "isCanceled()" to know whether or not the
* "isCanceled()" to know whether or not the user canceled the operation. * user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
* Otherwise, use the "getValue()" or "getValues()" to get the value(s) * value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
* entered by the user. Use the tool's "showDialog()" to display the dialog.
* <P> * <P>
*
* @param dialogTitle used as the name of the dialog's title bar * @param dialogTitle used as the name of the dialog's title bar
* @param labels values to use for the labels of the text fields * @param labels values to use for the labels of the text fields
* @param initialValues initial values to use for the text fields * @param initialValues initial values to use for the text fields
@ -120,12 +120,12 @@ public class InputDialog extends DialogComponentProvider {
} }
/** /**
* Creates a generic input dialog with the specified title, a text field, * Creates a generic input dialog with the specified title, a text field, labeled by the
* labeled by the specified label. The user should check the value of * specified label. The user should check the value of "isCanceled()" to know whether or not the
* "isCanceled()" to know whether or not the user canceled the operation. * user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
* Otherwise, use the "getValue()" or "getValues()" to get the value(s) * value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
* entered by the user. Use the tool's "showDialog()" to display the dialog.
* <P> * <P>
*
* @param dialogTitle used as the name of the dialog's title bar * @param dialogTitle used as the name of the dialog's title bar
* @param labels values to use for the labels of the text fields * @param labels values to use for the labels of the text fields
* @param initialValues initial values to use for the text fields * @param initialValues initial values to use for the text fields
@ -194,6 +194,7 @@ public class InputDialog extends DialogComponentProvider {
textFields = new MyTextField[inputLabels.length]; textFields = new MyTextField[inputLabels.length];
for (int i = 0; i < inputValues.length; i++) { for (int i = 0; i < inputValues.length; i++) {
textFields[i] = new MyTextField(initialValues[i]); textFields[i] = new MyTextField(initialValues[i]);
inputValues[i] = initialValues[i];
textFields[i].addKeyListener(keyListener); textFields[i].addKeyListener(keyListener);
textFields[i].setName("input.dialog.text.field." + i); textFields[i].setName("input.dialog.text.field." + i);
panel.add(new GLabel(inputLabels[i], SwingConstants.RIGHT)); panel.add(new GLabel(inputLabels[i], SwingConstants.RIGHT));
@ -221,11 +222,16 @@ public class InputDialog extends DialogComponentProvider {
@Override @Override
protected void cancelCallback() { protected void cancelCallback() {
isCanceled = true; isCanceled = true;
for (int v = 0; v < inputValues.length; v++) {
inputValues[v] = null;
}
close(); close();
} }
/** /**
* Returns if this dialog is cancelled * Returns if this dialog is cancelled
*
* @return true if cancelled * @return true if cancelled
*/ */
public boolean isCanceled() { public boolean isCanceled() {
@ -234,6 +240,7 @@ public class InputDialog extends DialogComponentProvider {
/** /**
* Return the value of the first (and maybe only) text field * Return the value of the first (and maybe only) text field
*
* @return the text field value * @return the text field value
*/ */
public String getValue() { public String getValue() {
@ -242,6 +249,7 @@ public class InputDialog extends DialogComponentProvider {
/** /**
* Sets the text of the primary text field * Sets the text of the primary text field
*
* @param text the text * @param text the text
*/ */
public void setValue(String text) { public void setValue(String text) {
@ -250,15 +258,18 @@ public class InputDialog extends DialogComponentProvider {
/** /**
* Sets the text of the text field at the given index * Sets the text of the text field at the given index
*
* @param text the text * @param text the text
* @param index the index of the text field * @param index the index of the text field
*/ */
public void setValue(String text, int index) { public void setValue(String text, int index) {
textFields[index].setText(text); textFields[index].setText(text);
inputValues[index] = text;
} }
/** /**
* Return the values for all the text field(s) * Return the values for all the text field(s)
*
* @return the text field values * @return the text field values
*/ */
public String[] getValues() { public String[] getValues() {
@ -285,7 +296,8 @@ public class InputDialog extends DialogComponentProvider {
} }
/** /**
* @see javax.swing.text.Document#insertString(int, java.lang.String, javax.swing.text.AttributeSet) * @see javax.swing.text.Document#insertString(int, java.lang.String,
* javax.swing.text.AttributeSet)
*/ */
@Override @Override
public void insertString(int offs, String str, AttributeSet a) public void insertString(int offs, String str, AttributeSet a)