mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 10:19:23 +02:00
Merge remote-tracking branch 'origin/GP-1222_Dan_traceDiff--REBASED-2--SQUASHED' into Ghidra_10.1
This commit is contained in:
commit
f68c8fa992
42 changed files with 2622 additions and 518 deletions
|
@ -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')
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 |
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue