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')
helpPath project(path: ':Base', configuration: 'helpPath')
helpPath project(path: ':ProgramDiff', configuration: 'helpPath')
testImplementation project(path: ':Framework-AsyncComm', 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/images/DebuggerTimePlugin.png||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/images/DebuggerWatchesPlugin.png||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/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_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/thread.png||GHIDRA||||END|
src/main/resources/images/time.png||FAMFAMFAM Icons - CC 2.5||||END|

View file

@ -157,6 +157,10 @@
sortgroup="p"
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"
sortgroup="q"
target="help/topics/DebuggerBots/DebuggerBots.html" />

View file

@ -56,15 +56,20 @@
<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>
<P>This toggle action is always available. It is enabled by default. The emulation service,
which enables trace extrapolation and interpolation, writes emulated state into the trace's
"scratch space," which comprises all negative snaps. When this toggle is enabled, those
snapshots are hidden. They can be displayed by disabling this toggle. Note that navigating into
scratch space may cause temporary undefined behavior in some windows, and may prevent
interaction with the target.</P>
<P>This toggle action is always available in the drop-down actions of the Time window. It is
enabled by default. The emulation service, which enables trace extrapolation and interpolation,
writes emulated state into the trace's "scratch space," which comprises all negative snaps.
When this toggle is enabled, those snapshots are hidden. They can be displayed by disabling
this toggle. Note that navigating into scratch space may cause temporary undefined behavior in
some windows, and may prevent interaction with the target.</P>
</BODY>
</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);
}
public DebuggerCoordinates withView(TraceProgramView newView) {
return all(trace, recorder, thread, newView, time, frame);
}
public TraceSchedule getTime() {
return time;
}

View file

