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')
|
api project(':ProposedUtils')
|
||||||
|
|
||||||
helpPath project(path: ':Base', configuration: 'helpPath')
|
helpPath project(path: ':Base', configuration: 'helpPath')
|
||||||
|
helpPath project(path: ':ProgramDiff', configuration: 'helpPath')
|
||||||
|
|
||||||
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts')
|
||||||
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
|
testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts')
|
||||||
|
|
|
@ -119,6 +119,9 @@ src/main/help/help/topics/DebuggerThreadsPlugin/images/stepinto.png||GHIDRA||||E
|
||||||
src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html||GHIDRA||||END|
|
src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerTimePlugin/images/DebuggerTimePlugin.png||GHIDRA||||END|
|
src/main/help/help/topics/DebuggerTimePlugin/images/DebuggerTimePlugin.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html||GHIDRA||||END|
|
src/main/help/help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html||GHIDRA||||END|
|
||||||
|
src/main/help/help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html||GHIDRA||||END|
|
||||||
|
src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTimeSelectionDialog.png||GHIDRA||||END|
|
||||||
|
src/main/help/help/topics/DebuggerTraceViewDiffPlugin/images/DebuggerTraceViewDiffPlugin.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html||GHIDRA||||END|
|
src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerWatchesPlugin/images/DebuggerWatchesPlugin.png||GHIDRA||||END|
|
src/main/help/help/topics/DebuggerWatchesPlugin/images/DebuggerWatchesPlugin.png||GHIDRA||||END|
|
||||||
src/main/resources/defaultTools/Debugger.tool||GHIDRA||||END|
|
src/main/resources/defaultTools/Debugger.tool||GHIDRA||||END|
|
||||||
|
@ -184,6 +187,7 @@ src/main/resources/images/stop.png||GHIDRA||||END|
|
||||||
src/main/resources/images/sync_enabled.png||GHIDRA||||END|
|
src/main/resources/images/sync_enabled.png||GHIDRA||||END|
|
||||||
src/main/resources/images/system-switch-user.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
src/main/resources/images/system-switch-user.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
||||||
src/main/resources/images/table.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
|
src/main/resources/images/table.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
|
||||||
|
src/main/resources/images/table_relationship.png||FAMFAMFAM Icons - CC 2.5||||END|
|
||||||
src/main/resources/images/text-xml.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
src/main/resources/images/text-xml.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
||||||
src/main/resources/images/thread.png||GHIDRA||||END|
|
src/main/resources/images/thread.png||GHIDRA||||END|
|
||||||
src/main/resources/images/time.png||FAMFAMFAM Icons - CC 2.5||||END|
|
src/main/resources/images/time.png||FAMFAMFAM Icons - CC 2.5||||END|
|
||||||
|
|
|
@ -157,6 +157,10 @@
|
||||||
sortgroup="p"
|
sortgroup="p"
|
||||||
target="help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html" />
|
target="help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html" />
|
||||||
|
|
||||||
|
<tocdef id="DebuggerTraceDiffPlugin" text="Comparing Times"
|
||||||
|
sortgroup="p1"
|
||||||
|
target="help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html" />
|
||||||
|
|
||||||
<tocdef id="DebuggerBots" text="Bots: Workflow Automation"
|
<tocdef id="DebuggerBots" text="Bots: Workflow Automation"
|
||||||
sortgroup="q"
|
sortgroup="q"
|
||||||
target="help/topics/DebuggerBots/DebuggerBots.html" />
|
target="help/topics/DebuggerBots/DebuggerBots.html" />
|
||||||
|
|
|
@ -56,15 +56,20 @@
|
||||||
|
|
||||||
<H2>Actions</H2>
|
<H2>Actions</H2>
|
||||||
|
|
||||||
<P>The time window provides the following action:</P>
|
<H3><A name="rename_snapshot"></A>Rename Snapshot</H3>
|
||||||
|
|
||||||
|
<P>This action is available in the <SPAN class="menu">Debugger</SPAN> menu whenever the focused
|
||||||
|
window has an associated snapshot. It will prompt for a new description for the current
|
||||||
|
snapshot. This is a shortcut to modifying the description in the time table, but can be
|
||||||
|
accessed outside of the time window.</P>
|
||||||
|
|
||||||
<H3><A name="hide_scratch"></A>Hide Scratch</H3>
|
<H3><A name="hide_scratch"></A>Hide Scratch</H3>
|
||||||
|
|
||||||
<P>This toggle action is always available. It is enabled by default. The emulation service,
|
<P>This toggle action is always available in the drop-down actions of the Time window. It is
|
||||||
which enables trace extrapolation and interpolation, writes emulated state into the trace's
|
enabled by default. The emulation service, which enables trace extrapolation and interpolation,
|
||||||
"scratch space," which comprises all negative snaps. When this toggle is enabled, those
|
writes emulated state into the trace's "scratch space," which comprises all negative snaps.
|
||||||
snapshots are hidden. They can be displayed by disabling this toggle. Note that navigating into
|
When this toggle is enabled, those snapshots are hidden. They can be displayed by disabling
|
||||||
scratch space may cause temporary undefined behavior in some windows, and may prevent
|
this toggle. Note that navigating into scratch space may cause temporary undefined behavior in
|
||||||
interaction with the target.</P>
|
some windows, and may prevent interaction with the target.</P>
|
||||||
</BODY>
|
</BODY>
|
||||||
</HTML>
|
</HTML>
|
||||||
|
|
|
@ -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);
|
return all(trace, recorder, thread, view, newTime, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DebuggerCoordinates withView(TraceProgramView newView) {
|
||||||
|
return all(trace, recorder, thread, newView, time, frame);
|
||||||
|
}
|
||||||
|
|
||||||
public TraceSchedule getTime() {
|
public TraceSchedule getTime() {
|
||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,6 +147,8 @@ public interface DebuggerResources {
|
||||||
ImageIcon ICON_READ_MEMORY = ICON_REGIONS;
|
ImageIcon ICON_READ_MEMORY = ICON_REGIONS;
|
||||||
//ResourceManager.loadImage("images/read-memory.png");
|
//ResourceManager.loadImage("images/read-memory.png");
|
||||||
|
|
||||||
|
ImageIcon ICON_RENAME_SNAPSHOT = ICON_TIME;
|
||||||
|
|
||||||
// TODO: Draw an icon
|
// TODO: Draw an icon
|
||||||
ImageIcon ICON_MAP_IDENTICALLY = ResourceManager.loadImage("images/doubleArrow.png");
|
ImageIcon ICON_MAP_IDENTICALLY = ResourceManager.loadImage("images/doubleArrow.png");
|
||||||
ImageIcon ICON_MAP_MODULES = ResourceManager.loadImage("images/modules.png");
|
ImageIcon ICON_MAP_MODULES = ResourceManager.loadImage("images/modules.png");
|
||||||
|
@ -175,6 +177,10 @@ public interface DebuggerResources {
|
||||||
ImageIcon ICON_CONFIG = ResourceManager.loadImage("images/conf.png");
|
ImageIcon ICON_CONFIG = ResourceManager.loadImage("images/conf.png");
|
||||||
ImageIcon ICON_TOGGLE = ResourceManager.loadImage("images/system-switch-user.png");
|
ImageIcon ICON_TOGGLE = ResourceManager.loadImage("images/system-switch-user.png");
|
||||||
|
|
||||||
|
ImageIcon ICON_DIFF = ResourceManager.loadImage("images/table_relationship.png");
|
||||||
|
ImageIcon ICON_DIFF_PREV = ResourceManager.loadImage("images/up.png");
|
||||||
|
ImageIcon ICON_DIFF_NEXT = ResourceManager.loadImage("images/down.png");
|
||||||
|
|
||||||
HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package");
|
HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package");
|
||||||
|
|
||||||
String HELP_ANCHOR_PLUGIN = "plugin";
|
String HELP_ANCHOR_PLUGIN = "plugin";
|
||||||
|
@ -367,6 +373,7 @@ public interface DebuggerResources {
|
||||||
String GROUP_TRACE_CLOSE = "Dbg7.b. Trace Close";
|
String GROUP_TRACE_CLOSE = "Dbg7.b. Trace Close";
|
||||||
String GROUP_MAINTENANCE = "Dbg8. Maintenance";
|
String GROUP_MAINTENANCE = "Dbg8. Maintenance";
|
||||||
String GROUP_MAPPING = "Dbg9. Map Modules/Sections";
|
String GROUP_MAPPING = "Dbg9. Map Modules/Sections";
|
||||||
|
String GROUP_DIFF_NAV = "DiffNavigate";
|
||||||
|
|
||||||
static void tableRowActivationAction(GTable table, Runnable runnable) {
|
static void tableRowActivationAction(GTable table, Runnable runnable) {
|
||||||
table.addMouseListener(new MouseAdapter() {
|
table.addMouseListener(new MouseAdapter() {
|
||||||
|
@ -1587,6 +1594,26 @@ public interface DebuggerResources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Perhaps to reduce overloading of "snapshot" we should use "event" instead?
|
||||||
|
interface RenameSnapshotAction {
|
||||||
|
String NAME = "Rename Current Snapshot";
|
||||||
|
String DESCRIPTION =
|
||||||
|
"Modify the description of the snapshot (event) in the current view";
|
||||||
|
String GROUP = GROUP_TRACE;
|
||||||
|
Icon ICON = ICON_RENAME_SNAPSHOT;
|
||||||
|
String HELP_ANCHOR = "rename_snapshot";
|
||||||
|
|
||||||
|
static ActionBuilder builder(Plugin owner) {
|
||||||
|
String ownerName = owner.getName();
|
||||||
|
return new ActionBuilder(NAME, ownerName)
|
||||||
|
.description(DESCRIPTION)
|
||||||
|
.menuPath(DebuggerPluginPackage.NAME, NAME)
|
||||||
|
.menuGroup(GROUP, "zzz")
|
||||||
|
.keyBinding("CTRL SHIFT N")
|
||||||
|
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface SynchronizeFocusAction {
|
interface SynchronizeFocusAction {
|
||||||
String NAME = "Synchronize Focus";
|
String NAME = "Synchronize Focus";
|
||||||
String DESCRIPTION = "Synchronize trace activation with debugger focus/select";
|
String DESCRIPTION = "Synchronize trace activation with debugger focus/select";
|
||||||
|
@ -1824,6 +1851,57 @@ public interface DebuggerResources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CompareTimesAction {
|
||||||
|
String NAME = "Compare";
|
||||||
|
String DESCRIPTION = "Compare this point in time to another";
|
||||||
|
String GROUP = "zzz"; // Same as for "Diff" action
|
||||||
|
Icon ICON = ICON_DIFF;
|
||||||
|
String HELP_ANCHOR = "compare";
|
||||||
|
|
||||||
|
static ToggleActionBuilder builder(Plugin owner) {
|
||||||
|
String ownerName = owner.getName();
|
||||||
|
return new ToggleActionBuilder(NAME, ownerName)
|
||||||
|
.description(DESCRIPTION)
|
||||||
|
.toolBarGroup(GROUP)
|
||||||
|
.toolBarIcon(ICON)
|
||||||
|
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PrevDifferenceAction {
|
||||||
|
String NAME = "Previous Difference";
|
||||||
|
String DESCRIPTION = "Go to the previous highlighted difference";
|
||||||
|
String GROUP = GROUP_DIFF_NAV;
|
||||||
|
Icon ICON = ICON_DIFF_PREV;
|
||||||
|
String HELP_ANCHOR = "prev_diff";
|
||||||
|
|
||||||
|
static ActionBuilder builder(Plugin owner) {
|
||||||
|
String ownerName = owner.getName();
|
||||||
|
return new ActionBuilder(NAME, ownerName)
|
||||||
|
.description(DESCRIPTION)
|
||||||
|
.toolBarGroup(GROUP)
|
||||||
|
.toolBarIcon(ICON)
|
||||||
|
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NextDifferenceAction {
|
||||||
|
String NAME = "Next Difference";
|
||||||
|
String DESCRIPTION = "Go to the next highlighted difference";
|
||||||
|
String GROUP = GROUP_DIFF_NAV;
|
||||||
|
Icon ICON = ICON_DIFF_NEXT;
|
||||||
|
String HELP_ANCHOR = "next_diff";
|
||||||
|
|
||||||
|
static ActionBuilder builder(Plugin owner) {
|
||||||
|
String ownerName = owner.getName();
|
||||||
|
return new ActionBuilder(NAME, ownerName)
|
||||||
|
.description(DESCRIPTION)
|
||||||
|
.toolBarGroup(GROUP)
|
||||||
|
.toolBarIcon(ICON)
|
||||||
|
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public abstract class AbstractDebuggerConnectionsNode extends GTreeNode {
|
public abstract class AbstractDebuggerConnectionsNode extends GTreeNode {
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
|
|
@ -16,16 +16,22 @@
|
||||||
package ghidra.app.plugin.core.debug.gui;
|
package ghidra.app.plugin.core.debug.gui;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
|
||||||
public class DebuggerSnapActionContext extends ActionContext {
|
public class DebuggerSnapActionContext extends ActionContext {
|
||||||
private final long tick;
|
private final Trace trace;
|
||||||
|
private final long snap;
|
||||||
|
|
||||||
public DebuggerSnapActionContext(long tick) {
|
public DebuggerSnapActionContext(Trace trace, long snap) {
|
||||||
// TODO: Also require track object?
|
this.trace = trace;
|
||||||
this.tick = tick;
|
this.snap = snap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTick() {
|
public Trace getTrace() {
|
||||||
return tick;
|
return trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSnap() {
|
||||||
|
return snap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -227,7 +227,7 @@ public class DebuggerTrackLocationTrait {
|
||||||
protected void doSetSpec(LocationTrackingSpec spec) {
|
protected void doSetSpec(LocationTrackingSpec spec) {
|
||||||
if (this.spec != spec) {
|
if (this.spec != spec) {
|
||||||
this.spec = spec;
|
this.spec = spec;
|
||||||
specChanged();
|
specChanged(spec);
|
||||||
}
|
}
|
||||||
doTrack();
|
doTrack();
|
||||||
}
|
}
|
||||||
|
@ -299,7 +299,7 @@ public class DebuggerTrackLocationTrait {
|
||||||
// Listener method
|
// Listener method
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void specChanged() {
|
protected void specChanged(LocationTrackingSpec spec) {
|
||||||
// Listener method
|
// Listener method
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
import ghidra.framework.options.AutoOptions.Wiring;
|
import ghidra.framework.options.AutoOptions.Wiring;
|
||||||
import ghidra.framework.options.annotation.AutoOptionConsumed;
|
import ghidra.framework.options.annotation.AutoOptionConsumed;
|
||||||
|
import ghidra.framework.plugintool.Plugin;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ class CursorBackgroundColorModel implements ListingBackgroundColorModel {
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final Wiring autoOptionsWiring;
|
private final Wiring autoOptionsWiring;
|
||||||
|
|
||||||
public CursorBackgroundColorModel(DebuggerListingPlugin plugin, ListingPanel listingPanel) {
|
public CursorBackgroundColorModel(Plugin plugin, ListingPanel listingPanel) {
|
||||||
autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
|
autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
|
||||||
modelDataChanged(listingPanel);
|
modelDataChanged(listingPanel);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
|
||||||
import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec;
|
import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.util.viewer.format.FormatManager;
|
import ghidra.app.util.viewer.format.FormatManager;
|
||||||
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
import ghidra.framework.options.AutoOptions;
|
import ghidra.framework.options.AutoOptions;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.options.annotation.AutoOptionDefined;
|
import ghidra.framework.options.annotation.AutoOptionDefined;
|
||||||
|
@ -162,6 +163,16 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
|
||||||
createActions();
|
createActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MultiBlendedListingBackgroundColorModel createListingBackgroundColorModel(
|
||||||
|
ListingPanel listingPanel) {
|
||||||
|
MultiBlendedListingBackgroundColorModel colorModel =
|
||||||
|
new MultiBlendedListingBackgroundColorModel();
|
||||||
|
colorModel.addModel(new MemoryStateListingBackgroundColorModel(this, listingPanel));
|
||||||
|
colorModel.addModel(new CursorBackgroundColorModel(this, listingPanel));
|
||||||
|
return colorModel;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DebuggerListingProvider createProvider(FormatManager formatManager,
|
protected DebuggerListingProvider createProvider(FormatManager formatManager,
|
||||||
boolean isConnected) {
|
boolean isConnected) {
|
||||||
|
@ -322,6 +333,21 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin<DebuggerLis
|
||||||
connectedProvider.setTrackingSpec(spec);
|
connectedProvider.setTrackingSpec(spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocationTrackingSpec getTrackingSpec() {
|
||||||
|
return connectedProvider.getTrackingSpec();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener) {
|
||||||
|
connectedProvider.addTrackingSpecChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener) {
|
||||||
|
connectedProvider.removeTrackingSpecChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCurrentSelection(ProgramSelection selection) {
|
public void setCurrentSelection(ProgramSelection selection) {
|
||||||
connectedProvider.setSelection(selection);
|
connectedProvider.setSelection(selection);
|
||||||
|
|
|
@ -53,6 +53,7 @@ import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionConte
|
||||||
import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils;
|
import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils;
|
||||||
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
|
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
|
import ghidra.app.services.DebuggerListingService.LocationTrackingSpecChangeListener;
|
||||||
import ghidra.app.util.viewer.format.FormatManager;
|
import ghidra.app.util.viewer.format.FormatManager;
|
||||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
|
@ -71,8 +72,9 @@ import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.modules.*;
|
import ghidra.trace.model.modules.*;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.util.HTMLUtilities;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
import ghidra.util.Swing;
|
import ghidra.util.*;
|
||||||
|
import ghidra.util.datastruct.ListenerSet;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.exception.VersionException;
|
import ghidra.util.exception.VersionException;
|
||||||
import ghidra.util.task.*;
|
import ghidra.util.task.*;
|
||||||
|
@ -182,6 +184,11 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
DebuggerListingProvider.this);
|
DebuggerListingProvider.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void specChanged(LocationTrackingSpec spec) {
|
||||||
|
trackingSpecChangeListeners.fire.locationTrackingSpecChanged(spec);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void locationTracked() {
|
protected void locationTracked() {
|
||||||
doGoToTracked();
|
doGoToTracked();
|
||||||
|
@ -247,9 +254,12 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
protected boolean followsCurrentThread = true;
|
protected boolean followsCurrentThread = true;
|
||||||
// TODO: followsCurrentSnap?
|
// TODO: followsCurrentSnap?
|
||||||
|
|
||||||
protected DebuggerGoToTrait goToTrait;
|
protected final DebuggerGoToTrait goToTrait;
|
||||||
protected ForListingTrackingTrait trackingTrait;
|
protected final ForListingTrackingTrait trackingTrait;
|
||||||
protected ForListingReadsMemoryTrait readsMemTrait;
|
protected final ForListingReadsMemoryTrait readsMemTrait;
|
||||||
|
|
||||||
|
protected final ListenerSet<LocationTrackingSpecChangeListener> trackingSpecChangeListeners =
|
||||||
|
new ListenerSet<>(LocationTrackingSpecChangeListener.class);
|
||||||
|
|
||||||
protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel();
|
protected final DebuggerLocationLabel locationLabel = new DebuggerLocationLabel();
|
||||||
|
|
||||||
|
@ -275,10 +285,8 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
readsMemTrait = new ForListingReadsMemoryTrait();
|
readsMemTrait = new ForListingReadsMemoryTrait();
|
||||||
|
|
||||||
ListingPanel listingPanel = getListingPanel();
|
ListingPanel listingPanel = getListingPanel();
|
||||||
colorModel = new MultiBlendedListingBackgroundColorModel();
|
colorModel = plugin.createListingBackgroundColorModel(listingPanel);
|
||||||
colorModel.addModel(trackingTrait.createListingBackgroundColorModel(listingPanel));
|
colorModel.addModel(trackingTrait.createListingBackgroundColorModel(listingPanel));
|
||||||
colorModel.addModel(new MemoryStateListingBackgroundColorModel(plugin, listingPanel));
|
|
||||||
colorModel.addModel(new CursorBackgroundColorModel(plugin, listingPanel));
|
|
||||||
listingPanel.setBackgroundColorModel(colorModel);
|
listingPanel.setBackgroundColorModel(colorModel);
|
||||||
|
|
||||||
autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||||
|
@ -493,12 +501,10 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
if (this.markerService != null) {
|
if (this.markerService != null) {
|
||||||
this.markerService.removeChangeListener(markerChangeListener);
|
this.markerService.removeChangeListener(markerChangeListener);
|
||||||
}
|
}
|
||||||
this.markerService = markerService;
|
|
||||||
updateMarkerServiceColorModel();
|
|
||||||
|
|
||||||
removeOldStaticTrackingMarker();
|
removeOldStaticTrackingMarker();
|
||||||
this.markerService = markerService;
|
this.markerService = markerService;
|
||||||
createNewStaticTrackingMarker();
|
createNewStaticTrackingMarker();
|
||||||
|
updateMarkerServiceColorModel();
|
||||||
|
|
||||||
if (this.markerService != null && !isMainListing()) {
|
if (this.markerService != null && !isMainListing()) {
|
||||||
// NOTE: Connected provider marker listener is taken care of by CodeBrowserPlugin
|
// NOTE: Connected provider marker listener is taken care of by CodeBrowserPlugin
|
||||||
|
@ -594,6 +600,38 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
setSubTitle(computeSubTitle());
|
setSubTitle(computeSubTitle());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String computePanelTitle(Program panelProgram) {
|
||||||
|
if (!(panelProgram instanceof TraceProgramView)) {
|
||||||
|
// really shouldn't happen anyway...
|
||||||
|
return super.computePanelTitle(panelProgram);
|
||||||
|
}
|
||||||
|
TraceProgramView view = (TraceProgramView) panelProgram;
|
||||||
|
TraceSnapshot snapshot =
|
||||||
|
view.getTrace().getTimeManager().getSnapshot(view.getSnap(), false);
|
||||||
|
if (snapshot == null) {
|
||||||
|
return Long.toString(view.getSnap());
|
||||||
|
}
|
||||||
|
String description = snapshot.getDescription();
|
||||||
|
String schedule = snapshot.getScheduleString();
|
||||||
|
if (description == null) {
|
||||||
|
description = "";
|
||||||
|
}
|
||||||
|
if (schedule == null) {
|
||||||
|
schedule = "";
|
||||||
|
}
|
||||||
|
if (!description.isBlank() && !schedule.isBlank()) {
|
||||||
|
return description + " (" + schedule + ")";
|
||||||
|
}
|
||||||
|
if (!description.isBlank()) {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
if (!schedule.isBlank()) {
|
||||||
|
return schedule;
|
||||||
|
}
|
||||||
|
return DateUtils.formatDateTimestamp(new Date(snapshot.getRealTime()));
|
||||||
|
}
|
||||||
|
|
||||||
protected void createActions() {
|
protected void createActions() {
|
||||||
if (isMainListing()) {
|
if (isMainListing()) {
|
||||||
actionSyncToStaticListing = new SyncToStaticListingAction();
|
actionSyncToStaticListing = new SyncToStaticListingAction();
|
||||||
|
@ -842,6 +880,14 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
return trackingTrait.getSpec();
|
return trackingTrait.getSpec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener) {
|
||||||
|
trackingSpecChangeListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener) {
|
||||||
|
trackingSpecChangeListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
public void setSyncToStaticListing(boolean sync) {
|
public void setSyncToStaticListing(boolean sync) {
|
||||||
if (!isMainListing()) {
|
if (!isMainListing()) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
|
|
|
@ -25,6 +25,7 @@ import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
import ghidra.app.util.viewer.util.AddressIndexMap;
|
import ghidra.app.util.viewer.util.AddressIndexMap;
|
||||||
import ghidra.framework.options.AutoOptions;
|
import ghidra.framework.options.AutoOptions;
|
||||||
import ghidra.framework.options.annotation.AutoOptionConsumed;
|
import ghidra.framework.options.annotation.AutoOptionConsumed;
|
||||||
|
import ghidra.framework.plugintool.Plugin;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.trace.model.TraceAddressSnapRange;
|
import ghidra.trace.model.TraceAddressSnapRange;
|
||||||
|
@ -47,7 +48,7 @@ public class MemoryStateListingBackgroundColorModel implements ListingBackground
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final AutoOptions.Wiring autoOptionsWiring;
|
private final AutoOptions.Wiring autoOptionsWiring;
|
||||||
|
|
||||||
public MemoryStateListingBackgroundColorModel(DebuggerListingPlugin plugin,
|
public MemoryStateListingBackgroundColorModel(Plugin plugin,
|
||||||
ListingPanel listingPanel) {
|
ListingPanel listingPanel) {
|
||||||
autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
|
autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
|
||||||
modelDataChanged(listingPanel);
|
modelDataChanged(listingPanel);
|
||||||
|
|
|
@ -379,7 +379,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
// TODO: Should I receive clicks on that renderer to seek to a given snap?
|
// TODO: Should I receive clicks on that renderer to seek to a given snap?
|
||||||
setDefaultWindowPosition(WindowPosition.BOTTOM);
|
setDefaultWindowPosition(WindowPosition.BOTTOM);
|
||||||
|
|
||||||
myActionContext = new DebuggerSnapActionContext(0);
|
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getViewSnap());
|
||||||
createActions();
|
createActions();
|
||||||
contextChanged();
|
contextChanged();
|
||||||
|
|
||||||
|
@ -617,7 +617,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||||
snap = 0;
|
snap = 0;
|
||||||
}
|
}
|
||||||
traceManager.activateSnap(snap);
|
traceManager.activateSnap(snap);
|
||||||
myActionContext = new DebuggerSnapActionContext(snap);
|
myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
|
||||||
contextChanged();
|
contextChanged();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
package ghidra.app.plugin.core.debug.gui.time;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import docking.ActionContext;
|
||||||
|
import docking.action.DockingAction;
|
||||||
|
import docking.widgets.dialogs.InputDialog;
|
||||||
|
import ghidra.app.context.ProgramLocationActionContext;
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
|
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.RenameSnapshotAction;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService;
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.trace.model.Trace;
|
||||||
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
import ghidra.trace.model.time.TraceTimeManager;
|
||||||
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
|
||||||
@PluginInfo(
|
@PluginInfo(
|
||||||
shortDescription = "Lists recorded snapshots in a trace",
|
shortDescription = "Lists recorded snapshots in a trace",
|
||||||
|
@ -39,8 +54,12 @@ import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
|
public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
|
||||||
protected DebuggerTimeProvider provider;
|
protected DebuggerTimeProvider provider;
|
||||||
|
|
||||||
|
protected DockingAction actionRenameSnapshot;
|
||||||
|
|
||||||
public DebuggerTimePlugin(PluginTool tool) {
|
public DebuggerTimePlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
|
|
||||||
|
createActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -49,6 +68,58 @@ public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
|
||||||
super.init();
|
super.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void createActions() {
|
||||||
|
actionRenameSnapshot = RenameSnapshotAction.builder(this)
|
||||||
|
.enabled(false)
|
||||||
|
.enabledWhen(ctx -> contextGetTraceSnap(ctx) != null)
|
||||||
|
.onAction(this::activatedRenameSnapshot)
|
||||||
|
.buildAndInstall(tool);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Entry<Trace, Long> contextGetTraceSnap(ActionContext context) {
|
||||||
|
if (context instanceof ProgramLocationActionContext) {
|
||||||
|
ProgramLocationActionContext ctx = (ProgramLocationActionContext) context;
|
||||||
|
Program program = ctx.getProgram();
|
||||||
|
if (program instanceof TraceProgramView) {
|
||||||
|
TraceProgramView view = (TraceProgramView) program;
|
||||||
|
return Map.entry(view.getTrace(), view.getSnap());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (context instanceof DebuggerSnapActionContext) {
|
||||||
|
DebuggerSnapActionContext ctx = (DebuggerSnapActionContext) context;
|
||||||
|
if (ctx.getTrace() != null) {
|
||||||
|
return Map.entry(ctx.getTrace(), ctx.getSnap());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void activatedRenameSnapshot(ActionContext context) {
|
||||||
|
Entry<Trace, Long> traceSnap = contextGetTraceSnap(context);
|
||||||
|
if (traceSnap == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Trace trace = traceSnap.getKey();
|
||||||
|
long snap = traceSnap.getValue();
|
||||||
|
TraceTimeManager manager = trace.getTimeManager();
|
||||||
|
TraceSnapshot snapshot = manager.getSnapshot(snap, false);
|
||||||
|
|
||||||
|
InputDialog dialog = new InputDialog("Rename Snapshot", "Description",
|
||||||
|
snapshot == null ? "" : snapshot.getDescription());
|
||||||
|
tool.showDialog(dialog);
|
||||||
|
if (dialog.isCanceled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Rename Snapshot", true)) {
|
||||||
|
if (snapshot == null) {
|
||||||
|
snapshot = manager.getSnapshot(snap, true);
|
||||||
|
}
|
||||||
|
snapshot.setDescription(dialog.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void dispose() {
|
protected void dispose() {
|
||||||
tool.removeComponentProvider(provider);
|
tool.removeComponentProvider(provider);
|
||||||
|
|
|
@ -17,138 +17,30 @@ package ghidra.app.plugin.core.debug.gui.time;
|
||||||
|
|
||||||
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.table.TableColumn;
|
|
||||||
import javax.swing.table.TableColumnModel;
|
|
||||||
|
|
||||||
import com.google.common.collect.Collections2;
|
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import docking.action.ToggleDockingAction;
|
import docking.action.ToggleDockingAction;
|
||||||
import docking.widgets.table.*;
|
|
||||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
|
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService;
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
import ghidra.framework.model.DomainObject;
|
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.AutoService.Wiring;
|
import ghidra.framework.plugintool.AutoService.Wiring;
|
||||||
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.trace.model.Trace;
|
|
||||||
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
|
||||||
import ghidra.trace.model.TraceDomainObjectListener;
|
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
|
||||||
import ghidra.trace.model.time.TraceTimeManager;
|
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
|
||||||
|
|
||||||
public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
private static final AutoConfigState.ClassHandler<DebuggerTimeProvider> CONFIG_STATE_HANDLER =
|
private static final AutoConfigState.ClassHandler<DebuggerTimeProvider> CONFIG_STATE_HANDLER =
|
||||||
AutoConfigState.wireHandler(DebuggerTimeProvider.class, MethodHandles.lookup());
|
AutoConfigState.wireHandler(DebuggerTimeProvider.class, MethodHandles.lookup());
|
||||||
|
|
||||||
protected enum SnapshotTableColumns
|
|
||||||
implements EnumeratedTableColumn<SnapshotTableColumns, SnapshotRow> {
|
|
||||||
SNAP("Snap", Long.class, SnapshotRow::getSnap),
|
|
||||||
TIMESTAMP("Timestamp", String.class, SnapshotRow::getTimeStamp), // TODO: Use Date type here
|
|
||||||
EVENT_THREAD("Event Thread", String.class, SnapshotRow::getEventThreadName),
|
|
||||||
SCHEDULE("Schedule", String.class, SnapshotRow::getSchedule),
|
|
||||||
DESCRIPTION("Description", String.class, SnapshotRow::getDescription, SnapshotRow::setDescription);
|
|
||||||
|
|
||||||
private final String header;
|
|
||||||
private final Function<SnapshotRow, ?> getter;
|
|
||||||
private final BiConsumer<SnapshotRow, Object> setter;
|
|
||||||
private final Class<?> cls;
|
|
||||||
|
|
||||||
<T> SnapshotTableColumns(String header, Class<T> cls, Function<SnapshotRow, T> getter) {
|
|
||||||
this(header, cls, getter, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
<T> SnapshotTableColumns(String header, Class<T> cls, Function<SnapshotRow, T> getter,
|
|
||||||
BiConsumer<SnapshotRow, T> setter) {
|
|
||||||
this.header = header;
|
|
||||||
this.cls = cls;
|
|
||||||
this.getter = getter;
|
|
||||||
this.setter = (BiConsumer<SnapshotRow, Object>) setter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<?> getValueClass() {
|
|
||||||
return cls;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getValueOf(SnapshotRow row) {
|
|
||||||
return getter.apply(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHeader() {
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEditable(SnapshotRow row) {
|
|
||||||
return setter != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setValueOf(SnapshotRow row, Object value) {
|
|
||||||
setter.accept(row, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SnapshotListener extends TraceDomainObjectListener {
|
|
||||||
public SnapshotListener() {
|
|
||||||
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
|
|
||||||
|
|
||||||
listenFor(TraceSnapshotChangeType.ADDED, this::snapAdded);
|
|
||||||
listenFor(TraceSnapshotChangeType.CHANGED, this::snapChanged);
|
|
||||||
listenFor(TraceSnapshotChangeType.DELETED, this::snapDeleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void objectRestored() {
|
|
||||||
loadSnapshots();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void snapAdded(TraceSnapshot snapshot) {
|
|
||||||
if (snapshot.getKey() < 0 && hideScratch) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SnapshotRow row = new SnapshotRow(current.getTrace(), snapshot);
|
|
||||||
snapshotTableModel.add(row);
|
|
||||||
if (current.getSnap() == snapshot.getKey()) {
|
|
||||||
snapshotFilterPanel.setSelectedItem(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void snapChanged(TraceSnapshot snapshot) {
|
|
||||||
if (snapshot.getKey() < 0 && hideScratch) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
snapshotTableModel.notifyUpdatedWith(row -> row.getSnapshot() == snapshot);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void snapDeleted(TraceSnapshot snapshot) {
|
|
||||||
if (snapshot.getKey() < 0 && hideScratch) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
snapshotTableModel.deleteWith(row -> row.getSnapshot() == snapshot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
|
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
|
||||||
if (!Objects.equals(a.getTrace(), b.getTrace())) {
|
if (!Objects.equals(a.getTrace(), b.getTrace())) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -162,28 +54,20 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
protected final DebuggerTimePlugin plugin;
|
protected final DebuggerTimePlugin plugin;
|
||||||
|
|
||||||
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||||
private Trace currentTrace; // copy for transition
|
|
||||||
|
|
||||||
protected final SnapshotListener listener = new SnapshotListener();
|
|
||||||
|
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
protected DebuggerTraceManagerService viewManager;
|
protected DebuggerTraceManagerService viewManager;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final Wiring autoServiceWiring;
|
private final Wiring autoServiceWiring;
|
||||||
|
|
||||||
private final JPanel mainPanel = new JPanel(new BorderLayout());
|
/*testing*/ final DebuggerSnapshotTablePanel mainPanel = new DebuggerSnapshotTablePanel();
|
||||||
|
|
||||||
/* testing */ final EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel =
|
|
||||||
new DefaultEnumeratedColumnTableModel<>("Snapshots", SnapshotTableColumns.class);
|
|
||||||
/* testing */ GTable snapshotTable;
|
|
||||||
/* testing */ GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel;
|
|
||||||
|
|
||||||
private DebuggerSnapActionContext myActionContext;
|
private DebuggerSnapActionContext myActionContext;
|
||||||
|
|
||||||
ToggleDockingAction actionHideScratch;
|
ToggleDockingAction actionHideScratch;
|
||||||
|
|
||||||
@AutoConfigStateField
|
@AutoConfigStateField
|
||||||
/* testing */ boolean hideScratch = true;
|
/*testing*/ boolean hideScratch = true;
|
||||||
|
|
||||||
public DebuggerTimeProvider(DebuggerTimePlugin plugin) {
|
public DebuggerTimeProvider(DebuggerTimePlugin plugin) {
|
||||||
super(plugin.getTool(), TITLE_PROVIDER_TIME, plugin.getName());
|
super(plugin.getTool(), TITLE_PROVIDER_TIME, plugin.getName());
|
||||||
|
@ -198,7 +82,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
buildMainPanel();
|
buildMainPanel();
|
||||||
|
|
||||||
myActionContext = new DebuggerSnapActionContext(0);
|
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getSnap());
|
||||||
createActions();
|
createActions();
|
||||||
contextChanged();
|
contextChanged();
|
||||||
|
|
||||||
|
@ -224,42 +108,22 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void buildMainPanel() {
|
protected void buildMainPanel() {
|
||||||
snapshotTable = new GTable(snapshotTableModel);
|
mainPanel.getSelectionModel().addListSelectionListener(evt -> {
|
||||||
snapshotTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
|
||||||
mainPanel.add(new JScrollPane(snapshotTable));
|
|
||||||
|
|
||||||
snapshotFilterPanel = new GhidraTableFilterPanel<>(snapshotTable, snapshotTableModel);
|
|
||||||
mainPanel.add(snapshotFilterPanel, BorderLayout.SOUTH);
|
|
||||||
|
|
||||||
snapshotTable.getSelectionModel().addListSelectionListener(evt -> {
|
|
||||||
if (evt.getValueIsAdjusting()) {
|
if (evt.getValueIsAdjusting()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SnapshotRow row = snapshotFilterPanel.getSelectedItem();
|
Long snap = mainPanel.getSelectedSnapshot();
|
||||||
if (row == null) {
|
if (snap == null) {
|
||||||
myActionContext = null;
|
myActionContext = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long snap = row.getSnap();
|
if (snap.longValue() == current.getSnap().longValue()) {
|
||||||
if (snap == current.getSnap().longValue()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
myActionContext = new DebuggerSnapActionContext(snap);
|
myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
|
||||||
viewManager.activateSnap(snap);
|
viewManager.activateSnap(snap);
|
||||||
contextChanged();
|
contextChanged();
|
||||||
});
|
});
|
||||||
|
|
||||||
TableColumnModel columnModel = snapshotTable.getColumnModel();
|
|
||||||
TableColumn snapCol = columnModel.getColumn(SnapshotTableColumns.SNAP.ordinal());
|
|
||||||
snapCol.setPreferredWidth(40);
|
|
||||||
TableColumn timeCol = columnModel.getColumn(SnapshotTableColumns.TIMESTAMP.ordinal());
|
|
||||||
timeCol.setPreferredWidth(200);
|
|
||||||
TableColumn etCol = columnModel.getColumn(SnapshotTableColumns.EVENT_THREAD.ordinal());
|
|
||||||
etCol.setPreferredWidth(40);
|
|
||||||
TableColumn schdCol = columnModel.getColumn(SnapshotTableColumns.SCHEDULE.ordinal());
|
|
||||||
schdCol.setPreferredWidth(60);
|
|
||||||
TableColumn descCol = columnModel.getColumn(SnapshotTableColumns.DESCRIPTION.ordinal());
|
|
||||||
descCol.setPreferredWidth(200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createActions() {
|
protected void createActions() {
|
||||||
|
@ -271,51 +135,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
|
|
||||||
private void activatedHideScratch(ActionContext ctx) {
|
private void activatedHideScratch(ActionContext ctx) {
|
||||||
hideScratch = !hideScratch;
|
hideScratch = !hideScratch;
|
||||||
if (hideScratch) {
|
mainPanel.setHideScratchSnapshots(hideScratch);
|
||||||
deleteScratchSnapshots();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
loadScratchSnapshots();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addNewListeners() {
|
|
||||||
if (currentTrace == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentTrace.addListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeOldListeners() {
|
|
||||||
if (currentTrace == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentTrace.removeListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void doSetTrace(Trace trace) {
|
|
||||||
if (currentTrace == trace) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
removeOldListeners();
|
|
||||||
currentTrace = trace;
|
|
||||||
addNewListeners();
|
|
||||||
loadSnapshots();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void doSetSnap(long snap) {
|
|
||||||
SnapshotRow sel = snapshotFilterPanel.getSelectedItem();
|
|
||||||
Long curSnap = sel == null ? null : sel.getSnap();
|
|
||||||
if (curSnap != null && curSnap.longValue() == snap) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SnapshotRow row = snapshotTableModel.findFirst(r -> r.getSnap() == snap);
|
|
||||||
if (row == null) {
|
|
||||||
snapshotTable.clearSelection();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
snapshotFilterPanel.setSelectedItem(row);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||||
|
@ -325,37 +145,8 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
current = coordinates;
|
current = coordinates;
|
||||||
|
|
||||||
doSetTrace(current.getTrace());
|
mainPanel.setTrace(current.getTrace());
|
||||||
doSetSnap(current.getSnap());
|
mainPanel.setSelectedSnapshot(current.getSnap());
|
||||||
}
|
|
||||||
|
|
||||||
protected void loadSnapshots() {
|
|
||||||
snapshotTableModel.clear();
|
|
||||||
Trace curTrace = current.getTrace();
|
|
||||||
if (curTrace == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TraceTimeManager manager = curTrace.getTimeManager();
|
|
||||||
Collection<? extends TraceSnapshot> snapshots = hideScratch
|
|
||||||
? manager.getSnapshots(0, true, Long.MAX_VALUE, true)
|
|
||||||
: manager.getAllSnapshots();
|
|
||||||
snapshotTableModel.addAll(Collections2.transform(snapshots,
|
|
||||||
s -> new SnapshotRow(curTrace, s)));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void deleteScratchSnapshots() {
|
|
||||||
snapshotTableModel.deleteWith(s -> s.getSnap() < 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void loadScratchSnapshots() {
|
|
||||||
Trace curTrace = current.getTrace();
|
|
||||||
if (curTrace == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TraceTimeManager manager = curTrace.getTimeManager();
|
|
||||||
snapshotTableModel.addAll(Collections2.transform(
|
|
||||||
manager.getSnapshots(Long.MIN_VALUE, true, 0, false),
|
|
||||||
s -> new SnapshotRow(curTrace, s)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeConfigState(SaveState saveState) {
|
public void writeConfigState(SaveState saveState) {
|
||||||
|
@ -366,5 +157,6 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||||
CONFIG_STATE_HANDLER.readConfigState(this, saveState);
|
CONFIG_STATE_HANDLER.readConfigState(this, saveState);
|
||||||
|
|
||||||
actionHideScratch.setSelected(hideScratch);
|
actionHideScratch.setSelected(hideScratch);
|
||||||
|
mainPanel.setHideScratchSnapshots(hideScratch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
return current.getFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Long> materialize(DebuggerCoordinates coordinates) {
|
||||||
|
if (coordinates.getTime().isSnapOnly()) {
|
||||||
|
return CompletableFuture.completedFuture(coordinates.getSnap());
|
||||||
|
}
|
||||||
|
Collection<? extends TraceSnapshot> suitable = coordinates.getTrace()
|
||||||
|
.getTimeManager()
|
||||||
|
.getSnapshotsWithSchedule(coordinates.getTime());
|
||||||
|
if (!suitable.isEmpty()) {
|
||||||
|
TraceSnapshot found = suitable.iterator().next();
|
||||||
|
return CompletableFuture.completedFuture(found.getKey());
|
||||||
|
}
|
||||||
|
if (emulationService == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Cannot navigate to coordinates with execution schedules, " +
|
||||||
|
"because the emulation service is not available.");
|
||||||
|
}
|
||||||
|
return emulationService.backgroundEmulate(coordinates.getTrace(), coordinates.getTime());
|
||||||
|
}
|
||||||
|
|
||||||
protected void prepareViewAndFireEvent(DebuggerCoordinates coordinates) {
|
protected void prepareViewAndFireEvent(DebuggerCoordinates coordinates) {
|
||||||
TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView();
|
TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView();
|
||||||
if (varView == null) { // Should only happen with NOWHERE
|
if (varView == null) { // Should only happen with NOWHERE
|
||||||
fireLocationEvent(coordinates);
|
fireLocationEvent(coordinates);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (coordinates.getTime().isSnapOnly()) {
|
materialize(coordinates).thenAcceptAsync(snap -> {
|
||||||
varView.setSnap(coordinates.getSnap());
|
if (!coordinates.equals(current)) {
|
||||||
|
return; // We navigated elsewhere before emulation completed
|
||||||
|
}
|
||||||
|
varView.setSnap(snap);
|
||||||
fireLocationEvent(coordinates);
|
fireLocationEvent(coordinates);
|
||||||
}
|
}, AsyncUtils.SWING_EXECUTOR);
|
||||||
else {
|
|
||||||
Collection<? extends TraceSnapshot> suitable = coordinates.getTrace()
|
|
||||||
.getTimeManager()
|
|
||||||
.getSnapshotsWithSchedule(coordinates.getTime());
|
|
||||||
if (!suitable.isEmpty()) {
|
|
||||||
TraceSnapshot found = suitable.iterator().next();
|
|
||||||
varView.setSnap(found.getKey());
|
|
||||||
fireLocationEvent(coordinates);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (emulationService == null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Cannot navigate to coordinates with execution schedules, " +
|
|
||||||
"because the emulation service is not available.");
|
|
||||||
}
|
|
||||||
CompletableFuture<Long> bg =
|
|
||||||
emulationService.backgroundEmulate(coordinates.getTrace(), coordinates.getTime());
|
|
||||||
bg.thenAccept(emuSnap -> Swing.runLater(() -> {
|
|
||||||
if (!coordinates.equals(current)) {
|
|
||||||
return; // We navigated elsewhere before emulation completed
|
|
||||||
}
|
|
||||||
varView.setSnap(emuSnap);
|
|
||||||
fireLocationEvent(coordinates);
|
|
||||||
})).exceptionally(ex -> {
|
|
||||||
Msg.showError(this, null, "Emulate", "Could not navigate to emulated coordinates",
|
|
||||||
ex);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void fireLocationEvent(DebuggerCoordinates coordinates) {
|
protected void fireLocationEvent(DebuggerCoordinates coordinates) {
|
||||||
|
@ -892,6 +887,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
future.completeExceptionally(e);
|
future.completeExceptionally(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return future;
|
return future;
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.utils;
|
package ghidra.app.plugin.core.debug.utils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
@ -24,8 +25,8 @@ import ghidra.framework.cmd.BackgroundCommand;
|
||||||
import ghidra.framework.model.DomainObject;
|
import ghidra.framework.model.DomainObject;
|
||||||
import ghidra.framework.model.UndoableDomainObject;
|
import ghidra.framework.model.UndoableDomainObject;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.util.task.CancelledListener;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.*;
|
||||||
|
|
||||||
public enum BackgroundUtils {
|
public enum BackgroundUtils {
|
||||||
;
|
;
|
||||||
|
@ -82,4 +83,59 @@ public enum BackgroundUtils {
|
||||||
tool.executeBackgroundCommand(cmd, obj);
|
tool.executeBackgroundCommand(cmd, obj);
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class PluginToolExecutorService extends AbstractExecutorService {
|
||||||
|
private final PluginTool tool;
|
||||||
|
private String name;
|
||||||
|
private boolean canCancel;
|
||||||
|
private boolean hasProgress;
|
||||||
|
private boolean isModal;
|
||||||
|
private final int delay;
|
||||||
|
|
||||||
|
public PluginToolExecutorService(PluginTool tool, String name, boolean canCancel,
|
||||||
|
boolean hasProgress, boolean isModal, int delay) {
|
||||||
|
this.tool = tool;
|
||||||
|
this.name = name;
|
||||||
|
this.canCancel = canCancel;
|
||||||
|
this.hasProgress = hasProgress;
|
||||||
|
this.isModal = isModal;
|
||||||
|
this.delay = delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Runnable> shutdownNow() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShutdown() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTerminated() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Runnable command) {
|
||||||
|
Task task = new Task(name, canCancel, hasProgress, isModal) {
|
||||||
|
@Override
|
||||||
|
public void run(TaskMonitor monitor) throws CancelledException {
|
||||||
|
command.run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tool.execute(task, delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,19 +17,89 @@ package ghidra.app.services;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
|
import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
|
||||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.listing.MultiBlendedListingBackgroundColorModel;
|
||||||
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
import ghidra.framework.plugintool.ServiceInfo;
|
import ghidra.framework.plugintool.ServiceInfo;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.util.ProgramSelection;
|
import ghidra.program.util.ProgramSelection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service providing access to the main listing panel
|
||||||
|
*/
|
||||||
@ServiceInfo( //
|
@ServiceInfo( //
|
||||||
defaultProvider = DebuggerListingPlugin.class, //
|
defaultProvider = DebuggerListingPlugin.class, //
|
||||||
description = "Replacement CodeViewerService for Debugger" //
|
description = "Replacement CodeViewerService for Debugger" //
|
||||||
)
|
)
|
||||||
public interface DebuggerListingService extends CodeViewerService {
|
public interface DebuggerListingService extends CodeViewerService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener for changes in location tracking specification
|
||||||
|
*/
|
||||||
|
interface LocationTrackingSpecChangeListener {
|
||||||
|
/**
|
||||||
|
* The specification has changed
|
||||||
|
*
|
||||||
|
* @param spec the new specification
|
||||||
|
*/
|
||||||
|
void locationTrackingSpecChanged(LocationTrackingSpec spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the tracking specification of the listing. Navigates immediately.
|
||||||
|
*
|
||||||
|
* @param spec the desired specification
|
||||||
|
*/
|
||||||
void setTrackingSpec(LocationTrackingSpec spec);
|
void setTrackingSpec(LocationTrackingSpec spec);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tracking specification of the listing.
|
||||||
|
*
|
||||||
|
* @return the current specification
|
||||||
|
*/
|
||||||
|
LocationTrackingSpec getTrackingSpec();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener for changes to the tracking specification.
|
||||||
|
*
|
||||||
|
* @param listener the listener to receive change notifications
|
||||||
|
*/
|
||||||
|
void addTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a listener for changes to the tracking specification.
|
||||||
|
*
|
||||||
|
* @param listener the listener receiving change notifications
|
||||||
|
*/
|
||||||
|
void removeTrackingSpecChangeListener(LocationTrackingSpecChangeListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the selection of addresses in this listing.
|
||||||
|
*
|
||||||
|
* @param selection the desired selection
|
||||||
|
*/
|
||||||
void setCurrentSelection(ProgramSelection selection);
|
void setCurrentSelection(ProgramSelection selection);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to the given address
|
||||||
|
*
|
||||||
|
* @param address the desired address
|
||||||
|
* @param centerOnScreen true to center the cursor in the listing
|
||||||
|
* @return true if the request was effective
|
||||||
|
*/
|
||||||
boolean goTo(Address address, boolean centerOnScreen);
|
boolean goTo(Address address, boolean centerOnScreen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain a coloring background model suitable for the given listing
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This may be used, e.g., to style an alternative view in the same manner as listings managed
|
||||||
|
* by this service. Namely, this provides coloring for memory state and the user's cursor.
|
||||||
|
* Coloring for tracked locations and the marker service in general must still be added
|
||||||
|
* separately, since they incorporate additional dependencies.
|
||||||
|
*
|
||||||
|
* @param listingPanel the panel to be colored
|
||||||
|
* @return a blended background color model implementing the common debugger listing style
|
||||||
|
*/
|
||||||
|
MultiBlendedListingBackgroundColorModel createListingBackgroundColorModel(
|
||||||
|
ListingPanel listingPanel);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,39 +20,117 @@ import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
||||||
|
import ghidra.async.AsyncReference;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
import ghidra.framework.plugintool.ServiceInfo;
|
import ghidra.framework.plugintool.ServiceInfo;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
import ghidra.util.TriConsumer;
|
import ghidra.util.TriConsumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface for managing open traces and navigating among them and their contents
|
||||||
|
*/
|
||||||
@ServiceInfo(defaultProvider = DebuggerTraceManagerServicePlugin.class)
|
@ServiceInfo(defaultProvider = DebuggerTraceManagerServicePlugin.class)
|
||||||
public interface DebuggerTraceManagerService {
|
public interface DebuggerTraceManagerService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An adapter that works nicely with an {@link AsyncReference}
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* TODO: Seems this is still leaking an implementation detail
|
||||||
|
*/
|
||||||
public interface BooleanChangeAdapter extends TriConsumer<Boolean, Boolean, Void> {
|
public interface BooleanChangeAdapter extends TriConsumer<Boolean, Boolean, Void> {
|
||||||
@Override
|
@Override
|
||||||
default void accept(Boolean oldVal, Boolean newVal, Void cause) {
|
default void accept(Boolean oldVal, Boolean newVal, Void cause) {
|
||||||
changed(newVal);
|
changed(newVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value has changed
|
||||||
|
*
|
||||||
|
* @param value the new value
|
||||||
|
*/
|
||||||
void changed(Boolean value);
|
void changed(Boolean value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the open traces
|
||||||
|
*
|
||||||
|
* @return all open traces
|
||||||
|
*/
|
||||||
Collection<Trace> getOpenTraces();
|
Collection<Trace> getOpenTraces();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current coordinates
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This entails everything except the current address
|
||||||
|
*
|
||||||
|
* @return the current coordinates
|
||||||
|
*/
|
||||||
DebuggerCoordinates getCurrent();
|
DebuggerCoordinates getCurrent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active trace
|
||||||
|
*
|
||||||
|
* @return the active trace, or null
|
||||||
|
*/
|
||||||
Trace getCurrentTrace();
|
Trace getCurrentTrace();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active view
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Every trace has an associated variable-snap view. When the manager navigates to a new point
|
||||||
|
* in time, it is accomplished by changing the snap of this view. This view is suitable for use
|
||||||
|
* in most places where a {@link Program} is ordinarily required.
|
||||||
|
*
|
||||||
|
* @return the active view, or null
|
||||||
|
*/
|
||||||
TraceProgramView getCurrentView();
|
TraceProgramView getCurrentView();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active thread
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* It is possible to have an active trace, but no active thread.
|
||||||
|
*
|
||||||
|
* @return the active thread, or null
|
||||||
|
*/
|
||||||
TraceThread getCurrentThread();
|
TraceThread getCurrentThread();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active thread for a given trace
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The manager remembers the last active thread for every open trace. If the trace has never
|
||||||
|
* been active, then the last active thread is null. If trace is the active trace, then this
|
||||||
|
* will return the currently active thread.
|
||||||
|
*
|
||||||
|
* @param trace the trace
|
||||||
|
* @return the thread, or null
|
||||||
|
*/
|
||||||
TraceThread getCurrentThreadFor(Trace trace);
|
TraceThread getCurrentThreadFor(Trace trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active snap
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Note that if emulation was used to materialize the current coordinates, then the current snap
|
||||||
|
* will differ from the view's snap.
|
||||||
|
*
|
||||||
|
* @return the active snap, or 0
|
||||||
|
*/
|
||||||
long getCurrentSnap();
|
long getCurrentSnap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active frame
|
||||||
|
*
|
||||||
|
* @return the active frame, or 0
|
||||||
|
*/
|
||||||
int getCurrentFrame();
|
int getCurrentFrame();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -105,10 +183,23 @@ public interface DebuggerTraceManagerService {
|
||||||
*/
|
*/
|
||||||
CompletableFuture<Void> saveTrace(Trace trace);
|
CompletableFuture<Void> saveTrace(Trace trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the given trace
|
||||||
|
*
|
||||||
|
* @param trace the trace to close
|
||||||
|
*/
|
||||||
void closeTrace(Trace trace);
|
void closeTrace(Trace trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close all traces
|
||||||
|
*/
|
||||||
void closeAllTraces();
|
void closeAllTraces();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close all traces except the given one
|
||||||
|
*
|
||||||
|
* @param keep the trace to keep open
|
||||||
|
*/
|
||||||
void closeOtherTraces(Trace keep);
|
void closeOtherTraces(Trace keep);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,24 +211,84 @@ public interface DebuggerTraceManagerService {
|
||||||
*/
|
*/
|
||||||
void closeDeadTraces();
|
void closeDeadTraces();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the given coordinates
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This operation may be completed asynchronously, esp., if emulation is required to materialize
|
||||||
|
* the coordinates. The coordinates are "resolved" as a means of filling in missing parts. For
|
||||||
|
* example, if the thread is not specified, the manager may activate the last-active thread for
|
||||||
|
* the desired trace.
|
||||||
|
*
|
||||||
|
* @param coordinates the desired coordinates
|
||||||
|
*/
|
||||||
void activate(DebuggerCoordinates coordinates);
|
void activate(DebuggerCoordinates coordinates);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the given trace
|
||||||
|
*
|
||||||
|
* @param trace the desired trace
|
||||||
|
*/
|
||||||
void activateTrace(Trace trace);
|
void activateTrace(Trace trace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the given thread
|
||||||
|
*
|
||||||
|
* @param thread the desired thread
|
||||||
|
*/
|
||||||
void activateThread(TraceThread thread);
|
void activateThread(TraceThread thread);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the given snapshot key
|
||||||
|
*
|
||||||
|
* @param snap the desired snapshot key
|
||||||
|
*/
|
||||||
void activateSnap(long snap);
|
void activateSnap(long snap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the given point in time, possibly invoking emulation
|
||||||
|
*
|
||||||
|
* @param time the desired schedule
|
||||||
|
*/
|
||||||
void activateTime(TraceSchedule time);
|
void activateTime(TraceSchedule time);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the given stack frame
|
||||||
|
*
|
||||||
|
* @param frameLevel the level of the desired frame, 0 being innermost
|
||||||
|
*/
|
||||||
void activateFrame(int frameLevel);
|
void activateFrame(int frameLevel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control whether the trace manager automatically activates the "present snapshot"
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Auto activation only applies when the current trace advances. It never changes to another
|
||||||
|
* trace.
|
||||||
|
*
|
||||||
|
* @param enabled true to enable auto activation
|
||||||
|
*/
|
||||||
void setAutoActivatePresent(boolean enabled);
|
void setAutoActivatePresent(boolean enabled);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the trace manager automatically activate the "present snapshot"
|
||||||
|
*
|
||||||
|
* @return true if auto activation is enabled
|
||||||
|
*/
|
||||||
boolean isAutoActivatePresent();
|
boolean isAutoActivatePresent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener for changes to auto activation enablement
|
||||||
|
*
|
||||||
|
* @param listener the listener to receive change notifications
|
||||||
|
*/
|
||||||
void addAutoActivatePresentChangeListener(BooleanChangeAdapter listener);
|
void addAutoActivatePresentChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a listener for changes to auto activation enablement
|
||||||
|
*
|
||||||
|
* @param listener the listener receiving change notifications
|
||||||
|
*/
|
||||||
void removeAutoActivatePresentChangeListener(BooleanChangeAdapter listener);
|
void removeAutoActivatePresentChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,8 +305,18 @@ public interface DebuggerTraceManagerService {
|
||||||
*/
|
*/
|
||||||
boolean isSynchronizeFocus();
|
boolean isSynchronizeFocus();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener for changes to focus synchronization enablement
|
||||||
|
*
|
||||||
|
* @param listener the listener to receive change notifications
|
||||||
|
*/
|
||||||
void addSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
|
void addSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a listener for changes to focus synchronization enablement
|
||||||
|
*
|
||||||
|
* @param listener the listener receiving change notifications
|
||||||
|
*/
|
||||||
void removeSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
|
void removeSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -172,8 +333,18 @@ public interface DebuggerTraceManagerService {
|
||||||
*/
|
*/
|
||||||
boolean isSaveTracesByDefault();
|
boolean isSaveTracesByDefault();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener for changes to save-by-default enablement
|
||||||
|
*
|
||||||
|
* @param listener the listener to receive change notifications
|
||||||
|
*/
|
||||||
void addSaveTracesByDefaultChangeListener(BooleanChangeAdapter listener);
|
void addSaveTracesByDefaultChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a listener for changes to save-by-default enablement
|
||||||
|
*
|
||||||
|
* @param listener the listener receiving change notifications
|
||||||
|
*/
|
||||||
void removeSaveTracesByDefaultChangeListener(BooleanChangeAdapter listener);
|
void removeSaveTracesByDefaultChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -190,15 +361,40 @@ public interface DebuggerTraceManagerService {
|
||||||
*/
|
*/
|
||||||
boolean isAutoCloseOnTerminate();
|
boolean isAutoCloseOnTerminate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener for changes to close-on-terminate enablement
|
||||||
|
*
|
||||||
|
* @param listener the listener to receive change notifications
|
||||||
|
*/
|
||||||
void addAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener);
|
void addAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a listener for changes to close-on-terminate enablement
|
||||||
|
*
|
||||||
|
* @param listener the listener receiving change notifications
|
||||||
|
*/
|
||||||
void removeAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener);
|
void removeAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill in an incomplete coordinate specification, using the manager's "best judgement"
|
* Fill in an incomplete coordinate specification, using the manager's "best judgment"
|
||||||
*
|
*
|
||||||
* @param coords the possibly-incomplete coordinates
|
* @param coords the possibly-incomplete coordinates
|
||||||
* @return the complete resolved coordinates
|
* @return the complete resolved coordinates
|
||||||
*/
|
*/
|
||||||
DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coords);
|
DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coordinates);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Materialize the given coordinates to a snapshot in the same trace
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If the given coordinates do not require emulation, then this must complete immediately with
|
||||||
|
* the snapshot key given by the coordinates. If the given schedule is already materialized in
|
||||||
|
* the trace, then this may complete immediately with the previously-materialized snapshot key.
|
||||||
|
* Otherwise, this must invoke emulation, store the result into a chosen snapshot, and complete
|
||||||
|
* with its key.
|
||||||
|
*
|
||||||
|
* @param coordinates the coordinates to materialize
|
||||||
|
* @return a future that completes with the snapshot key of the materialized coordinates
|
||||||
|
*/
|
||||||
|
CompletableFuture<Long> materialize(DebuggerCoordinates coordinates);
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 663 B |
|
@ -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.*;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
@ -40,11 +41,13 @@ import org.junit.runner.Description;
|
||||||
import docking.widgets.tree.GTree;
|
import docking.widgets.tree.GTree;
|
||||||
import docking.widgets.tree.GTreeNode;
|
import docking.widgets.tree.GTreeNode;
|
||||||
import generic.Unique;
|
import generic.Unique;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.action.*;
|
||||||
import ghidra.app.plugin.core.debug.mapping.*;
|
import ghidra.app.plugin.core.debug.mapping.*;
|
||||||
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceInternal;
|
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceInternal;
|
||||||
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin;
|
import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin;
|
||||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
import ghidra.dbg.model.AbstractTestTargetRegisterBank;
|
import ghidra.dbg.model.AbstractTestTargetRegisterBank;
|
||||||
import ghidra.dbg.model.TestDebuggerModelBuilder;
|
import ghidra.dbg.model.TestDebuggerModelBuilder;
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
|
@ -57,6 +60,7 @@ import ghidra.program.model.data.DataType;
|
||||||
import ghidra.program.model.lang.*;
|
import ghidra.program.model.lang.*;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.util.DefaultLanguageService;
|
import ghidra.program.util.DefaultLanguageService;
|
||||||
|
import ghidra.program.util.ProgramLocation;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
import ghidra.test.TestEnv;
|
import ghidra.test.TestEnv;
|
||||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
|
@ -409,6 +413,64 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
||||||
clickMouse(button, m);
|
clickMouse(button, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static void assertListingBackgroundAt(Color expected, ListingPanel panel,
|
||||||
|
Address addr, int yAdjust) throws AWTException, InterruptedException {
|
||||||
|
ProgramLocation oneBack = new ProgramLocation(panel.getProgram(), addr.previous());
|
||||||
|
runSwing(() -> panel.goTo(addr));
|
||||||
|
runSwing(() -> panel.goTo(oneBack, false));
|
||||||
|
waitForPass(() -> {
|
||||||
|
Rectangle r = panel.getBounds();
|
||||||
|
// Capture off screen, so that focus/stacking doesn't matter
|
||||||
|
BufferedImage image = new BufferedImage(r.width, r.height, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics g = image.getGraphics();
|
||||||
|
try {
|
||||||
|
runSwing(() -> panel.paint(g));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
g.dispose();
|
||||||
|
}
|
||||||
|
Point locP = panel.getLocationOnScreen();
|
||||||
|
Point locFP = panel.getLocationOnScreen();
|
||||||
|
locFP.translate(-locP.x, -locP.y);
|
||||||
|
Rectangle cursor = panel.getCursorBounds();
|
||||||
|
assertNotNull("Cannot get cursor bounds", cursor);
|
||||||
|
Color actual = new Color(image.getRGB(locFP.x + cursor.x - 1,
|
||||||
|
locFP.y + cursor.y + cursor.height * 3 / 2 + yAdjust));
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void goTo(ListingPanel listingPanel, ProgramLocation location) {
|
||||||
|
waitForPass(() -> {
|
||||||
|
runSwing(() -> listingPanel.goTo(location));
|
||||||
|
ProgramLocation confirm = listingPanel.getCursorLocation();
|
||||||
|
assertNotNull(confirm);
|
||||||
|
assertEquals(location.getAddress(), confirm.getAddress());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static LocationTrackingSpec getLocationTrackingSpec(String name) {
|
||||||
|
return LocationTrackingSpec.fromConfigName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static AutoReadMemorySpec getAutoReadMemorySpec(String name) {
|
||||||
|
return AutoReadMemorySpec.fromConfigName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final LocationTrackingSpec trackNone =
|
||||||
|
getLocationTrackingSpec(NoneLocationTrackingSpec.CONFIG_NAME);
|
||||||
|
protected final LocationTrackingSpec trackPc =
|
||||||
|
getLocationTrackingSpec(PCLocationTrackingSpec.CONFIG_NAME);
|
||||||
|
protected final LocationTrackingSpec trackSp =
|
||||||
|
getLocationTrackingSpec(SPLocationTrackingSpec.CONFIG_NAME);
|
||||||
|
|
||||||
|
protected final AutoReadMemorySpec readNone =
|
||||||
|
getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME);
|
||||||
|
protected final AutoReadMemorySpec readVisible =
|
||||||
|
getAutoReadMemorySpec(VisibleAutoReadMemorySpec.CONFIG_NAME);
|
||||||
|
protected final AutoReadMemorySpec readVisROOnce =
|
||||||
|
getAutoReadMemorySpec(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME);
|
||||||
|
|
||||||
protected TestEnv env;
|
protected TestEnv env;
|
||||||
protected PluginTool tool;
|
protected PluginTool tool;
|
||||||
|
|
||||||
|
|
|
@ -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 ghidra.lifecycle.Unfinished.TODO;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.Color;
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
@ -37,14 +36,13 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.action.*;
|
import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog;
|
||||||
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
|
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
|
||||||
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction;
|
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow;
|
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.LogRow;
|
||||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext;
|
import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext;
|
||||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
|
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
|
||||||
import ghidra.async.SwingExecutorService;
|
import ghidra.async.SwingExecutorService;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.plugin.importer.ImporterPlugin;
|
import ghidra.plugin.importer.ImporterPlugin;
|
||||||
|
@ -68,27 +66,6 @@ import ghidra.util.exception.VersionException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
static LocationTrackingSpec getLocationTrackingSpec(String name) {
|
|
||||||
return LocationTrackingSpec.fromConfigName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static AutoReadMemorySpec getAutoReadMemorySpec(String name) {
|
|
||||||
return AutoReadMemorySpec.fromConfigName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
final LocationTrackingSpec trackNone =
|
|
||||||
getLocationTrackingSpec(NoneLocationTrackingSpec.CONFIG_NAME);
|
|
||||||
final LocationTrackingSpec trackPc =
|
|
||||||
getLocationTrackingSpec(PCLocationTrackingSpec.CONFIG_NAME);
|
|
||||||
final LocationTrackingSpec trackSp =
|
|
||||||
getLocationTrackingSpec(SPLocationTrackingSpec.CONFIG_NAME);
|
|
||||||
|
|
||||||
final AutoReadMemorySpec readNone =
|
|
||||||
getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME);
|
|
||||||
final AutoReadMemorySpec readVisible =
|
|
||||||
getAutoReadMemorySpec(VisibleAutoReadMemorySpec.CONFIG_NAME);
|
|
||||||
final AutoReadMemorySpec readVisROOnce =
|
|
||||||
getAutoReadMemorySpec(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME);
|
|
||||||
|
|
||||||
protected DebuggerListingPlugin listingPlugin;
|
protected DebuggerListingPlugin listingPlugin;
|
||||||
protected DebuggerListingProvider listingProvider;
|
protected DebuggerListingProvider listingProvider;
|
||||||
|
@ -110,12 +87,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void goToDyn(ProgramLocation location) {
|
protected void goToDyn(ProgramLocation location) {
|
||||||
waitForPass(() -> {
|
goTo(listingProvider.getListingPanel(), location);
|
||||||
runSwing(() -> listingProvider.goTo(location.getProgram(), location));
|
|
||||||
ProgramLocation confirm = listingProvider.getLocation();
|
|
||||||
assertNotNull(confirm);
|
|
||||||
assertEquals(location.getAddress(), confirm.getAddress());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static byte[] incBlock() {
|
protected static byte[] incBlock() {
|
||||||
|
@ -572,32 +544,6 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
assertEquals(ss.getAddress(0x00601234), loc.getAddress());
|
assertEquals(ss.getAddress(0x00601234), loc.getAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertListingBackgroundAt(Color expected, ListingPanel panel,
|
|
||||||
Address addr, int yAdjust) throws AWTException, InterruptedException {
|
|
||||||
ProgramLocation oneBack = new ProgramLocation(panel.getProgram(), addr.previous());
|
|
||||||
runSwing(() -> panel.goTo(addr));
|
|
||||||
runSwing(() -> panel.goTo(oneBack, false));
|
|
||||||
waitForPass(() -> {
|
|
||||||
Rectangle r = panel.getBounds();
|
|
||||||
// Capture off screen, so that focus/stacking doesn't matter
|
|
||||||
BufferedImage image = new BufferedImage(r.width, r.height, BufferedImage.TYPE_INT_ARGB);
|
|
||||||
Graphics g = image.getGraphics();
|
|
||||||
try {
|
|
||||||
runSwing(() -> panel.paint(g));
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
g.dispose();
|
|
||||||
}
|
|
||||||
Point locP = panel.getLocationOnScreen();
|
|
||||||
Point locFP = panel.getLocationOnScreen();
|
|
||||||
locFP.translate(-locP.x, -locP.y);
|
|
||||||
Rectangle cursor = panel.getCursorBounds();
|
|
||||||
Color actual = new Color(image.getRGB(locFP.x + cursor.x - 1,
|
|
||||||
locFP.y + cursor.y + cursor.height * 3 / 2 + yAdjust));
|
|
||||||
assertEquals(expected, actual);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDynamicListingMarksTrackedRegister() throws Exception {
|
public void testDynamicListingMarksTrackedRegister() throws Exception {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
|
|
|
@ -66,26 +66,6 @@ import ghidra.util.database.UndoableTransaction;
|
||||||
|
|
||||||
@Category(NightlyCategory.class)
|
@Category(NightlyCategory.class)
|
||||||
public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
static LocationTrackingSpec getLocationTrackingSpec(String name) {
|
|
||||||
return LocationTrackingSpec.fromConfigName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static AutoReadMemorySpec getAutoReadMemorySpec(String name) {
|
|
||||||
return AutoReadMemorySpec.fromConfigName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
final LocationTrackingSpec trackNone =
|
|
||||||
getLocationTrackingSpec(NoneLocationTrackingSpec.CONFIG_NAME);
|
|
||||||
final LocationTrackingSpec trackPc =
|
|
||||||
getLocationTrackingSpec(PCLocationTrackingSpec.CONFIG_NAME);
|
|
||||||
final LocationTrackingSpec trackSp =
|
|
||||||
getLocationTrackingSpec(SPLocationTrackingSpec.CONFIG_NAME);
|
|
||||||
|
|
||||||
final AutoReadMemorySpec readNone = getAutoReadMemorySpec(NoneAutoReadMemorySpec.CONFIG_NAME);
|
|
||||||
final AutoReadMemorySpec readVisible =
|
|
||||||
getAutoReadMemorySpec(VisibleAutoReadMemorySpec.CONFIG_NAME);
|
|
||||||
final AutoReadMemorySpec readVisROOnce =
|
|
||||||
getAutoReadMemorySpec(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME);
|
|
||||||
|
|
||||||
protected DebuggerMemoryBytesPlugin memBytesPlugin;
|
protected DebuggerMemoryBytesPlugin memBytesPlugin;
|
||||||
protected DebuggerMemoryBytesProvider memBytesProvider;
|
protected DebuggerMemoryBytesProvider memBytesProvider;
|
||||||
|
|
|
@ -23,7 +23,10 @@ import java.util.List;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import docking.widgets.dialogs.InputDialog;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||||
|
import ghidra.trace.database.time.DBTraceSnapshot;
|
||||||
import ghidra.trace.database.time.DBTraceTimeManager;
|
import ghidra.trace.database.time.DBTraceTimeManager;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
|
@ -64,11 +67,11 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertProviderEmpty() {
|
protected void assertProviderEmpty() {
|
||||||
assertTrue(timeProvider.snapshotTableModel.getModelData().isEmpty());
|
assertTrue(timeProvider.mainPanel.snapshotTableModel.getModelData().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertProviderPopulated() {
|
protected void assertProviderPopulated() {
|
||||||
List<SnapshotRow> snapsDisplayed = timeProvider.snapshotTableModel.getModelData();
|
List<SnapshotRow> snapsDisplayed = timeProvider.mainPanel.snapshotTableModel.getModelData();
|
||||||
// I should be able to assume this is sorted by key
|
// I should be able to assume this is sorted by key
|
||||||
assertEquals(2, snapsDisplayed.size());
|
assertEquals(2, snapsDisplayed.size());
|
||||||
|
|
||||||
|
@ -85,6 +88,42 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
// Timestamp is left unchecked, since default is current time
|
// Timestamp is left unchecked, since default is current time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // TODO: Technically, this is a plugin action.... Different test case?
|
||||||
|
public void testActionRenameSnapshot() throws Exception {
|
||||||
|
// Need some docked provider to provide action context
|
||||||
|
|
||||||
|
addPlugin(tool, DebuggerListingPlugin.class);
|
||||||
|
assertFalse(timePlugin.actionRenameSnapshot.isEnabled());
|
||||||
|
|
||||||
|
createSnaplessTrace();
|
||||||
|
addSnapshots();
|
||||||
|
assertFalse(timePlugin.actionRenameSnapshot.isEnabled());
|
||||||
|
|
||||||
|
traceManager.openTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
assertFalse(timePlugin.actionRenameSnapshot.isEnabled());
|
||||||
|
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
waitForSwing();
|
||||||
|
assertTrue(timePlugin.actionRenameSnapshot.isEnabled());
|
||||||
|
traceManager.activateSnap(10);
|
||||||
|
waitForSwing();
|
||||||
|
assertTrue(timePlugin.actionRenameSnapshot.isEnabled());
|
||||||
|
|
||||||
|
performAction(timePlugin.actionRenameSnapshot, false);
|
||||||
|
InputDialog dialog = waitForDialogComponent(InputDialog.class);
|
||||||
|
assertEquals("Snap 10", dialog.getValue());
|
||||||
|
|
||||||
|
dialog.setValue("My Snapshot");
|
||||||
|
dialog.close(); // isCancelled (private) defaults to false
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
DBTraceSnapshot snapshot = tb.trace.getTimeManager().getSnapshot(10, false);
|
||||||
|
assertEquals("My Snapshot", snapshot.getDescription());
|
||||||
|
|
||||||
|
// TODO: Test cancelled has no effect
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEmpty() {
|
public void testEmpty() {
|
||||||
assertProviderEmpty();
|
assertProviderEmpty();
|
||||||
|
@ -158,7 +197,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
}
|
}
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
|
|
||||||
assertEquals(1, timeProvider.snapshotTableModel.getModelData().size());
|
assertEquals(1, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -238,7 +277,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
SnapshotRow row = timeProvider.snapshotTableModel.getModelData().get(0);
|
SnapshotRow row = timeProvider.mainPanel.snapshotTableModel.getModelData().get(0);
|
||||||
runSwing(() -> row.setDescription("Custom Description"));
|
runSwing(() -> row.setDescription("Custom Description"));
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
|
|
||||||
|
@ -258,14 +297,14 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData();
|
List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
|
||||||
|
|
||||||
timeProvider.snapshotFilterPanel.setSelectedItem(data.get(0));
|
timeProvider.mainPanel.snapshotFilterPanel.setSelectedItem(data.get(0));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(0, traceManager.getCurrentSnap());
|
assertEquals(0, traceManager.getCurrentSnap());
|
||||||
|
|
||||||
timeProvider.snapshotFilterPanel.setSelectedItem(data.get(1));
|
timeProvider.mainPanel.snapshotFilterPanel.setSelectedItem(data.get(1));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(10, traceManager.getCurrentSnap());
|
assertEquals(10, traceManager.getCurrentSnap());
|
||||||
|
@ -283,22 +322,22 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData();
|
List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
|
||||||
|
|
||||||
traceManager.activateSnap(0);
|
traceManager.activateSnap(0);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(data.get(0), timeProvider.snapshotFilterPanel.getSelectedItem());
|
assertEquals(data.get(0), timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
|
||||||
|
|
||||||
traceManager.activateSnap(10);
|
traceManager.activateSnap(10);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(data.get(1), timeProvider.snapshotFilterPanel.getSelectedItem());
|
assertEquals(data.get(1), timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
|
||||||
|
|
||||||
traceManager.activateSnap(5);
|
traceManager.activateSnap(5);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertNull(timeProvider.snapshotFilterPanel.getSelectedItem());
|
assertNull(timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -312,7 +351,7 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData();
|
List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
|
||||||
assertEquals(2, data.size());
|
assertEquals(2, data.size());
|
||||||
for (SnapshotRow row : data) {
|
for (SnapshotRow row : data) {
|
||||||
assertTrue(row.getSnap() >= 0);
|
assertTrue(row.getSnap() >= 0);
|
||||||
|
@ -329,12 +368,12 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(2, timeProvider.snapshotTableModel.getModelData().size());
|
assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
|
||||||
|
|
||||||
addScratchSnapshot();
|
addScratchSnapshot();
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
|
|
||||||
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData();
|
List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
|
||||||
assertEquals(2, data.size());
|
assertEquals(2, data.size());
|
||||||
for (SnapshotRow row : data) {
|
for (SnapshotRow row : data) {
|
||||||
assertTrue(row.getSnap() >= 0);
|
assertTrue(row.getSnap() >= 0);
|
||||||
|
@ -353,17 +392,17 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(true, timeProvider.hideScratch);
|
assertEquals(true, timeProvider.hideScratch);
|
||||||
assertEquals(2, timeProvider.snapshotTableModel.getModelData().size());
|
assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
|
||||||
|
|
||||||
performAction(timeProvider.actionHideScratch);
|
performAction(timeProvider.actionHideScratch);
|
||||||
|
|
||||||
assertEquals(false, timeProvider.hideScratch);
|
assertEquals(false, timeProvider.hideScratch);
|
||||||
assertEquals(3, timeProvider.snapshotTableModel.getModelData().size());
|
assertEquals(3, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
|
||||||
|
|
||||||
performAction(timeProvider.actionHideScratch);
|
performAction(timeProvider.actionHideScratch);
|
||||||
|
|
||||||
assertEquals(true, timeProvider.hideScratch);
|
assertEquals(true, timeProvider.hideScratch);
|
||||||
assertEquals(2, timeProvider.snapshotTableModel.getModelData().size());
|
assertEquals(2, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -380,6 +419,6 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(false, timeProvider.hideScratch);
|
assertEquals(false, timeProvider.hideScratch);
|
||||||
assertEquals(3, timeProvider.snapshotTableModel.getModelData().size());
|
assertEquals(3, timeProvider.mainPanel.snapshotTableModel.getModelData().size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -327,6 +327,17 @@ public class DBTraceMemoryManager
|
||||||
return delegateRead(start.getAddressSpace(), m -> m.getBufferAt(snap, start, byteOrder));
|
return delegateRead(start.getAddressSpace(), m -> m.getBufferAt(snap, start, byteOrder));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getSnapOfMostRecentChangeToBlock(long snap, Address address) {
|
||||||
|
return delegateRead(address.getAddressSpace(),
|
||||||
|
m -> m.getSnapOfMostRecentChangeToBlock(snap, address));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBlockSize() {
|
||||||
|
return DBTraceMemorySpace.BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void pack() {
|
public void pack() {
|
||||||
delegateWriteAll(getActiveSpaces(), m -> m.pack());
|
delegateWriteAll(getActiveSpaces(), m -> m.pack());
|
||||||
|
|
|
@ -884,6 +884,26 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getSnapOfMostRecentChangeToBlock(long snap, Address address) {
|
||||||
|
assertInSpace(address);
|
||||||
|
try (LockHold hold = LockHold.lock(lock.readLock())) {
|
||||||
|
long offset = address.getOffset();
|
||||||
|
long roundOffset = offset & BLOCK_MASK;
|
||||||
|
OffsetSnap loc = new OffsetSnap(roundOffset, snap);
|
||||||
|
DBTraceMemoryBlockEntry ent = findMostRecentBlockEntry(loc, true);
|
||||||
|
if (ent == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ent.getSnap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBlockSize() {
|
||||||
|
return BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
public long getFirstChange(Range<Long> span, AddressRange range) {
|
public long getFirstChange(Range<Long> span, AddressRange range) {
|
||||||
assertInSpace(range);
|
assertInSpace(range);
|
||||||
long lower = DBTraceUtils.lowerEndpoint(span);
|
long lower = DBTraceUtils.lowerEndpoint(span);
|
||||||
|
|
|
@ -472,6 +472,32 @@ public interface TraceMemoryOperations {
|
||||||
: ByteOrder.LITTLE_ENDIAN);
|
: ByteOrder.LITTLE_ENDIAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the internal storage block that most-recently defines the value at the given snap and
|
||||||
|
* address, and return the block's snap.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This method reveals portions of the internal storage so that clients can optimize difference
|
||||||
|
* computations by eliminating corresponding ranges defined by the same block. If the underlying
|
||||||
|
* implementation cannot answer this question, this returns the given snap.
|
||||||
|
*
|
||||||
|
* @param snap the time
|
||||||
|
* @param address the location
|
||||||
|
* @return the most snap for the most recent containing block
|
||||||
|
*/
|
||||||
|
Long getSnapOfMostRecentChangeToBlock(long snap, Address address);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the block size used by internal storage.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This method reveals portions of the internal storage so that clients can optimize searches.
|
||||||
|
* If the underlying implementation cannot answer this question, this returns 0.
|
||||||
|
*
|
||||||
|
* @return the block size
|
||||||
|
*/
|
||||||
|
int getBlockSize();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optimize storage space
|
* Optimize storage space
|
||||||
*
|
*
|
||||||
|
|
|
@ -368,7 +368,7 @@ public class Sequence implements Comparable<Sequence> {
|
||||||
*
|
*
|
||||||
* @param trace the trace to which the machine is bound
|
* @param trace the trace to which the machine is bound
|
||||||
* @param eventThread the thread for the first step, if it applies to the "last thread"
|
* @param eventThread the thread for the first step, if it applies to the "last thread"
|
||||||
* @param machine the machine to step
|
* @param machine the machine to step, or null to validate the sequence
|
||||||
* @param action the action to step each thread
|
* @param action the action to step each thread
|
||||||
* @param monitor a monitor for cancellation and progress reports
|
* @param monitor a monitor for cancellation and progress reports
|
||||||
* @return the last trace thread stepped during execution
|
* @return the last trace thread stepped during execution
|
||||||
|
@ -384,6 +384,22 @@ public class Sequence implements Comparable<Sequence> {
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate this sequence for the given trace
|
||||||
|
*
|
||||||
|
* @param trace the trace
|
||||||
|
* @param eventThread the thread for the first step, if it applies to the "last thread"
|
||||||
|
* @return the last trace thread that would be stepped by this sequence
|
||||||
|
*/
|
||||||
|
public TraceThread validate(Trace trace, TraceThread eventThread) {
|
||||||
|
try {
|
||||||
|
return execute(trace, eventThread, null, null, null);
|
||||||
|
}
|
||||||
|
catch (CancelledException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the key of the last thread stepped
|
* Get the key of the last thread stepped
|
||||||
*
|
*
|
||||||
|
|
|
@ -91,8 +91,8 @@ public interface Step extends Comparable<Step> {
|
||||||
TraceThread thread = isEventThread() ? eventThread : tm.getThread(getThreadKey());
|
TraceThread thread = isEventThread() ? eventThread : tm.getThread(getThreadKey());
|
||||||
if (thread == null) {
|
if (thread == null) {
|
||||||
if (isEventThread()) {
|
if (isEventThread()) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException("Thread must be given, e.g., 0:t1-3, " +
|
||||||
"Thread key -1 can only be used if last/event thread is given");
|
"since the last thread or snapshot event thread is not given.");
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Thread with key " + getThreadKey() + " does not exist in given trace");
|
"Thread with key " + getThreadKey() + " does not exist in given trace");
|
||||||
|
@ -160,6 +160,10 @@ public interface Step extends Comparable<Step> {
|
||||||
PcodeMachine<T> machine, Consumer<PcodeThread<T>> stepAction, TaskMonitor monitor)
|
PcodeMachine<T> machine, Consumer<PcodeThread<T>> stepAction, TaskMonitor monitor)
|
||||||
throws CancelledException {
|
throws CancelledException {
|
||||||
TraceThread thread = getThread(tm, eventThread);
|
TraceThread thread = getThread(tm, eventThread);
|
||||||
|
if (machine == null) {
|
||||||
|
// Just performing validation (specifically thread parts)
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
PcodeThread<T> emuThread = machine.getThread(thread.getPath(), true);
|
PcodeThread<T> emuThread = machine.getThread(thread.getPath(), true);
|
||||||
execute(emuThread, stepAction, monitor);
|
execute(emuThread, stepAction, monitor);
|
||||||
return thread;
|
return thread;
|
||||||
|
|
|
@ -344,6 +344,22 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
pSteps.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor);
|
pSteps.execute(trace, lastThread, machine, PcodeThread::stepPcodeOp, monitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate this schedule for the given trace
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This performs a dry run of the sequence on the given trace. If the schedule starts on the
|
||||||
|
* "last thread," it verifies the snapshot gives the event thread. It also checks that every
|
||||||
|
* thread key in the sequence exists in the trace.
|
||||||
|
*
|
||||||
|
* @param trace the trace against which to validate this schedule
|
||||||
|
*/
|
||||||
|
public void validate(Trace trace) {
|
||||||
|
TraceThread lastThread = getEventThread(trace);
|
||||||
|
lastThread = steps.validate(trace, lastThread);
|
||||||
|
lastThread = pSteps.validate(trace, lastThread);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Realize the machine state for this schedule using the given trace and pre-positioned machine
|
* Realize the machine state for this schedule using the given trace and pre-positioned machine
|
||||||
*
|
*
|
||||||
|
@ -385,13 +401,13 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
* This schedule is left unmodified. If it had any p-code steps, those steps are dropped in the
|
* This schedule is left unmodified. If it had any p-code steps, those steps are dropped in the
|
||||||
* resulting schedule.
|
* resulting schedule.
|
||||||
*
|
*
|
||||||
* @param thread the thread to step
|
* @param thread the thread to step, or null for the "last thread"
|
||||||
* @param tickCount the number of ticks to take the thread forward
|
* @param tickCount the number of ticks to take the thread forward
|
||||||
* @return the resulting schedule
|
* @return the resulting schedule
|
||||||
*/
|
*/
|
||||||
public TraceSchedule steppedForward(TraceThread thread, long tickCount) {
|
public TraceSchedule steppedForward(TraceThread thread, long tickCount) {
|
||||||
Sequence steps = this.steps.clone();
|
Sequence steps = this.steps.clone();
|
||||||
steps.advance(new TickStep(thread.getKey(), tickCount));
|
steps.advance(new TickStep(thread == null ? -1 : thread.getKey(), tickCount));
|
||||||
return new TraceSchedule(snap, steps, new Sequence());
|
return new TraceSchedule(snap, steps, new Sequence());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,13 +457,13 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
* Returns the equivalent of executing the schedule followed by stepping the given thread
|
* Returns the equivalent of executing the schedule followed by stepping the given thread
|
||||||
* {@code pTickCount} more p-code operations
|
* {@code pTickCount} more p-code operations
|
||||||
*
|
*
|
||||||
* @param thread the thread to step
|
* @param thread the thread to step, or null for the "last thread"
|
||||||
* @param pTickCount the number of p-code ticks to take the thread forward
|
* @param pTickCount the number of p-code ticks to take the thread forward
|
||||||
* @return the resulting schedule
|
* @return the resulting schedule
|
||||||
*/
|
*/
|
||||||
public TraceSchedule steppedPcodeForward(TraceThread thread, int pTickCount) {
|
public TraceSchedule steppedPcodeForward(TraceThread thread, int pTickCount) {
|
||||||
Sequence pTicks = this.pSteps.clone();
|
Sequence pTicks = this.pSteps.clone();
|
||||||
pTicks.advance(new TickStep(thread.getKey(), pTickCount));
|
pTicks.advance(new TickStep(thread == null ? -1 : thread.getKey(), pTickCount));
|
||||||
return new TraceSchedule(snap, steps.clone(), pTicks);
|
return new TraceSchedule(snap, steps.clone(), pTicks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,14 +75,16 @@ public interface AutoService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Wiring wireServicesConsumed(Plugin plugin, Object receiver) {
|
public static Wiring wireServicesConsumed(PluginTool tool, Object receiver) {
|
||||||
// TODO: Validate against PluginInfo?
|
|
||||||
|
|
||||||
AutoServiceListener<Object> listener = new AutoServiceListener<>(receiver);
|
AutoServiceListener<Object> listener = new AutoServiceListener<>(receiver);
|
||||||
PluginTool tool = plugin.getTool();
|
|
||||||
tool.addServiceListener(listener);
|
tool.addServiceListener(listener);
|
||||||
listener.notifyCurrentServices(tool);
|
listener.notifyCurrentServices(tool);
|
||||||
|
|
||||||
return new WiringImpl(listener);
|
return new WiringImpl(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Wiring wireServicesConsumed(Plugin plugin, Object receiver) {
|
||||||
|
// TODO: Validate against PluginInfo?
|
||||||
|
return wireServicesConsumed(plugin.getTool(), receiver);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,14 +41,14 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
protected static final String OPT2_NAME = "Test Option 2";
|
protected static final String OPT2_NAME = "Test Option 2";
|
||||||
protected static final String OPT2_DEFAULT = "Default value";
|
protected static final String OPT2_DEFAULT = "Default value";
|
||||||
protected static final String OPT2_DESC = "Another test option";
|
protected static final String OPT2_DESC = "Another test option";
|
||||||
|
protected static final String OPT2_NEW_VALUE = "A new value";
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class",//
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsPlugin extends Plugin {
|
public static class AnnotatedWithOptionsPlugin extends Plugin {
|
||||||
@AutoOptionDefined(name = OPT1_NAME, description = OPT1_DESC)
|
@AutoOptionDefined(name = OPT1_NAME, description = OPT1_DESC)
|
||||||
private int myIntOption = OPT1_DEFAULT;
|
private int myIntOption = OPT1_DEFAULT;
|
||||||
|
@ -65,13 +65,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsNoParamPlugin extends AnnotatedWithOptionsPlugin {
|
public static class AnnotatedWithOptionsNoParamPlugin extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateNoParamCount;
|
protected int updateNoParamCount;
|
||||||
|
|
||||||
|
@ -85,13 +84,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsNewOnlyParamDefaultPlugin
|
public static class AnnotatedWithOptionsNewOnlyParamDefaultPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateNewOnlyParamDefaultNew;
|
protected int updateNewOnlyParamDefaultNew;
|
||||||
|
@ -107,13 +105,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsNewOnlyParamAnnotatedPlugin
|
public static class AnnotatedWithOptionsNewOnlyParamAnnotatedPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateNewOnlyParamAnnotatedNew;
|
protected int updateNewOnlyParamAnnotatedNew;
|
||||||
|
@ -128,13 +125,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsOldOnlyParamAnnotatedPlugin
|
public static class AnnotatedWithOptionsOldOnlyParamAnnotatedPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateOldOnlyParamAnnotatedOld;
|
protected int updateOldOnlyParamAnnotatedOld;
|
||||||
|
@ -149,13 +145,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsNewOldParamDefaultPlugin
|
public static class AnnotatedWithOptionsNewOldParamDefaultPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateNewOldParamDefaultNew;
|
protected int updateNewOldParamDefaultNew;
|
||||||
|
@ -172,13 +167,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsNewOldParamNewAnnotPlugin
|
public static class AnnotatedWithOptionsNewOldParamNewAnnotPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateNewOldParamNewAnnotNew;
|
protected int updateNewOldParamNewAnnotNew;
|
||||||
|
@ -195,13 +189,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsNewOldParamOldAnnotPlugin
|
public static class AnnotatedWithOptionsNewOldParamOldAnnotPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateNewOldParamOldAnnotNew;
|
protected int updateNewOldParamOldAnnotNew;
|
||||||
|
@ -218,13 +211,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsNewOldParamNewOldAnnotPlugin
|
public static class AnnotatedWithOptionsNewOldParamNewOldAnnotPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateNewOldParamNewOldAnnotNew;
|
protected int updateNewOldParamNewOldAnnotNew;
|
||||||
|
@ -242,13 +234,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsOldNewParamNewAnnotPlugin
|
public static class AnnotatedWithOptionsOldNewParamNewAnnotPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateOldNewParamNewAnnotNew;
|
protected int updateOldNewParamNewAnnotNew;
|
||||||
|
@ -265,13 +256,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsOldNewParamOldAnnotPlugin
|
public static class AnnotatedWithOptionsOldNewParamOldAnnotPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateOldNewParamOldAnnotNew;
|
protected int updateOldNewParamOldAnnotNew;
|
||||||
|
@ -288,13 +278,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "A plugin class replete with auto option annotations", //
|
description = "A plugin class replete with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "An annotated plugin class", //
|
shortDescription = "An annotated plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedWithOptionsOldNewParamOldNewAnnotPlugin
|
public static class AnnotatedWithOptionsOldNewParamOldNewAnnotPlugin
|
||||||
extends AnnotatedWithOptionsPlugin {
|
extends AnnotatedWithOptionsPlugin {
|
||||||
protected int updateOldNewParamOldNewAnnotNew;
|
protected int updateOldNewParamOldNewAnnotNew;
|
||||||
|
@ -312,13 +301,12 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginInfo(//
|
@PluginInfo(
|
||||||
category = "Testing", //
|
category = "Testing",
|
||||||
description = "Consumer-only plugin class with auto option annotations", //
|
description = "Consumer-only plugin class with auto option annotations",
|
||||||
packageName = MiscellaneousPluginPackage.NAME, //
|
packageName = MiscellaneousPluginPackage.NAME,
|
||||||
shortDescription = "A consumer-only plugin class", //
|
shortDescription = "A consumer-only plugin class",
|
||||||
status = PluginStatus.HIDDEN //
|
status = PluginStatus.HIDDEN)
|
||||||
)
|
|
||||||
public static class AnnotatedConsumerOnlyPlugin extends Plugin {
|
public static class AnnotatedConsumerOnlyPlugin extends Plugin {
|
||||||
@AutoOptionConsumed(name = OPT1_NAME)
|
@AutoOptionConsumed(name = OPT1_NAME)
|
||||||
private int othersIntOption;
|
private int othersIntOption;
|
||||||
|
@ -387,7 +375,18 @@ public class AutoOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
assertEquals(6, plugin.myIntOption);
|
assertEquals(6, plugin.myIntOption);
|
||||||
options.setInt(OPT1_NAME, OPT1_NEW_VALUE);
|
options.setInt(OPT1_NAME, OPT1_NEW_VALUE);
|
||||||
|
|
||||||
assertEquals(10, plugin.myIntOption);
|
assertEquals(OPT1_NEW_VALUE, plugin.myIntOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOptionsUpdatedExplicitCategory() throws PluginException {
|
||||||
|
AnnotatedWithOptionsPlugin plugin = addPlugin(tool, AnnotatedWithOptionsPlugin.class);
|
||||||
|
|
||||||
|
ToolOptions options = tool.getOptions(OPT2_CATEGORY);
|
||||||
|
assertEquals(1, options.getOptionNames().size());
|
||||||
|
options.setString(OPT2_NAME, OPT2_NEW_VALUE);
|
||||||
|
|
||||||
|
assertEquals(OPT2_NEW_VALUE, plugin.myStringOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -705,6 +705,16 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension point to specify titles when dual panels are active
|
||||||
|
*
|
||||||
|
* @param panelProgram the program assigned to the panel whose title is requested
|
||||||
|
* @return the title of the panel for the given program
|
||||||
|
*/
|
||||||
|
protected String computePanelTitle(Program panelProgram) {
|
||||||
|
return panelProgram.getDomainFile().toString();
|
||||||
|
}
|
||||||
|
|
||||||
public void setPanel(ListingPanel lp) {
|
public void setPanel(ListingPanel lp) {
|
||||||
Program myProgram = listingPanel.getListingModel().getProgram();
|
Program myProgram = listingPanel.getListingModel().getProgram();
|
||||||
Program otherProgram = lp.getListingModel().getProgram();
|
Program otherProgram = lp.getListingModel().getProgram();
|
||||||
|
@ -712,10 +722,10 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter
|
||||||
String otherName = myName;
|
String otherName = myName;
|
||||||
|
|
||||||
if (myProgram != null) {
|
if (myProgram != null) {
|
||||||
myName = myProgram.getDomainFile().toString();
|
myName = computePanelTitle(myProgram);
|
||||||
}
|
}
|
||||||
if (otherProgram != null) {
|
if (otherProgram != null) {
|
||||||
otherName = otherProgram.getDomainFile().toString();
|
otherName = computePanelTitle(otherProgram);
|
||||||
}
|
}
|
||||||
if (otherPanel != null) {
|
if (otherPanel != null) {
|
||||||
removeHoverServices(otherPanel);
|
removeHoverServices(otherPanel);
|
||||||
|
|
|
@ -43,12 +43,12 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
private InputDialogListener listener;
|
private InputDialogListener listener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a provider for a generic input dialog with the specified title, a text field,
|
* Creates a provider for a generic input dialog with the specified title, a text field, labeled
|
||||||
* labeled by the specified label. The user should check the value of
|
* by the specified label. The user should check the value of "isCanceled()" to know whether or
|
||||||
* "isCanceled()" to know whether or not the user canceled the operation.
|
* not the user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get
|
||||||
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
|
* the value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
|
||||||
* entered by the user. Use the tool's "showDialog()" to display the dialog.
|
|
||||||
* <P>
|
* <P>
|
||||||
|
*
|
||||||
* @param dialogTitle used as the name of the dialog's title bar
|
* @param dialogTitle used as the name of the dialog's title bar
|
||||||
* @param label value to use for the label of the text field
|
* @param label value to use for the label of the text field
|
||||||
*/
|
*/
|
||||||
|
@ -57,12 +57,12 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a generic input dialog with the specified title, a text field,
|
* Creates a generic input dialog with the specified title, a text field, labeled by the
|
||||||
* labeled by the specified label. The user should check the value of
|
* specified label. The user should check the value of "isCanceled()" to know whether or not the
|
||||||
* "isCanceled()" to know whether or not the user canceled the operation.
|
* user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
|
||||||
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
|
* value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
|
||||||
* entered by the user. Use the tool's "showDialog()" to display the dialog.
|
|
||||||
* <P>
|
* <P>
|
||||||
|
*
|
||||||
* @param dialogTitle used as the name of the dialog's title bar
|
* @param dialogTitle used as the name of the dialog's title bar
|
||||||
* @param label value to use for the label of the text field
|
* @param label value to use for the label of the text field
|
||||||
* @param initialValue initial value to use for the text field
|
* @param initialValue initial value to use for the text field
|
||||||
|
@ -72,12 +72,12 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a generic input dialog with the specified title, a text field,
|
* Creates a generic input dialog with the specified title, a text field, labeled by the
|
||||||
* labeled by the specified label. The user should check the value of
|
* specified label. The user should check the value of "isCanceled()" to know whether or not the
|
||||||
* "isCanceled()" to know whether or not the user canceled the operation.
|
* user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
|
||||||
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
|
* value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
|
||||||
* entered by the user. Use the tool's "showDialog()" to display the dialog.
|
|
||||||
* <P>
|
* <P>
|
||||||
|
*
|
||||||
* @param dialogTitle used as the name of the dialog's title bar
|
* @param dialogTitle used as the name of the dialog's title bar
|
||||||
* @param label value to use for the label of the text field
|
* @param label value to use for the label of the text field
|
||||||
* @param initialValue initial value to use for the text field
|
* @param initialValue initial value to use for the text field
|
||||||
|
@ -89,12 +89,12 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a generic input dialog with the specified title, a text field,
|
* Creates a generic input dialog with the specified title, a text field, labeled by the
|
||||||
* labeled by the specified label. The user should check the value of
|
* specified label. The user should check the value of "isCanceled()" to know whether or not the
|
||||||
* "isCanceled()" to know whether or not the user canceled the operation.
|
* user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
|
||||||
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
|
* value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
|
||||||
* entered by the user. Use the tool's "showDialog()" to display the dialog.
|
|
||||||
* <P>
|
* <P>
|
||||||
|
*
|
||||||
* @param dialogTitle used as the name of the dialog's title bar
|
* @param dialogTitle used as the name of the dialog's title bar
|
||||||
* @param label value to use for the label of the text field
|
* @param label value to use for the label of the text field
|
||||||
* @param initialValue initial value to use for the text field
|
* @param initialValue initial value to use for the text field
|
||||||
|
@ -105,12 +105,12 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a generic input dialog with the specified title, a text field,
|
* Creates a generic input dialog with the specified title, a text field, labeled by the
|
||||||
* labeled by the specified label. The user should check the value of
|
* specified label. The user should check the value of "isCanceled()" to know whether or not the
|
||||||
* "isCanceled()" to know whether or not the user canceled the operation.
|
* user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
|
||||||
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
|
* value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
|
||||||
* entered by the user. Use the tool's "showDialog()" to display the dialog.
|
|
||||||
* <P>
|
* <P>
|
||||||
|
*
|
||||||
* @param dialogTitle used as the name of the dialog's title bar
|
* @param dialogTitle used as the name of the dialog's title bar
|
||||||
* @param labels values to use for the labels of the text fields
|
* @param labels values to use for the labels of the text fields
|
||||||
* @param initialValues initial values to use for the text fields
|
* @param initialValues initial values to use for the text fields
|
||||||
|
@ -120,12 +120,12 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a generic input dialog with the specified title, a text field,
|
* Creates a generic input dialog with the specified title, a text field, labeled by the
|
||||||
* labeled by the specified label. The user should check the value of
|
* specified label. The user should check the value of "isCanceled()" to know whether or not the
|
||||||
* "isCanceled()" to know whether or not the user canceled the operation.
|
* user canceled the operation. Otherwise, use the "getValue()" or "getValues()" to get the
|
||||||
* Otherwise, use the "getValue()" or "getValues()" to get the value(s)
|
* value(s) entered by the user. Use the tool's "showDialog()" to display the dialog.
|
||||||
* entered by the user. Use the tool's "showDialog()" to display the dialog.
|
|
||||||
* <P>
|
* <P>
|
||||||
|
*
|
||||||
* @param dialogTitle used as the name of the dialog's title bar
|
* @param dialogTitle used as the name of the dialog's title bar
|
||||||
* @param labels values to use for the labels of the text fields
|
* @param labels values to use for the labels of the text fields
|
||||||
* @param initialValues initial values to use for the text fields
|
* @param initialValues initial values to use for the text fields
|
||||||
|
@ -194,6 +194,7 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
textFields = new MyTextField[inputLabels.length];
|
textFields = new MyTextField[inputLabels.length];
|
||||||
for (int i = 0; i < inputValues.length; i++) {
|
for (int i = 0; i < inputValues.length; i++) {
|
||||||
textFields[i] = new MyTextField(initialValues[i]);
|
textFields[i] = new MyTextField(initialValues[i]);
|
||||||
|
inputValues[i] = initialValues[i];
|
||||||
textFields[i].addKeyListener(keyListener);
|
textFields[i].addKeyListener(keyListener);
|
||||||
textFields[i].setName("input.dialog.text.field." + i);
|
textFields[i].setName("input.dialog.text.field." + i);
|
||||||
panel.add(new GLabel(inputLabels[i], SwingConstants.RIGHT));
|
panel.add(new GLabel(inputLabels[i], SwingConstants.RIGHT));
|
||||||
|
@ -221,11 +222,16 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
@Override
|
@Override
|
||||||
protected void cancelCallback() {
|
protected void cancelCallback() {
|
||||||
isCanceled = true;
|
isCanceled = true;
|
||||||
|
for (int v = 0; v < inputValues.length; v++) {
|
||||||
|
inputValues[v] = null;
|
||||||
|
}
|
||||||
|
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if this dialog is cancelled
|
* Returns if this dialog is cancelled
|
||||||
|
*
|
||||||
* @return true if cancelled
|
* @return true if cancelled
|
||||||
*/
|
*/
|
||||||
public boolean isCanceled() {
|
public boolean isCanceled() {
|
||||||
|
@ -234,6 +240,7 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the value of the first (and maybe only) text field
|
* Return the value of the first (and maybe only) text field
|
||||||
|
*
|
||||||
* @return the text field value
|
* @return the text field value
|
||||||
*/
|
*/
|
||||||
public String getValue() {
|
public String getValue() {
|
||||||
|
@ -242,6 +249,7 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the text of the primary text field
|
* Sets the text of the primary text field
|
||||||
|
*
|
||||||
* @param text the text
|
* @param text the text
|
||||||
*/
|
*/
|
||||||
public void setValue(String text) {
|
public void setValue(String text) {
|
||||||
|
@ -250,15 +258,18 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the text of the text field at the given index
|
* Sets the text of the text field at the given index
|
||||||
|
*
|
||||||
* @param text the text
|
* @param text the text
|
||||||
* @param index the index of the text field
|
* @param index the index of the text field
|
||||||
*/
|
*/
|
||||||
public void setValue(String text, int index) {
|
public void setValue(String text, int index) {
|
||||||
textFields[index].setText(text);
|
textFields[index].setText(text);
|
||||||
|
inputValues[index] = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the values for all the text field(s)
|
* Return the values for all the text field(s)
|
||||||
|
*
|
||||||
* @return the text field values
|
* @return the text field values
|
||||||
*/
|
*/
|
||||||
public String[] getValues() {
|
public String[] getValues() {
|
||||||
|
@ -285,7 +296,8 @@ public class InputDialog extends DialogComponentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see javax.swing.text.Document#insertString(int, java.lang.String, javax.swing.text.AttributeSet)
|
* @see javax.swing.text.Document#insertString(int, java.lang.String,
|
||||||
|
* javax.swing.text.AttributeSet)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void insertString(int offs, String str, AttributeSet a)
|
public void insertString(int offs, String str, AttributeSet a)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue