GP-1584: Unify state-editing story across Debugger UI.

This commit is contained in:
Dan 2022-04-15 12:17:20 -04:00
parent 067fd41b62
commit 12493ab734
75 changed files with 2873 additions and 818 deletions

View File

@ -105,6 +105,11 @@ src/main/help/help/topics/DebuggerRegistersPlugin/images/DebuggerRegistersPlugin
src/main/help/help/topics/DebuggerRegistersPlugin/images/select-registers.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerStackPlugin/DebuggerStackPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerStackPlugin/images/DebuggerStackPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-disabled.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-emulator.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-target.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-trace.png||Tango Icons - Public Domain||||END|
src/main/help/help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html||GHIDRA||||END|
src/main/help/help/topics/DebuggerStaticMappingPlugin/images/DebuggerStaticMappingPlugin.png||GHIDRA||||END|
src/main/help/help/topics/DebuggerTargetsPlugin/DebuggerTargetsPlugin.html||GHIDRA||||END|
@ -192,6 +197,10 @@ src/main/resources/images/table_relationship.png||FAMFAMFAM Icons - CC 2.5||||EN
src/main/resources/images/text-xml.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/resources/images/thread.png||GHIDRA||||END|
src/main/resources/images/time.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/write-disabled.png||GHIDRA||||END|
src/main/resources/images/write-emulator.png||GHIDRA||||END|
src/main/resources/images/write-target.png||GHIDRA||||END|
src/main/resources/images/write-trace.png||Tango Icons - Public Domain||||END|
src/main/svg/attach.svg||GHIDRA||||END|
src/main/svg/blank.svg||GHIDRA||||END|
src/main/svg/breakpoint-clear.svg||GHIDRA||||END|
@ -217,6 +226,7 @@ src/main/svg/kill.svg||GHIDRA||||END|
src/main/svg/launch.svg||GHIDRA||||END|
src/main/svg/memory.svg||GHIDRA||||END|
src/main/svg/object-terminated.svg||GHIDRA||||END|
src/main/svg/pencil.png||GHIDRA||||END|
src/main/svg/process.svg||GHIDRA||||END|
src/main/svg/recording.svg||GHIDRA||||END|
src/main/svg/register-marker.svg||GHIDRA||||END|
@ -230,3 +240,7 @@ src/main/svg/stepout.svg||GHIDRA||||END|
src/main/svg/stepover.svg||GHIDRA||||END|
src/main/svg/stop.svg||GHIDRA||||END|
src/main/svg/thread.svg||GHIDRA||||END|
src/main/svg/write-disabled.svg||GHIDRA||||END|
src/main/svg/write-emulator.svg||GHIDRA||||END|
src/main/svg/write-target.svg||GHIDRA||||END|
src/main/svg/write-trace.svg||Tango Icons - Public Domain||||END|

View File

@ -149,6 +149,10 @@
sortgroup="n"
target="help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html" />
<tocdef id="DebuggerStateEditingPlugin" text="Editing Machine State"
sortgroup="n1"
target="help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html" />
<tocdef id="DebuggerMemviewPlugin" text="Memview Plot"
sortgroup="o"
target="help/topics/DebuggerMemviewPlugin/DebuggerMemviewPlugin.html" />

View File

@ -23,17 +23,17 @@
</TABLE>
<P><A name="Toggle_Header"></A>The dynamic listing is analogous to Ghidra's listing for static
analysis, but in the dynamic context. That is, it displays memory contents from a target. More
analysis, but in the dynamic context. It displays annotated memory contents from a target. More
precisely, it displays recorded memory contents in a trace. In most use cases, that trace is
"at the present," meaning it is the most recent memory from a live target. Multiple listings
can be displayed simultaneously, using the same pattern as many other Ghidra windows. The
"primary" listing is always displayed and generally tracks with the rest of the tool. Any
listing can be "snapshotted," i.e., duplicated. This is where dynamic listings differ from
static listings. Static snapshots remain in place; they do not automatically navigate. Dynamic
snapshots can still be configured to navigate, following the rest of the tool. A common use is
to configure a "snapshot" to follow the stack pointer. Still, you can disable a listing's
automatic navigation, so it behaves like a true snapshot. A current limitation is that you
cannot use snapshots to display different points in time for the same trace.</P>
listing can be "snapshotted," i.e., cloned. This is where dynamic listings differ from static
listings. Static clones remain in place; they do not automatically navigate. Dynamic clones can
still be configured to navigate, following the rest of the tool. A common use is to configure a
clone to follow the stack pointer. Still, you can disable a listing's automatic navigation, so
it behaves like a true clone. A current limitation is that you cannot use clones to display
different points in time for the same trace.</P>
<P>Where applicable, the static listing's location is synchronized to the dynamic listing, and
vice versa. The dynamic listing permits most of the same mark-up as the static listing. Any
@ -58,6 +58,12 @@
failed, the first address in the failed range is displayed with a pink background. Otherwise,
up-to-date contents are displayed with the default background color.</P>
<P>The dynamic listing supports editing memory via the <A href=
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">Machine State Editing
Plugin and Service</A>. Such edits are performed as usual: Via the <A href=
"help/topics/AssemblerPlugin/Assembler.htm">Patch</A> actions, or by pasting byte strings.
These edits may be directed toward a live target, the trace, or the emulator.</P>
<H2>Actions</H2>
<P>The listing provides a variety of actions, some for managing and configuring listings, and
@ -67,7 +73,7 @@
<P>This action is always available in the <SPAN class="menu">Window &rarr; Debugger</SPAN>
menu. It creates a new listing window with the same configuration as the primary dynamic
listing. It is equivalent to "snapshotting" the primary dynamic listing.</P>
listing. It is equivalent to cloning the primary dynamic listing.</P>
<H3><A name="export_view"></A>Export Trace View</H3>
@ -78,11 +84,11 @@
<H3><A name="follows_thread"></A>Follows Selected Thread</H3>
<P>This action is only available on snapshot dynamic listings. The primary listing always
follows the tool's current thread. Disabling this toggle causes the snapshot to remain on its
own current thread rather than following the tool's. The current thread is used when computing
a location to navigate to automatically. It is only applicable when "Track Location" is set to
something other than "Do Not Track."</P>
<P>This action is only available on cloned dynamic listings. The primary listing always follows
the tool's current thread. Disabling this toggle causes the clone to remain on its own current
thread rather than following the tool's. The current thread is used when computing a location
to navigate to automatically. It is only applicable when "Track Location" is set to something
other than "Do Not Track."</P>
<H3><A name="track_location"></A>Track Location</H3>

View File

@ -23,18 +23,17 @@
</TABLE>
<P><A name="Toggle_Header"></A>The memory, or dynamic bytes, window is analogous to Ghidra's
bytes window for static analysis, but in the dynamic context. That is, it displays memory
contents from a target. More precisely, it displays recorded memory contents in a trace. In
most use cases, that trace is "at the present," meaning it is the most recent memory from a
live target. Multiple memory windows can be displayed simultaneously, using the same pattern as
many other Ghidra windows. The "primary" window is always displayed and generally tracks with
the rest of the tool. Any window can be "snapshotted," i.e., duplicated. This is where memory
windows differ from static bytes windows. Static snapshots remain in place; they do not
automatically navigate. Dynamic snapshots can still be configured to navigate, following the
rest of the tool. A common use is to configure a "snapshot" to follow the stack pointer. Still,
you can disable a window's automatic navigation, so it behaves like a true snapshot. A current
limitation is that you cannot use snapshots to display different points in time for the same
trace.</P>
bytes window for static analysis, but in the dynamic context. It displays memory contents from
a target. More precisely, it displays recorded memory contents in a trace. In most use cases,
that trace is "at the present," meaning it is the most recent memory from a live target.
Multiple memory windows can be displayed simultaneously, using the same pattern as many other
Ghidra windows. The "primary" window is always displayed and generally tracks with the rest of
the tool. Any window can be "snapshotted," i.e., cloned. This is where memory windows differ
from static bytes windows. Static clones remain in place; they do not automatically navigate.
Dynamic clones can still be configured to navigate, following the rest of the tool. A common
use is to configure a clone to follow the stack pointer. Still, you can disable a window's
automatic navigation, so it behaves like a true clone. A current limitation is that you cannot
use clones to display different points in time for the same trace.</P>
<P>Because not all memory is recorded, some background coloring is used to indicate the state
of attempted memory reads. Regardless of state, the most-recent contents, as recorded in the
@ -45,11 +44,13 @@
failed, the first address in the failed range is displayed with a pink background. Otherwise,
up-to-date contents are displayed with the default background color.</P>
<P>NOTE: Modification of trace byte contents is a work in progress. At the moment, the feature
is simply disabled. In general, modifications to bytes when the window is "at the present" are
directed to the target. Otherwise, they simply modify the historical or emulated values stored
in the trace. A modification at a given time remains in effect, but stale, for all future times
up to but excluding the time of the next recorded value.</P>
<P>The dynamic listing supports editing memory via the <A href=
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">Machine State Editing
Plugin and Service</A>. Such edits are performed as usual: Toggling edits and typing into the
editor, or by pasting byte strings. These edits may be directed toward a live target, the
trace, or the emulator. <B>NOTE:</B> Please be wary of hand-typing large edits into the
emulator, since every keystroke may produce a unique scratch snapshot. It is better to paste
such edits instead.</P>
<H2>Actions</H2>
@ -60,24 +61,24 @@
<P>This action is always available in the <SPAN class="menu">Window &rarr; Debugger</SPAN>
menu. It creates a new memory window with the same configuration as the primary memory window.
It is equivalent to "snapshotting" the primary memory window.</P>
It is equivalent to cloning the primary memory window.</P>
<H3><A name="follows_thread"></A>Follows Selected Thread</H3>
<P>This action is only available on snapshot memory windows. The primary window always follows
the tool's current thread. Disabling this toggle causes the snapshot to remain on its own
current thread rather than following the tool's. The current thread is used when computing a
location to navigate to automatically. It is only applicable when "Track Location" is set to
something other than "Do Not Track."</P>
<P>This action is only available on cloned memory windows. The primary window always follows
the tool's current thread. Disabling this toggle causes the clone to remain on its own current
thread rather than following the tool's. The current thread is used when computing a location
to navigate to automatically. It is only applicable when "Track Location" is set to something
other than "Do Not Track."</P>
<H3><A name="track_location"></A>Track Location</H3>
<P>This action is always available on all memory windows. It configures automatic navigation
for the window. When location tracking is enabled, the window is automatically navigated to an
address computed from the trace's or target's machine state. The address is also highlighted in
green. The computed address is affected by the tool's current "coordinates," that is the
selected thread, frame, and point in time. The options are pluggable, but currently consist
of:</P>
address computed from the trace's or target's machine state. <B>NOTE:</B> This feature is
disabled when the edit toggle is on. The address is also highlighted in green. The computed
address is affected by the tool's current "coordinates," that is the selected thread, frame,
and point in time. The options are pluggable, but currently consist of:</P>
<UL>
<LI>Do Not Track - disables automatic navigation.</LI>
@ -143,8 +144,10 @@
<H3><A name="Enable_Disable_Byteviewer_Editing"></A>Toggle Editing</H3>
<P>This action does the same as it does for the static context. NOTE: This feature is currently
disabled, as it is a work in progress.</P>
<P>This action does the same as it does for the static context. Edits may be rejected if the
trace's editing mode is set to Read-Only in the tool. <B>NOTE:</B> This toggle also disables
automatic navigation in order to prevent the cursor from being moved unexpectedly while typing
edits.</P>
<H2><A name="colors"></A>Tool Options: Colors</H2>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -46,9 +46,10 @@
<LI>Value - the value of the register as recorded in the trace. When the value refers to a
valid memory offset, right-clicking the row allows the user to navigate to that offset in a
selected memory space. This field is user modifiable when the <B>Enable Edits</B> toggle is
on, and the register is not part of <CODE>contextreg</CODE>. Changes are sent to the target
if the trace is live and "at the present." Otherwise, the change is materialized via
emulation. Values changed by the last event are displayed in <FONT color=
on, and the <A href=
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">editing service</A>
reports the register as modifiable. Edits may be directed toward a live target, the trace, or
the emulator. Values changed by the last event are displayed in <FONT color=
"red">red</FONT>.</LI>
<LI>Type - the type of the register as marked up in the trace. There is generally no default
@ -92,21 +93,17 @@
<H3><A name="enable_edits"></A>Enable Edits</H3>
<P>This toggle is a write protector for target machine state. To modify register values, this
toggle must be enabled. Editing a value when the trace is live and "at the present" will cause
the value to be modified on the target. Editing emulated values is permitted, but ity has no
effect on the target. Editing historical values is not permitted. All edits to non-live trace
values are performed in emulation. Specifically, it appends a patch command to the current
emulation schedule. This keeps trace history in tact, and it allows patched emulated states to
be annotated and recalled later, since they are stored in the trace's scratch space. Note that
only the raw "Value" column can be edited directory. The "Repr" column cannot be edited,
yet.</P>
<P>This toggle is a write protector for machine state. To modify register values, this toggle
must be enabled. Edits are directed according the to <A href=
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">State Editing Plugin
and Service</A>. <B>Note:</B> Only the raw "Value" column can be edited directly. The "Repr"
column cannot be edited, yet.</P>
<H3><A name="snapshot_window"></A>Snapshot Window</H3>
<P>This button is analogous to the "snapshot" action of other Ghidra windows. It generates a
copy of this window. The copy will no longer follow the current thread, but it will follow the
current time.</P>
clone of this window. The clone will no longer follow the current thread, but it will follow
the current time.</P>
<H2><A name="colors"></A>Tool Options: Colors</H2>

View File

@ -0,0 +1,87 @@
<!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: Editing Machine State</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: Editing Machine State</H1>
<P>This plugin controls the modification of machine state. It provides a multi-state action in
the main toolbar for controlling the editing mode of each trace and associated target. It is
backed by a corresponding service plugin which manages the editing modes and dispatches
machine-state edits accordingly. Scripts can also use the service to perform machine-state
edits that behave consistently with the rest of the UI.</P>
<H2>Actions</H2>
<P>The plugin provides a single action:</P>
<H3><A name="edit_mode"></A>Edit Mode</H3>
<P>This action is available whenever a trace is active. It changes the machine-state editing
mode for the active trace and, if applicable, its associated target. The possible modes
are:</P>
<UL>
<LI><IMG alt="" src="images/write-disabled.png"> Read-Only - Rejects all edits.</LI>
<LI><IMG alt="" src="images/write-target.png"> Write Target - The default, this directs edits
to the live target. To accept changes, the UI must be "live and at the present." If the trace
has no associated target, i.e., it is dead; or if the current view is in the past or includes
any steps of emulation, i.e., it is not at the present; then edits are rejected.</LI>
<LI><IMG alt="" src="images/write-trace.png"> Write Trace - Directs all edits to the trace.
Edits are generally always accepted, and they are applied directly to the trace.</LI>
<LI><IMG alt="" src="images/write-emulator.png"> Write Emulator - Materializes edits via
emulation. Instead of editing the trace, this generates a patch and appends it to the current
coordinates' emulation schedule. See the <A href=
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html#goto_time">Go To Time</A>
action. Essentially, the change is applied in the trace's scratch space, leaving the original
recording in tact. Due to implementation details, a thread must be selected, even if edits
only affect memory. Additionally, the disassembly context register cannot be modified.</LI>
</UL>
<H2>Recommendations</H2>
<P>Write Target is the default mode, because in most cases, this is the desired behavior. When
the target dies, Write Target essentially means Read Only. For the most part, modifying the
recording itself is discouraged. There are few reasons, perhaps including 1) Hand-generating an
experimental trace; 2) Generating a trace from a script, e.g., importing an event log,
recording an emulated target; 3) Patching in state missed by the original recording. More often
than not, when experimenting with the emulator, the mode should be Write Emulator. Using Write
Trace with the emulator will almost certainly result in issues with cache staleness.</P>
<P>Some background and an example: To display emulated machine state, the emulator executes a
specified schedule and writes the resulting state into the trace's scratch space, keyed by the
schedule. Suppose you emulate a step forward but then realize that some state was incorrect, or
you just want to try the same step with an alternative initial state. If you step back then
edit the trace and then repeat the step forward, the UI will simply recall the cached snapshot,
rendering the state change ineffective. Instead, use Write Emulator to edit the state and then
step forward. Because the patch is encoded in the emulation schedule, the UI will not recall
the stale snapshot. Instead the emulator will execute the new schedule and generate a new
scratch snapshot. Furthermore, the original trace recording remains in tact, while the modified
state is stored in scratch space with the schedule explaining where it came from. Still better,
the first scratch snapshot (for the step taken without first modifying the state) also remains
in tact, and the two can be <A href=
"help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html">compared</A>.</P>
<P>To prevent edits to a live target, use the Read-Only mode. This will prevent most accidental
edits. This is only effective against edits from the UI, and only when all plugins and scripts
use the state editing service. This cannot prevent a component from accessing Ghidra's Target
API to modify a target. Nor can it prevent edits via the connected debugger's command-line
interpreter. The following components all use the service: <A href=
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Dynamic Listing</A>, <A href=
"help/topics/DebuggerMemoryBytesPlugin/DebuggerMemoryBytesPlugin.html">Memory (Dynamic
Bytes)</A>, <A href=
"help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A>, and <A href=
"help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html">Watches</A>.</P>
</BODY>
</HTML>

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

View File

@ -48,7 +48,9 @@
snapshot. This always applies to "scratch" snapshots produced by emulation, but may also
apply if the stepping schedule between recorded events is somehow known. Typically, it is
just the number of steps of the source snapshot's event thread; however, the notation does
allow other threads to be stepped, too.</LI>
allow other threads to be stepped, too. See the <A href=
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html#goto_time">Go To Time</A>
action.</LI>
<LI>Description - a user-modifiable description of the snapshot or event. This defaults to
the debugger's description of the event.</LI>

View File

@ -25,27 +25,33 @@
<P>Watches refer to expressions which are evaluated each pause in order to monitor the value of
variables in the target machine state. The watch variables are expressed in Sleigh and
evaluated in the current thread's context at the current point in time. If the current trace is
live and at the present, then the necessary target state is queried and recorded. The watch can
be assigned a data type so that the raw data is rendered in a meaningful way. When applicable,
that data type can optionally be applied to the trace database. Some metadata about the watch
is also given, e.g., the address of the value.</P>
live and at the present, then the necessary target state is retrieved and recorded. The watch
can be assigned a data type so that the raw data is rendered in a meaningful way. When
applicable, that data type can optionally be applied to the trace database. Some metadata about
the watch is also given, e.g., the address of the value.</P>
<H2>Examples</H2>
<P>For those less familiar with Sleigh, here are some example expressions:</P>
<UL>
<LI><CODE>*:4 (RSP+8)</CODE>: Display 4 bytes of [ram] starting 8 bytes after the offset
given by register RSP.</LI>
<LI><CODE>RSP</CODE>: Display the value of register RSP.</LI>
<LI><CODE>*:4 0x7fff0004:8</CODE>: Display 4 bytes starting at ram:7fff0004. The extraneous,
but required, size specifier on constant derefs is a known issue. Just use the target's
pointer size in bytes.</LI>
<LI><CODE>*:4 0x7fff0004:8</CODE>: Display 4 bytes starting at ram:7fff0004, e.g., to read
the <CODE>int</CODE> at address 7fff0004. The extraneous but required size specifier on the
constant 0x7fff0004 is a known issue. Just use the target's pointer size in bytes &mdash; 8
in the example.</LI>
<LI><CODE>*:8 RSP</CODE>: Display 8 bytes of [ram] starting at the offset given by register
RSP.</LI>
RSP, e.g., to read a <CODE>long</CODE> on the stack.</LI>
<LI><CODE>RSP</CODE>: Display the value of register RSP.</LI>
<LI><CODE>*:4 (RSP+8)</CODE>: Display 4 bytes of [ram] starting 8 bytes after the offset
given by register RSP, e.g., to read a <CODE>int</CODE> on the stack.</LI>
<LI><CODE>*:4 ((*:8 RSP)+4)</CODE>: Display 4 bytes of [ram] starting 4 bytes after the
offset given by reading 8 bytes of [ram] starting at the offset given by register RSP. This
might be used to read the second <CODE>int</CODE> of an array whose base pointer is on the
stack.</LI>
</UL>
<H2>Table Columns</H2>
@ -62,9 +68,11 @@
<LI>Value - the raw bytes of the watched buffer. If the expression is a register, then this
is its hexadecimal value. This field is user modifiable when the <B>Enable Edits</B> toggle
is on. Changes are sent to the target if the trace is live and "at the present." Otherwise,
the change is materialized via emulation. If the value has changed since the last navigation
event, this cell is rendered in <FONT color="red">red</FONT>.</LI>
is on, and the <A href=
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">editing service</A>
reports the register as modifiable. Edits may be directed toward a live target, the trace, or
the emulator. If the value has changed since the last navigation event, this cell is rendered
in <FONT color="red">red</FONT>.</LI>
<LI>Type - the user-modifiable type of the watch. Note the type is not marked up in the
trace. Clicking the Apply Data Type action will apply it to the current trace, if
@ -135,15 +143,11 @@
<H3><A name="enable_edits"></A>Enable Edits</H3>
<P>This toggle is a write protector for target machine state. To modify a watch's value, this
toggle must be enabled. Editing a value when the trace is live and "at the present" will cause
the value to be modified on the target. Editing emulated values is permitted, but it has no
effect on the target. Editing historical values is not permitted. All edits to non-live trace
values are performed in emulation. Specifically, it appends a patch command to the current
emulation schedule. This keeps trace history in tact, and it allows patched emulated states to
be annotated and recalled later, since they are stored in the trace's scratch space. Note that
only the raw "Value" column can be edited directly. The "Repr" column cannot be edited,
yet.</P>
<P>This toggle is a write protector for machine state. To modify a watch's value, this toggle
must be enabled. Edits are directed according the to <A href=
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">State Editing Plugin
and Service</A>. <B>Note:</B> Only the raw "Value" column can be edited directly. The "Repr"
column cannot be edited, yet.</P>
<H2><A name="colors"></A>Tool Options: Colors</H2>

View File

@ -28,6 +28,7 @@ import ghidra.framework.model.*;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.database.DBTraceContentHandler;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread;
@ -81,9 +82,28 @@ public class DebuggerCoordinates {
null, null, null);
}
public static DebuggerCoordinates rawView(TraceProgramView view) {
return all(view.getTrace(), null, null, view, TraceSchedule.snap(view.getSnap()), null);
}
public static DebuggerCoordinates view(TraceProgramView view) {
return all(view == null ? null : view.getTrace(), null, null, view,
view == null ? null : TraceSchedule.snap(view.getSnap()), null);
if (view == null) {
return NOWHERE;
}
long snap = view.getSnap();
if (!DBTraceUtils.isScratch(snap)) {
return rawView(view);
}
Trace trace = view.getTrace();
TraceSnapshot snapshot = trace.getTimeManager().getSnapshot(snap, false);
if (snapshot == null) {
return rawView(view);
}
TraceSchedule schedule = snapshot.getSchedule();
if (schedule == null) {
return rawView(view);
}
return trace(trace).withTime(schedule);
}
public static DebuggerCoordinates snap(long snap) {

View File

@ -22,12 +22,15 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.*;
import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import docking.action.builder.*;
import docking.menu.ActionState;
import docking.widgets.table.*;
import docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
@ -47,6 +50,7 @@ import ghidra.app.plugin.core.debug.gui.thread.DebuggerThreadsPlugin;
import ghidra.app.plugin.core.debug.gui.time.DebuggerTimePlugin;
import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin;
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
import ghidra.app.services.MarkerService;
import ghidra.async.AsyncUtils;
@ -162,7 +166,7 @@ public interface DebuggerResources {
// TODO: Draw an icon?
ImageIcon ICON_CAPTURE_SYMBOLS = ResourceManager.loadImage("images/closedFolderLabels.png");
ImageIcon ICON_LOG_FATAL = ResourceManager.loadImage("images/edit-bomg.png");
ImageIcon ICON_LOG_FATAL = ResourceManager.loadImage("images/edit-bomb.png");
ImageIcon ICON_LOG_ERROR = ResourceManager.loadImage("images/dialog-warning_red.png");
ImageIcon ICON_LOG_WARN = ResourceManager.loadImage("images/dialog-warning.png");
@ -182,6 +186,17 @@ public interface DebuggerResources {
ImageIcon ICON_DIFF_PREV = ResourceManager.loadImage("images/up.png");
ImageIcon ICON_DIFF_NEXT = ResourceManager.loadImage("images/down.png");
ImageIcon ICON_EDIT_MODE_READ_ONLY = ResourceManager.loadImage("images/write-disabled.png");
ImageIcon ICON_EDIT_MODE_WRITE_TARGET = ResourceManager.loadImage("images/write-target.png");
ImageIcon ICON_EDIT_MODE_WRITE_TRACE = ResourceManager.loadImage("images/write-trace.png");
ImageIcon ICON_EDIT_MODE_WRITE_EMULATOR =
ResourceManager.loadImage("images/write-emulator.png");
String NAME_EDIT_MODE_READ_ONLY = "Read Only";
String NAME_EDIT_MODE_WRITE_TARGET = "Write Target";
String NAME_EDIT_MODE_WRITE_TRACE = "Write Trace";
String NAME_EDIT_MODE_WRITE_EMULATOR = "Write Emulator";
HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package");
String HELP_ANCHOR_PLUGIN = "plugin";
@ -2034,6 +2049,26 @@ public interface DebuggerResources {
}
}
interface EditModeAction {
String NAME = "Edit Mode";
String DESCRIPTION = "Choose what to edit in dynamic views";
String GROUP = GROUP_GENERAL;
Icon ICON = StateEditingMode.values()[0].icon;
String HELP_ANCHOR = "edit_mode";
static MultiStateActionBuilder<StateEditingMode> builder(Plugin owner) {
String ownerName = owner.getName();
return new MultiStateActionBuilder<StateEditingMode>(NAME, ownerName)
.description(DESCRIPTION)
.toolBarGroup(GROUP)
.toolBarIcon(ICON_EDIT_MODE_WRITE_TARGET)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR))
.addStates(Stream.of(StateEditingMode.values())
.map(m -> new ActionState<>(m.name, m.icon, m))
.collect(Collectors.toList()));
}
}
public abstract class AbstractDebuggerConnectionsNode extends GTreeNode {
@Override
public String getName() {

View File

@ -192,7 +192,9 @@ public class DebuggerTrackLocationTrait {
public void setSpec(LocationTrackingSpec spec) {
// TODO: What if action == null?
action.setCurrentActionStateByUserData(spec);
if (action != null) {
action.setCurrentActionStateByUserData(spec);
}
}
public LocationTrackingSpec getSpec() {

View File

@ -24,22 +24,21 @@ import ghidra.app.services.DebuggerModelService;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
@PluginInfo( //
shortDescription = "Debugger breakpoints manager", //
description = "GUI to manage breakpoints", //
category = PluginCategoryNames.DEBUGGER, //
packageName = DebuggerPluginPackage.NAME, //
status = PluginStatus.RELEASED, //
servicesRequired = { //
DebuggerLogicalBreakpointService.class, //
DebuggerModelService.class, //
@PluginInfo(
shortDescription = "Debugger breakpoints manager",
description = "GUI to manage breakpoints",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
servicesRequired = {
DebuggerLogicalBreakpointService.class,
DebuggerModelService.class,
},
eventsConsumed = {
TraceOpenedPluginEvent.class, //
TraceClosedPluginEvent.class, //
TraceActivatedPluginEvent.class, //
} //
)
TraceOpenedPluginEvent.class,
TraceClosedPluginEvent.class,
TraceActivatedPluginEvent.class,
})
public class DebuggerBreakpointsPlugin extends AbstractDebuggerPlugin {
protected DebuggerBreakpointsProvider provider;

View File

@ -0,0 +1,139 @@
/* ###
* 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.editing;
import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction;
import docking.widgets.EventTrigger;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.*;
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.EditModeAction;
import ghidra.app.services.DebuggerStateEditingService;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.DebuggerStateEditingService.StateEditingModeChangeListener;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.trace.model.Trace;
@PluginInfo(
shortDescription = "Debugger machine-state Editing GUI",
description = "GUI to edit target, trace, and/or emulation machine state",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
eventsConsumed = {
TraceActivatedPluginEvent.class,
TraceClosedPluginEvent.class,
},
servicesRequired = {
DebuggerStateEditingService.class,
})
public class DebuggerStateEditingPlugin extends AbstractDebuggerPlugin {
private final StateEditingModeChangeListener listenerForModeChanges = this::modeChanged;
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
protected MultiStateDockingAction<StateEditingMode> actionEditMode;
// @AutoServiceConsumed // via method
private DebuggerStateEditingService editingService;
public DebuggerStateEditingPlugin(PluginTool tool) {
super(tool);
createActions();
}
protected void createActions() {
actionEditMode = EditModeAction.builder(this)
.enabled(false)
.enabledWhen(c -> current.getTrace() != null)
.onActionStateChanged(this::activateEditMode)
.buildAndInstall(tool);
}
protected void activateEditMode(ActionState<StateEditingMode> state, EventTrigger trigger) {
if (current.getTrace() == null) {
return;
}
editingService.setCurrentMode(current.getTrace(), state.getUserData());
// TODO: Limit selectable modes?
// No sense showing Write Target, if the trace can never be live, again....
}
private void modeChanged(Trace trace, StateEditingMode mode) {
if (current.getTrace() == trace) {
refreshActionMode();
}
}
protected void coordinatesActivated(DebuggerCoordinates coords) {
current = coords;
refreshActionMode();
// tool.contextChanged(null);
}
private StateEditingMode computeCurrentEditingMode() {
if (editingService == null) {
return StateEditingMode.READ_ONLY;
}
if (current.getTrace() == null) {
return StateEditingMode.READ_ONLY;
}
return editingService.getCurrentMode(current.getTrace());
}
private void refreshActionMode() {
actionEditMode.setCurrentActionStateByUserData(computeCurrentEditingMode());
}
protected void traceClosed(Trace trace) {
if (current.getTrace() == trace) {
current = DebuggerCoordinates.NOWHERE;
}
refreshActionMode();
// tool.contextChanged(null);
}
@AutoServiceConsumed
protected void setEditingService(DebuggerStateEditingService editingService) {
if (this.editingService != null) {
this.editingService.removeModeChangeListener(listenerForModeChanges);
}
this.editingService = editingService;
if (this.editingService != null) {
this.editingService.addModeChangeListener(listenerForModeChanges);
}
refreshActionMode();
}
@Override
public void processEvent(PluginEvent event) {
super.processEvent(event);
if (event instanceof TraceActivatedPluginEvent) {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
coordinatesActivated(ev.getActiveCoordinates());
}
else if (event instanceof TraceClosedPluginEvent) {
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
traceClosed(ev.getTrace());
}
}
}

View File

@ -19,9 +19,6 @@ import static ghidra.app.plugin.core.debug.gui.DebuggerResources.ICON_REGISTER_M
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.OPTION_NAME_COLORS_TRACKING_MARKERS;
import java.awt.Color;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.*;
import java.util.stream.Collectors;
@ -41,7 +38,6 @@ import docking.menu.MultiStateDockingAction;
import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.app.nav.ListingPanelContainer;
import ghidra.app.plugin.core.clipboard.CodeBrowserClipboardProvider;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
@ -56,6 +52,7 @@ import ghidra.app.plugin.core.marker.MarkerMarginProvider;
import ghidra.app.plugin.core.marker.MarkerOverviewProvider;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerListingService.LocationTrackingSpecChangeListener;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.framework.model.DomainFile;
@ -223,6 +220,8 @@ public class DebuggerListingProvider extends CodeViewerProvider {
@AutoServiceConsumed
private DebuggerConsoleService consoleService;
@AutoServiceConsumed
private DebuggerStateEditingService editingService;
@AutoServiceConsumed
private ProgramManager programManager;
@AutoServiceConsumed
private FileImporterService importerService;
@ -351,7 +350,15 @@ public class DebuggerListingProvider extends CodeViewerProvider {
@Override
public boolean isReadOnly() {
return current.isAliveAndPresent();
if (editingService == null) {
return true;
}
Trace trace = current.getTrace();
if (trace == null) {
return true;
}
StateEditingMode mode = editingService.getCurrentMode(trace);
return !mode.canEdit(current);
}
@Override
@ -429,28 +436,6 @@ public class DebuggerListingProvider extends CodeViewerProvider {
super.addToTool();
}
@Override
protected CodeBrowserClipboardProvider newClipboardProvider() {
/**
* TODO: Ensure memory writes via paste are properly directed to the target process. In the
* meantime, just prevent byte pastes altogether. I cannot disable the clipboard altogether,
* because there are still excellent cases for copying from the dynamic listing, and we
* should still permit pastes of annotations.
*/
return new CodeBrowserClipboardProvider(tool, this) {
@Override
protected boolean pasteBytes(Transferable pasteData)
throws UnsupportedFlavorException, IOException {
return false;
}
@Override
protected boolean pasteByteString(String string) {
return false;
}
};
}
protected void updateMarkerServiceColorModel() {
colorModel.removeModel(markerServiceColorModel);
if (markerService != null) {
@ -825,7 +810,7 @@ public class DebuggerListingProvider extends CodeViewerProvider {
if (loc == null) { // Redundant?
return;
}
if (mappingService == null) {
if (mappingService == null) {
return;
}
ProgramLocation mapped = mappingService.getStaticLocationFromDynamic(loc);

View File

@ -23,7 +23,8 @@ import java.util.*;
import org.apache.commons.lang3.StringUtils;
import docking.ActionContext;
import docking.action.*;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.menu.MultiStateDockingAction;
import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.app.plugin.core.byteviewer.*;
@ -42,6 +43,8 @@ import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.Trace;
@ -187,6 +190,25 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
setHelpLocation(DebuggerResources.HELP_PROVIDER_MEMORY_BYTES);
}
@Override
protected ProgramByteBlockSet newByteBlockSet(ByteBlockChangeManager changeManager) {
if (program == null) {
return null;
}
// A bit of work to get it to ignore existing instructions. Let them be clobbered!
return new ProgramByteBlockSet(this, program, changeManager) {
@Override
protected MemoryByteBlock newMemoryByteBlock(Memory memory, MemoryBlock memBlock) {
return new MemoryByteBlock(program, memory, memBlock) {
@Override
protected boolean editAllowed(Address addr, long length) {
return true;
}
};
}
};
}
/**
* TODO: I'd rather this not be here
*/
@ -209,6 +231,7 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
@Override
protected ByteViewerPanel newByteViewerPanel() {
initTraits();
// For highlighting, e.g., state, pc
return new DebuggerMemoryBytesPanel(this);
}
@ -351,6 +374,9 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
}
protected void doGoToTracked() {
if (editModeAction.isSelected()) {
return;
}
ProgramLocation loc = trackingTrait.getTrackedLocation();
if (loc == null) {
return;
@ -439,26 +465,4 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
newProvider.panel.setViewerPosition(vp);
});
}
@Override
protected ProgramByteBlockSet newByteBlockSet(ByteBlockChangeManager changeManager) {
if (program == null) {
return null;
}
return new WritesTargetProgramByteBlockSet(this, program, changeManager);
}
@Override
public void addLocalAction(DockingActionIf action) {
/**
* TODO This is a terrible hack, but it's temporary. We do not yet support writing target
* memory from the bytes provider. Once we do, we should obviously take this hack out. I
* don't think we'll forget, because the only way to get the write toggle button back is to
* delete this override.
*/
if (action == editModeAction) {
return;
}
super.addLocalAction(action);
}
}

View File

@ -1,179 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.memory;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import ghidra.app.plugin.core.byteviewer.MemoryByteBlock;
import ghidra.app.plugin.core.format.ByteBlockAccessException;
import ghidra.app.services.TraceRecorder;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
/**
* An extension of MemoryByteBlock that redirects applicable writes to a debug target
*/
public class WritesTargetMemoryByteBlock extends MemoryByteBlock {
protected final WritesTargetProgramByteBlockSet blockSet;
/**
* Constructor.
*
* @param blockSet the containing block set
* @param program the trace program view for the block
* @param memory the view's memory
* @param block the view's memory block
*/
public WritesTargetMemoryByteBlock(WritesTargetProgramByteBlockSet blockSet, Program program,
Memory memory, MemoryBlock block) {
super(program, memory, block);
this.blockSet = blockSet;
}
/**
* Check writes should be redirected, based on the provider's coordinates.
*
* <p>
* Note that redirecting the write prevents the edit from being written (dirtectly) into the
* trace. If the edit is successful, the trace recorder will record it to the trace.
*
* @return true to redirect
*/
protected boolean shouldWriteTarget() {
return blockSet.provider.current.isAliveAndPresent();
}
/**
* Get the recorder.
*
* <p>
* This should only be used for redirected writes. If we're not live, this will return null.
*
* @return the recorder
*/
protected TraceRecorder getTraceRecorder() {
return blockSet.provider.current.getRecorder();
}
@Override
public void setByte(BigInteger index, byte value)
throws ByteBlockAccessException {
if (!shouldWriteTarget()) {
super.setByte(index, value);
return;
}
Address addr = getAddress(index);
writeTargetByte(addr, value);
}
@Override
public void setInt(BigInteger index, int value)
throws ByteBlockAccessException {
if (!shouldWriteTarget()) {
super.setInt(index, value);
return;
}
Address addr = getAddress(index);
writeTargetInt(addr, value, isBigEndian());
}
@Override
public void setLong(BigInteger index, long value)
throws ByteBlockAccessException {
if (!shouldWriteTarget()) {
super.setLong(index, value);
return;
}
Address addr = getAddress(index);
writeTargetLong(addr, value, isBigEndian());
}
/**
* Write an array of bytes to the target's memory.
*
* @param addr the starting address
* @param data the data to write, prepared in correct endianness
*/
public void writeTarget(Address addr, byte[] data) {
TraceRecorder recorder = getTraceRecorder();
recorder.writeProcessMemory(addr, data);
}
/**
* Allocate a buffer for encoding values into bytes.
*
* @param size the number of bytes to allocate
* @param bigEndian true to order the buffer in {@link ByteOrder#BIG_ENDIAN}.
* @return the buffer, allocated and configured.
*/
protected ByteBuffer newBuffer(int size, boolean bigEndian) {
ByteBuffer buf = ByteBuffer.allocate(size);
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
return buf;
}
/**
* Write a single byte to the target.
*
* <p>
* Endianness is meaningless
*
* @param addr the address
* @param value the byte
*/
public void writeTargetByte(Address addr, byte value) {
writeTarget(addr, new byte[] { value });
}
/**
* Write a single int to the target
*
* @param addr the minimum address to modify
* @param value the integer
* @param bigEndian true for big endian, false for little
*/
public void writeTargetInt(Address addr, int value, boolean bigEndian) {
ByteBuffer buf = newBuffer(Integer.BYTES, bigEndian);
buf.putInt(value);
writeTarget(addr, buf.array());
}
/**
* Write a single long to the target
*
* @param addr the minimum address to modify
* @param value the long
* @param bigEndian true for big endian, false for little
*/
public void writeTargetLong(Address addr, long value, boolean bigEndian) {
ByteBuffer buf = newBuffer(Long.BYTES, bigEndian);
buf.putLong(value);
writeTarget(addr, buf.array());
}
@Override
protected boolean editAllowed(Address addr, long length) {
/**
* Traces are much more permissive when it comes to writes. The instruction will just get
* clobbered, from this time forward.
*/
return true;
}
}

View File

@ -1,36 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.debug.gui.memory;
import ghidra.app.plugin.core.byteviewer.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
public class WritesTargetProgramByteBlockSet extends ProgramByteBlockSet {
protected final DebuggerMemoryBytesProvider provider;
public WritesTargetProgramByteBlockSet(DebuggerMemoryBytesProvider provider,
Program program, ByteBlockChangeManager bbcm) {
super(provider, program, bbcm);
this.provider = provider;
}
@Override
protected MemoryByteBlock newMemoryByteBlock(Memory memory, MemoryBlock memBlock) {
return new WritesTargetMemoryByteBlock(this, program, memory, memBlock);
}
}

View File

@ -897,7 +897,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
return;
}
doLoadPcodeFrameFromEmulator(emulationService.getCachedEmulator(trace, time));
}, SwingExecutorService.INSTANCE);
}, SwingExecutorService.LATER);
}
protected void doLoadPcodeFrameFromEmulator(DebuggerTracePcodeEmulator emu) {

View File

@ -46,6 +46,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerProvider;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.async.AsyncLazyValue;
import ghidra.async.AsyncUtils;
import ghidra.base.widgets.table.DataTypeTableCellEditor;
@ -72,7 +73,6 @@ import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.trace.util.*;
import ghidra.util.Msg;
import ghidra.util.Swing;
@ -388,6 +388,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
@AutoServiceConsumed
private DebuggerListingService listingService;
@AutoServiceConsumed
private DebuggerStateEditingService editingService;
@AutoServiceConsumed
private MarkerService markerService; // TODO: Mark address types (separate plugin?)
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@ -758,37 +760,15 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
}
}
boolean canWriteTarget() {
if (!current.isAliveAndPresent()) {
return false;
}
TraceRecorder recorder = current.getRecorder();
TargetRegisterBank targetRegs =
recorder.getTargetRegisterBank(current.getThread(), current.getFrame());
if (targetRegs == null) {
return false;
}
return true;
}
boolean canWriteRegister(Register register) {
if (!isEditsEnabled()) {
return false;
}
if (register.isProcessorContext()) {
return false; // TODO: Limitation from using Sleigh for patching
}
return true;
}
boolean canWriteTargetRegister(Register register) {
if (!isEditsEnabled()) {
if (editingService == null) {
return false;
}
if (!canWriteTarget()) {
return false;
}
return current.getRecorder().isRegisterOnTarget(current.getThread(), register);
StateEditor editor = editingService.createStateEditor(current);
return editor.isRegisterEditable(register);
}
BigInteger getRegisterValue(Register register) {
@ -804,37 +784,40 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
}
void writeRegisterValue(RegisterValue rv) {
if (canWriteTargetRegister(rv.getRegister())) {
rv = combineWithTraceBaseRegisterValue(rv);
CompletableFuture<Void> future = current.getRecorder()
.writeThreadRegisters(current.getThread(), current.getFrame(),
Map.of(rv.getRegister(), rv));
future.exceptionally(ex -> {
ex = AsyncUtils.unwrapThrowable(ex);
if (ex instanceof DebuggerModelAccessException) {
Msg.error(this, "Could not write target register", ex);
plugin.getTool()
.setStatusInfo("Could not write target register: " + ex.getMessage());
}
else {
Msg.showError(this, getComponent(), "Edit Register",
"Could not write target register", ex);
}
return null;
});
if (editingService == null) {
Msg.showError(this, getComponent(), "Edit Register", "No editing service.");
return;
}
StateEditor editor = editingService.createStateEditor(current);
if (!editor.isRegisterEditable(rv.getRegister())) {
rv = combineWithTraceBaseRegisterValue(rv);
}
if (!editor.isRegisterEditable(rv.getRegister())) {
Msg.showError(this, getComponent(), "Edit Register",
"Neither the register nor its base can be edited.");
return;
}
TraceSchedule time = current.getTime().patched(current.getThread(), generateSleigh(rv));
traceManager.activateTime(time);
}
protected String generateSleigh(RegisterValue rv) {
return String.format("%s=0x%s", rv.getRegister(), rv.getUnsignedValue().toString(16));
CompletableFuture<Void> future = editor.setRegister(rv);
future.exceptionally(ex -> {
ex = AsyncUtils.unwrapThrowable(ex);
if (ex instanceof DebuggerModelAccessException) {
Msg.error(this, "Could not write target register", ex);
plugin.getTool()
.setStatusInfo("Could not write target register: " + ex.getMessage());
}
else {
Msg.showError(this, getComponent(), "Edit Register",
"Could not write target register", ex);
}
return null;
});
return;
}
private RegisterValue combineWithTraceBaseRegisterValue(RegisterValue rv) {
TraceMemoryRegisterSpace regs = getRegisterMemorySpace(false);
long snap = current.getSnap();
long snap = current.getViewSnap();
return TraceRegisterUtils.combineWithTraceBaseRegisterValue(rv, snap, regs, true);
}
@ -847,7 +830,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
try (UndoableTransaction tid =
UndoableTransaction.start(current.getTrace(), "Edit Register Type", false)) {
TraceCodeRegisterSpace space = getRegisterMemorySpace(true).getCodeSpace(true);
long snap = current.getSnap();
long snap = current.getViewSnap();
space.definedUnits().clear(Range.closed(snap, snap), register, TaskMonitor.DUMMY);
if (dataType != null) {
space.definedData().create(Range.atLeast(snap), register, dataType);
@ -864,7 +847,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
if (space == null) {
return null;
}
long snap = current.getSnap();
long snap = current.getViewSnap();
return space.definedData().getForRegister(snap, register);
}

View File

@ -253,6 +253,8 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
@AutoServiceConsumed
private DebuggerTraceManagerService traceManager; // For goto time (emu mods)
@AutoServiceConsumed
protected DebuggerStateEditingService editingService;
@AutoServiceConsumed
private DebuggerStaticMappingService mappingService; // For listing action
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;

View File

@ -24,6 +24,8 @@ import org.apache.commons.lang3.tuple.Pair;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.services.DebuggerStateEditingService;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.docking.settings.SettingsImpl;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.trace.TraceBytesPcodeExecutorState;
@ -32,14 +34,12 @@ import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.ByteMemBufferImpl;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.*;
public class WatchRow {
@ -142,7 +142,7 @@ public class WatchRow {
MemBuffer buffer = new ByteMemBufferImpl(address, value, language.isBigEndian());
return dataType.getRepresentation(buffer, SettingsImpl.NO_SETTINGS, value.length);
}
protected Object parseAsDataTypeObj() {
if (dataType == null || value == null) {
return null;
@ -372,9 +372,20 @@ public class WatchRow {
public Object getValueObj() {
return valueObj;
}
public boolean isValueEditable() {
return address != null && provider.isEditsEnabled();
if (!provider.isEditsEnabled()) {
return false;
}
if (address == null) {
return false;
}
DebuggerStateEditingService editingService = provider.editingService;
if (editingService == null) {
return false;
}
StateEditor editor = editingService.createStateEditor(coordinates);
return editor.isVariableEditable(address, getValueLength());
}
public void setRawValueString(String valueString) {
@ -415,55 +426,16 @@ public class WatchRow {
if (bytes.length != value.length) {
throw new IllegalArgumentException("Byte array values must match length of variable");
}
// Allow writes to unmappable registers to fall through to trace
// However, attempts to write "weird" register addresses is forbidden
if (coordinates.isAliveAndPresent() && coordinates.getRecorder()
.isVariableOnTarget(coordinates.getThread(), address, bytes.length)) {
coordinates.getRecorder()
.writeVariable(coordinates.getThread(), coordinates.getFrame(), address, bytes)
.exceptionally(ex -> {
Msg.showError(this, null, "Write Failed",
"Could not modify watch value (on target)", ex);
return null;
});
// NB: if successful, recorder will write to trace
return;
DebuggerStateEditingService editingService = provider.editingService;
if (editingService == null) {
throw new AssertionError("No editing service");
}
/*try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Write watch at " + address, true)) {
final TraceMemorySpace space;
if (address.isRegisterAddress()) {
space = trace.getMemoryManager()
.getMemoryRegisterSpace(coordinates.getThread(), coordinates.getFrame(),
true);
}
else {
space = trace.getMemoryManager().getMemorySpace(address.getAddressSpace(), true);
}
space.putBytes(coordinates.getViewSnap(), address, ByteBuffer.wrap(bytes));
}*/
TraceSchedule time =
coordinates.getTime().patched(coordinates.getThread(), generateSleigh(bytes));
provider.goToTime(time);
}
protected String generateSleigh(byte[] bytes) {
BigInteger value = Utils.bytesToBigInteger(bytes, bytes.length,
trace.getBaseLanguage().isBigEndian(), false);
if (address.isMemoryAddress()) {
AddressSpace space = address.getAddressSpace();
return String.format("*[%s]:%d 0x%s:%d=0x%s",
space.getName(), bytes.length,
address.getOffsetAsBigInteger().toString(16), space.getPointerSize(),
value.toString(16));
}
Register register = trace.getBaseLanguage().getRegister(address, bytes.length);
if (register == null) {
throw new AssertionError("Can only modify memory or register");
}
return String.format("%s=0x%s", register, value.toString(16));
StateEditor editor = editingService.createStateEditor(coordinates);
editor.setVariable(address, bytes).exceptionally(ex -> {
Msg.showError(this, null, "Write Failed",
"Could not modify watch value (on target)", ex);
return null;
});
}
public int getValueLength() {

View File

@ -0,0 +1,483 @@
/* ###
* 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.service.editing;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.*;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.*;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent;
import ghidra.app.services.*;
import ghidra.async.AsyncUtils;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceProgramViewListener;
import ghidra.trace.model.memory.TraceMemoryOperations;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceProgramViewMemory;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.PatchStep;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.trace.util.TraceRegisterUtils;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@PluginInfo(
shortDescription = "Debugger machine-state editing service plugin",
description = "Centralizes machine-state editing across the tool",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
eventsConsumed = {
TraceOpenedPluginEvent.class,
TraceClosedPluginEvent.class,
},
servicesRequired = {
DebuggerTraceManagerService.class,
DebuggerEmulationService.class,
},
servicesProvided = {
DebuggerStateEditingService.class,
})
public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
implements DebuggerStateEditingService {
protected abstract class AbstractStateEditor implements StateEditor {
@Override
public boolean isVariableEditable(Address address, int length) {
DebuggerCoordinates coordinates = getCoordinates();
Trace trace = coordinates.getTrace();
switch (getCurrentMode(trace)) {
case READ_ONLY:
return false;
case WRITE_TARGET:
return isTargetVariableEditable(coordinates, address, length);
case WRITE_TRACE:
return isTraceVariableEditable(coordinates, address, length);
case WRITE_EMULATOR:
return isEmulatorVariableEditable(coordinates, address, length);
}
throw new AssertionError();
}
protected boolean isTargetVariableEditable(DebuggerCoordinates coordinates, Address address,
int length) {
if (!coordinates.isAliveAndPresent()) {
return false;
}
TraceRecorder recorder = coordinates.getRecorder();
return recorder.isVariableOnTarget(coordinates.getThread(), address, length);
}
protected boolean isTraceVariableEditable(DebuggerCoordinates coordinates, Address address,
int length) {
return address.isMemoryAddress() || coordinates.getThread() != null;
}
protected boolean isEmulatorVariableEditable(DebuggerCoordinates coordinates,
Address address, int length) {
if (!isTraceVariableEditable(coordinates, address, length)) {
return false;
}
// TODO: Limitation from using Sleigh for patching
Register ctxReg = coordinates.getTrace().getBaseLanguage().getContextBaseRegister();
if (ctxReg == Register.NO_CONTEXT) {
return true;
}
AddressRange ctxRange = TraceRegisterUtils.rangeForRegister(ctxReg);
if (ctxRange.contains(address)) {
return false;
}
return true;
}
@Override
public CompletableFuture<Void> setVariable(Address address, byte[] data) {
DebuggerCoordinates coordinates = getCoordinates();
Trace trace = coordinates.getTrace();
switch (getCurrentMode(trace)) {
case READ_ONLY:
return CompletableFuture
.failedFuture(new MemoryAccessException("Read-only mode"));
case WRITE_TARGET:
return writeTargetVariable(coordinates, address, data);
case WRITE_TRACE:
return writeTraceVariable(coordinates, address, data);
case WRITE_EMULATOR:
return writeEmulatorVariable(coordinates, address, data);
}
throw new AssertionError();
}
protected CompletableFuture<Void> writeTargetVariable(DebuggerCoordinates coordinates,
Address address, byte[] data) {
TraceRecorder recorder = coordinates.getRecorder();
if (recorder == null) {
return CompletableFuture
.failedFuture(new MemoryAccessException("Trace has no live target"));
}
if (!coordinates.isPresent()) {
return CompletableFuture
.failedFuture(new MemoryAccessException("View is not the present"));
}
return recorder.writeVariable(coordinates.getThread(), coordinates.getFrame(), address,
data);
}
protected CompletableFuture<Void> writeTraceVariable(DebuggerCoordinates coordinates,
Address address, byte[] data) {
Trace trace = coordinates.getTrace();
long snap = coordinates.getViewSnap();
TraceMemoryOperations memOrRegs;
try (UndoableTransaction txid =
UndoableTransaction.start(trace, "Edit Variable", true)) {
if (address.isRegisterAddress()) {
TraceThread thread = coordinates.getThread();
if (thread == null) {
throw new IllegalArgumentException("Register edits require a thread.");
}
memOrRegs = trace.getMemoryManager()
.getMemoryRegisterSpace(thread, coordinates.getFrame(),
true);
}
else {
memOrRegs = trace.getMemoryManager();
}
if (memOrRegs.putBytes(snap, address, ByteBuffer.wrap(data)) != data.length) {
return CompletableFuture.failedFuture(new MemoryAccessException());
}
}
return AsyncUtils.NIL;
}
protected CompletableFuture<Void> writeEmulatorVariable(DebuggerCoordinates coordinates,
Address address, byte[] data) {
TraceThread thread = coordinates.getThread();
if (thread == null) {
// TODO: Well, technically, only for register edits
throw new IllegalArgumentException("Emulator edits require a thread.");
}
TraceSchedule time = coordinates.getTime()
.patched(thread, PatchStep.generateSleigh(
coordinates.getTrace().getBaseLanguage(), address, data));
DebuggerCoordinates withTime = coordinates.withTime(time);
Long found = traceManager.findSnapshot(withTime);
// Materialize it on the same thread (even if swing)
// It shouldn't take long, since we're only appending one step.
if (found == null) {
// TODO: Could still do it async on another thread, no?
// Not sure it buys anything, since program view will call .get on swing thread
try {
emulationSerivce.emulate(coordinates.getTrace(), time, TaskMonitor.DUMMY);
}
catch (CancelledException e) {
throw new AssertionError(e);
}
}
return traceManager.activateAndNotify(withTime, false);
}
}
protected class DefaultStateEditor extends AbstractStateEditor {
private final DebuggerCoordinates coordinates;
public DefaultStateEditor(DebuggerCoordinates coordinates) {
this.coordinates = Objects.requireNonNull(coordinates);
}
@Override
public DebuggerStateEditingService getService() {
return DebuggerStateEditingServicePlugin.this;
}
@Override
public DebuggerCoordinates getCoordinates() {
return this.coordinates;
}
}
protected class FollowsManagerStateEditor extends AbstractStateEditor {
private final Trace trace;
public FollowsManagerStateEditor(Trace trace) {
this.trace = trace;
}
@Override
public DebuggerStateEditingService getService() {
return DebuggerStateEditingServicePlugin.this;
}
@Override
public DebuggerCoordinates getCoordinates() {
DebuggerCoordinates current = traceManager.getCurrentFor(trace);
if (current != null) {
return current;
}
DebuggerCoordinates resolved =
traceManager.resolveCoordinates(DebuggerCoordinates.trace(trace));
if (resolved != null) {
return resolved;
}
throw new IllegalStateException(
"Trace " + trace + " is not opened in the trace manager.");
}
}
public class FollowsViewStateEditor extends AbstractStateEditor
implements StateEditingMemoryHandler {
private final TraceProgramView view;
public FollowsViewStateEditor(TraceProgramView view) {
this.view = view;
}
@Override
public DebuggerStateEditingService getService() {
return DebuggerStateEditingServicePlugin.this;
}
@Override
public DebuggerCoordinates getCoordinates() {
return traceManager.resolveCoordinates(DebuggerCoordinates.view(view));
}
@Override
public void clearCache() {
// Nothing to do
}
@Override
public byte getByte(Address addr) throws MemoryAccessException {
ByteBuffer buf = ByteBuffer.allocate(1);
view.getTrace().getMemoryManager().getViewBytes(view.getSnap(), addr, buf);
return buf.get(0);
}
@Override
public int getBytes(Address address, byte[] buffer, int startIndex, int size)
throws MemoryAccessException {
return view.getTrace()
.getMemoryManager()
.getViewBytes(view.getSnap(), address,
ByteBuffer.wrap(buffer, startIndex, size));
}
@Override
public void putByte(Address address, byte value) throws MemoryAccessException {
try {
setVariable(address, new byte[] { value }).get(1, TimeUnit.SECONDS);
}
catch (ExecutionException e) {
throw new MemoryAccessException("Failed to write " + address + ": " + e.getCause());
}
catch (TimeoutException | InterruptedException e) {
throw new MemoryAccessException("Failed to write " + address + ": " + e);
}
}
@Override
public int putBytes(Address address, byte[] source, int startIndex, int size)
throws MemoryAccessException {
try {
setVariable(address, Arrays.copyOfRange(source, startIndex, startIndex + size))
.get(1, TimeUnit.SECONDS);
}
catch (ExecutionException e) {
throw new MemoryAccessException("Failed to write " + address + ": " + e.getCause());
}
catch (TimeoutException | InterruptedException e) {
throw new MemoryAccessException("Failed to write " + address + ": " + e);
}
return size;
}
@Override
public void addLiveMemoryListener(LiveMemoryListener listener) {
throw new UnsupportedOperationException();
}
@Override
public void removeLiveMemoryListener(LiveMemoryListener listener) {
throw new UnsupportedOperationException();
}
}
protected class ListenerForEditorInstallation implements TraceProgramViewListener {
@Override
public void viewCreated(TraceProgramView view) {
installMemoryEditor(view);
}
}
//@AutoServiceConsumed // via method
private DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
private DebuggerEmulationService emulationSerivce;
@AutoServiceConsumed
private DebuggerModelService modelService;
protected final ListenerForEditorInstallation listenerForEditorInstallation =
new ListenerForEditorInstallation();
public DebuggerStateEditingServicePlugin(PluginTool tool) {
super(tool);
}
private static final StateEditingMode DEFAULT_MODE = StateEditingMode.WRITE_TARGET;
private final Map<Trace, StateEditingMode> currentModes = new HashMap<>();
private final ListenerSet<StateEditingModeChangeListener> listeners =
new ListenerSet<>(StateEditingModeChangeListener.class);
@Override
public StateEditingMode getCurrentMode(Trace trace) {
synchronized (currentModes) {
return currentModes.getOrDefault(Objects.requireNonNull(trace), DEFAULT_MODE);
}
}
@Override
public void setCurrentMode(Trace trace, StateEditingMode mode) {
boolean fire = false;
synchronized (currentModes) {
StateEditingMode old =
currentModes.getOrDefault(Objects.requireNonNull(trace), DEFAULT_MODE);
if (mode != old) {
currentModes.put(trace, mode);
fire = true;
}
}
if (fire) {
listeners.fire.modeChanged(trace, mode);
tool.contextChanged(null);
}
}
@Override
public void addModeChangeListener(StateEditingModeChangeListener listener) {
listeners.add(listener);
}
@Override
public void removeModeChangeListener(StateEditingModeChangeListener listener) {
listeners.remove(listener);
}
@Override
public StateEditor createStateEditor(DebuggerCoordinates coordinates) {
return new DefaultStateEditor(coordinates);
}
@Override
public StateEditor createStateEditor(Trace trace) {
return new FollowsManagerStateEditor(trace);
}
@Override
public StateEditingMemoryHandler createStateEditor(TraceProgramView view) {
return new FollowsViewStateEditor(view);
}
protected void installMemoryEditor(TraceProgramView view) {
TraceProgramViewMemory memory = view.getMemory();
if (memory.getLiveMemoryHandler() != null) {
return;
}
memory.setLiveMemoryHandler(createStateEditor(view));
}
protected void uninstallMemoryEditor(TraceProgramView view) {
TraceProgramViewMemory memory = view.getMemory();
LiveMemoryHandler handler = memory.getLiveMemoryHandler();
if (!(handler instanceof StateEditingMemoryHandler)) {
return;
}
StateEditingMemoryHandler editor = (StateEditingMemoryHandler) handler;
if (editor.getService() != this) {
return;
}
memory.setLiveMemoryHandler(null);
}
protected void installAllMemoryEditors(Trace trace) {
trace.addProgramViewListener(listenerForEditorInstallation);
for (TraceProgramView view : trace.getAllProgramViews()) {
installMemoryEditor(view);
}
}
protected void installAllMemoryEditors() {
if (traceManager == null) {
return;
}
for (Trace trace : traceManager.getOpenTraces()) {
installAllMemoryEditors(trace);
}
}
protected void uninstallAllMemoryEditors(Trace trace) {
trace.removeProgramViewListener(listenerForEditorInstallation);
for (TraceProgramView view : trace.getAllProgramViews()) {
uninstallMemoryEditor(view);
}
}
protected void uninstallAllMemoryEditors() {
if (traceManager == null) {
return;
}
for (Trace trace : traceManager.getOpenTraces()) {
uninstallAllMemoryEditors(trace);
}
}
@Override
public void processEvent(PluginEvent event) {
super.processEvent(event);
if (event instanceof TraceOpenedPluginEvent) {
TraceOpenedPluginEvent ev = (TraceOpenedPluginEvent) event;
installAllMemoryEditors(ev.getTrace());
}
else if (event instanceof TraceClosedPluginEvent) {
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
uninstallAllMemoryEditors(ev.getTrace());
}
}
@AutoServiceConsumed
private void setTraceManager(DebuggerTraceManagerService traceManager) {
uninstallAllMemoryEditors();
this.traceManager = traceManager;
installAllMemoryEditors();
}
}

View File

@ -59,7 +59,7 @@ import ghidra.util.task.TaskMonitor;
description = "Manages and cache trace emulation states",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.UNSTABLE,
status = PluginStatus.RELEASED,
eventsConsumed = {
TraceClosedPluginEvent.class,
ProgramActivatedPluginEvent.class,

View File

@ -256,7 +256,7 @@ public class DebuggerConnectDialog extends DialogComponentProvider
result.completeAsync(() -> m);
result = null;
}
}, SwingExecutorService.INSTANCE).exceptionally(e -> {
}, SwingExecutorService.LATER).exceptionally(e -> {
e = AsyncUtils.unwrapThrowable(e);
if (!(e instanceof CancellationException)) {
Msg.showError(this, getComponent(), "Could not connect", e);

View File

@ -280,7 +280,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
"The given launcher path is not a TargetLauncher, according to its schema");
}
return new ValueExpecter(m, launcherPath);
}, SwingExecutorService.INSTANCE).thenCompose(l -> {
}, SwingExecutorService.LATER).thenCompose(l -> {
monitor.incrementProgress(1);
monitor.setMessage("Launching");
TargetLauncher launcher = (TargetLauncher) l;

View File

@ -32,9 +32,8 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.services.*;
import ghidra.async.*;
import ghidra.async.AsyncConfigFieldCodec.BooleanAsyncConfigFieldCodec;
import ghidra.async.AsyncReference;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.*;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.client.NotConnectedException;
@ -60,16 +59,25 @@ import ghidra.util.datastruct.CollectionChangeListener;
import ghidra.util.exception.*;
import ghidra.util.task.*;
@PluginInfo(shortDescription = "Debugger Trace View Management Plugin", description = "Manages UI Components, Wrappers, Focus, etc.", category = PluginCategoryNames.DEBUGGER, packageName = DebuggerPluginPackage.NAME, status = PluginStatus.RELEASED, eventsProduced = {
TraceActivatedPluginEvent.class,
}, eventsConsumed = {
TraceActivatedPluginEvent.class,
TraceClosedPluginEvent.class,
ModelObjectFocusedPluginEvent.class,
TraceRecorderAdvancedPluginEvent.class,
}, servicesRequired = {}, servicesProvided = {
DebuggerTraceManagerService.class,
})
@PluginInfo(
shortDescription = "Debugger Trace View Management Plugin",
description = "Manages UI Components, Wrappers, Focus, etc.",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED,
eventsProduced = {
TraceActivatedPluginEvent.class,
},
eventsConsumed = {
TraceActivatedPluginEvent.class,
TraceClosedPluginEvent.class,
ModelObjectFocusedPluginEvent.class,
TraceRecorderAdvancedPluginEvent.class,
},
servicesRequired = {},
servicesProvided = {
DebuggerTraceManagerService.class,
})
public class DebuggerTraceManagerServicePlugin extends Plugin
implements DebuggerTraceManagerService {
private static final AutoConfigState.ClassHandler<DebuggerTraceManagerServicePlugin> CONFIG_STATE_HANDLER =
@ -623,6 +631,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return current;
}
@Override
public DebuggerCoordinates getCurrentFor(Trace trace) {
return lastCoordsByTrace.get(trace);
}
@Override
public Trace getCurrentTrace() {
return current.getTrace();
@ -654,17 +667,25 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return current.getFrame();
}
@Override
public CompletableFuture<Long> materialize(DebuggerCoordinates coordinates) {
public Long findSnapshot(DebuggerCoordinates coordinates) {
if (coordinates.getTime().isSnapOnly()) {
return CompletableFuture.completedFuture(coordinates.getSnap());
return 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());
return found.getKey();
}
return null;
}
@Override
public CompletableFuture<Long> materialize(DebuggerCoordinates coordinates) {
Long found = findSnapshot(coordinates);
if (found != null) {
return CompletableFuture.completedFuture(found);
}
if (emulationService == null) {
throw new IllegalStateException(
@ -674,19 +695,19 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return emulationService.backgroundEmulate(coordinates.getTrace(), coordinates.getTime());
}
protected void prepareViewAndFireEvent(DebuggerCoordinates coordinates) {
protected CompletableFuture<Void> prepareViewAndFireEvent(DebuggerCoordinates coordinates) {
TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView();
if (varView == null) { // Should only happen with NOWHERE
fireLocationEvent(coordinates);
return;
return AsyncUtils.NIL;
}
materialize(coordinates).thenAcceptAsync(snap -> {
return materialize(coordinates).thenAcceptAsync(snap -> {
if (!coordinates.equals(current)) {
return; // We navigated elsewhere before emulation completed
}
varView.setSnap(snap);
fireLocationEvent(coordinates);
}, AsyncUtils.SWING_EXECUTOR);
}, SwingExecutorService.MAYBE_NOW);
}
protected void fireLocationEvent(DebuggerCoordinates coordinates) {
@ -977,14 +998,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return thread;
}
}
//if (!Objects.equals(prev.getTrace(), resolved.getTrace())) {
return recorder.getTarget();
//}
//return null;
}
@Override
public void activate(DebuggerCoordinates coordinates) {
public CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates,
boolean syncTargetFocus) {
DebuggerCoordinates prev;
DebuggerCoordinates resolved;
synchronized (listenersByTrace) {
@ -992,34 +1011,34 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
resolved = doSetCurrent(coordinates);
}
if (resolved == null) {
return;
return AsyncUtils.NIL;
}
CompletableFuture<Void> future = prepareViewAndFireEvent(resolved);
if (!syncTargetFocus) {
return future;
}
prepareViewAndFireEvent(resolved);
if (!synchronizeFocus.get()) {
return;
return future;
}
TraceRecorder recorder = resolved.getRecorder();
if (recorder == null) {
return;
return future;
}
TargetObject focus = translateToFocus(prev, resolved);
if (focus == null || !focus.isValid()) {
return;
return future;
}
recorder.requestFocus(focus);
return future;
}
@Override
public void activate(DebuggerCoordinates coordinates) {
activateAndNotify(coordinates, true); // Drop future on floor
}
public void activateNoFocusChange(DebuggerCoordinates coordinates) {
//DebuggerCoordinates prev;
DebuggerCoordinates resolved;
synchronized (listenersByTrace) {
//prev = current;
resolved = doSetCurrent(coordinates);
}
if (resolved == null) {
return;
}
prepareViewAndFireEvent(resolved);
activateAndNotify(coordinates, false); // Drop future on floor
}
@Override

View File

@ -295,22 +295,29 @@ public class DisassembleAtPcDebuggerBot implements DebuggerBot {
@Override
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
synchronized (injects) {
if (codeManager.definedUnits().containsAddress(ks, start)) {
try {
if (codeManager.definedUnits().containsAddress(ks, start)) {
return true;
}
for (DisassemblyInject i : injects) {
i.pre(plugin.getTool(), this, view, thread,
new AddressSet(start, start),
disassemblable);
}
boolean result = super.applyTo(obj, monitor);
if (!result) {
Msg.error(this, "Auto-disassembly error: " + getStatusMsg());
return true; // No pop-up errors
}
for (DisassemblyInject i : injects) {
i.post(plugin.getTool(), view, getDisassembledAddressSet());
}
return true;
}
for (DisassemblyInject i : injects) {
i.pre(plugin.getTool(), this, view, thread,
new AddressSet(start, start),
disassemblable);
catch (Throwable e) {
Msg.error(this, "Auto-disassembly error: " + e);
return true; // No pop-up errors
}
boolean result = super.applyTo(obj, monitor);
if (!result) {
return false;
}
for (DisassemblyInject i : injects) {
i.post(plugin.getTool(), view, getDisassembledAddressSet());
}
return result;
}
}
};

View File

@ -31,9 +31,8 @@ import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.program.TraceProgramView;
@ServiceInfo( //
defaultProvider = DebuggerLogicalBreakpointServicePlugin.class, //
description = "Aggregate breakpoints for programs and live traces" //
)
defaultProvider = DebuggerLogicalBreakpointServicePlugin.class,
description = "Aggregate breakpoints for programs and live traces")
public interface DebuggerLogicalBreakpointService {
/**
* Get all logical breakpoints known to the tool.

View File

@ -0,0 +1,121 @@
/* ###
* 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.services;
import java.util.concurrent.CompletableFuture;
import javax.swing.Icon;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
import ghidra.framework.plugintool.ServiceInfo;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.mem.LiveMemoryHandler;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
@ServiceInfo(
defaultProvider = DebuggerStateEditingServicePlugin.class,
description = "Centralized service for modifying machine states")
public interface DebuggerStateEditingService {
enum StateEditingMode {
READ_ONLY(DebuggerResources.NAME_EDIT_MODE_READ_ONLY, //
DebuggerResources.ICON_EDIT_MODE_READ_ONLY) {
@Override
public boolean canEdit(DebuggerCoordinates coordinates) {
return false;
}
},
WRITE_TARGET(DebuggerResources.NAME_EDIT_MODE_WRITE_TARGET, //
DebuggerResources.ICON_EDIT_MODE_WRITE_TARGET) {
@Override
public boolean canEdit(DebuggerCoordinates coordinates) {
return coordinates.isAliveAndPresent();
}
},
WRITE_TRACE(DebuggerResources.NAME_EDIT_MODE_WRITE_TRACE, //
DebuggerResources.ICON_EDIT_MODE_WRITE_TRACE) {
@Override
public boolean canEdit(DebuggerCoordinates coordinates) {
return coordinates.getTrace() != null;
}
},
WRITE_EMULATOR(DebuggerResources.NAME_EDIT_MODE_WRITE_EMULATOR, //
DebuggerResources.ICON_EDIT_MODE_WRITE_EMULATOR) {
@Override
public boolean canEdit(DebuggerCoordinates coordinates) {
return coordinates.getTrace() != null;
}
};
public final String name;
public final Icon icon;
private StateEditingMode(String name, Icon icon) {
this.name = name;
this.icon = icon;
}
public abstract boolean canEdit(DebuggerCoordinates coordinates);
}
interface StateEditor {
DebuggerStateEditingService getService();
DebuggerCoordinates getCoordinates();
boolean isVariableEditable(Address address, int length);
default boolean isRegisterEditable(Register register) {
return isVariableEditable(register.getAddress(), register.getNumBytes());
}
CompletableFuture<Void> setVariable(Address address, byte[] data);
default CompletableFuture<Void> setRegister(RegisterValue value) {
Register register = value.getRegister();
boolean isBigEndian = getCoordinates().getTrace().getBaseLanguage().isBigEndian();
byte[] bytes = Utils.bigIntegerToBytes(value.getUnsignedValue(), register.getNumBytes(),
isBigEndian);
return setVariable(register.getAddress(), bytes);
}
}
interface StateEditingMemoryHandler extends StateEditor, LiveMemoryHandler {
}
interface StateEditingModeChangeListener {
void modeChanged(Trace trace, StateEditingMode mode);
}
StateEditingMode getCurrentMode(Trace trace);
void setCurrentMode(Trace trace, StateEditingMode mode);
void addModeChangeListener(StateEditingModeChangeListener listener);
void removeModeChangeListener(StateEditingModeChangeListener listener);
StateEditor createStateEditor(DebuggerCoordinates coordinates);
StateEditor createStateEditor(Trace trace);
StateEditingMemoryHandler createStateEditor(TraceProgramView view);
}

View File

@ -73,6 +73,14 @@ public interface DebuggerTraceManagerService {
*/
DebuggerCoordinates getCurrent();
/**
* Get the current coordinates for a given trace
*
* @param trace the trace
* @return the current coordinates for the trace
*/
DebuggerCoordinates getCurrentFor(Trace trace);
/**
* Get the active trace
*
@ -212,13 +220,28 @@ public interface DebuggerTraceManagerService {
void closeDeadTraces();
/**
* Activate the given coordinates
* Activate the given coordinates with future notification
*
* <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.
* the coordinates. The returned future is completed when the coordinates are actually
* materialized and active. 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
* @param syncTargetFocus true synchronize the current target to the same coordinates
* @return a future which completes when emulation and navigation is complete
*/
CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates,
boolean syncTargetFocus);
/**
* Activate the given coordinates, synchronizing the current target, if possible
*
* <p>
* If asynchronous notification is needed, use
* {@link #activateAndNotify(DebuggerCoordinates, boolean)}.
*
* @param coordinates the desired coordinates
*/
@ -383,6 +406,18 @@ public interface DebuggerTraceManagerService {
*/
DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coordinates);
/**
* If the given coordinates are already materialized, get the snapshot
*
* <p>
* If the coordinates do not include a schedule, this simply returns the coordinates' snapshot.
* Otherwise, it searches for the first snapshot whose schedule is the coordinates' schedule.
*
* @param coordinates the coordinates
* @return the materialized snapshot key, or null if not materialized.
*/
Long findSnapshot(DebuggerCoordinates coordinates);
/**
* Materialize the given coordinates to a snapshot in the same trace
*

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
id="svg8"
version="1.1"
viewBox="0 0 4.2333331 4.2333331"
height="15.999999"
width="15.999999">
<defs
id="defs2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-140.75833,-215.50833)"
id="layer1">
<image
width="4.2333331"
height="4.2333331"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAANJJREFU
OI2dUbsRgzAMfcqlYYSMYUpGoMwKpPMaWcGdXWYFRqAUY2QEypciMQcHAl9USb73kyxT61FSVR+Y
+6n1kvtrKZldN8+SArPIpcgegKS0+34qUPWBqgqq2xUpSuCwjJ/WN2jeA723D6lPBX4CsQMeCXjd
HUMIGG6NwDlHqwCQ6kh1BPCdScYYCYBT6+0VRASqOs+x28cdfmPeXeoR+NpvMIdHzETLfZNARDYu
R+4rgT2yRVqWuUImi4gFWSewwGcpZoGSuKZAXdcYx/EvgQ9cP3cPT6z/fgAAAABJRU5ErkJggg==
"
id="image4638"
x="140.75833"
y="215.50833" />
<path
id="path4643"
d="m 141.16138,215.91138 3.42712,3.42722"
style="fill:#ff0000;stroke:#ff0000;stroke-width:0.39167586;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
id="svg8"
version="1.1"
viewBox="0 0 4.2333331 4.2333331"
height="15.999999"
width="15.999999">
<defs
id="defs2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-140.75833,-215.50833)"
id="layer1">
<g
transform="matrix(0.26458333,0,0,0.26458333,140.75833,215.50833)"
id="layer1-5">
<path
id="path5852"
d="M 4.0000781,6.0000002 V 7.1308596 A 4,4 0 0 0 2.1485156,8.199219 L 1.17,7.6347658 0.17,9.3652346 1.1485156,9.9296877 A 4,4 0 0 0 1.0000781,11 4,4 0 0 0 1.1465625,12.070313 L 0.17,12.634766 l 1,1.730468 0.9765625,-0.564453 a 4,4 0 0 0 1.8535156,1.066407 V 16 H 6.000078 v -1.130859 a 4,4 0 0 0 1.851562,-1.06836 l 0.978516,0.564453 1,-1.730468 L 8.85164,12.070313 A 4,4 0 0 0 9.000078,11 4,4 0 0 0 8.853594,9.9296877 l 0.976562,-0.5644531 -1,-1.7304688 L 7.853594,8.199219 A 4,4 0 0 0 6.000078,7.1328127 V 6.0000002 Z M 5.000078,10 a 1,1 0 0 1 1,1 1,1 0 0 1 -1,1 1,1 0 0 1 -0.9999999,-1 1,1 0 0 1 0.9999999,-1 z"
style="opacity:1;fill:#2c5aa0;fill-opacity:1;stroke:none;stroke-width:1.14285719;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path898"
d="M 1.5449219,0 A 4,4 0 0 0 1.1328125,1 H 0 v 2 h 1.1308594 a 4,4 0 0 0 1.0683594,1.8515625 l -0.5644532,0.9785156 1.7304688,1 0.5644531,-0.9785156 A 4,4 0 0 0 5,6 4,4 0 0 0 6.0703125,5.8535156 l 0.5644531,0.9765625 1.7304688,-1 L 7.8007812,4.8535156 A 4,4 0 0 0 8.8671875,3 H 10 V 1 H 8.8691406 A 4,4 0 0 0 8.4550781,0 Z M 5,1 A 1,1 0 0 1 6,2 1,1 0 0 1 5,3 1,1 0 0 1 4,2 1,1 0 0 1 5,1 Z"
style="opacity:1;fill:#666666;fill-opacity:1;stroke:none;stroke-width:1.14285719;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path906"
d="m 12.365234,6.1699219 -1.730468,1 0.564453,0.9765625 A 4,4 0 0 0 10.132812,10 H 9 v 2 h 1.130859 a 4,4 0 0 0 1.06836,1.851562 l -0.564453,0.978516 1.730468,1 0.564454,-0.978516 A 4,4 0 0 0 14,15 4,4 0 0 0 15.070312,14.853516 L 15.634766,15.830078 16,15.619141 V 6.3808594 L 15.634766,6.1699219 15.070312,7.1484375 A 4,4 0 0 0 14,7 4,4 0 0 0 12.929688,7.1464844 Z M 14,10 a 1,1 0 0 1 1,1 1,1 0 0 1 -1,1 1,1 0 0 1 -1,-1 1,1 0 0 1 1,-1 z"
style="opacity:1;fill:#666666;fill-opacity:1;stroke:none;stroke-width:1.14285719;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
<image
width="4.2333331"
height="4.2333331"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAG1BMVEUxMTEAAACcYwCcnJzOzs73 Smv/Y2P/zjH///8dghBMAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsT AQCanBgAAAAHdElNRQfmAxgRMR1me/WzAAAAN0lEQVQI12NgAAFBATDFwJiWCGWIQRkihVCGeCFE DaN7EZRRLghVUq6IqoRBsAjGgCphgPIJAwBLAAY82pfN3gAAAABJRU5ErkJggg== "
id="image4544"
x="140.75833"
y="215.50833" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
id="svg8"
version="1.1"
viewBox="0 0 4.2333331 4.2333331"
height="15.999999"
width="15.999999">
<defs
id="defs2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-140.75833,-215.50833)"
id="layer1">
<g
transform="matrix(0.26458333,0,0,0.26458333,140.75833,215.50833)"
id="g4579">
<g
transform="matrix(1.5,0,0,1.5,-4,-3.9999972)"
id="g3700"
style="fill:#000000">
<circle
r="4"
cy="7.9999981"
cx="8"
id="circle3698"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.14285719;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
<g
style="fill:#ff2a2a"
id="layer1-6">
<circle
style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:1.14285719;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path821"
cx="8"
cy="7.9999981"
r="4" />
</g>
</g>
<image
width="4.2333331"
height="4.2333331"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAG1BMVEUxMTEAAACcYwCcnJzOzs73 Smv/Y2P/zjH///8dghBMAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsT AQCanBgAAAAHdElNRQfmAxgRMR1me/WzAAAAN0lEQVQI12NgAAFBATDFwJiWCGWIQRkihVCGeCFE DaN7EZRRLghVUq6IqoRBsAjGgCphgPIJAwBLAAY82pfN3gAAAABJRU5ErkJggg== "
id="image4544"
x="140.75833"
y="215.50833" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
id="svg8"
version="1.1"
viewBox="0 0 4.2333331 4.2333331"
height="15.999999"
width="15.999999">
<defs
id="defs2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-140.75833,-215.50833)"
id="layer1">
<image
width="4.2333331"
height="4.2333331"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1gEEDDonMwh5AwAAADV0RVh0Q29tbWVudAAoYykgMjAw NCBKYWt1YiBTdGVpbmVyCgpDcmVhdGVkIHdpdGggVGhlIEdJTVCQ2YtvAAACg0lEQVQ4y52T30tT YRjHP+ec6ZxnxjbEmw3NYrpjWmpqGSpECf2gCNb02qsu6loEqTtB/AdCCIzubAai+CvNQL0QkpLQ nbOoC/XCaTGlneN25qZduEnaTfXcvO8Lz/Pheb/f5xE4Fb19PR1AAKgDXEAUWAKCXZ3dA6fzhd8K LwL9NdW1VxVFQc6Xj5OMPQNVVfm0/HEReNTV2f35BCBTPB3wtxXZC+zoRgwzmSB9kEYSJay5edjl AvSYTvDN622gNQsRM6D+gL+tSJAgpK2wvrGGmTQBMJMm6xtrhLQVBAkC/rYioD/bgdTb19NRU137 uLikmK/fvrC/v08opBLWwnjLvExOTBGN7uByOYnpMdxuDwKCp7KqYn1menZZBAKKoqAbMQA0Lcz8 3AKGbhz9XzeYn1tA08IA6EYMRVHICI0FqJPzZbZ+/MThdBCPx1EUH/cf3AM4PuPxOA6nAzOZwHHG ScYlLBmrSB+ksVgs3Ll7+4RNVquVQPvD43f6IJ29urIiRgEkUSKVSjE+NkFwcAjTzIhomgQHhxgf myCVSiGJUhYQzQKWjD0Da24euzu72Gw2VFVjZHgUgJHhUVRVw2azsbuzizU3D2PPIDNciEBQVVXs cgEAPl85zS1NyPajQZLtMs0tTfh85QDY5QJUVQUIAkgz07PLlVUVt7znvR6H00FMjyEIAg1X6hFF kdJzpUQ2I6yvbdDYeI1kIsm79zOLXZ3dTwAkgJutNz6E1FV/he+C7HZ7KCwsBODw8JAcSw5l3nLq 6xpIJpK8fDUQjUS2np0tLbGGVtXNf9qFqbeTX7e3v0+ZpnkdmANeCP+zjYF2/2XgOTD6B+BvI9Du fwpc+gUuhglRSTLyIQAAAABJRU5ErkJggg== "
id="image4608"
x="140.75833"
y="215.50833" />
<image
width="4.2333331"
height="4.2333331"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAG1BMVEUxMTEAAACcYwCcnJzOzs73 Smv/Y2P/zjH///8dghBMAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsT AQCanBgAAAAHdElNRQfmAxgRMR1me/WzAAAAN0lEQVQI12NgAAFBATDFwJiWCGWIQRkihVCGeCFE DaN7EZRRLghVUq6IqoRBsAjGgCphgPIJAwBLAAY82pfN3gAAAABJRU5ErkJggg== "
id="image4544"
x="140.75833"
y="215.50833" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -69,8 +69,8 @@ public class DebuggerPcodeStepperPluginScreenShots extends GhidraScreenShotGener
PcodeExecutor<byte[]> exe =
TraceSleighUtils.buildByteExecutor(tb.trace, snap0, thread, 0);
exe.executeLine("RIP = 0x00400000");
exe.executeLine("RSP = 0x0010fff8");
exe.executeSleighLine("RIP = 0x00400000");
exe.executeSleighLine("RSP = 0x0010fff8");
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(snap0));
asm.assemble(tb.addr(0x00400000), "SUB RSP,0x40");

View File

@ -63,14 +63,14 @@ public class DebuggerWatchesPluginScreenShots extends GhidraScreenShotGenerator
PcodeExecutor<byte[]> executor0 =
TraceSleighUtils.buildByteExecutor(tb.trace, snap0, thread, 0);
executor0.executeLine("RSP = 0x7ffefff8");
executor0.executeLine("*:4 (RSP+8) = 0x4030201");
executor0.executeSleighLine("RSP = 0x7ffefff8");
executor0.executeSleighLine("*:4 (RSP+8) = 0x4030201");
PcodeExecutor<byte[]> executor1 =
TraceSleighUtils.buildByteExecutor(tb.trace, snap1, thread, 0);
executor1.executeLine("RSP = 0x7ffefff8");
executor1.executeLine("*:4 (RSP+8) = 0x1020304");
executor1.executeLine("*:4 0x7fff0004:8 = 0x4A9A70C8");
executor1.executeSleighLine("RSP = 0x7ffefff8");
executor1.executeSleighLine("*:4 (RSP+8) = 0x1020304");
executor1.executeSleighLine("*:4 0x7fff0004:8 = 0x4A9A70C8");
}
watchesProvider.addWatch("RSP");

View File

@ -583,6 +583,38 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
modelService.addModel(mb.testModel);
}
protected TraceRecorder recordAndWaitSync() throws Exception {
createTestModel();
mb.createTestProcessesAndThreads();
mb.createTestThreadRegisterBanks();
// NOTE: Test mapper uses TOYBE64
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
Register::isBaseRegister);
mb.testProcess1.addRegion(".text", mb.rng(0x00400000, 0x00401000), "rx");
mb.testProcess1.addRegion(".data", mb.rng(0x00600000, 0x00601000), "rw");
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
waitFor(() -> {
TraceThread thread = recorder.getTraceThread(mb.testThread1);
if (thread == null) {
return false;
}
/*
DebuggerRegisterMapper mapper = recorder.getRegisterMapper(thread);
if (mapper == null) {
return false;
}
if (!mapper.getRegistersOnTarget().containsAll(baseRegs)) {
return false;
}
*/
return true;
});
return recorder;
}
protected void nop() {
}

View File

@ -0,0 +1,231 @@
/* ###
* 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.editing;
import static org.junit.Assert.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.util.Set;
import org.junit.Test;
import com.google.common.collect.Range;
import docking.ActionContext;
import docking.action.DockingActionIf;
import docking.dnd.GClipboard;
import docking.widgets.OptionDialog;
import ghidra.app.plugin.core.assembler.AssemblerPlugin;
import ghidra.app.plugin.core.assembler.AssemblerPluginTestHelper;
import ghidra.app.plugin.core.clipboard.ClipboardPlugin;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.services.DebuggerStateEditingService;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.program.model.data.ShortDataType;
import ghidra.program.model.listing.Instruction;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.program.TraceVariableSnapProgramView;
import ghidra.util.database.UndoableTransaction;
/**
* Tests for editing machine state that don't naturally fit elsewhere.
*
* <p>
* In these and other machine-state-editing integration tests, we use
* {@link StateEditingMode#WRITE_EMULATOR} as a stand-in for any mode. We also use
* {@link StateEditingMode#READ_ONLY} just to verify the mode is heeded. Other modes may be tested
* if bugs crop up in various combinations.
*/
public class DebuggerStateEditingPluginIntegrationTest extends AbstractGhidraHeadedDebuggerGUITest {
@Test
public void testPatchInstructionActionInDynamicListingEmu() throws Throwable {
DebuggerListingPlugin listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
AssemblerPlugin assemblerPlugin = addPlugin(tool, AssemblerPlugin.class);
DebuggerStateEditingPlugin editingPlugin =
addPlugin(tool, DebuggerStateEditingPlugin.class);
DebuggerStateEditingService editingService =
tool.getService(DebuggerStateEditingService.class);
assertFalse(editingPlugin.actionEditMode.isEnabled());
createAndOpenTrace();
TraceVariableSnapProgramView view = tb.trace.getProgramView();
try (UndoableTransaction tid = tb.startTransaction()) {
tb.getOrAddThread("Threads[0]", 0);
tb.trace.getMemoryManager()
.createRegion("Memory[bin:.text]", 0, tb.range(0x00400000, 0x00401000),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
}
CodeViewerProvider listingProvider = listingPlugin.getProvider();
AssemblerPluginTestHelper helper =
new AssemblerPluginTestHelper(assemblerPlugin, listingProvider, view);
traceManager.activateTrace(tb.trace);
waitForSwing();
assertTrue(editingPlugin.actionEditMode.isEnabled());
runSwing(() -> editingPlugin.actionEditMode
.setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
assertFalse(
helper.patchInstructionAction.isAddToPopup(listingProvider.getActionContext(null)));
runSwing(() -> editingPlugin.actionEditMode
.setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
assertTrue(
helper.patchInstructionAction.isAddToPopup(listingProvider.getActionContext(null)));
Instruction ins = helper.patchInstructionAt(tb.addr(0x00400123), "", "imm r0,#1234");
assertEquals(2, ins.getLength());
long snap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(snap));
byte[] bytes = new byte[2];
view.getMemory().getBytes(tb.addr(0x00400123), bytes);
assertArrayEquals(tb.arr(0x40, 1234), bytes);
}
@Test
public void testPatchDataActionInDynamicListingEmu() throws Throwable {
DebuggerListingPlugin listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
AssemblerPlugin assemblerPlugin = addPlugin(tool, AssemblerPlugin.class);
DebuggerStateEditingPlugin editingPlugin =
addPlugin(tool, DebuggerStateEditingPlugin.class);
DebuggerStateEditingService editingService =
tool.getService(DebuggerStateEditingService.class);
assertFalse(editingPlugin.actionEditMode.isEnabled());
createAndOpenTrace();
TraceVariableSnapProgramView view = tb.trace.getProgramView();
try (UndoableTransaction tid = tb.startTransaction()) {
tb.getOrAddThread("Threads[0]", 0);
tb.trace.getMemoryManager()
.createRegion("Memory[bin:.text]", 0, tb.range(0x00400000, 0x00401000),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
tb.trace.getCodeManager()
.definedData()
.create(Range.atLeast(0L), tb.addr(0x00400123), ShortDataType.dataType);
}
CodeViewerProvider listingProvider = listingPlugin.getProvider();
AssemblerPluginTestHelper helper =
new AssemblerPluginTestHelper(assemblerPlugin, listingProvider, view);
traceManager.activateTrace(tb.trace);
waitForSwing();
assertTrue(editingPlugin.actionEditMode.isEnabled());
runSwing(() -> editingPlugin.actionEditMode
.setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
assertFalse(helper.patchDataAction.isAddToPopup(listingProvider.getActionContext(null)));
runSwing(() -> editingPlugin.actionEditMode
.setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
goTo(listingProvider.getListingPanel(), new ProgramLocation(view, tb.addr(0x00400123)));
assertTrue(helper.patchDataAction.isAddToPopup(listingProvider.getActionContext(null)));
/**
* TODO: There's a bug in the trace forking: Data units are not replaced when bytes changed.
* Thus, we'll make no assertions about the data unit.
*/
/*Data data =*/ helper.patchDataAt(tb.addr(0x00400123), "0h", "5h");
// assertEquals(2, data.getLength());
long snap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(snap));
byte[] bytes = new byte[2];
view.getMemory().getBytes(tb.addr(0x00400123), bytes);
assertArrayEquals(tb.arr(0, 5), bytes);
}
@Test
public void testPasteActionInDynamicListingEmu() throws Throwable {
DebuggerListingPlugin listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
DebuggerStateEditingPlugin editingPlugin =
addPlugin(tool, DebuggerStateEditingPlugin.class);
addPlugin(tool, ClipboardPlugin.class);
DebuggerStateEditingService editingService =
tool.getService(DebuggerStateEditingService.class);
CodeViewerProvider listingProvider = listingPlugin.getProvider();
DockingActionIf pasteAction = getLocalAction(listingProvider, "Paste");
assertFalse(editingPlugin.actionEditMode.isEnabled());
createAndOpenTrace();
TraceVariableSnapProgramView view = tb.trace.getProgramView();
try (UndoableTransaction tid = tb.startTransaction()) {
tb.getOrAddThread("Threads[0]", 0);
tb.trace.getMemoryManager()
.createRegion("Memory[bin:.text]", 0, tb.range(0x00400000, 0x00401000),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
}
traceManager.activateTrace(tb.trace);
waitForSwing();
ActionContext ctx;
assertTrue(editingPlugin.actionEditMode.isEnabled());
runSwing(() -> editingPlugin.actionEditMode
.setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
ctx = listingProvider.getActionContext(null);
assertTrue(pasteAction.isAddToPopup(ctx));
assertFalse(pasteAction.isEnabledForContext(ctx));
runSwing(() -> editingPlugin.actionEditMode
.setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
goTo(listingPlugin.getListingPanel(), new ProgramLocation(view, tb.addr(0x00400123)));
ctx = listingProvider.getActionContext(null);
assertTrue(pasteAction.isAddToPopup(ctx));
assertFalse(pasteAction.isEnabledForContext(ctx));
Clipboard clipboard = GClipboard.getSystemClipboard();
clipboard.setContents(new StringSelection("12 34 56 78"), null);
ctx = listingProvider.getActionContext(null);
assertTrue(pasteAction.isAddToPopup(ctx));
assertTrue(pasteAction.isEnabledForContext(ctx));
performAction(pasteAction, listingProvider, false);
OptionDialog confirm = waitForDialogComponent(OptionDialog.class);
pressButtonByText(confirm, "Yes");
byte[] bytes = new byte[4];
waitForPass(noExc(() -> {
long snap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(snap));
view.getMemory().getBytes(tb.addr(0x00400123), bytes);
assertArrayEquals(tb.arr(0x12, 0x34, 0x56, 0x78), bytes);
}));
}
}

View File

@ -236,7 +236,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
createAndOpenTrace();
TraceThread thread1;
TraceThread thread2;
DebuggerListingProvider extraProvider = SwingExecutorService.INSTANCE
DebuggerListingProvider extraProvider = SwingExecutorService.LATER
.submit(() -> listingPlugin.createListingIfMissing(trackPc, true))
.get();
try (UndoableTransaction tid = tb.startTransaction()) {

View File

@ -21,31 +21,38 @@ import static org.junit.Assert.*;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Objects;
import org.junit.*;
import org.junit.experimental.categories.Category;
import com.google.common.collect.Range;
import docking.ActionContext;
import docking.action.DockingActionIf;
import docking.dnd.GClipboard;
import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction;
import docking.widgets.EventTrigger;
import docking.widgets.OptionDialog;
import generic.test.category.NightlyCategory;
import ghidra.GhidraOptions;
import ghidra.app.plugin.core.byteviewer.ByteViewerComponent;
import ghidra.app.plugin.core.byteviewer.ByteViewerPanel;
import ghidra.app.plugin.core.clipboard.ClipboardPlugin;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
import ghidra.app.services.DebuggerStateEditingService;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.TraceRecorder;
import ghidra.async.SwingExecutorService;
import ghidra.program.model.address.*;
@ -70,11 +77,15 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
protected DebuggerMemoryBytesPlugin memBytesPlugin;
protected DebuggerMemoryBytesProvider memBytesProvider;
protected DebuggerStateEditingService editingService;
@Before
public void setUpMemoryBytesProviderTest() throws Exception {
memBytesPlugin = addPlugin(tool, DebuggerMemoryBytesPlugin.class);
memBytesProvider = waitForComponentProvider(DebuggerMemoryBytesProvider.class);
memBytesProvider.setVisible(true);
editingService = addPlugin(tool, DebuggerStateEditingServicePlugin.class);
}
protected void goToDyn(Address address) {
@ -236,7 +247,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
createAndOpenTrace();
TraceThread thread1;
TraceThread thread2;
DebuggerMemoryBytesProvider extraProvider = SwingExecutorService.INSTANCE
DebuggerMemoryBytesProvider extraProvider = SwingExecutorService.LATER
.submit(() -> memBytesPlugin.createViewerIfMissing(trackPc, true))
.get();
try (UndoableTransaction tid = tb.startTransaction()) {
@ -1068,7 +1079,6 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
}
@Test
@Ignore("TODO")
public void testEditLiveBytesWritesTarget() throws Exception {
createTestModel();
mb.createTestProcessesAndThreads();
@ -1077,19 +1087,15 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
editingService.setCurrentMode(trace, StateEditingMode.WRITE_TARGET);
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
goToDyn(addr(trace, 0x55550800));
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
performAction(actionEdit);
Robot robot = new Robot();
robot.keyPress(KeyEvent.VK_4);
robot.keyRelease(KeyEvent.VK_4);
robot.keyPress(KeyEvent.VK_2);
robot.keyRelease(KeyEvent.VK_2);
triggerText(memBytesProvider.getByteViewerPanel().getCurrentComponent(), "42");
performAction(actionEdit);
byte[] data = new byte[4];
@ -1100,8 +1106,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
}
@Test
@Ignore("TODO")
public void testEditPastBytesWritesNotTarget() throws Exception {
public void testEditTraceBytesWritesNotTarget() throws Exception {
createTestModel();
mb.createTestProcessesAndThreads();
@ -1109,31 +1114,22 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
editingService.setCurrentMode(trace, StateEditingMode.WRITE_TRACE);
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
TraceSnapshot present = recorder.forceSnapshot();
goToDyn(addr(trace, 0x55550800));
long snap = present.getKey() - 1;
traceManager.activateSnap(snap);
waitForSwing();
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
performAction(actionEdit);
Robot robot = new Robot();
robot.keyPress(KeyEvent.VK_4);
robot.keyRelease(KeyEvent.VK_4);
robot.keyPress(KeyEvent.VK_2);
robot.keyRelease(KeyEvent.VK_2);
triggerText(memBytesProvider.getByteViewerPanel().getCurrentComponent(), "42");
performAction(actionEdit);
byte[] data = new byte[4];
AddressSpace space = trace.getBaseAddressFactory().getDefaultAddressSpace();
trace.getMemoryManager()
.getBytes(snap, space.getAddress(0x55550800), ByteBuffer.wrap(data));
.getBytes(traceManager.getCurrentSnap(), space.getAddress(0x55550800),
ByteBuffer.wrap(data));
assertArrayEquals(mb.arr(0x42, 0, 0, 0), data);
// Verify the target was not touched
Arrays.fill(data, (byte) 0); // test model uses semisparse array
@ -1144,8 +1140,10 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
}
@Test
@Ignore("TODO")
public void testPasteLiveBytesWritesTarget() throws Exception {
addPlugin(tool, ClipboardPlugin.class);
ActionContext ctx;
createTestModel();
mb.createTestProcessesAndThreads();
@ -1153,6 +1151,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
editingService.setCurrentMode(trace, StateEditingMode.WRITE_TARGET);
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
@ -1160,10 +1160,19 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
performAction(actionEdit);
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
Clipboard clipboard = GClipboard.getSystemClipboard();
clipboard.setContents(new StringSelection("42 53 64 75"), null);
DockingActionIf actionPaste = getAction(memBytesPlugin, "Paste");
performAction(actionPaste);
DockingActionIf actionPaste =
Objects.requireNonNull(getLocalAction(memBytesProvider, "Paste"));
ctx = waitForValue(() -> memBytesProvider.getActionContext(null));
assertTrue(actionPaste.isAddToPopup(ctx));
assertTrue(actionPaste.isEnabledForContext(ctx));
performAction(actionPaste, memBytesProvider, false);
OptionDialog confirm = waitForDialogComponent(OptionDialog.class);
pressButtonByText(confirm, "Yes");
performAction(actionEdit);
byte[] data = new byte[4];

View File

@ -75,7 +75,7 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg
thread = tb.getOrAddThread("1", 0);
PcodeExecutor<byte[]> init = TraceSleighUtils.buildByteExecutor(tb.trace, 0, thread, 0);
init.executeLine("pc = 0x00400000");
init.executeSleighLine("pc = 0x00400000");
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
iit = asm.assemble(start,

View File

@ -33,15 +33,17 @@ 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.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider.RegisterTableColumns;
import ghidra.app.services.ActionSource;
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
import ghidra.app.services.DebuggerStateEditingService;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncTestUtils;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.listing.DBTraceCodeRegisterSpace;
import ghidra.trace.model.Trace;
import ghidra.trace.model.listing.*;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
@ -56,6 +58,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
protected DebuggerRegistersPlugin registersPlugin;
protected DebuggerRegistersProvider registersProvider;
protected DebuggerListingPlugin listingPlugin;
protected DebuggerStateEditingService editingService;
protected Register r0;
protected Register pc;
@ -76,6 +79,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
registersPlugin = addPlugin(tool, DebuggerRegistersPlugin.class);
registersProvider = waitForComponentProvider(DebuggerRegistersProvider.class);
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
editingService = addPlugin(tool, DebuggerStateEditingServicePlugin.class);
createTrace();
r0 = tb.language.getRegister("r0");
@ -138,36 +142,6 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
}
}
protected TraceRecorder recordAndWaitSync() throws Exception {
createTestModel();
mb.createTestProcessesAndThreads();
mb.createTestThreadRegisterBanks();
// NOTE: Test mapper uses TOYBE64
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
Register::isBaseRegister);
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
waitFor(() -> {
TraceThread thread = recorder.getTraceThread(mb.testThread1);
if (thread == null) {
return false;
}
/*
DebuggerRegisterMapper mapper = recorder.getRegisterMapper(thread);
if (mapper == null) {
return false;
}
if (!mapper.getRegistersOnTarget().containsAll(baseRegs)) {
return false;
}
*/
return true;
});
return recorder;
}
protected RegisterRow findRegisterRow(Register reg) {
RegisterRow row = getRegisterRow(reg);
if (row == null) {
@ -388,13 +362,15 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
// TODO: Make contextreg modifiable by Registers window
@Test
public void testDeadModifyValueEmulates() throws Exception {
public void testModifyValueEmu() throws Exception {
traceManager.openTrace(tb.trace);
TraceThread thread = addThread();
traceManager.activateThread(thread);
waitForSwing();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
assertTrue(registersProvider.actionEnableEdits.isEnabled());
performAction(registersProvider.actionEnableEdits);
@ -410,56 +386,13 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
waitForSwing();
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap));
assertEquals(BigInteger.valueOf(0x1234),
regVals.getValue(viewSnap, r0).getUnsignedValue());
assertEquals(BigInteger.valueOf(0x1234), row.getValue());
});
}
@Test
public void testLiveModifyValueAffectsTarget() throws Exception {
TraceRecorder recorder = recordAndWaitSync();
traceManager.openTrace(recorder.getTrace());
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
waitForSwing();
assertTrue(registersProvider.actionEnableEdits.isEnabled());
performAction(registersProvider.actionEnableEdits);
RegisterRow row = findRegisterRow(r0);
assertTrue(row.isValueEditable());
setRowText(row, "0102030405060708");
waitForSwing();
assertArrayEquals(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }, mb.testBank1.regVals.get("r0"));
}
@Test
public void testLiveModifySubValueAffectsTarget() throws Throwable {
TraceRecorder recorder = recordAndWaitSync();
Trace trace = recorder.getTrace();
traceManager.openTrace(trace);
TraceThread thread = recorder.getTraceThread(mb.testThread1);
traceManager.activateThread(thread);
waitForSwing();
assertTrue(registersProvider.actionEnableEdits.isEnabled());
performAction(registersProvider.actionEnableEdits);
mb.testBank1.writeRegistersNamed(Map.of("r0", new byte[] { 0 }));
waitOn(mb.testModel.flushEvents());
waitForDomainObject(trace);
RegisterRow rowL = findRegisterRow(r0l);
waitForPass(() -> assertTrue(rowL.isValueEditable()));
setRowText(rowL, "05060708");
waitForSwing();
assertArrayEquals(new byte[] { 0, 0, 0, 0, 5, 6, 7, 8 }, mb.testBank1.regVals.get("r0"));
}
// NOTE: Value modification only allowed on live target
@Test

View File

@ -33,9 +33,10 @@ 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.register.*;
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.services.ActionSource;
import ghidra.app.services.TraceRecorder;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.async.AsyncTestUtils;
import ghidra.dbg.model.TestTargetRegisterBankInThread;
import ghidra.program.model.address.*;
@ -44,6 +45,7 @@ import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.mem.Memory;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.model.DefaultTraceLocation;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.*;
@ -69,6 +71,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
protected DebuggerListingProvider listingProvider;
protected DebuggerStaticMappingServicePlugin mappingService;
protected CodeViewerProvider codeViewerProvider;
protected DebuggerStateEditingService editingService;
protected Register r0;
protected Register r1;
@ -88,6 +91,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
listingProvider = waitForComponentProvider(DebuggerListingProvider.class);
mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class);
editingService = addPlugin(tool, DebuggerStateEditingServicePlugin.class);
createTrace();
r0 = tb.language.getRegister("r0");
@ -259,7 +263,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertNoErr(row);
}
protected void runTestDeadIsEditable(String expression, boolean expectWritable) {
protected void runTestIsEditableEmu(String expression, boolean expectWritable) {
setRegisterValues(thread);
performAction(watchesProvider.actionAdd);
@ -269,6 +273,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertFalse(row.isValueEditable());
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
waitForSwing();
assertNoErr(row);
@ -279,21 +284,21 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
}
@Test
public void testDeadIsRegisterEditable() {
runTestDeadIsEditable("r0", true);
public void testIsRegisterEditableEmu() {
runTestIsEditableEmu("r0", true);
}
@Test
public void testDeadIsUniqueEditable() {
runTestDeadIsEditable("r0 + 8", false);
public void testIsUniqueEditableEmu() {
runTestIsEditableEmu("r0 + 8", false);
}
@Test
public void testDeadIsMemoryEditable() {
runTestDeadIsEditable("*:8 r0", true);
public void testIsMemoryEditableEmu() {
runTestIsEditableEmu("*:8 r0", true);
}
protected WatchRow prepareTestDeadEdit(String expression) {
protected WatchRow prepareTestEditEmu(String expression) {
setRegisterValues(thread);
performAction(watchesProvider.actionAdd);
@ -302,20 +307,23 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
performAction(watchesProvider.actionEnableEdits);
return row;
}
@Test
public void testDeadEditRegister() {
WatchRow row = prepareTestDeadEdit("r0");
public void testEditRegisterEmu() {
WatchRow row = prepareTestEditEmu("r0");
TraceMemoryRegisterSpace regVals =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
row.setRawValueString("0x1234");
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap));
assertEquals(BigInteger.valueOf(0x1234),
regVals.getValue(viewSnap, r0).getUnsignedValue());
assertEquals("0x1234", row.getRawValueString());
@ -324,6 +332,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
row.setRawValueString("1234"); // Decimal this time
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap));
assertEquals(BigInteger.valueOf(1234),
regVals.getValue(viewSnap, r0).getUnsignedValue());
assertEquals("0x4d2", row.getRawValueString());
@ -331,14 +340,15 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
}
@Test
public void testDeadEditMemory() {
WatchRow row = prepareTestDeadEdit("*:8 r0");
public void testEditMemoryEmu() {
WatchRow row = prepareTestEditEmu("*:8 r0");
TraceMemoryOperations mem = tb.trace.getMemoryManager();
ByteBuffer buf = ByteBuffer.allocate(8);
row.setRawValueString("0x1234");
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap));
buf.clear();
mem.getBytes(viewSnap, tb.addr(0x00400000), buf);
buf.flip();
@ -348,6 +358,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
row.setRawValueString("{ 12 34 56 78 9a bc de f0 }");
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap));
buf.clear();
mem.getBytes(viewSnap, tb.addr(0x00400000), buf);
buf.flip();
@ -355,7 +366,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
});
}
protected WatchRow prepareTestLiveEdit(String expression) throws Exception {
protected WatchRow prepareTestEditTarget(String expression) throws Exception {
createTestModel();
mb.createTestProcessesAndThreads();
bank = mb.testThread1.addRegisterBank();
@ -372,6 +383,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(trace);
traceManager.activateThread(thread);
editingService.setCurrentMode(trace, StateEditingMode.WRITE_TARGET);
waitForSwing();
performAction(watchesProvider.actionAdd);
@ -383,44 +395,35 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
}
@Test
public void testLiveEditRegister() throws Throwable {
WatchRow row = prepareTestLiveEdit("r0");
public void testEditRegisterTarget() throws Throwable {
WatchRow row = prepareTestEditTarget("r0");
row.setRawValueString("0x1234");
retryVoid(() -> {
assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34), bank.regVals.get("r0"));
assertArrayEquals(mb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34), bank.regVals.get("r0"));
}, List.of(AssertionError.class));
}
@Test
public void testLiveEditMemory() throws Throwable {
WatchRow row = prepareTestLiveEdit("*:8 r0");
public void testEditMemoryTarget() throws Throwable {
WatchRow row = prepareTestEditTarget("*:8 r0");
row.setRawValueString("0x1234");
retryVoid(() -> {
assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34),
waitOn(mb.testProcess1.memory.readMemory(tb.addr(0x00400000), 8)));
waitOn(mb.testProcess1.memory.readMemory(mb.addr(0x00400000), 8)));
}, List.of(AssertionError.class));
}
@Test
public void testLiveEditNonMappableRegister() throws Throwable {
WatchRow row = prepareTestLiveEdit("r1");
@Test(expected = IllegalArgumentException.class)
public void testEditNonMappableRegisterTarget() throws Throwable {
WatchRow row = prepareTestEditTarget("r1");
TraceThread thread = recorder.getTraceThread(mb.testThread1);
// Sanity check
assertFalse(recorder.isRegisterOnTarget(thread, r1));
assertFalse(row.isValueEditable());
row.setRawValueString("0x1234");
waitForPass(() -> {
TraceMemoryRegisterSpace regs =
recorder.getTrace().getMemoryManager().getMemoryRegisterSpace(thread, false);
assertNotNull(regs);
long viewSnap = traceManager.getCurrent().getViewSnap();
assertEquals(BigInteger.valueOf(0x1234),
regs.getValue(viewSnap, r1).getUnsignedValue());
});
assertFalse(bank.regVals.containsKey("r1"));
}
protected void setupUnmappedDataSection() throws Throwable {

View File

@ -0,0 +1,443 @@
/* ###
* 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.service.editing;
import static org.junit.Assert.*;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import org.junit.Before;
import org.junit.Test;
import ghidra.app.plugin.assembler.Assembler;
import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.services.DebuggerStateEditingService;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncTestUtils;
import ghidra.dbg.target.TargetRegisterBank;
import ghidra.pcode.exec.AsyncPcodeExecutor;
import ghidra.pcode.exec.TracePcodeUtils;
import ghidra.program.model.lang.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.database.memory.DBTraceMemoryRegisterSpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.database.UndoableTransaction;
public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebuggerGUITest
implements AsyncTestUtils {
private DebuggerStateEditingService editingService;
private Register r0;
private Register r0h;
private RegisterValue rv1234;
private RegisterValue rv5678;
private RegisterValue rvHigh1234;
@Before
public void setUpEditorTest() throws Exception {
editingService = addPlugin(tool, DebuggerStateEditingServicePlugin.class);
Language toy = getToyBE64Language();
r0 = toy.getRegister("r0");
r0h = toy.getRegister("r0h");
rv1234 = new RegisterValue(r0, BigInteger.valueOf(1234));
rv5678 = new RegisterValue(r0, BigInteger.valueOf(5678));
rvHigh1234 = new RegisterValue(r0h, BigInteger.valueOf(1234));
}
@Test(expected = IllegalArgumentException.class)
public void testWriteEmuMemoryNoThreadErr() throws Throwable {
/**
* TODO: It'd be nice if this worked, since memory edits don't really require a thread
* context. That would require some changes in the TraceSchedule and its execution. IINM,
* each step currently requires a thread. We'd have to relax that for patch steps, and it'd
* only work if they don't refer to any register.
*/
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)));
}
@Test(expected = IllegalArgumentException.class)
public void testWriteEmuRegisterNoThreadErr() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setRegister(rv1234));
}
@Test
public void testWriteEmuMemory() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread
tb.getOrAddThread("Threads[0]", 0);
}
waitForSwing();
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)));
waitForSwing();
DebuggerCoordinates current = traceManager.getCurrent();
long snap = current.getViewSnap();
assertTrue(DBTraceUtils.isScratch(snap));
ByteBuffer buf = ByteBuffer.allocate(4);
tb.trace.getMemoryManager().getBytes(snap, tb.addr(0x00400000), buf);
assertArrayEquals(tb.arr(1, 2, 3, 4), buf.array());
}
@Test
public void testWriteEmuRegister() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
TraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread
thread = tb.getOrAddThread("Threads[0]", 0);
}
waitForSwing();
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setRegister(rv1234));
waitForSwing();
DebuggerCoordinates current = traceManager.getCurrent();
long snap = current.getViewSnap();
assertTrue(DBTraceUtils.isScratch(snap));
RegisterValue value =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false).getValue(snap, r0);
assertEquals(rv1234, value);
}
@Test
public void testWriteEmuMemoryAfterStep() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread
TraceThread thread = tb.getOrAddThread("Threads[0]", 0);
AsyncPcodeExecutor<byte[]> executor =
TracePcodeUtils.executorForCoordinates(
DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0));
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
asm.assemble(tb.addr(0x00400000), "imm r0,#123");
executor.executeSleighLine("pc = 0x00400000");
}
waitForSwing();
TraceSchedule step1 = TraceSchedule.parse("0:t0-1");
traceManager.activateTime(step1);
waitForPass(() -> assertEquals(step1, traceManager.getCurrent().getTime()));
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setVariable(tb.addr(0x00600000), tb.arr(1, 2, 3, 4)));
waitForSwing();
DebuggerCoordinates current = traceManager.getCurrent();
assertEquals(0, current.getSnap().longValue()); // Chain edits, don't source from scratch
long snap = current.getViewSnap();
assertTrue(DBTraceUtils.isScratch(snap));
ByteBuffer buf = ByteBuffer.allocate(4);
tb.trace.getMemoryManager().getBytes(snap, tb.addr(0x00600000), buf);
assertArrayEquals(tb.arr(1, 2, 3, 4), buf.array());
}
@Test
public void testWriteEmuRegisterAfterStep() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
TraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread
thread = tb.getOrAddThread("Threads[0]", 0);
AsyncPcodeExecutor<byte[]> executor =
TracePcodeUtils.executorForCoordinates(
DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0));
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
asm.assemble(tb.addr(0x00400000), "imm r0,#123");
executor.executeSleighLine("pc = 0x00400000");
}
waitForSwing();
TraceSchedule step1 = TraceSchedule.parse("0:t0-1");
traceManager.activateTime(step1);
waitForPass(() -> assertEquals(step1, traceManager.getCurrent().getTime()));
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setRegister(rv1234));
waitForSwing();
DebuggerCoordinates current = traceManager.getCurrent();
assertEquals(0, current.getSnap().longValue()); // Chain edits, don't source from scratch
long snap = current.getViewSnap();
assertTrue(DBTraceUtils.isScratch(snap));
RegisterValue value =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false).getValue(snap, r0);
assertEquals(rv1234, value);
}
@Test
public void testWriteEmuMemoryTwice() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread
tb.getOrAddThread("Threads[0]", 0);
}
waitForSwing();
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)));
waitOn(editor.setVariable(tb.addr(0x00400002), tb.arr(5, 6, 7, 8)));
waitForSwing();
DebuggerCoordinates current = traceManager.getCurrent();
long snap = current.getViewSnap();
assertTrue(DBTraceUtils.isScratch(snap));
assertEquals(1, current.getTime().patchCount()); // Check coalesced
ByteBuffer buf = ByteBuffer.allocate(6);
tb.trace.getMemoryManager().getBytes(snap, tb.addr(0x00400000), buf);
assertArrayEquals(tb.arr(1, 2, 5, 6, 7, 8), buf.array());
}
@Test
public void testWriteEmuRegisterTwice() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
TraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread
thread = tb.getOrAddThread("Threads[0]", 0);
}
waitForSwing();
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setRegister(rv1234));
waitOn(editor.setRegister(rv5678));
waitForSwing();
DebuggerCoordinates current = traceManager.getCurrent();
long snap = current.getViewSnap();
assertTrue(DBTraceUtils.isScratch(snap));
assertEquals(1, current.getTime().patchCount()); // Check coalesced
RegisterValue value =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false).getValue(snap, r0);
assertEquals(rv5678, value);
}
@Test
public void testWriteTraceMemory() throws Throwable {
// NB. Definitely no thread required
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
// NB. Editor creates its own transaction
waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)));
waitForSwing();
DebuggerCoordinates current = traceManager.getCurrent();
long snap = current.getViewSnap();
assertEquals(0, snap);
ByteBuffer buf = ByteBuffer.allocate(4);
tb.trace.getMemoryManager().getBytes(snap, tb.addr(0x00400000), buf);
assertArrayEquals(tb.arr(1, 2, 3, 4), buf.array());
}
@Test(expected = IllegalArgumentException.class)
public void testWriteTraceRegisterNoThreadErr() throws Throwable {
// NB. Definitely no thread required
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
// NB. Editor creates its own transaction
waitOn(editor.setRegister(rv1234));
}
@Test
public void testWriteTraceRegister() throws Throwable {
// NB. Definitely no thread required
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
TraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread
thread = tb.getOrAddThread("Threads[0]", 0);
}
waitForSwing();
StateEditor editor = editingService.createStateEditor(tb.trace);
// NB. Editor creates its own transaction
waitOn(editor.setRegister(rv1234));
waitForSwing();
DebuggerCoordinates current = traceManager.getCurrent();
long snap = current.getViewSnap();
assertEquals(0, snap);
RegisterValue value =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false).getValue(snap, r0);
assertEquals(rv1234, value);
}
@Test
public void testWriteTargetMemory() throws Throwable {
TraceRecorder recorder = recordAndWaitSync();
traceManager.openTrace(recorder.getTrace());
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
waitForSwing();
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)));
assertArrayEquals(mb.arr(1, 2, 3, 4),
waitOn(mb.testProcess1.memory.readMemory(mb.addr(0x00400000), 4)));
}
@Test
public void testWriteTargetRegister() throws Throwable {
TraceRecorder recorder = recordAndWaitSync();
TargetRegisterBank bank =
(TargetRegisterBank) mb.testThread1.getCachedAttribute("RegisterBank");
traceManager.openTrace(recorder.getTrace());
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
waitForSwing();
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setRegister(rv1234));
assertArrayEquals(mb.arr(0, 0, 0, 0, 0, 0, 4, 0xd2), waitOn(bank.readRegister("r0")));
}
@Test
public void testWriteTargetSubRegister() throws Throwable {
TraceRecorder recorder = recordAndWaitSync();
TargetRegisterBank bank =
(TargetRegisterBank) mb.testThread1.getCachedAttribute("RegisterBank");
traceManager.openTrace(recorder.getTrace());
TraceThread thread = recorder.getTraceThread(mb.testThread1);
traceManager.activateThread(thread);
waitForSwing();
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setRegister(rv1234));
waitForPass(() -> {
DBTraceMemoryRegisterSpace regs = tb.trace.getMemoryManager()
.getMemoryRegisterSpace(thread, false);
assertNotNull(regs);
RegisterValue value = regs.getValue(traceManager.getCurrentSnap(), r0);
assertEquals(rv1234, value);
});
waitOn(editor.setRegister(rvHigh1234));
assertArrayEquals(mb.arr(0, 0, 4, 0xd2, 0, 0, 4, 0xd2), waitOn(bank.readRegister("r0")));
}
@Test(expected = MemoryAccessException.class)
public void testWriteTargetMemoryNotPresentErr() throws Throwable {
TraceRecorder recorder = recordAndWaitSync();
traceManager.openTrace(recorder.getTrace());
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
waitForSwing();
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
traceManager.activateSnap(traceManager.getCurrentSnap() - 1);
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)));
}
@Test(expected = MemoryAccessException.class)
public void testWriteTargetRegisterNotPresentErr() throws Throwable {
TraceRecorder recorder = recordAndWaitSync();
traceManager.openTrace(recorder.getTrace());
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
waitForSwing();
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
traceManager.activateSnap(traceManager.getCurrentSnap() - 1);
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setRegister(rv1234));
}
@Test(expected = MemoryAccessException.class)
public void testWriteTargetMemoryNotAliveErr() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TARGET);
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)));
}
@Test(expected = MemoryAccessException.class)
public void testWriteTargetRegisterNotAliveErr() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TARGET);
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setRegister(rv1234));
}
@Test(expected = MemoryAccessException.class)
public void testWriteReadOnlyMemoryErr() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.READ_ONLY);
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)));
}
@Test(expected = MemoryAccessException.class)
public void testWriteReadOnlyRegisterErr() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.READ_ONLY);
StateEditor editor = editingService.createStateEditor(tb.trace);
waitOn(editor.setRegister(rv1234));
}
}

View File

@ -409,7 +409,7 @@ public interface AsyncUtils<T> {
Cleaner CLEANER = Cleaner.create();
ExecutorService FRAMEWORK_EXECUTOR = Executors.newWorkStealingPool();
ExecutorService SWING_EXECUTOR = SwingExecutorService.INSTANCE;
ExecutorService SWING_EXECUTOR = SwingExecutorService.LATER;
CompletableFuture<Void> NIL = CompletableFuture.completedFuture(null);

View File

@ -22,14 +22,30 @@ import javax.swing.SwingUtilities;
import ghidra.async.seq.AsyncSequenceActionRuns;
import ghidra.async.seq.AsyncSequenceWithoutTemp;
import ghidra.util.Swing;
/**
* A wrapper for {@link SwingUtilities#invokeLater(Runnable)} that implements
* {@link ExecutorService}. This makes it a suitable first parameter to
* {@link AsyncSequenceWithoutTemp#then(ExecutorService, AsyncSequenceActionRuns)} and similar.
*/
public class SwingExecutorService extends AbstractExecutorService {
public static final SwingExecutorService INSTANCE = new SwingExecutorService();
public abstract class SwingExecutorService extends AbstractExecutorService {
public static final SwingExecutorService LATER = new SwingExecutorService() {
@Override
public void execute(Runnable command) {
SwingUtilities.invokeLater(command);
}
};
/**
* Wraps {@link Swing#runIfSwingOrRunLater(Runnable)} instead
*/
public static final SwingExecutorService MAYBE_NOW = new SwingExecutorService() {
@Override
public void execute(Runnable command) {
Swing.runIfSwingOrRunLater(command);
}
};
private SwingExecutorService() {
}
@ -59,8 +75,4 @@ public class SwingExecutorService extends AbstractExecutorService {
throw new UnsupportedOperationException();
}
@Override
public void execute(Runnable command) {
SwingUtilities.invokeLater(command);
}
}

View File

@ -18,8 +18,6 @@ package ghidra.async;
import java.util.Collection;
import java.util.concurrent.*;
import org.apache.commons.lang3.exception.ExceptionUtils;
import ghidra.async.AsyncUtils.TemperamentalRunnable;
import ghidra.async.AsyncUtils.TemperamentalSupplier;
import ghidra.util.Msg;
@ -30,7 +28,7 @@ public interface AsyncTestUtils {
SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
static final long RETRY_INTERVAL_MS = 100;
default <T> T waitOnNoValidate(CompletableFuture<T> future) {
default <T> T waitOnNoValidate(CompletableFuture<T> future) throws Throwable {
// Do this instead of plain ol' .get(time), to ease debugging
// When suspended in .get(time), you can't introspect much, otherwise
long started = System.currentTimeMillis();
@ -40,15 +38,11 @@ public interface AsyncTestUtils {
}
catch (TimeoutException e) {
if (Long.compareUnsigned(System.currentTimeMillis() - started, TIMEOUT_MS) >= 0) {
throw new RuntimeException(AsyncUtils.unwrapThrowable(e));
throw e;
}
}
catch (Exception e) {
Throwable unwrapped = AsyncUtils.unwrapThrowable(e);
if (unwrapped instanceof RuntimeException) {
throw (RuntimeException) unwrapped;
}
return ExceptionUtils.rethrow(e);
throw AsyncUtils.unwrapThrowable(e);
}
}
}

View File

@ -54,10 +54,12 @@ import ghidra.trace.database.thread.DBTraceThreadManager;
import ghidra.trace.database.time.DBTraceTimeManager;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.util.TraceChangeManager;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.*;
import ghidra.util.database.*;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.datastruct.WeakValueHashMap;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
@ -135,6 +137,8 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
protected DBTraceVariableSnapProgramView programView;
protected Map<DBTraceVariableSnapProgramView, Void> programViews = new WeakHashMap<>();
protected Map<Long, DBTraceProgramView> fixedProgramViews = new WeakValueHashMap<>();
protected ListenerSet<TraceProgramViewListener> viewListeners =
new ListenerSet<>(TraceProgramViewListener.class);
public DBTrace(String name, CompilerSpec baseCompilerSpec, Object consumer)
throws IOException, LanguageNotFoundException {
@ -562,29 +566,34 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
// NOTE: addListener synchronizes on this and might generate callbacks immediately
public synchronized DBTraceProgramView getFixedProgramView(long snap) {
// NOTE: The new viewport will need to read from the time manager during init
DBTraceProgramView view;
try (LockHold hold = lockRead()) {
synchronized (fixedProgramViews) {
DBTraceProgramView view = fixedProgramViews.computeIfAbsent(snap, t -> {
Msg.debug(this, "Creating fixed view at snap=" + snap);
return new DBTraceProgramView(this, snap, baseCompilerSpec);
});
return view;
view = fixedProgramViews.get(snap);
if (view != null) {
return view;
}
Msg.debug(this, "Creating fixed view at snap=" + snap);
view = new DBTraceProgramView(this, snap, baseCompilerSpec);
}
}
viewListeners.fire.viewCreated(view);
return view;
}
@Override
// NOTE: Ditto getFixedProgramView
public synchronized DBTraceVariableSnapProgramView createProgramView(long snap) {
// NOTE: The new viewport will need to read from the time manager during init
DBTraceVariableSnapProgramView view;
try (LockHold hold = lockRead()) {
synchronized (programViews) {
DBTraceVariableSnapProgramView view =
new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec);
view = new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec);
programViews.put(view, null);
return view;
}
}
viewListeners.fire.viewCreated(view);
return view;
}
@Override
@ -721,6 +730,18 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
return getOptions(TRACE_INFO).getDate(DATE_CREATED, new Date(0));
}
@Override
public Collection<TraceProgramView> getAllProgramViews() {
Collection<TraceProgramView> all = new ArrayList<>();
synchronized (programViews) {
all.addAll(programViews.keySet());
}
synchronized (fixedProgramViews) {
all.addAll(fixedProgramViews.values());
}
return all;
}
protected void allViews(Consumer<DBTraceProgramView> action) {
Collection<DBTraceProgramView> all = new ArrayList<>();
synchronized (programViews) {
@ -734,6 +755,16 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
}
}
@Override
public void addProgramViewListener(TraceProgramViewListener listener) {
viewListeners.add(listener);
}
@Override
public void removeProgramViewListener(TraceProgramViewListener listener) {
viewListeners.remove(listener);
}
public void updateViewsAddRegionBlock(TraceMemoryRegion region) {
allViews(v -> v.updateMemoryAddRegionBlock(region));
}

View File

@ -27,7 +27,8 @@ import ghidra.framework.store.LockException;
import ghidra.program.database.mem.*;
import ghidra.program.model.address.*;
import ghidra.program.model.mem.*;
import ghidra.trace.database.memory.*;
import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.program.TraceProgramView;
@ -47,6 +48,8 @@ public abstract class AbstractDBTraceProgramViewMemory
protected boolean forceFullView = false;
protected long snap;
protected LiveMemoryHandler memoryWriteRedirect;
public AbstractDBTraceProgramViewMemory(DBTraceProgramView program) {
this.program = program;
this.memoryManager = program.trace.getMemoryManager();
@ -155,7 +158,7 @@ public abstract class AbstractDBTraceProgramViewMemory
@Override
public void setLiveMemoryHandler(LiveMemoryHandler handler) {
throw new UnsupportedOperationException();
this.memoryWriteRedirect = handler;
}
@Override
@ -329,6 +332,10 @@ public abstract class AbstractDBTraceProgramViewMemory
@Override
public void setByte(Address addr, byte value) throws MemoryAccessException {
if (memoryWriteRedirect != null) {
memoryWriteRedirect.putByte(addr, value);
return;
}
DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true);
if (space.putBytes(snap, addr, ByteBuffer.wrap(new byte[] { value })) != 1) {
throw new MemoryAccessException();
@ -338,6 +345,10 @@ public abstract class AbstractDBTraceProgramViewMemory
@Override
public void setBytes(Address addr, byte[] source, int sIndex, int size)
throws MemoryAccessException {
if (memoryWriteRedirect != null) {
memoryWriteRedirect.putBytes(addr, source, sIndex, size);
return;
}
DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true);
if (space.putBytes(snap, addr, ByteBuffer.wrap(source, sIndex, size)) != size) {
throw new MemoryAccessException();

View File

@ -369,6 +369,10 @@ public interface Trace extends DataTypeManagerDomainObject {
public static final TraceSnapshotChangeType<Void> DELETED = new TraceSnapshotChangeType<>();
}
public interface TraceProgramViewListener {
void viewCreated(TraceProgramView view);
}
Language getBaseLanguage();
CompilerSpec getBaseCompilerSpec();
@ -412,6 +416,13 @@ public interface Trace extends DataTypeManagerDomainObject {
TraceVariableSnapProgramView createProgramView(long snap);
/**
* Collect all program views, fixed or variable, of this trace.
*
* @return the current set of program views
*/
Collection<TraceProgramView> getAllProgramViews();
/**
* Get the "canonical" program view for this trace
*
@ -423,6 +434,10 @@ public interface Trace extends DataTypeManagerDomainObject {
*/
TraceVariableSnapProgramView getProgramView();
void addProgramViewListener(TraceProgramViewListener listener);
void removeProgramViewListener(TraceProgramViewListener listener);
LockHold lockRead();
LockHold lockWrite();

View File

@ -15,6 +15,7 @@
*/
package ghidra.trace.model.program;
import ghidra.program.model.mem.LiveMemoryHandler;
import ghidra.program.model.mem.Memory;
public interface TraceProgramViewMemory extends Memory, SnapSpecificTraceView {
@ -24,4 +25,13 @@ public interface TraceProgramViewMemory extends Memory, SnapSpecificTraceView {
void setForceFullView(boolean forceFullView);
boolean isForceFullView();
/**
* {@inheritDoc}
*
* <p>
* For trace views, this only redirects memory writes.
*/
@Override
void setLiveMemoryHandler(LiveMemoryHandler handler);
}

View File

@ -15,21 +15,133 @@
*/
package ghidra.trace.model.time.schedule;
import java.util.List;
import java.util.Objects;
import java.math.BigInteger;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.stream.Stream;
import javax.help.UnsupportedOperationException;
import com.google.common.collect.*;
import com.google.common.primitives.UnsignedLong;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.PcodeProgram;
import ghidra.pcode.exec.*;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class PatchStep implements Step {
protected final long threadKey;
protected final String sleigh;
protected final int hashCode;
protected String sleigh;
protected int hashCode;
public static String generateSleigh(Language language, Address address, byte[] data,
int length) {
BigInteger value = Utils.bytesToBigInteger(data, length, language.isBigEndian(), false);
if (address.isMemoryAddress()) {
AddressSpace space = address.getAddressSpace();
if (language.getDefaultSpace() == space) {
return String.format("*:%d 0x%s:%d=0x%s",
length,
address.getOffsetAsBigInteger().toString(16), space.getPointerSize(),
value.toString(16));
}
return String.format("*[%s]:%d 0x%s:%d=0x%s",
space.getName(), length,
address.getOffsetAsBigInteger().toString(16), space.getPointerSize(),
value.toString(16));
}
Register register = language.getRegister(address, length);
if (register == null) {
throw new AssertionError("Can only modify memory or register");
}
return String.format("%s=0x%s", register, value.toString(16));
}
public static String generateSleigh(Language language, Address address, byte[] data) {
return generateSleigh(language, address, data, data.length);
}
protected static List<String> generateSleigh(Language language,
Map<AddressSpace, SemisparseByteArray> patches) {
List<String> result = new ArrayList<>();
for (Entry<AddressSpace, SemisparseByteArray> entry : patches.entrySet()) {
generateSleigh(result, language, entry.getKey(), entry.getValue());
}
return result;
}
protected static void generateSleigh(List<String> result, Language language, AddressSpace space,
SemisparseByteArray array) {
if (space.isRegisterSpace()) {
generateRegisterSleigh(result, language, space, array);
}
else {
generateMemorySleigh(result, language, space, array);
}
}
protected static void generateMemorySleigh(List<String> result, Language language,
AddressSpace space, SemisparseByteArray array) {
byte[] data = new byte[8];
for (Range<UnsignedLong> range : array.getInitialized(0, -1).asRanges()) {
assert range.lowerBoundType() == BoundType.CLOSED;
Address start = space.getAddress(range.lowerEndpoint().longValue());
Address end = space.getAddress(range.upperEndpoint().longValue() -
(range.upperBoundType() == BoundType.OPEN ? 1 : 0));
for (AddressRange chunk : new AddressRangeChunker(start, end, data.length)) {
Address min = chunk.getMinAddress();
int length = (int) chunk.getLength();
array.getData(min.getOffset(), data, 0, length);
result.add(generateSleigh(language, min, data, length));
}
}
}
protected static Range<UnsignedLong> rangeOfRegister(Register r) {
long lower = r.getAddress().getOffset();
long upper = lower + r.getNumBytes();
return Range.closedOpen(UnsignedLong.fromLongBits(lower), UnsignedLong.fromLongBits(upper));
}
protected static boolean isContained(Register r, RangeSet<UnsignedLong> remains) {
return remains.encloses(rangeOfRegister(r));
}
protected static void generateRegisterSleigh(List<String> result, Language language,
AddressSpace space, SemisparseByteArray array) {
byte[] data = new byte[8];
RangeSet<UnsignedLong> remains = TreeRangeSet.create(array.getInitialized(0, -1));
while (!remains.isEmpty()) {
Range<UnsignedLong> span = remains.span();
assert span.lowerBoundType() == BoundType.CLOSED;
Address min = space.getAddress(span.lowerEndpoint().longValue());
Register register = Stream.of(language.getRegisters(min))
.filter(r -> r.getAddress().equals(min))
.filter(r -> r.getNumBytes() <= data.length)
.filter(r -> isContained(r, remains))
.sorted(Comparator.comparing(r -> -r.getNumBytes()))
.findFirst()
.orElse(null);
if (register == null) {
throw new IllegalArgumentException("Could not find a register for " + min);
}
int length = register.getNumBytes();
array.getData(min.getOffset(), data, 0, length);
BigInteger value = Utils.bytesToBigInteger(data, length, language.isBigEndian(), false);
result.add(String.format("%s=0x%s", register, value.toString(16)));
remains.remove(rangeOfRegister(register));
}
}
public static PatchStep parse(long threadKey, String stepSpec) {
// TODO: Can I parse and validate the sleigh here?
@ -45,6 +157,11 @@ public class PatchStep implements Step {
this.hashCode = Objects.hash(threadKey, sleigh); // TODO: May become mutable
}
private void setSleigh(String sleigh) {
this.sleigh = sleigh;
this.hashCode = Objects.hash(threadKey, sleigh);
}
@Override
public int hashCode() {
return hashCode;
@ -162,4 +279,120 @@ public class PatchStep implements Step {
PcodeProgram prog = emuThread.getMachine().compileSleigh("schedule", List.of(sleigh + ";"));
emuThread.getExecutor().execute(prog, emuThread.getUseropLibrary());
}
@Override
public long coalescePatches(Language language, List<Step> steps) {
long threadKey = -1;
int toRemove = 0;
Map<AddressSpace, SemisparseByteArray> patches = new TreeMap<>();
for (int i = steps.size() - 1; i >= 0; i--) {
Step step = steps.get(i);
long stk = step.getThreadKey();
if (threadKey == -1) {
threadKey = stk;
}
else if (stk != -1 && stk != threadKey) {
break;
}
if (!(step instanceof PatchStep)) {
break;
}
PatchStep ps = (PatchStep) step;
Map<AddressSpace, SemisparseByteArray> subs = ps.getPatches(language);
if (subs == null) {
break;
}
mergePatches(subs, patches);
patches = subs;
toRemove++;
}
List<String> sleighPatches = generateSleigh(language, patches);
assert sleighPatches.size() <= toRemove;
for (String sleighPatch : sleighPatches) {
PatchStep ps = (PatchStep) steps.get(steps.size() - toRemove);
ps.setSleigh(sleighPatch);
toRemove--;
}
return toRemove;
}
protected void mergePatches(Map<AddressSpace, SemisparseByteArray> into,
Map<AddressSpace, SemisparseByteArray> from) {
for (Entry<AddressSpace, SemisparseByteArray> entry : from.entrySet()) {
if (!into.containsKey(entry.getKey())) {
into.put(entry.getKey(), entry.getValue());
}
else {
into.get(entry.getKey()).putAll(entry.getValue());
}
}
}
protected Map<AddressSpace, SemisparseByteArray> getPatches(Language language) {
PcodeProgram prog = SleighProgramCompiler.compileProgram((SleighLanguage) language,
"schedule", List.of(sleigh + ";"), SleighUseropLibrary.nil());
// SemisparseArray is a bit overkill, no?
Map<AddressSpace, SemisparseByteArray> result = new TreeMap<>();
for (PcodeOp op : prog.getCode()) {
// Only accept patches in form [mem/reg] = [constant]
switch (op.getOpcode()) {
case PcodeOp.COPY:
if (!getPatchCopyOp(language, result, op)) {
return null;
}
break;
case PcodeOp.STORE:
if (!getPatchStoreOp(language, result, op)) {
return null;
}
break;
default:
return null;
}
}
return result;
}
protected boolean getPatchCopyOp(Language language,
Map<AddressSpace, SemisparseByteArray> result, PcodeOp op) {
Varnode output = op.getOutput();
if (!output.isAddress() && !output.isRegister()) {
return false;
}
Varnode input = op.getInput(0);
if (!input.isConstant()) {
return false;
}
Address address = output.getAddress();
SemisparseByteArray array = result.computeIfAbsent(address.getAddressSpace(),
as -> new SemisparseByteArray());
array.putData(address.getOffset(),
Utils.longToBytes(input.getOffset(), input.getSize(),
language.isBigEndian()));
return true;
}
protected boolean getPatchStoreOp(Language language,
Map<AddressSpace, SemisparseByteArray> result,
PcodeOp op) {
Varnode vnSpace = op.getInput(0);
if (!vnSpace.isConstant()) {
return false;
}
AddressSpace space =
language.getAddressFactory().getAddressSpace((int) vnSpace.getOffset());
Varnode vnOffset = op.getInput(1);
if (!vnOffset.isConstant()) {
return false;
}
Varnode vnValue = op.getInput(2);
if (!vnValue.isConstant()) {
return false;
}
SemisparseByteArray array = result.computeIfAbsent(space, as -> new SemisparseByteArray());
array.putData(vnOffset.getOffset(), Utils.longToBytes(vnValue.getOffset(),
vnValue.getSize(), language.isBigEndian()));
return true;
}
}

View File

@ -23,6 +23,7 @@ import org.apache.commons.lang3.StringUtils;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.PcodeThread;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.thread.TraceThreadManager;
@ -122,7 +123,7 @@ public class Sequence implements Comparable<Sequence> {
return;
}
if (steps.isEmpty()) {
steps.add(step);
steps.add(step.clone());
return;
}
Step last = steps.get(steps.size() - 1);
@ -157,6 +158,17 @@ public class Sequence implements Comparable<Sequence> {
steps.addAll(clone.subList(2, size));
}
public void coalescePatches(Language language) {
if (steps.isEmpty()) {
return;
}
Step last = steps.get(steps.size() - 1);
long toRemove = last.coalescePatches(language, steps);
for (; toRemove > 0; toRemove--) {
steps.remove(steps.size() - 1);
}
}
/**
* Rewind this sequence the given step count
*

View File

@ -15,10 +15,12 @@
*/
package ghidra.trace.model.time.schedule;
import java.util.List;
import java.util.function.Consumer;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.PcodeThread;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.thread.TraceThreadManager;
import ghidra.util.exception.CancelledException;
@ -171,4 +173,6 @@ public interface Step extends Comparable<Step> {
<T> void execute(PcodeThread<T> emuThread, Consumer<PcodeThread<T>> stepAction,
TaskMonitor monitor) throws CancelledException;
long coalescePatches(Language language, List<Step> steps);
}

View File

@ -15,9 +15,11 @@
*/
package ghidra.trace.model.time.schedule;
import java.util.List;
import java.util.function.Consumer;
import ghidra.pcode.emu.PcodeThread;
import ghidra.program.model.lang.Language;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -194,4 +196,9 @@ public class TickStep implements Step {
stepAction.accept(emuThread);
}
}
@Override
public long coalescePatches(Language language, List<Step> steps) {
return 0;
}
}

View File

@ -487,6 +487,10 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
return new TraceSchedule(snap, steps.clone(), pTicks);
}
private long keyOf(TraceThread thread) {
return thread == null ? -1 : thread.getKey();
}
/**
* Returns the equivalent of executing this schedule then performing a given patch
*
@ -497,10 +501,12 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
if (!this.pSteps.isNop()) {
Sequence pTicks = this.pSteps.clone();
pTicks.advance(new PatchStep(thread.getKey(), sleigh));
pTicks.coalescePatches(thread.getTrace().getBaseLanguage());
return new TraceSchedule(snap, steps.clone(), pTicks);
}
Sequence ticks = this.steps.clone();
ticks.advance(new PatchStep(thread.getKey(), sleigh));
ticks.advance(new PatchStep(keyOf(thread), sleigh));
ticks.coalescePatches(thread.getTrace().getBaseLanguage());
return new TraceSchedule(snap, ticks, new Sequence());
}
}

View File

@ -334,7 +334,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
public PcodeFrame getFrame() {
return null;
}
@Override
public Instruction getInstruction() {
return null;
@ -564,4 +564,27 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
time.finish(tb.trace, TraceSchedule.parse("1:4;t0-4"), machine, TaskMonitor.DUMMY);
}
}
@Test
public void testCoalescePatches() throws Exception {
// TODO: Should parse require coalescing? Can't without passing a language...
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
TraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) {
thread = tb.trace.getThreadManager().createThread("Threads[0]", 0);
}
TraceSchedule time = TraceSchedule.parse("0");
time = time.patched(thread, "r0l=1");
assertEquals("0:t0-{r0l=0x1}", time.toString());
time = time.patched(thread, "r0h=2");
assertEquals("0:t0-{r0=0x200000001}", time.toString());
time = time.patched(thread, "r1l=3").patched(thread, "*[ram]:4 0xcafe:8=0xdeadbeef");
assertEquals("0:t0-{*:4 0xcafe:8=0xdeadbeef};t0-{r0=0x200000001};t0-{r1l=0x3}",
time.toString());
time = time.patched(thread, "*:8 0xcb00:8 = 0x1122334455667788");
assertEquals("0:t0-{*:8 0xcafe:8=0xdead112233445566};t0-{*:2 0xcb06:8=0x7788};" +
"t0-{r0=0x200000001};t0-{r1l=0x3}", time.toString());
}
}
}

View File

@ -23,6 +23,8 @@ import java.util.Map;
import com.google.common.collect.*;
import com.google.common.primitives.UnsignedLong;
import ghidra.util.MathUtilities;
/**
* A sparse byte array characterized by contiguous dense regions
*
@ -251,6 +253,32 @@ public class SemisparseByteArray {
}
}
/**
* Copy the contents on another semisparse array into this one
*
* @param from the source array
*/
public synchronized void putAll(SemisparseByteArray from) {
byte[] temp = new byte[4096];
for (Range<UnsignedLong> range : from.defined.asRanges()) {
long length;
long lower = range.lowerEndpoint().longValue();
if (range.upperBoundType() == BoundType.CLOSED) {
assert range.upperEndpoint() == UnsignedLong.MAX_VALUE;
length = -lower;
}
else {
length = range.upperEndpoint().longValue() - lower;
}
for (long i = 0; Long.compareUnsigned(i, length) < 0;) {
int l = MathUtilities.unsignedMin(temp.length, length - i);
from.getData(lower + i, temp, 0, l);
this.putData(lower + i, temp, 0, l);
i += l;
}
}
}
/**
* Check how many contiguous bytes are available starting at the given address
*

View File

@ -72,7 +72,7 @@ public class PcodeExecutor<T> {
return state;
}
public void executeLine(String line) {
public void executeSleighLine(String line) {
PcodeProgram program = SleighProgramCompiler.compileProgram(language,
"line", List.of(line + ";"), SleighUseropLibrary.NIL);
execute(program, SleighUseropLibrary.nil());

View File

@ -116,6 +116,10 @@ public class PcodeProgram {
return language;
}
public List<PcodeOp> getCode() {
return code;
}
public <T> void execute(PcodeExecutor<T> executor, SleighUseropLibrary<T> library) {
executor.execute(this, library);
}

View File

@ -15,8 +15,7 @@
*/
package ghidra.generic.util.datastruct;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Random;
@ -26,8 +25,6 @@ import org.junit.Test;
import com.google.common.collect.*;
import com.google.common.primitives.UnsignedLong;
import ghidra.generic.util.datastruct.SemisparseByteArray;
public class SemisparseByteArrayTest {
private static final String HELLO_WORLD = "Hello, World!";
protected static final byte[] HW = HELLO_WORLD.getBytes();
@ -117,4 +114,37 @@ public class SemisparseByteArrayTest {
assertEquals(0, read[1 + chunk.length + i]); // Test length param. Should not see HW.
}
}
@Test
public void testPutAll() {
SemisparseByteArray first = new SemisparseByteArray();
SemisparseByteArray second = new SemisparseByteArray();
second.putData(0, new byte[] { 1, 2, 3, 4 });
second.putData(-HW.length, HW);
first.putData(2, new byte[] { 1, 2, 3, 4 });
first.putData(-HW.length - 1, new byte[] { 10, 11 });
first.putAll(second);
RangeSet<UnsignedLong> expectedInit = TreeRangeSet.create();
expectedInit.add(Range.closedOpen(
UnsignedLong.fromLongBits(0), UnsignedLong.fromLongBits(6)));
expectedInit.add(Range.closed(
UnsignedLong.fromLongBits(-HW.length - 1), UnsignedLong.fromLongBits(-1)));
assertEquals(expectedInit, first.getInitialized(0, -1));
byte[] read = new byte[6];
first.getData(0, read);
assertArrayEquals(new byte[] { 1, 2, 3, 4, 3, 4 }, read);
read = new byte[HW.length];
first.getData(-HW.length, read);
assertArrayEquals(HW, read);
read = new byte[2];
first.getData(-HW.length - 1, read);
assertArrayEquals(new byte[] { 10, 'H' }, read);
}
}

View File

@ -133,8 +133,13 @@ public class VerticalPixelAddressMapImpl implements VerticalPixelAddressMap {
return new AddressSet();
}
if (viewedAddresses == null) {
Address start = getStartAddress();
Address end = getEndAddress();
if (start == null || end == null) {
return new AddressSet();
}
viewedAddresses =
map.getOriginalAddressSet().intersectRange(getStartAddress(), getEndAddress());
map.getOriginalAddressSet().intersectRange(start, end);
}
return viewedAddresses;
}

View File

@ -15,19 +15,12 @@
*/
package ghidra.app.plugin.core.assembler;
import static org.junit.Assert.*;
import java.util.List;
import java.util.Objects;
import javax.swing.JTextField;
import static org.junit.Assert.assertEquals;
import org.junit.*;
import ghidra.app.plugin.assembler.Assembler;
import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.core.assembler.AssemblyDualTextField.AssemblyCompletion;
import ghidra.app.plugin.core.assembler.AssemblyDualTextField.AssemblyInstruction;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
@ -41,7 +34,6 @@ import ghidra.program.model.data.ShortDataType;
import ghidra.program.model.data.TerminatedStringDataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.util.ProgramLocation;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
import ghidra.util.task.TaskMonitor;
@ -55,8 +47,7 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
private CodeViewerProvider codeViewer;
private AssemblyDualTextField instructionInput;
private JTextField dataInput;
private AssemblerPluginTestHelper helper;
private ProgramDB program;
private AddressSpace space;
@ -73,24 +64,20 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
assemblerPlugin = addPlugin(tool, AssemblerPlugin.class);
codeViewer = waitForComponentProvider(CodeViewerProvider.class);
instructionInput = assemblerPlugin.patchInstructionAction.input;
dataInput = assemblerPlugin.patchDataAction.input;
program = createDefaultProgram(getName(), "Toy:BE:64:default", this);
space = program.getAddressFactory().getDefaultAddressSpace();
memory = program.getMemory();
listing = program.getListing();
helper = new AssemblerPluginTestHelper(assemblerPlugin, codeViewer, program);
try (ProgramTransaction trans = ProgramTransaction.open(program, "Setup")) {
memory.createInitializedBlock(".text", space.getAddress(0x00400000), 0x1000, (byte) 0,
TaskMonitor.DUMMY, false);
trans.commit();
}
// Snuff the assembler's warning prompt
assemblerPlugin.patchInstructionAction.shownWarning.put(program.getLanguage(), true);
env.showTool();
programManager.openProgram(program);
}
@ -100,45 +87,10 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
env.dispose();
}
protected void assertDualFields() {
assertFalse(instructionInput.getAssemblyField().isVisible());
assertTrue(instructionInput.getMnemonicField().isVisible());
assertTrue(instructionInput.getOperandsField().isVisible());
}
protected List<AssemblyCompletion> inputAndGetCompletions(String text) {
return runSwing(() -> {
instructionInput.setText(text);
instructionInput.auto.startCompletion(instructionInput.getOperandsField());
instructionInput.auto.flushUpdates();
return instructionInput.auto.getSuggestions();
});
}
private void goTo(Address address) {
runSwing(() -> codeViewer.goTo(program, new ProgramLocation(program, address)));
waitForSwing();
}
@Test
public void testActionPatchInstructionNoExisting() throws Exception {
Address address = space.getAddress(0x00400000);
goTo(address);
performAction(assemblerPlugin.patchInstructionAction, codeViewer, true);
assertDualFields();
assertEquals("", instructionInput.getText());
assertEquals(address, assemblerPlugin.patchInstructionAction.getAddress());
List<AssemblyCompletion> completions = inputAndGetCompletions("imm r0, #1234");
AssemblyCompletion first = completions.get(0);
assertTrue(first instanceof AssemblyInstruction);
AssemblyInstruction ai = (AssemblyInstruction) first;
runSwing(() -> assemblerPlugin.patchInstructionAction.accept(ai));
waitForProgram(program);
Instruction ins = Objects.requireNonNull(listing.getInstructionAt(address));
Instruction ins = helper.patchInstructionAt(address, "", "imm r0, #1234");
assertEquals("imm r0,#0x4d2", ins.toString());
}
@ -151,45 +103,13 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
trans.commit();
}
goTo(address);
performAction(assemblerPlugin.patchInstructionAction, codeViewer, true);
assertDualFields();
assertEquals("imm r0,#0x4d2", instructionInput.getText());
assertEquals(address, assemblerPlugin.patchInstructionAction.getAddress());
List<AssemblyCompletion> completions = inputAndGetCompletions("imm r0, #123");
AssemblyCompletion first = completions.get(0);
assertTrue(first instanceof AssemblyInstruction);
AssemblyInstruction ai = (AssemblyInstruction) first;
runSwing(() -> assemblerPlugin.patchInstructionAction.accept(ai));
waitForProgram(program);
Instruction ins = Objects.requireNonNull(listing.getInstructionAt(address));
Instruction ins = helper.patchInstructionAt(address, "imm r0,#0x4d2", "imm r0, #123");
assertEquals("imm r0,#0x7b", ins.toString());
}
// TODO: Test disabled on uninitialized memory
// TODO: Test disabled on read-only listings
protected Data doPatchAt(Address address, String expText, String newText) {
goTo(address);
performAction(assemblerPlugin.patchDataAction, codeViewer, true);
assertTrue(dataInput.isVisible());
assertEquals(expText, dataInput.getText());
assertEquals(address, assemblerPlugin.patchDataAction.getAddress());
runSwing(() -> {
dataInput.setText(newText);
assemblerPlugin.patchDataAction.accept();
});
waitForProgram(program);
return Objects.requireNonNull(listing.getDataAt(address));
}
@Test
public void testActionPatchDataShortHexValid() throws Exception {
Address address = space.getAddress(0x00400000);
@ -198,7 +118,7 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
trans.commit();
}
Data data = doPatchAt(address, "0h", "1234h");
Data data = helper.patchDataAt(address, "0h", "1234h");
assertEquals("1234h", data.getDefaultValueRepresentation());
}
@ -211,7 +131,7 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
trans.commit();
}
Data data = doPatchAt(address, "0", "1234");
Data data = helper.patchDataAt(address, "0", "1234");
assertEquals("1234", data.getDefaultValueRepresentation());
}
@ -224,7 +144,8 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
trans.commit();
}
Data data = doPatchAt(address, "\"Hello, World!\"", "\"Hello, Patch!\"");
Data data = helper.patchDataAt(address, "\"Hello, World!\"",
"\"Hello, Patch!\"");
assertEquals("\"Hello, Patch!\"", data.getDefaultValueRepresentation());
}
@ -237,7 +158,8 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
trans.commit();
}
Data data = doPatchAt(address, "\"Hello, World!\"", "\"Hello!\"");
Data data =
helper.patchDataAt(address, "\"Hello, World!\"", "\"Hello!\"");
assertEquals("\"Hello!\"", data.getDefaultValueRepresentation());
assertEquals(7, data.getLength());
}
@ -251,7 +173,7 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
trans.commit();
}
Data data = doPatchAt(address, "\"Hello, World!\"", "\"Hello to you, too!\"");
Data data = helper.patchDataAt(address, "\"Hello, World!\"", "\"Hello to you, too!\"");
assertEquals("\"Hello to you, too!\"", data.getDefaultValueRepresentation());
assertEquals(19, data.getLength());
}