@ -147,6 +147,8 @@ public interface DebuggerResources {
ImageIcon ICON_READ_MEMORY = ICON_REGIONS;
//ResourceManager.loadImage("images/read-memory.png");
ImageIcon ICON_RENAME_SNAPSHOT = ICON_TIME;
// TODO: Draw an icon
ImageIcon ICON_MAP_IDENTICALLY = ResourceManager.loadImage("images/doubleArrow.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_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");
String HELP_ANCHOR_PLUGIN = "plugin";
@ -367,6 +373,7 @@ public interface DebuggerResources {
String GROUP_TRACE_CLOSE = "Dbg7.b. Trace Close";
String GROUP_MAINTENANCE = "Dbg8. Maintenance";
String GROUP_MAPPING = "Dbg9. Map Modules/Sections";
String GROUP_DIFF_NAV = "DiffNavigate";
static void tableRowActivationAction(GTable table, Runnable runnable) {
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 {
String NAME = "Synchronize Focus";
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 {
@Override
public String getName() {

View file

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

View file

@ -227,7 +227,7 @@ public class DebuggerTrackLocationTrait {
protected void doSetSpec(LocationTrackingSpec spec) {
if (this.spec != spec) {
this.spec = spec;
specChanged();
specChanged(spec);
}
doTrack();
}
@ -299,7 +299,7 @@ public class DebuggerTrackLocationTrait {
// Listener method
}
protected void specChanged() {
protected void specChanged(LocationTrackingSpec spec) {
// 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.Wiring;
import ghidra.framework.options.annotation.AutoOptionConsumed;
import ghidra.framework.plugintool.Plugin;
import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramLocation;
@ -41,7 +42,7 @@ class CursorBackgroundColorModel implements ListingBackgroundColorModel {
@SuppressWarnings("unused")
private final Wiring autoOptionsWiring;
public CursorBackgroundColorModel(DebuggerListingPlugin plugin, ListingPanel listingPanel) {
public CursorBackgroundColorModel(Plugin plugin, ListingPanel listingPanel) {
autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
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.services.*;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.framework.options.AutoOptions;
import ghidra.framework.options.SaveState;
import ghidra.framework.options.annotation.AutoOptionDefined;
@ -162,6 +163,16 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
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
protected DebuggerListingProvider createProvider(FormatManager formatManager,
boolean isConnected) {
@ -322,6 +333,21 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
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
public void setCurrentSelection(ProgramSelection 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.ProgramURLUtils;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerListingService.LocationTrackingSpecChangeListener;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.framework.model.DomainFile;
@ -71,8 +72,9 @@ import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.Trace;
import ghidra.trace.model.modules.*;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.util.HTMLUtilities;
import ghidra.util.Swing;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.*;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.*;
@ -182,6 +184,11 @@ public class DebuggerListingProvider extends CodeViewerProvider {
DebuggerListingProvider.this);
}
@Override
protected void specChanged(LocationTrackingSpec spec) {
trackingSpecChangeListeners.fire.locationTrackingSpecChanged(spec);
}
@Override
protected void locationTracked() {
doGoToTracked();
@ -247,9 +254,12 @@ public class DebuggerListingProvider extends CodeViewerProvider {
protected boolean followsCurrentThread = true;
// TODO: followsCurrentSnap?
protected DebuggerGoToTrait goToTrait;
protected ForListingTrackingTrait trackingTrait;
protected ForListingReadsMemoryTrait readsMemTrait;
protected final DebuggerGoToTrait goToTrait;
protected final ForListingTrackingTrait trackingTrait;
protected final ForListingReadsMemoryTrait readsMemTrait;
protected final ListenerSet<LocationTrackingSpecChangeListener> trackingSpecChangeListeners =
new ListenerSet<>(LocationTrackingSpecChangeListener.class);
protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel();
@ -275,10 +285,8 @@ public class DebuggerListingProvider extends CodeViewerProvider {
readsMemTrait = new ForListingReadsMemoryTrait();
ListingPanel listingPanel = getListingPanel();
colorModel = new MultiBlendedListingBackgroundColorModel();
colorModel = plugin.createListingBackgroundColorModel(listingPanel);
colorModel.addModel(trackingTrait.createListingBackgroundColorModel(listingPanel));
colorModel.addModel(new MemoryStateListingBackgroundColorModel(plugin, listingPanel));
colorModel.addModel(new CursorBackgroundColorModel(plugin, listingPanel));
listingPanel.setBackgroundColorModel(colorModel);
autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
@ -493,12 +501,10 @@ public class DebuggerListingProvider extends CodeViewerProvider {
if (this.markerService != null) {
this.markerService.removeChangeListener(markerChangeListener);
}
this.markerService = markerService;
updateMarkerServiceColorModel();
removeOldStaticTrackingMarker();
this.markerService = markerService;
createNewStaticTrackingMarker();
updateMarkerServiceColorModel();
if (this.markerService != null && !isMainListing()) {
// NOTE: Connected provider marker listener is taken care of by CodeBrowserPlugin
@ -594,6 +600,38 @@ public class DebuggerListingProvider extends CodeViewerProvider {
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() {
if (isMainListing()) {
actionSyncToStaticListing = new SyncToStaticListingAction();
@ -842,6 +880,14 @@ public class DebuggerListingProvider extends CodeViewerProvider {
return trackingTrait.getSpec();
}
public void addTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener) {
trackingSpecChangeListeners.add(listener);
}
public void removeTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener) {
trackingSpecChangeListeners.remove(listener);
}
public void setSyncToStaticListing(boolean sync) {
if (!isMainListing()) {
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.framework.options.AutoOptions;
import ghidra.framework.options.annotation.AutoOptionConsumed;
import ghidra.framework.plugintool.Plugin;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.TraceAddressSnapRange;
@ -47,7 +48,7 @@ public class MemoryStateListingBackgroundColorModel implements ListingBackground
@SuppressWarnings("unused")
private final AutoOptions.Wiring autoOptionsWiring;
public MemoryStateListingBackgroundColorModel(DebuggerListingPlugin plugin,
public MemoryStateListingBackgroundColorModel(Plugin plugin,
ListingPanel listingPanel) {
autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
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?
setDefaultWindowPosition(WindowPosition.BOTTOM);
myActionContext = new DebuggerSnapActionContext(0);
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getViewSnap());
createActions();
contextChanged();
@ -617,7 +617,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
snap = 0;
}
traceManager.activateSnap(snap);
myActionContext = new DebuggerSnapActionContext(snap);
myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
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;
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.core.debug.AbstractDebuggerPlugin;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
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.framework.options.SaveState;
import ghidra.framework.plugintool.*;
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(
shortDescription = "Lists recorded snapshots in a trace",
@ -39,8 +54,12 @@ import ghidra.framework.plugintool.util.PluginStatus;
public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
protected DebuggerTimeProvider provider;
protected DockingAction actionRenameSnapshot;
public DebuggerTimePlugin(PluginTool tool) {
super(tool);
createActions();
}
@Override
@ -49,6 +68,58 @@ public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
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
protected void dispose() {
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 java.awt.BorderLayout;
import java.awt.event.MouseEvent;
import java.lang.invoke.MethodHandles;
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 javax.swing.JComponent;
import docking.ActionContext;
import docking.action.DockingActionIf;
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.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.framework.model.DomainObject;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.AutoService.Wiring;
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
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 {
private static final AutoConfigState.ClassHandler<DebuggerTimeProvider> CONFIG_STATE_HANDLER =
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) {
if (!Objects.equals(a.getTrace(), b.getTrace())) {
return false;
@ -162,21 +54,13 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
protected final DebuggerTimePlugin plugin;
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
private Trace currentTrace; // copy for transition
protected final SnapshotListener listener = new SnapshotListener();
@AutoServiceConsumed
protected DebuggerTraceManagerService viewManager;
@SuppressWarnings("unused")
private final Wiring autoServiceWiring;
private final JPanel mainPanel = new JPanel(new BorderLayout());
/* testing */ final EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel =
new DefaultEnumeratedColumnTableModel<>("Snapshots", SnapshotTableColumns.class);
/* testing */ GTable snapshotTable;
/* testing */ GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel;
/*testing*/ final DebuggerSnapshotTablePanel mainPanel = new DebuggerSnapshotTablePanel();
private DebuggerSnapActionContext myActionContext;
@ -198,7 +82,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
buildMainPanel();
myActionContext = new DebuggerSnapActionContext(0);
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getSnap());
createActions();
contextChanged();
@ -224,42 +108,22 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
}
protected void buildMainPanel() {
snapshotTable = new GTable(snapshotTableModel);
snapshotTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mainPanel.add(new JScrollPane(snapshotTable));
snapshotFilterPanel = new GhidraTableFilterPanel<>(snapshotTable, snapshotTableModel);
mainPanel.add(snapshotFilterPanel, BorderLayout.SOUTH);
snapshotTable.getSelectionModel().addListSelectionListener(evt -> {
mainPanel.getSelectionModel().addListSelectionListener(evt -> {
if (evt.getValueIsAdjusting()) {
return;
}
SnapshotRow row = snapshotFilterPanel.getSelectedItem();
if (row == null) {
Long snap = mainPanel.getSelectedSnapshot();
if (snap == null) {
myActionContext = null;
return;
}
long snap = row.getSnap();
if (snap == current.getSnap().longValue()) {
if (snap.longValue() == current.getSnap().longValue()) {
return;
}
myActionContext = new DebuggerSnapActionContext(snap);
myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
viewManager.activateSnap(snap);
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() {
@ -271,51 +135,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
private void activatedHideScratch(ActionContext ctx) {
hideScratch = !hideScratch;
if (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);
}
mainPanel.setHideScratchSnapshots(hideScratch);
}
public void coordinatesActivated(DebuggerCoordinates coordinates) {
@ -325,37 +145,8 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
}
current = coordinates;
doSetTrace(current.getTrace());
doSetSnap(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)));
mainPanel.setTrace(current.getTrace());
mainPanel.setSelectedSnapshot(current.getSnap());
}
public void writeConfigState(SaveState saveState) {
@ -366,5 +157,6 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
CONFIG_STATE_HANDLER.readConfigState(this, saveState);
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();
}
protected void prepareViewAndFireEvent(DebuggerCoordinates coordinates) {
TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView();
if (varView == null) { // Should only happen with NOWHERE
fireLocationEvent(coordinates);
@Override
public CompletableFuture<Long> materialize(DebuggerCoordinates coordinates) {
if (coordinates.getTime().isSnapOnly()) {
return CompletableFuture.completedFuture(coordinates.getSnap());
}
else if (coordinates.getTime().isSnapOnly()) {
varView.setSnap(coordinates.getSnap());
fireLocationEvent(coordinates);
}
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;
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.");
}
CompletableFuture<Long> bg =
emulationService.backgroundEmulate(coordinates.getTrace(), coordinates.getTime());
bg.thenAccept(emuSnap -> Swing.runLater(() -> {
return emulationService.backgroundEmulate(coordinates.getTrace(), coordinates.getTime());
}
protected void prepareViewAndFireEvent(DebuggerCoordinates coordinates) {
TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView();
if (varView == null) { // Should only happen with NOWHERE
fireLocationEvent(coordinates);
return;
}
materialize(coordinates).thenAcceptAsync(snap -> {
if (!coordinates.equals(current)) {
return; // We navigated elsewhere before emulation completed
}
varView.setSnap(emuSnap);
varView.setSnap(snap);
fireLocationEvent(coordinates);
})).exceptionally(ex -> {
Msg.showError(this, null, "Emulate", "Could not navigate to emulated coordinates",
ex);
return null;
});
}
}, AsyncUtils.SWING_EXECUTOR);
}
protected void fireLocationEvent(DebuggerCoordinates coordinates) {
@ -892,6 +887,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
future.completeExceptionally(e);
}
}
});
}
return future;

View file

@ -15,6 +15,7 @@
*/
package ghidra.app.plugin.core.debug.utils;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.BiFunction;
@ -24,8 +25,8 @@ import ghidra.framework.cmd.BackgroundCommand;
import ghidra.framework.model.DomainObject;
import ghidra.framework.model.UndoableDomainObject;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.task.CancelledListener;
import ghidra.util.task.TaskMonitor;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.*;
public enum BackgroundUtils {
;
@ -82,4 +83,59 @@ public enum BackgroundUtils {
tool.executeBackgroundCommand(cmd, obj);
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.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.program.model.address.Address;
import ghidra.program.util.ProgramSelection;
/**
* A service providing access to the main listing panel
*/
@ServiceInfo( //
defaultProvider = DebuggerListingPlugin.class, //
description = "Replacement CodeViewerService for Debugger" //
)
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);
/**
* 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);
/**
* 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);
/**
* 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.service.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.async.AsyncReference;
import ghidra.framework.model.DomainFile;
import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.TriConsumer;
/**
* The interface for managing open traces and navigating among them and their contents
*/
@ServiceInfo(defaultProvider = DebuggerTraceManagerServicePlugin.class)
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> {
@Override
default void accept(Boolean oldVal, Boolean newVal, Void cause) {
changed(newVal);
}
/**
* The value has changed
*
* @param value the new value
*/
void changed(Boolean value);
}
/**
* Get all the open traces
*
* @return all open traces
*/
Collection<Trace> getOpenTraces();
/**
* Get the current coordinates
*
* <p>
* This entails everything except the current address
*
* @return the current coordinates
*/
DebuggerCoordinates getCurrent();
/**
* Get the active trace
*
* @return the active trace, or null
*/
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();
/**
* 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();
/**
* 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);
/**
* 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();
/**
* Get the active frame
*
* @return the active frame, or 0
*/
int getCurrentFrame();
/**
@ -105,10 +183,23 @@ public interface DebuggerTraceManagerService {
*/
CompletableFuture<Void> saveTrace(Trace trace);
/**
* Close the given trace
*
* @param trace the trace to close
*/
void closeTrace(Trace trace);
/**
* Close all traces
*/
void closeAllTraces();
/**
* Close all traces except the given one
*
* @param keep the trace to keep open
*/
void closeOtherTraces(Trace keep);
/**
@ -120,24 +211,84 @@ public interface DebuggerTraceManagerService {
*/
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);
/**
* Activate the given trace
*
* @param trace the desired trace
*/
void activateTrace(Trace trace);
/**
* Activate the given thread
*
* @param thread the desired thread
*/
void activateThread(TraceThread thread);
/**
* Activate the given snapshot key
*
* @param snap the desired snapshot key
*/
void activateSnap(long snap);
/**
* Activate the given point in time, possibly invoking emulation
*
* @param time the desired schedule
*/
void activateTime(TraceSchedule time);
/**
* Activate the given stack frame
*
* @param frameLevel the level of the desired frame, 0 being innermost
*/
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);
/**
* Check if the trace manager automatically activate the "present snapshot"
*
* @return true if auto activation is enabled
*/
boolean isAutoActivatePresent();
/**
* Add a listener for changes to auto activation enablement
*
* @param listener the listener to receive change notifications
*/
void addAutoActivatePresentChangeListener(BooleanChangeAdapter listener);
/**
* Remove a listener for changes to auto activation enablement
*
* @param listener the listener receiving change notifications
*/
void removeAutoActivatePresentChangeListener(BooleanChangeAdapter listener);
/**
@ -154,8 +305,18 @@ public interface DebuggerTraceManagerService {
*/
boolean isSynchronizeFocus();
/**
* Add a listener for changes to focus synchronization enablement
*
* @param listener the listener to receive change notifications
*/
void addSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
/**
* Remove a listener for changes to focus synchronization enablement
*
* @param listener the listener receiving change notifications
*/
void removeSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
/**
@ -172,8 +333,18 @@ public interface DebuggerTraceManagerService {
*/
boolean isSaveTracesByDefault();
/**
* Add a listener for changes to save-by-default enablement
*
* @param listener the listener to receive change notifications
*/
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);
/**
@ -190,15 +361,40 @@ public interface DebuggerTraceManagerService {
*/
boolean isAutoCloseOnTerminate();
/**
* Add a listener for changes to close-on-terminate enablement
*
* @param listener the listener to receive change notifications
*/
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);
/**
* 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
* @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.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
@ -40,11 +41,13 @@ import org.junit.runner.Description;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import generic.Unique;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.mapping.*;
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.tracemgr.DebuggerTraceManagerServicePlugin;
import ghidra.app.services.*;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.dbg.model.AbstractTestTargetRegisterBank;
import ghidra.dbg.model.TestDebuggerModelBuilder;
import ghidra.dbg.target.*;
@ -57,6 +60,7 @@ import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.DefaultLanguageService;
import ghidra.program.util.ProgramLocation;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
import ghidra.trace.database.ToyDBTraceBuilder;
@ -409,6 +413,64 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
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 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 org.junit.Assert.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.Color;
import java.io.IOException;
import java.math.BigInteger;
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.DebuggerResources;
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.DebuggerConsoleProvider.BoundAction;
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.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.services.*;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.async.SwingExecutorService;
import ghidra.framework.model.*;
import ghidra.plugin.importer.ImporterPlugin;
@ -68,27 +66,6 @@ import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
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 DebuggerListingProvider listingProvider;
@ -110,12 +87,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
}
protected void goToDyn(ProgramLocation location) {
waitForPass(() -> {
runSwing(() -> listingProvider.goTo(location.getProgram(), location));
ProgramLocation confirm = listingProvider.getLocation();
assertNotNull(confirm);
assertEquals(location.getAddress(), confirm.getAddress());
});
goTo(listingProvider.getListingPanel(), location);
}
protected static byte[] incBlock() {
@ -572,32 +544,6 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
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
public void testDynamicListingMarksTrackedRegister() throws Exception {
createAndOpenTrace();

View file

@ -66,26 +66,6 @@ import ghidra.util.database.UndoableTransaction;
@Category(NightlyCategory.class)
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 DebuggerMemoryBytesProvider memBytesProvider;

View file

@ -23,7 +23,10 @@ import java.util.List;
import org.junit.Before;
import org.junit.Test;
import docking.widgets.dialogs.InputDialog;
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.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
@ -64,11 +67,11 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
}
protected void assertProviderEmpty() {
assertTrue(timeProvider.snapshotTableModel.getModelData().isEmpty());
assertTrue(timeProvider.mainPanel.snapshotTableModel.getModelData().isEmpty());
}
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
assertEquals(2, snapsDisplayed.size());
@ -85,6 +88,42 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
// 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
public void testEmpty() {
assertProviderEmpty();
@ -158,7 +197,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
}
waitForDomainObject(tb.trace);
assertEquals(1, timeProvider.snapshotTableModel.getModelData().size());
assertEquals(1, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
}
@Test
@ -238,7 +277,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
traceManager.activateTrace(tb.trace);
waitForSwing();
SnapshotRow row = timeProvider.snapshotTableModel.getModelData().get(0);
SnapshotRow row = timeProvider.mainPanel.snapshotTableModel.getModelData().get(0);
runSwing(() -> row.setDescription("Custom Description"));
waitForDomainObject(tb.trace);
@ -258,14 +297,14 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
traceManager.activateTrace(tb.trace);
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();
assertEquals(0, traceManager.getCurrentSnap());
timeProvider.snapshotFilterPanel.setSelectedItem(data.get(1));
timeProvider.mainPanel.snapshotFilterPanel.setSelectedItem(data.get(1));
waitForSwing();
assertEquals(10, traceManager.getCurrentSnap());
@ -283,22 +322,22 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
traceManager.activateTrace(tb.trace);
waitForSwing();
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData();
List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
traceManager.activateSnap(0);
waitForSwing();
assertEquals(data.get(0), timeProvider.snapshotFilterPanel.getSelectedItem());
assertEquals(data.get(0), timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
traceManager.activateSnap(10);
waitForSwing();
assertEquals(data.get(1), timeProvider.snapshotFilterPanel.getSelectedItem());
assertEquals(data.get(1), timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
traceManager.activateSnap(5);
waitForSwing();
assertNull(timeProvider.snapshotFilterPanel.getSelectedItem());
assertNull(timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
}
@Test
@ -312,7 +351,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
traceManager.activateTrace(tb.trace);
waitForSwing();
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData();
List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
assertEquals(2, data.size());
for (SnapshotRow row : data) {
assertTrue(row.getSnap() >= 0);
@ -329,12 +368,12 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
traceManager.activateTrace(tb.trace);
waitForSwing();
assertEquals(2, timeProvider.snapshotTableModel.getModelData().size());
assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
addScratchSnapshot();
waitForDomainObject(tb.trace);
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData();
List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
assertEquals(2, data.size());
for (SnapshotRow row : data) {
assertTrue(row.getSnap() >= 0);
@ -353,17 +392,17 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
waitForSwing();
assertEquals(true, timeProvider.hideScratch);
assertEquals(2, timeProvider.snapshotTableModel.getModelData().size());
assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
performAction(timeProvider.actionHideScratch);
assertEquals(false, timeProvider.hideScratch);
assertEquals(3, timeProvider.snapshotTableModel.getModelData().size());
assertEquals(3, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
performAction(timeProvider.actionHideScratch);
assertEquals(true, timeProvider.hideScratch);
assertEquals(2, timeProvider.snapshotTableModel.getModelData().size());
assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
}
@Test
@ -380,6 +419,6 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
waitForSwing();
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));
}
@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
public void pack() {
delegateWriteAll(getActiveSpaces(), m -> m.pack());

View file

@ -884,6 +884,26 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
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) {
assertInSpace(range);
long lower = DBTraceUtils.lowerEndpoint(span);

View file

@ -472,6 +472,32 @@ public interface TraceMemoryOperations {
: 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
*

View file

@ -368,7 +368,7 @@ public class Sequence implements Comparable<Sequence> {
*
* @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 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 monitor a monitor for cancellation and progress reports
* @return the last trace thread stepped during execution
@ -384,6 +384,22 @@ public class Sequence implements Comparable<Sequence> {
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
*

View file

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

View file

@ -344,6 +344,22 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
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
*
@ -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
* 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
* @return the resulting schedule
*/
public TraceSchedule steppedForward(TraceThread thread, long tickCount) {
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());
}
@ -441,13 +457,13 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
* Returns the equivalent of executing the schedule followed by stepping the given thread
* {@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
* @return the resulting schedule
*/
public TraceSchedule steppedPcodeForward(TraceThread thread, int pTickCount) {
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);
}

View file

@ -75,14 +75,16 @@ public interface AutoService {
}
}
public static Wiring wireServicesConsumed(Plugin plugin, Object receiver) {
// TODO: Validate against PluginInfo?
public static Wiring wireServicesConsumed(PluginTool tool, Object receiver) {
AutoServiceListener<Object> listener = new AutoServiceListener<>(receiver);
PluginTool tool = plugin.getTool();
tool.addServiceListener(listener);
listener.notifyCurrentServices(tool);
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_DEFAULT = "Default value";
protected static final String OPT2_DESC = "Another test option";
protected static final String OPT2_NEW_VALUE = "A new value";
@PluginInfo(//
category = "Testing", //
description = "A plugin class replete with auto option annotations", //
packageName = MiscellaneousPluginPackage.NAME, //
shortDescription = "An annotated plugin class",//
status = PluginStatus.HIDDEN //
)
@PluginInfo(
category = "Testing",
description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN)
public static class AnnotatedWithOptionsPlugin extends Plugin {
@AutoOptionDefined(name = OPT1_NAME, description = OPT1_DESC)
private int myIntOption = OPT1_DEFAULT;
@ -65,13 +65,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
}
}
@PluginInfo(//
category = "Testing", //
description = "A plugin class replete with auto option annotations", //
packageName = MiscellaneousPluginPackage.NAME, //
shortDescription = "An annotated plugin class", //
status = PluginStatus.HIDDEN //
)
@PluginInfo(
category = "Testing",
description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN)
public static class AnnotatedWithOptionsNoParamPlugin extends AnnotatedWithOptionsPlugin {
protected int updateNoParamCount;
@ -85,13 +84,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
}
}
@PluginInfo(//
category = "Testing", //
description = "A plugin class replete with auto option annotations", //
packageName = MiscellaneousPluginPackage.NAME, //
shortDescription = "An annotated plugin class", //
status = PluginStatus.HIDDEN //
)
@PluginInfo(
category = "Testing",
description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN)
public static class AnnotatedWithOptionsNewOnlyParamDefaultPlugin
extends AnnotatedWithOptionsPlugin {
protected int updateNewOnlyParamDefaultNew;
@ -107,13 +105,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
}
@PluginInfo(//
category = "Testing", //
description = "A plugin class replete with auto option annotations", //
packageName = MiscellaneousPluginPackage.NAME, //
shortDescription = "An annotated plugin class", //
status = PluginStatus.HIDDEN //
)
@PluginInfo(
category = "Testing",
description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN)
public static class AnnotatedWithOptionsNewOnlyParamAnnotatedPlugin
extends AnnotatedWithOptionsPlugin {
protected int updateNewOnlyParamAnnotatedNew;
@ -128,13 +125,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
}
}
@PluginInfo(//
category = "Testing", //
description = "A plugin class replete with auto option annotations", //
packageName = MiscellaneousPluginPackage.NAME, //
shortDescription = "An annotated plugin class", //
status = PluginStatus.HIDDEN //
)
@PluginInfo(
category = "Testing",
description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN)
public static class AnnotatedWithOptionsOldOnlyParamAnnotatedPlugin
extends AnnotatedWithOptionsPlugin {
protected int updateOldOnlyParamAnnotatedOld;
@ -149,13 +145,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
}
}
@PluginInfo(//
category = "Testing", //
description = "A plugin class replete with auto option annotations", //
packageName = MiscellaneousPluginPackage.NAME, //
shortDescription = "An annotated plugin class", //
status = PluginStatus.HIDDEN //
)
@PluginInfo(
category = "Testing",
description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN)
public static class AnnotatedWithOptionsNewOldParamDefaultPlugin
extends AnnotatedWithOptionsPlugin {
protected int updateNewOldParamDefaultNew;
@ -172,13 +167,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
}
}
@PluginInfo(//
category = "Testing", //
description = "A plugin class replete with auto option annotations", //
packageName = MiscellaneousPluginPackage.NAME, //
shortDescription = "An annotated plugin class", //
status = PluginStatus.HIDDEN //
)
@PluginInfo(
category = "Testing",
description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN)
public static class AnnotatedWithOptionsNewOldParamNewAnnotPlugin
extends AnnotatedWithOptionsPlugin {
protected int updateNewOldParamNewAnnotNew;
@ -195,13 +189,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
}
}
@PluginInfo(//
category = "Testing", //
description = "A plugin class replete with auto option annotations", //
packageName = MiscellaneousPluginPackage.NAME, //
shortDescription = "An annotated plugin class", //
status = PluginStatus.HIDDEN //
)
@PluginInfo(
category = "Testing",
description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN)
public static class AnnotatedWithOptionsNewOldParamOldAnnotPlugin
extends AnnotatedWithOptionsPlugin {
protected int updateNewOldParamOldAnnotNew;
@ -218,13 +211,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
}
}
@PluginInfo(//
category = "Testing", //
description = "A plugin class replete with auto option annotations", //
packageName = MiscellaneousPluginPackage.NAME, //
shortDescription = "An annotated plugin class", //
status = PluginStatus.HIDDEN //
)
@PluginInfo(
category = "Testing",
description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN)
public static class AnnotatedWithOptionsNewOldParamNewOldAnnotPlugin
extends AnnotatedWithOptionsPlugin {
protected int updateNewOldParamNewOldAnnotNew;
@ -242,13 +234,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
}
}
@PluginInfo(//
category = "Testing", //
description = "A plugin class replete with auto option annotations", //
packageName = MiscellaneousPluginPackage.NAME, //
shortDescription = "An annotated plugin class", //
status = PluginStatus.HIDDEN //
)
@PluginInfo(
category = "Testing",
description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN)
public static class AnnotatedWithOptionsOldNewParamNewAnnotPlugin
extends AnnotatedWithOptionsPlugin {
protected int updateOldNewParamNewAnnotNew;
@ -265,13 +256,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
}
}
@PluginInfo(//
category = "Testing", //
description = "A plugin class replete with auto option annotations", //
packageName = MiscellaneousPluginPackage.NAME, //
shortDescription = "An annotated plugin class", //
status = PluginStatus.HIDDEN //
)
@PluginInfo(
category = "Testing",
description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN)
public static class AnnotatedWithOptionsOldNewParamOldAnnotPlugin
extends AnnotatedWithOptionsPlugin {
protected int updateOldNewParamOldAnnotNew;
@ -288,13 +278,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
}
}
@PluginInfo(//
category = "Testing", //
description = "A plugin class replete with auto option annotations", //
packageName = MiscellaneousPluginPackage.NAME, //
shortDescription = "An annotated plugin class", //
status = PluginStatus.HIDDEN //
)
@PluginInfo(
category = "Testing",
description = "A plugin class replete with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "An annotated plugin class",
status = PluginStatus.HIDDEN)
public static class AnnotatedWithOptionsOldNewParamOldNewAnnotPlugin
extends AnnotatedWithOptionsPlugin {
protected int updateOldNewParamOldNewAnnotNew;
@ -312,13 +301,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
}
}
@PluginInfo(//
category = "Testing", //
description = "Consumer-only plugin class with auto option annotations", //
packageName = MiscellaneousPluginPackage.NAME, //
shortDescription = "A consumer-only plugin class", //
status = PluginStatus.HIDDEN //
)
@PluginInfo(
category = "Testing",
description = "Consumer-only plugin class with auto option annotations",
packageName = MiscellaneousPluginPackage.NAME,
shortDescription = "A consumer-only plugin class",
status = PluginStatus.HIDDEN)
public static class AnnotatedConsumerOnlyPlugin extends Plugin {
@AutoOptionConsumed(name = OPT1_NAME)
private int othersIntOption;
@ -387,7 +375,18 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals(6, plugin.myIntOption);
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

View file

@ -705,6 +705,16 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
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) {
Program myProgram = listingPanel.getListingModel().getProgram();
Program otherProgram = lp.getListingModel().getProgram();
@ -712,10 +722,10 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
String otherName = myName;
if (myProgram != null) {
myName = myProgram.getDomainFile().toString();
myName = computePanelTitle(myProgram);
}
if (otherProgram != null) {
otherName = otherProgram.getDomainFile().toString();
otherName = computePanelTitle(otherProgram);
}
if (otherPanel != null) {
removeHoverServices(otherPanel);

View file

@ -43,12 +43,12 @@ public class InputDialog extends DialogComponentProvider {
private InputDialogListener listener;
/**
* Creates a provider for a generic input dialog with the specified title, a text field,
* labeled by the specified label. The user should check the value of
* "isCanceled()" to know whether or not the user canceled the operation.
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
* entered by the user. Use the tool's "showDialog()" to display the dialog.
* Creates a provider for a generic input dialog with the specified title, a text field, labeled
* by the specified label. The user should check the value of "isCanceled()" to know whether or
* not the user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get
* the value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
* <P>
*
* @param dialogTitle used as the name of the dialog's title bar
* @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,
* labeled by the specified label. The user should check the value of
* "isCanceled()" to know whether or not the user canceled the operation.
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
* entered by the user. Use the tool's "showDialog()" to display the dialog.
* Creates a generic input dialog with the specified title, a text field, labeled by the
* specified label. The user should check the value of "isCanceled()" to know whether or not the
* user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
* value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
* <P>
*
* @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 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,
* labeled by the specified label. The user should check the value of
* "isCanceled()" to know whether or not the user canceled the operation.
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
* entered by the user. Use the tool's "showDialog()" to display the dialog.
* Creates a generic input dialog with the specified title, a text field, labeled by the
* specified label. The user should check the value of "isCanceled()" to know whether or not the
* user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
* value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
* <P>
*
* @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 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,
* labeled by the specified label. The user should check the value of
* "isCanceled()" to know whether or not the user canceled the operation.
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
* entered by the user. Use the tool's "showDialog()" to display the dialog.
* Creates a generic input dialog with the specified title, a text field, labeled by the
* specified label. The user should check the value of "isCanceled()" to know whether or not the
* user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
* value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
* <P>
*
* @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 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,
* labeled by the specified label. The user should check the value of
* "isCanceled()" to know whether or not the user canceled the operation.
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
* entered by the user. Use the tool's "showDialog()" to display the dialog.
* Creates a generic input dialog with the specified title, a text field, labeled by the
* specified label. The user should check the value of "isCanceled()" to know whether or not the
* user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
* value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
* <P>
*
* @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 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,
* labeled by the specified label. The user should check the value of
* "isCanceled()" to know whether or not the user canceled the operation.
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
* entered by the user. Use the tool's "showDialog()" to display the dialog.
* Creates a generic input dialog with the specified title, a text field, labeled by the
* specified label. The user should check the value of "isCanceled()" to know whether or not the
* user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
* value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
* <P>
*
* @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 initialValues initial values to use for the text fields
@ -194,6 +194,7 @@ public class InputDialog extends DialogComponentProvider {
textFields = new MyTextField[inputLabels.length];
for (int i = 0; i < inputValues.length; i++) {
textFields[i] = new MyTextField(initialValues[i]);
inputValues[i] = initialValues[i];
textFields[i].addKeyListener(keyListener);
textFields[i].setName("input.dialog.text.field." + i);
panel.add(new GLabel(inputLabels[i], SwingConstants.RIGHT));
@ -221,11 +222,16 @@ public class InputDialog extends DialogComponentProvider {
@Override
protected void cancelCallback() {
isCanceled = true;
for (int v = 0; v < inputValues.length; v++) {
inputValues[v] = null;
}
close();
}
/**
* Returns if this dialog is cancelled
*
* @return true if cancelled
*/
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 text field value
*/
public String getValue() {
@ -242,6 +249,7 @@ public class InputDialog extends DialogComponentProvider {
/**
* Sets the text of the primary text field
*
* @param text the 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
*
* @param text the text
* @param index the index of the text field
*/
public void setValue(String text, int index) {
textFields[index].setText(text);
inputValues[index] = text;
}
/**
* Return the values for all the text field(s)
*
* @return the text field values
*/
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
public void insertString(int offs, String str, AttributeSet a)