View File

@ -0,0 +1,131 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.assembler;
import static org.junit.Assert.*;
import java.util.List;
import java.util.Objects;
import javax.swing.JTextField;
import docking.test.AbstractDockingTest;
import generic.test.AbstractGTest;
import generic.test.AbstractGenericTest;
import ghidra.app.plugin.core.assembler.AssemblyDualTextField.AssemblyCompletion;
import ghidra.app.plugin.core.assembler.AssemblyDualTextField.AssemblyInstruction;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.util.ProgramLocation;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
public class AssemblerPluginTestHelper {
public final AssemblerPlugin assemblerPlugin;
private final CodeViewerProvider provider;
private final Program program;
public final PatchInstructionAction patchInstructionAction;
public final PatchDataAction patchDataAction;
public final AssemblyDualTextField instructionInput;
public final JTextField dataInput;
private final Listing listing;
public AssemblerPluginTestHelper(AssemblerPlugin assemblerPlugin, CodeViewerProvider provider,
Program program) {
this.assemblerPlugin = assemblerPlugin;
this.provider = provider;
this.program = program;
this.patchInstructionAction = assemblerPlugin.patchInstructionAction;
this.patchDataAction = assemblerPlugin.patchDataAction;
this.instructionInput = assemblerPlugin.patchInstructionAction.input;
this.dataInput = assemblerPlugin.patchDataAction.input;
this.listing = program.getListing();
// Snuff the assembler's warning prompt
patchInstructionAction.shownWarning.put(program.getLanguage(), true);
}
public void assertDualFields() {
assertFalse(instructionInput.getAssemblyField().isVisible());
assertTrue(instructionInput.getMnemonicField().isVisible());
assertTrue(instructionInput.getOperandsField().isVisible());
}
public List<AssemblyCompletion> inputAndGetCompletions(String text) {
return AbstractGenericTest.runSwing(() -> {
instructionInput.setText(text);
instructionInput.auto.startCompletion(instructionInput.getOperandsField());
instructionInput.auto.flushUpdates();
return instructionInput.auto.getSuggestions();
});
}
public void goTo(Address address) {
ListingPanel listingPanel = provider.getListingPanel();
ProgramLocation location = new ProgramLocation(program, address);
AbstractGTest.waitForCondition(() -> {
AbstractGenericTest.runSwing(() -> listingPanel.goTo(location));
ProgramLocation confirm = listingPanel.getCursorLocation();
if (confirm == null) {
return false;
}
if (!address.equals(confirm.getAddress())) {
return false;
}
return true;
});
}
public Instruction patchInstructionAt(Address address, String expText, String newText) {
goTo(address);
AbstractDockingTest.performAction(assemblerPlugin.patchInstructionAction, provider, true);
assertDualFields();
assertEquals(expText, instructionInput.getText());
assertEquals(address, assemblerPlugin.patchInstructionAction.getAddress());
List<AssemblyCompletion> completions = inputAndGetCompletions(newText);
AssemblyCompletion first = completions.get(0);
assertTrue(first instanceof AssemblyInstruction);
AssemblyInstruction ai = (AssemblyInstruction) first;
AbstractGenericTest.runSwing(() -> assemblerPlugin.patchInstructionAction.accept(ai));
AbstractGhidraHeadedIntegrationTest.waitForProgram(program);
return Objects.requireNonNull(listing.getInstructionAt(address));
}
public Data patchDataAt(Address address, String expText, String newText) {
goTo(address);
AbstractDockingTest.performAction(assemblerPlugin.patchDataAction, provider, true);
assertTrue(dataInput.isVisible());
assertEquals(expText, dataInput.getText());
assertEquals(address, assemblerPlugin.patchDataAction.getAddress());
AbstractGenericTest.runSwing(() -> {
dataInput.setText(newText);
assemblerPlugin.patchDataAction.accept();
});
AbstractGhidraHeadedIntegrationTest.waitForProgram(program);
return Objects.requireNonNull(listing.getDataAt(address));
}
}

View File

@ -24,6 +24,7 @@ import java.util.List;
import javax.swing.JComponent;
import docking.action.ToggleDockingAction;
import ghidra.GhidraOptions;
import ghidra.GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES;
import ghidra.app.plugin.core.format.*;
@ -88,7 +89,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
protected Map<String, ByteViewerComponent> viewMap = new HashMap<>();
protected ToggleEditAction editModeAction;
protected ToggleDockingAction editModeAction;
protected OptionsAction setOptionsAction;
protected ProgramByteBlockSet blockSet;